流量管理的位置在com.oneplus.security.network下面
包含
-
calibrate 校準功能包
calibrate部分代碼結構圖- AutoCalibrateBootReceiver
<receiver android:name="com.oneplus.security.network.calibrate.AutoCalibrateBootReceiver"
android:exported="true"
android:enabled="true" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.DATE_CHANGED"/>
</intent-filter>
</receiver>
接收日期變更和開機的廣播來自行流量自動校驗
String action = intent.getAction();
if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
Log.d(TAG, "set up auto calibrate function according to preference config state");
Intent changeDateIntent = new Intent(context, AutoCalibrateService.class);
changeDateIntent.putExtra(AutoCalibrateUtil.KEY_AUTO_CALIBRATE_SERVICE_TASK,
AutoCalibrateUtil.INDEX_CONFIG_AUTO_CALIBRATE_TASK);
context.startService(changeDateIntent);
} else if (Intent.ACTION_DATE_CHANGED.equals(action)) {
Intent changeDateIntent = new Intent(context, AutoCalibrateService.class);
changeDateIntent.putExtra(AutoCalibrateUtil.KEY_AUTO_CALIBRATE_SERVICE_TASK,
AutoCalibrateUtil.INDEX_CHECK_CALIBRATE_DATE_TASK);
context.startService(changeDateIntent);
}
- AutoCalibrateService
這個文件是自動流量校準的主服務
@Override
public int onStartCommand (Intent intent, int flags, int startId) {
Log.i(TAG, "called on start command ");
if (null != intent) {
int taskIndex = intent.getIntExtra(AutoCalibrateUtil.KEY_AUTO_CALIBRATE_SERVICE_TASK,
AutoCalibrateUtil.INDEX_INVALID_TASK);
mCurrentSlotId = mSimcardDataModel.getCurrentTrafficRunningSlotId();
if (AutoCalibrateUtil.INDEX_START_AUTO_CALIBRATE_TASK == taskIndex) {
//啟動一個異步任務來調用歐朋sdk的短信查詢接口,在回調中對本地保存的流量數據及上一次校準信息做處理
} else if (AutoCalibrateUtil.INDEX_CHECK_CALIBRATE_DATE_TASK == taskIndex) {
// 響應日期變化并且更新與月結日相關的功能邏輯
} else if (AutoCalibrateUtil.INDEX_CONFIG_AUTO_CALIBRATE_TASK == taskIndex) {
// 發生手機重啟時,檢查是否已經配置了流量自動校準服務,并相應選擇恢復或者退出
stopSelf();
} else {
stopSelf();
}
} else {
stopSelf();
}
return super.onStartCommand(intent, flags, startId);
}
- AutoCalibrateUtil
這個文件是為了保存和獲取雙卡場景下流量自動校準配置信息所做的工具類,數據存儲使用SharedPreference
public static final int INDEX_CHECK_CALIBRATE_DATE_TASK = 0;//接收到日期變更后觸發,任務發送給AutoCalibrateService處理
public static final int INDEX_START_AUTO_CALIBRATE_TASK = 1;//在設置中使用,打開自動校準開關后生效
public static final int INDEX_CONFIG_AUTO_CALIBRATE_TASK = 2;//接收到開機廣播觸發,用于重啟因手機掉電導致的Alarm丟失
// 以下為針對雙卡場景定義的用于保存自動校準相關SharedPreference的key
private static final String KEY_AUTO_CALIBRATE_SWITCH_SIM_ONE = "key_auto_calibrate_switch_sim_one";
private static final String KEY_AUTO_CALIBRATE_SWITCH_SIM_TWO = "key_auto_calibrate_switch_sim_two";
private static final String KEY_AUTO_CALIBRATE_TIME_INTERVAL_INDEX_SIM_ONE =
"key_auto_calibrate_time_interval_index_sim_one";
private static final String KEY_AUTO_CALIBRATE_TIME_INTERVAL_INDEX_SIM_TWO =
"key_auto_calibrate_time_interval_index_sim_two";
private static final String KEY_AUTO_CALIBRATE_TIME_INTERVAL_SIM_ONE =
"key_auto_calibrate_time_interval_sim_one";
private static final String KEY_AUTO_CALIBRATE_TIME_INTERVAL_SIM_TWO =
"key_auto_calibrate_time_interval_sim_two";
-
operator 運營商信息管理包
用于運營商相關的流量信息讀取、配置和管理
operator包文件結構-
OperatorModelInterface的定義和使用
OperatorModelInterface結構圖- AbstractOperatorDataModel 抽象流量數據模型,定義了具體類里需要使用的回調集合類并且實現了注冊和釋放對應接口的方法
-
protected List<OperatorProvinceUpdater> mProvinceUpdaterList;
protected List<OperatorBrandUpdater> mBrandUpdaterList;
protected List<OperatorPackageUsageUpdater> mOperatorQueryResultUpdaterList;
protected List<OperatorAccountDayUpdater> mOperatorAccountDayUpdaterList;
* OperatorProvinceUpdater 運營商省份更新接口
* OperatorBrandUpdater 運營商品牌更新接口
* OperatorPackageUsageUpdater 運營商流量使用情況更新接口
* OperatorAccountDayUpdater 運營商月結日更新接口
* NativeOperatorDataModel 僅使用建立在原生流量統計基礎上的流量模型進行流量數據管理
@Override
public void requesetPkgMonthlyUsageAndTotalInByte (final int slotId) {
checkThreadPoolExistence();
mThreadPool.execute(new Runnable() {
@Override
public void run () {
long lastCalibrateTime = AutoCalibrateUtil.getLastCalibrateTime(mContext.get(),
slotId);
long used = NativeOperatorDataManager.getPkgUsedMonthlyInByte(mContext.get(), slotId);
final long start = (used == OperaConst.PKG_USAGE_INVALID_VALUE)
? TimeUtils.getTrafficStaticsMonthlyStartTime(getAccountDay(slotId))
: lastCalibrateTime;
final long end = TimeUtils.getTrafficStaticsMonthlyEndTime();
long extra = mTrafficDataModel.getDataUsageWithinSpecificTime
(slotId, start, end);
boolean isLastCalibrateTimeLongAgo = ((System.currentTimeMillis() -
lastCalibrateTime) > 1000 * 60 * 1);
notifyMonthlyUsageAndTotalChanged(slotId, getPkgTotalInByte(slotId),
isLastCalibrateTimeLongAgo ? used + extra : used);
}
});
}
get數據使用情況的幾個方法
@Override
public long getPkgTotalInByte (int slotId) {
return NativeOperatorDataManager.getPkgTotalInByte(mContext.get(), slotId);
}
@Override
public int getAccountDay (int slotId) {
return NativeOperatorDataManager.getAccountDay(mContext.get(), slotId);
}
@Override
public long getPkgUsedMonthlyInByte (int slotId) {
return NativeOperatorDataManager.getPkgUsedMonthlyInByte(mContext.get(), slotId);
}
NativeOperatorDataManager —— 一個用于保存本地流量值的SharedPreference管理類
* InvalidOperatorDataModel 空模式對應的流量模型,無sim卡時得到一個空模型,關鍵的接口實現直接返回無效值,可以參考調用端代碼
@Override
public long getPkgTotalInByte (int slotId) {
return OperaConst.PKG_USAGE_INVALID_VALUE;
}
@Override
public int getAccountDay (int slotId) {
return OperaConst.ACCOUNT_DAY_INVALID_VALUE;
}
@Override
public long getPkgUsedMonthlyInByte (int slotId) {
return OperaConst.PKG_USAGE_INVALID_VALUE;
}
* OperaOperatorDataModel 歐朋流量服務對應的流量模型,對于移動、聯通、電信三大運營商生效
調用歐朋sdk服務,進行短信校準回調注冊,PackageQueryServiceAgent是一個抽象了歐朋sdk接口調用的可實例化的類
@Override
public void addQueryResultListener (final int slotId) {
if (null != mPackageQueryServiceAgent && mPackageQueryServiceAgent.isServiceBound()) {
addQueryResultListenrToAgentService(slotId);
} else {
if (null != mPackageQueryServiceAgent) {
mPackageQueryServiceAgent.addConnectionListener(new PackageQueryServiceAgent.ServiceConnectionListener() {
@Override
public void onConnected (IPackageQueryService service) {
Log.d(TAG, "recall add sms query result listener after service bound");
addQueryResultListenrToAgentService(slotId);
}
@Override
public void onDisconnected () {
}
});
} else {
Log.e(TAG, "package service agent is null, impossible to fetch query result.");
}
}
}
private void addQueryResultListenrToAgentService (int slotId) {
mPackageQueryServiceAgent.addQueryResultListener(slotId, this);
}
獲取流量信息的請求方法
該方法直接使用本對象初始化時創建的線程池進行異步操作,所以回調不在主線程中,更新數據時需要注意
@Override
public void requesetPkgMonthlyUsageAndTotalInByte (final int slotId) {
if (null != mPackageQueryServiceAgent && mPackageQueryServiceAgent.isServiceBound()) {
startQueryPackageTotalAndMonthlyUsage(slotId);
} else {
if (null != mPackageQueryServiceAgent) {
mPackageQueryServiceAgent.addConnectionListener(new PackageQueryServiceAgent.ServiceConnectionListener() {
@Override
public void onConnected (IPackageQueryService service) {
Log.d(TAG, "recall start query after service bound");
startQueryPackageTotalAndMonthlyUsage(slotId);
}
@Override
public void onDisconnected () {
}
});
} else {
Log.e(TAG, "package service agent is null, impossible to fetch query result.");
}
}
}
private void startQueryPackageTotalAndMonthlyUsage (final int slotId) {
if (null != mThreadPool && !mThreadPool.isShutdown()) {
mThreadPool.execute(new Runnable() {
@Override
public void run () {
int total = mPackageQueryServiceAgent.getPkgTotalInKb(slotId);
int used = mPackageQueryServiceAgent.getPkgUsedInKb(slotId);
long extra = 0L;
long lastCalibrateTime = AutoCalibrateUtil.getLastCalibrateTime(mContext,
slotId);
if (lastCalibrateTime != AutoCalibrateUtil.DEFAULT_INVALID_LAST_CALIBRATE_DAY) {
long currentTime = System.currentTimeMillis();
// if last calibrate time is more than 20 minutes ago, we would like to double check
// monthly usage rather than just show monthly usage result obtained from sms.
boolean isLastCalibrateTimeLongAgo = (currentTime - lastCalibrateTime) >
(1 * 60 * 1000);
if (isLastCalibrateTimeLongAgo) {
// add used value.
final long start = (used == OperaConst.PKG_USAGE_INVALID_VALUE)
? TimeUtils.getTrafficStaticsMonthlyStartTime(getAccountDay(slotId))
: AutoCalibrateUtil.getLastCalibrateTime(mContext, slotId);
final long end = TimeUtils.getTrafficStaticsMonthlyEndTime();
extra = mNativeTrafficDataModel.getDataUsageWithinSpecificTime
(slotId, start, end);
Log.i(TAG, "last calibrate time is 20 minutes long ago, we add " +
"calibrated result with native saved value from last " +
"calibration till now value is " + extra);
}
}
notifyMonthlyUsageAndTotalChanged(
slotId,
(total == OperaConst.PKG_USAGE_INVALID_VALUE)
? OperaConst.PKG_USAGE_INVALID_VALUE
: total * LONG_BYTE_FACTOR,
(used == OperaConst.PKG_USAGE_INVALID_VALUE)
? OperaConst.PKG_USAGE_INVALID_VALUE
: used * LONG_BYTE_FACTOR + extra);
}
});
}
}
短信校準結果的回調處理
@Override
public void onQueryResult (JSONObject result) {
Log.d(TAG, "onQueryResult");
SmsQueryResult queryResult = SmsQueryResultUtils.analyzeAndSaveSmsQueryResult(mContext, result,
mSmsQueryStateManager);
if (queryResult.isQuerySuccessed) {
requesetPkgMonthlyUsageAndTotalInByte(queryResult.queriedSlotId);
}
for (SmsQueryResultListener listener : mOperatorSmsQueryResultListenerList) {
listener.onSmsQueryResultAnalyzed(queryResult.queriedSlotId, queryResult.queriedErrorCode);
}
}
各個get接口的實現方式
@Override
public long getPkgTotalInByte (int slotId) {
int total = mPackageQueryServiceAgent.getPkgTotalInKb(slotId);
return (total == OperaConst.PKG_USAGE_INVALID_VALUE)
? OperaConst.PKG_USAGE_INVALID_VALUE
: total * LONG_BYTE_FACTOR;
}
@Override
public int getAccountDay (int slotId) {
//int accountday = mPackageQueryServiceAgent.getAccountDay(slotId);
int accountday = AccountDayLocalCache.getAccountDay(mContext, slotId);
return accountday;
}
@Override
public long getPkgUsedMonthlyInByte (int slotId) {
int used = mPackageQueryServiceAgent.getPkgUsedInKb(slotId);
return (used == OperaConst.PKG_USAGE_INVALID_VALUE)
? OperaConst.PKG_USAGE_INVALID_VALUE
: used * LONG_BYTE_FACTOR;
}
- 本包中其他回調接口的定義
- OperatorDataModelFactory工廠類
根據sim卡是否為歐朋sdk支持的運營商來決定生成哪一個流量模型
public class OperatorDataModelFactory {
private static final String TAG = "OperatorDataModelFactory";
public static OperatorModelInterface getOperatorDataModel (Context context, int slotId,
boolean isSdkSupported) {
OperatorModelInterface mOperatorDataModel;
if (isSdkSupported) {
Log.d(TAG, "build opera operator model.");
mOperatorDataModel = OperaOperatorDataModel.getInstance(context);
} else {
// use native operator data model.
Log.d(TAG, "build native operator model.");
mOperatorDataModel = NativeOperatorDataModel.getInstance(context);
}
return mOperatorDataModel;
}
}
-
settings 設置界面及功能管理包settings文件結構圖
- 界面相關類
CarrierConfigFragment, CarrierConfigSettingsActivity, 用于使用歐朋sdk時進行運營商信息配置
TrafficMonthlyUsageConfigActivity, 用于配置月已用流量
TrafficTotalAndClosingDayConfigAcitivity, 用于配置月總流量和月結日
TrafficUsageSettingsActivity 用于展示整個設置界面,包含了一個PreferenceFragment內部類TrafficUsageSettingsFragment - 設置數據模型類
- OpertorPrefModel
- 界面相關類
abstract String getProvinceAndBrandPrefKey();
abstract String getTrafficTotalAndClosingDayPrefKey();
abstract String getTrafficMonthlyUsagePrefKey();
* SimPreferenceValueUpdater
用于統一處理設置需要顯示的值如省份、品牌、流量值等變更的接口,接收OperatorPrefModel為參數,使用其中定義的抽象方法動態獲取到具體的OperatorPrefModel實現,并進行數據更新
public interface SimPreferenceValueUpdater {
void updateProvinceAndBrand (OperatorPrefModel model);
void updateAccountDay(OperatorPrefModel model);
void updateTrafficTotal(OperatorPrefModel model);
void updateTrafficMonthlyUsed(OperatorPrefModel model);
void updateSmsQueryResult(int errorCode);
}
-
simcard 手機sim卡信息管理包
simcard包結構圖- SimcardDataModelInterface及其實現類SimcardDataModel
可以說有一些多余,因為這個接口只有一個具體實現,應該是一個過度設計的問題
這個類主要負責獲取sim卡的相關信息,使用接口定義,看的更加清楚吧,避免陷入實現細節
- SimcardDataModelInterface及其實現類SimcardDataModel
public interface SimcardDataModelInterface {
int SIM_CARD_ONE_INDEX = 0;
int SIM_CARD_TWO_INDEX = 1;
int INVALID_SIM_SLOT_ID = -1;
int INVALID_CLOSING_DAY = -1;
String KEY_SIM_CARD_SLOT = "sim_card_slot";
String OPERATOR_CODE_CHINA_MOBILE = "46000";
String OPERATOR_CODE_CHINA_MOBILE_1 = "46002";
String OPERATOR_CODE_CHINA_UNICOM = "46001";
String OPERATOR_CODE_CHINA_TELECOM = "46003";
String DEFAULT_OPERATOR_NUMBER = "";
int getCurrentUsingSimNumber ();
int getPhoneCount ();
int getCurrentTrafficRunningSlotId();
void registerSimStateListener (SimStateListener listener);
void removeSimStateListener (SimStateListener listener);
boolean isSlotSimInserted (int slotId);
boolean isSlotOperatorSupportedBySdk(int slotId);
String getSlotOperatorName(int slotId);
String getSlotOperatorNumber(int slotId);
int getSubIdBySlotId(int slotId);
String getIMSIBySlotId(int slotId);
void setDataEnabled(boolean state);
}
- SimStateListener的用途
public interface SimStateListener {
String SS_ABSENT = "ABSENT";
String SS_READY = "READY";
// 監聽sim卡狀態變化的廣播回調,插拔卡是一種觸發場景
void onSimStateChanged (String simState);
// 需求中需要更新運營商,但是單純依賴sim卡廣播回調,不能獲取到運營商碼,且運營商碼的變更不會有sim卡狀態廣播發出,
// 所以自定義了一個運營商碼變更事件
void onSimOperatorCodeChanged(int slotId, String simValue);
}
SimStateListener的調用端代碼,定義在SimcardDataModel中,動態注冊一個監聽sim卡狀態的廣播,在廣播回調中被處理
private BroadcastReceiver mSimStateChangeReceiver = new BroadcastReceiver() {
@Override
public void onReceive (Context context, Intent intent) {
String action = intent.getAction();
String state = intent.getStringExtra(SIM_STATE_KEY);
if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
boolean hasSimStateReallyChanged = false;
for (int i = 0; i < mPhoneCount; i++) {
int originalState = mMSimState[i];
mMSimState[i] = TelephonyManager.getDefault().getSimState(i);
if (originalState != mMSimState[i]) {
hasSimStateReallyChanged = true;
}
Log.d(TAG, "original state " + originalState + " new state " + mMSimState[i]);
parseOperatorCode(i, getOperatorCode(i));
}
if (hasSimStateReallyChanged) {
notifySimStateChanged(state);
}
}
}
};
...
private void notifySimStateChanged (String simState) {
if (SimStateListener.SS_ABSENT.equals(simState) || SimStateListener.SS_READY.equals
(simState)) {
for (SimStateListener s : mSimStateListeners) {
s.onSimStateChanged(simState);
}
} else {
Log.e(TAG, "simState value " + simState + " not supported yet.");
}
}
- smsquery 短信查詢功能管理包
- SmsQueryStateManager 單例實現,用于整個流量模塊獲取和更新當前短信查詢的狀態
查詢同時對卡1和卡2的狀態做跟蹤,可能出現的值及初始化方式都定義在下面代碼中
- SmsQueryStateManager 單例實現,用于整個流量模塊獲取和更新當前短信查詢的狀態
public static final int SMS_QUERY_INITIALIZED = 100;
public static final int SMS_QUERY_PROCESSING = 101;
public static final int SMS_QUERY_FINISHED = 102;
public static final int SMS_QUERY_ERROR = 103;
private int slotOneSmsQueryState = SMS_QUERY_INITIALIZED;
private int slotTwoSmsQueryState = SMS_QUERY_INITIALIZED;
實現中發現,完全只靠內存中對象做記錄的話,會出現SmsQueryStateManager因為各種原因對象被重建導致短信查詢裝填出錯,一直提示查詢中的狀況,所以存儲設計為使用SharedPreference進行值的長時間保存
public synchronized int getSmsQueryStateBySlotId (int slotId) {
SharedPreferences sp = mAppContext.getSharedPreferences(KEY_QUERY_STATE_SAVER, Activity
.MODE_PRIVATE);
if (SimcardDataModelInterface.SIM_CARD_ONE_INDEX == slotId) {
slotOneSmsQueryState = getSavedQueryStateOnSlot(sp, KEY_QUERY_STATE_SLOT_ONE_SAVER);
return slotOneSmsQueryState;
} else if (SimcardDataModelInterface.SIM_CARD_TWO_INDEX == slotId) {
slotTwoSmsQueryState = getSavedQueryStateOnSlot(sp, KEY_QUERY_STATE_SLOT_TWO_SAVER);
return slotTwoSmsQueryState;
} else {
Log.e(TAG, "queried with invalid slotId");
return SMS_QUERY_ERROR;
}
}
public static synchronized void clearQueryState(Context mAppContext) {
SharedPreferences sp = mAppContext.getSharedPreferences(KEY_QUERY_STATE_SAVER, Activity
.MODE_PRIVATE);
SharedPreferences.Editor e = sp.edit();
e.putInt(KEY_QUERY_STATE_SLOT_ONE_SAVER, SMS_QUERY_INITIALIZED);
e.putInt(KEY_QUERY_STATE_SLOT_TWO_SAVER, SMS_QUERY_INITIALIZED);
e.apply();
}
public synchronized void setSmsQueryStateBySlotId (int slotId, int state) {
boolean isStateValueInvalid =
state != SMS_QUERY_PROCESSING
&& state != SMS_QUERY_FINISHED
&& state != SMS_QUERY_INITIALIZED;
if (isStateValueInvalid) {
Log.e(TAG, "set query state with invalid state value " + state);
return;
}
SharedPreferences sp = mAppContext.getSharedPreferences(KEY_QUERY_STATE_SAVER, Activity.MODE_PRIVATE);
SharedPreferences.Editor e = sp.edit();
slotOneSmsQueryState = getSavedQueryStateOnSlot(sp, KEY_QUERY_STATE_SLOT_ONE_SAVER);
slotTwoSmsQueryState = getSavedQueryStateOnSlot(sp, KEY_QUERY_STATE_SLOT_TWO_SAVER);
boolean changed = false;
if (SimcardDataModelInterface.SIM_CARD_ONE_INDEX == slotId) {
if (slotOneSmsQueryState != state) {
slotOneSmsQueryState = state;
changed = true;
e.putInt(KEY_QUERY_STATE_SLOT_ONE_SAVER, state);
e.apply();
}
} else if (SimcardDataModelInterface.SIM_CARD_TWO_INDEX == slotId) {
if (slotTwoSmsQueryState != state) {
slotTwoSmsQueryState = state;
changed = true;
e.putInt(KEY_QUERY_STATE_SLOT_TWO_SAVER, state);
e.apply();
}
} else {
Log.e(TAG, "set query state with invalid slotId");
}
Log.d(TAG, "notify smsQueryStateMananger state change " + changed + " on " + slotId);
if (changed) {
for (SmsQueryStateUpdater updater : mQueryStateList) {
updater.onSmsQueryStateValueUpdate(slotId, state);
}
} else {
// force update query finish state in case query is successfully but never change UI
// state.
if (slotOneSmsQueryState == SMS_QUERY_FINISHED) {
for (SmsQueryStateUpdater updater : mQueryStateList) {
updater.onSmsQueryStateValueUpdate(SimcardDataModelInterface
.SIM_CARD_ONE_INDEX, slotOneSmsQueryState);
}
} else if (slotTwoSmsQueryState == SMS_QUERY_FINISHED) {
for (SmsQueryStateUpdater updater : mQueryStateList) {
updater.onSmsQueryStateValueUpdate(SimcardDataModelInterface
.SIM_CARD_TWO_INDEX, slotTwoSmsQueryState);
}
}
Log.d(TAG, "state is " + state + "slot 1 " + slotOneSmsQueryState + " slot 2 " +
slotTwoSmsQueryState);
}
}
在主Activity,SecurityMainActivity退出時,也需要強制做一次數據重置,后續迭代更改了應用架構的話,要注意這一點
@Override
protected void onDestroy () {
super.onDestroy();
// when finish activity actively, reset sms query state as initialized.
if (hasRegisteredColorThemeChangeReceiver) {
unregisterReceiver(mChangeColorThemeReceiver);
hasRegisteredColorThemeChangeReceiver = false;
} else {
// do nothing.
}
SmsQueryStateManager sqm = SmsQueryStateManager.getInstance(getApplicationContext());
sqm.setSmsQueryStateBySlotId(
SimcardDataModelInterface.SIM_CARD_ONE_INDEX
, SmsQueryStateManager.SMS_QUERY_INITIALIZED);
sqm.setSmsQueryStateBySlotId(
SimcardDataModelInterface.SIM_CARD_TWO_INDEX
, SmsQueryStateManager.SMS_QUERY_INITIALIZED);
}
- statics 流量使用信息統計包
這個包是做第一個版本的流量詳情統計和流量權限開關時實現的,目前因需求調整不會上線,需要對無用代碼做清理,因為涉及到數據庫操作,需要避免不必要的影響 - trafficalarm 流量告警功能管理包
目前的基本思路是在app中注冊監聽發生數據行為的廣播,收到廣播后根據當前是否在使用數據流量而啟動流量使用狀況分析的服務,在后臺定時的查詢流量使用狀況,觸發了異常條件后進行對話框彈出提醒
該服務直接繼承于Service類,后續修改時要注意添加新分支后需要主動調用stop Service的相關方法流量告警功能結構圖- TrafficUsageAlarmReceiver說明
<receiver android:name="com.oneplus.security.network.trafficalarm.TrafficUsageAlarmReceiver"
android:exported="false"
android:enabled="true" >
<intent-filter>
<action android:name="android.net.conn.DATA_ACTIVITY_CHANGE"/>
</intent-filter>
</receiver>
@Override
public void onReceive (Context context, Intent intent) {
if (!FunctionUtils.isNetworkEnabled) {
return;
}
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if (!pm.isInteractive()) {
Log.i(TAG, "screen is off stop any data usage check process. buy action is " + intent
.getAction());
cancelAnyTrafficUsageService(context);
return;
}
String action = intent.getAction();
if (ConnectivityManager.ACTION_DATA_ACTIVITY_CHANGE.equals(action)) {
if (ConnectivityUtil.isMobileConnected(context)) {
boolean isDataNetworkActive = intent.getBooleanExtra(ConnectivityManager
.EXTRA_IS_ACTIVE, false);
Log.d(TAG, "state is " + isDataNetworkActive);
if (isDataNetworkActive) {
enableTrafficUsageAlertServices(context);
} else {
cancelAnyTrafficUsageService(context);
}
} else {
// is using wifi now, do nothing.
}
}
}
- TrafficUsageAlarmIntentService說明
在onCreate時進行必要的資源初始化操作
@Override
public void onCreate () {
super.onCreate();
Log.d(TAG, "create usage alarm service");
mHandler = new Handler(this);
mSimcardDataModel = SimcardDataModel.getInstance(getApplicationContext());
mCurrentSlotId = mSimcardDataModel.getCurrentTrafficRunningSlotId();
if (mCurrentSlotId < 0) {
Log.e(TAG, "query data alert with invalid slotId.");
}
mOperatorDataModel = OperatorDataModelFactory.getOperatorDataModel(getApplicationContext
(), mCurrentSlotId, mSimcardDataModel.isSlotOperatorSupportedBySdk(mCurrentSlotId));
mOperatorDataModel.addTrafficUsageUpdater(this);
}
在onStartCommand時,調用OperatorModelInterface上的異步查詢流量使用情況的方法
@Override
public int onStartCommand (Intent intent, int flags, int startId) {
mOperatorDataModel.requesetPkgMonthlyUsageAndTotalInByte(mCurrentSlotId);
mServiceStartedAction = intent.getAction();
return super.onStartCommand(intent, flags, startId);
}
在流量總量和月用量的回調時,進行是否需要發出流量使用警告的邏輯判斷,跟據當前的slotId,對應slotId上面的流量警告配置狀況做決定,如果需要進行彈框提醒,則發消息給主線程的Handler處理
@Override
public void onTrafficTotalAndUsedUpdate (long totalByte, long usedbyte, int slotId) {
if (null == mHandler) {
stopSelf();
return;
}
Log.d(TAG, "total is " + totalByte + " used is " + usedbyte);
if (totalByte < 0) {
Log.d(TAG, "total pkg usage returned is invalid");
return;
}
boolean isPkgRunOut = usedbyte >= totalByte;
boolean hasLeftLessThanTenPercent = ((totalByte - usedbyte) * 10 - totalByte) < 0;
if (ACTION_CONFIRM_WHETHER_TO_START_DATA_ALARM.equals(mServiceStartedAction)) {
final Context c = TrafficUsageAlarmIntentService.this;
boolean hasLeftLessThanTwentyPercent = (totalByte - usedbyte) * 5 - totalByte < 0;
// less than 30 MB.
final long LOW_DATA_LIMIT = 30 * 1024 * 1024L;
boolean isRemainingDataLessThan30Mb = totalByte - usedbyte < LOW_DATA_LIMIT;
if (isRemainingDataLessThan30Mb) {
TrafficUsageAlarmUtils.startTrafficUsageRunningOutService(c);
} else {
TrafficUsageAlarmUtils.cancelTrafficUsageRunningOutService(c);
}
if (hasLeftLessThanTwentyPercent && !hasLeftLessThanTenPercent) {
TrafficUsageAlarmUtils.startAlertTenPercentLeftService(c);
}
if (isPkgRunOut) {
TrafficUsageAlarmUtils.cancelAnyTrafficRunningOutCheckService
(TrafficUsageAlarmIntentService.this);
}
}
if (isPkgRunOut) {
// pkg run off.
if (TrafficUsageAlarmUtils.shouldAlertTrafficRunningOut(TrafficUsageAlarmIntentService
.this, mCurrentSlotId)) {
Message message = mHandler.obtainMessage(MESSAGE_ALERT_PKG_RUN_OUT);
mHandler.sendMessage(message);
} else if (TrafficUsageAlarmUtils.shouldAlertTrafficRunningOutAutoClose
(TrafficUsageAlarmIntentService.this, mCurrentSlotId)) {
Message message = mHandler.obtainMessage(MESSAGE_ALERT_PKG_RUN_OUT);
mHandler.sendMessage(message);
} else {
stopSelf();
}
} else {
if (hasLeftLessThanTenPercent) {
if (TrafficUsageAlarmUtils.shouldAlertLessThanTenPercentPkgLeft
(TrafficUsageAlarmIntentService.this, mCurrentSlotId)) {
Message message = mHandler.obtainMessage(MESSAGE_ALERT_TEN_PERCENT_LOW);
mHandler.sendMessage(message);
}
} else {
stopSelf();
}
}
}
主線程中的Handler回調,用于真正啟動警告窗口
@Override
public boolean handleMessage (Message msg) {
switch (msg.what) {
case MESSAGE_ALERT_PKG_RUN_OUT:
boolean isAutoCloseNetworkEnabled = TrafficUsageAlarmUtils
.getAutoCloseNetworkWhenDataRunningOut(this, false, mCurrentSlotId);
if (isAutoCloseNetworkEnabled) {
showPkgRunningOutAutoCloseAlertDialog();
} else {
showPkgRunningOutAlertDialog();
}
break;
case MESSAGE_ALERT_TEN_PERCENT_LOW:
showTenPercentLeftAlertDialog();
break;
default:
stopSelf();
break;
}
return false;
}
對于對話框提醒的部分,設計了提醒過之后就不再提醒的功能,以及發生過流量校準,及手工修改流量后,清空不再提醒標志的功能,后續涉及到相關邏輯的修改,以及增加新功能時,需要關注到
目前我想到還沒做的 重新設置開關后,是否需要重置 已發出對話框提醒的標志
public static boolean getTenPercentPkgLeftAlert (Context context, boolean defaultValue, int slotId) {
if (SimcardDataModelInterface.INVALID_SIM_SLOT_ID == slotId) {
return false;
}
return getTrafficUsagePreferenceBooleanValue(context,
getTenPercentDataLeftConfigKey(slotId), defaultValue);
}
public static void setHasAlertedTenPercentLeft (Context context, boolean state, int slotId) {
if (SimcardDataModelInterface.SIM_CARD_ONE_INDEX == slotId) {
setTrafficUsagePreferenceBooleanValue(context, KEY_HAS_SIM_1_ALERT_TEN_PERCENT_LEFT, state);
} else if (SimcardDataModelInterface.SIM_CARD_TWO_INDEX == slotId) {
setTrafficUsagePreferenceBooleanValue(context, KEY_HAS_SIM_2_ALERT_TEN_PERCENT_LEFT,
state);
} else {
logOutUsingInvalidSlotId();
}
}
- trafficinfo 基于原生流量統計的原生流量信息獲取包
public interface TrafficDataModelInterface {
long getDataUsageWithinSpecificTime (int slotId, long start, long end);
long getDailyUsageBySlotId (int slotId, long monthlyUsage, int accountDay);//不再需要日已用提醒,所以該接口實際已無用了
void clearTrafficData();
}