流量管理工作总结

流量管理的位置在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 运营商省份更新接口![根据sim卡id更新省份信息,仅用于欧朋sdk中生效](http://upload-images.jianshu.io/upload_images/1437965-7d43d324d630bf58.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  * OperatorBrandUpdater 运营商品牌更新接口
运营商品牌更新接口,比如“全球通”,“动感地带”等,仅用于欧朋sdk

* OperatorPackageUsageUpdater 运营商流量使用情况更新接口


早起定义了1,2,4三个接口,后期使用接口3直接替代1,2但声明尚未删除

* OperatorAccountDayUpdater 运营商月结日更新接口


根据sim卡卡槽更新月结日,早期有绑定欧朋sdk,后面迭代过程中,欧朋不再支持月结日变更,全部切换到本地管理的月结日逻辑中

本包中定义的两个本地月结日管理相关类

* 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();
OpertorPrefModel作为抽象类定义上述三个抽象方法,实现放在具体类中,用于获取双卡工作时需要拿到的Preference key

OperatorPrefModel中对SimPreferenceValueUpdater的处理

相关类截图

* 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卡的相关信息,使用接口定义,看的更加清楚吧,避免陷入实现细节
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.");
        }
    }
SimStateListener在项目中的实现类
  • smsquery 短信查询功能管理包
    • SmsQueryStateManager 单例实现,用于整个流量模块获取和更新当前短信查询的状态
      查询同时对卡1和卡2的状态做跟踪,可能出现的值及初始化方式都定义在下面代码中
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();
}
原生流量信息统计模型
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容