【干货】Android系统定制基础篇:第六部分-Android扩展服务-AndroidX

AndroidX 做为一个后台 Service 应用,开机自动运行,配合系统做一些定制化功能,并且对外提供 API。

主要功能:
● 硬件看门狗代理
● USB Host/Device 切换
● 4G 网络保活
● 系统日志写入文件
● 键值拦截
● 启用应用

项目地址:https://github.com/aystshen/AndroidX

硬件看门狗代理

硬件看门狗代理主要负责下面几项工作:
● 对外提供看门狗 API,比如:打开关闭看门狗、设置超时时长、获取超时时长、判断看门狗是否打开。
● 定时发送看门狗心跳(喂狗)。
● 恢复看门狗状态(因进入 OTA 升级和用户关机时,会临时关闭看门狗,当升级完成或重新开机需要恢复看门狗状态)。

API

// IWatchdogService.aidl
package com.ayst.androidx;

// Declare any non-default types here with import statements

interface IWatchdogService {
    boolean openWatchdog();
    boolean closeWatchdog();
    boolean setWatchdogTimeout(int timeout);
    int getWatchdogTimeout();
    boolean watchdogIsOpen();
}

使用

1、在 APP 源码 aidl/android/os/ 目录下新建 IWatchdogService.aidl,如下:

// IWatchdogService.aidl
package com.ayst.androidx;

// Declare any non-default types here with import statements

interface IWatchdogService {
    boolean openWatchdog();
    boolean closeWatchdog();
    boolean setWatchdogTimeout(int timeout);
    int getWatchdogTimeout();
    boolean watchdogIsOpen();
}

2.实现下面代码:

Intent intent = new Intent();
intent.setPackage("com.ayst.androidx");
intent.setAction("com.ayst.androidx.WATCHDOG_SERVICE");
mContext.bindService(intent, mWatchdogServiceConnection, Context.BIND_AUTO_CREATE);

private ServiceConnection mWatchdogServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.d(TAG, "IWatchdogService, onServiceConnected...");
        mWatchdogService = IWatchdogService.Stub.asInterface(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.d(TAG, "IWatchdogService, onServiceDisconnected...");
        mWatchdogService = null;
    }
};

/**
 * 打开、关闭看门狗
 *
 * @param on
 */
public void toggleWatchdog(boolean on) {
    if (null != mWatchdogService) {
        try {
            if (on) {
                mWatchdogService.openWatchdog();
            } else {
                mWatchdogService.closeWatchdog();
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 设置看门狗超时时长
 *
 * @param timeout
 */
public void setWatchdogTimeout(int timeout) {
    if (null != mWatchdogService) {
        try {
            mWatchdogService.setWatchdogTimeout(timeout);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

USB Host/Device 切换

通常为了复用 USB 功能,使同一个 USB 口可以同时做为 Host 和 Device(调试) 口使用,我们可以通过软件来切换功能。

Android 默认提供了切换方法,不同的系统文件路径可能不同,如下:

# 路径1,向下面文件写入:0:自动,1:HOST,2:OTG
/sys/bus/platform/drivers/usb20_otg/force_usb_mode

# 路径2, 向下面文件写入:peripheral:自动,host:HOST,otg:OTG
/sys/devices/platform/ff770000.syscon/ff770000.syscon:usb2-phy@e450/otg_mode

这里为了提供更统一的方法,我们封装 Java API。

API

// IOtgService.aidl
package com.ayst.androidx;

// Declare any non-default types here with import statements

interface IOtgService {
    boolean setOtgMode(String mode);
    String getOtgMode();
}

1、使用
在 APP 源码 aidl/android/os/ 目录下新建 IOtgService.aidl,如下:

// IOtgService.aidl
package com.ayst.androidx;

// Declare any non-default types here with import statements

interface IOtgService {
    boolean setOtgMode(String mode);
    String getOtgMode();
}

2.实现下面代码:

Intent intent = new Intent();
intent.setPackage("com.ayst.androidx");
intent.setAction("com.ayst.androidx.OTG_SERVICE");
mContext.bindService(intent, mOtgServiceConnection, Context.BIND_AUTO_CREATE);

private ServiceConnection mOtgServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.d(TAG, "IOtgService, onServiceConnected...");
        mOtgService = IOtgService.Stub.asInterface(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.d(TAG, "IOtgService, onServiceDisconnected...");
        mOtgService = null;
    }
};

/**
 * 调otg口usb工作模式
 *
 * @param mode
 *      0:auto,由硬件决定
 *      1:host,usb模式
 *      2:device,otg调试模式
 */
public void setOtgMode(String mode) {
    if (null != mOtgService) {
        try {
            if (!mOtgService.setOtgMode(mode)) {
                mAndroidXView.updateAndroidXOtgMode(mOtgService.getOtgMode());
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 获取当前otg模式
 *
 * @return
 *      0:auto,由硬件决定
 *      1:host,usb模式
 *      2:device,otg调试模式
 */
public String getOtgMode() {
    if (null != mOtgService) {
        try {
            return mOtgService.getOtgMode();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    return "0";
}

4G 网络保活
考虑到一些 4G 模块长时间运行的稳定性,可能会出现 4G 模块工作异常而导致网络断开并无法恢复。为了保证 4G 网络在长时间掉网后能够自行恢复,增加 4G 网络保活服务,当网络长时间掉网并不能自恢复时,直接复位 4G 模块,使 4G 模块重启工作。

API

// IModemService.aidl
package com.ayst.androidx;

// Declare any non-default types here with import statements

interface IModemService {
    boolean open4gKeepLive();
    boolean close4gKeepLive();
    boolean keepLiveIsOpen();
}

1.在 APP 源码 aidl/android/os/ 目录下新建 IModemService.aidl,如下:

// IModemService.aidl
package com.ayst.androidx;

// Declare any non-default types here with import statements

interface IModemService {
    boolean open4gKeepLive();
    boolean close4gKeepLive();
    boolean keepLiveIsOpen();
}

2.实现下面代码:.

Intent intent = new Intent();
intent.setPackage("com.ayst.androidx");
intent.setAction("com.ayst.androidx.MODEM_SERVICE");
mContext.bindService(intent, mModemServiceConnection, Context.BIND_AUTO_CREATE);

private ServiceConnection mModemServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.d(TAG, "IModemService, onServiceConnected...");
        mModemService = IModemService.Stub.asInterface(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.d(TAG, "IModemService, onServiceDisconnected...");
        mModemService = null;
    }
};

/**
 * 打开、关闭4G保活服务
 *
 * @param on
 */
public void toggle4GKeepLive(boolean on) {
    if (null != mModemService) {
        try {
            if (on) {
                mModemService.open4gKeepLive();
            } else {
                mModemService.close4gKeepLive();
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

系统日志写入文件

为了方便产品上线后 debug,我们默认将 logcat 日志和内核日志写入文件保存,debug 时可以随时导出分析问题。日志文件采用循环保存,默认最多保存10个日志文件,支持设置,单个日志文件最大20M。

日志路径:/sdcard/lastlog/

cnnho:/sdcard/lastlog/android $ ls -l
total 12880
-rw-rw---- 1 root sdcard_rw 1288359 2020-03-02 17:26 2020-03-02_17-23-09.log
-rw-rw---- 1 root sdcard_rw 1301092 2020-03-02 17:31 2020-03-02_17-26-44.log
-rw-rw---- 1 root sdcard_rw 1466984 2020-03-02 17:40 2020-03-02_17-32-23.log
-rw-rw---- 1 root sdcard_rw 1212094 2020-03-02 17:43 2020-03-02_17-41-11.log
-rw-rw---- 1 root sdcard_rw 1328673 2020-03-02 17:49 2020-03-02_17-44-05.log
-rw-rw---- 1 root sdcard_rw 2403373 2020-03-02 18:38 2020-03-02_17-50-00.log
-rw-rw---- 1 root sdcard_rw   79347 2020-03-02 18:41 2020-03-02_18-38-16.log
-rw-rw---- 1 root sdcard_rw 1292712 2020-03-02 18:53 2020-03-02_18-49-41.log
-rw-rw---- 1 root sdcard_rw 1567416 2020-03-02 19:06 2020-03-02_18-53-59.log
-rw-rw---- 1 root sdcard_rw 1227984 2020-03-02 19:09 2020-03-02_19-07-29.log

API

// ILog2fileService.aidl
package com.ayst.androidx;

// Declare any non-default types here with import statements

interface ILog2fileService {
    void openLog2file();
    void closeLog2file();
    boolean isOpen();
    boolean setLogFileNum(int num);
    int getLogFileNum();
}

1.在 APP 源码 aidl/android/os/ 目录下新建 ILog2fileService.aidl,如下:

// ILog2fileService.aidl
package com.ayst.androidx;

// Declare any non-default types here with import statements

interface ILog2fileService {
    void openLog2file();
    void closeLog2file();
    boolean isOpen();
    boolean setLogFileNum(int num);
    int getLogFileNum();
}

2.实现下面代码:

Intent intent = new Intent();
intent.setPackage("com.ayst.androidx");
intent.setAction("com.ayst.androidx.LOG2FILE_SERVICE");
mContext.bindService(intent, mLog2fileServiceConnection, Context.BIND_AUTO_CREATE);

private ServiceConnection mLog2fileServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.d(TAG, "ILog2fileService, onServiceConnected...");
        mLog2fileService = ILog2fileService.Stub.asInterface(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.d(TAG, "ILog2fileService, onServiceDisconnected...");
        mLog2fileService = null;
    }
};

/**
 * 打开、关闭日志写入文件
 *
 * @param on true:打开 false:关闭
 */
public void toggleLog2file(boolean on) {
    if (null != mLog2fileService) {
        try {
            if (on) {
                mLog2fileService.openLog2file();
            } else {
                mLog2fileService.closeLog2file();
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 设置最大保存日志文件数
 *
 * @param num 最大日志文件数
 * @return true:成功 false:失败
 */
private boolean setLogFileNum(int num) {
    if (null != mLog2fileService) {
        try {
            return mLog2fileService.setLogFileNum(num);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
    return false;
}

/**
 * 获取最大保存日志文件数
 *
 * @return 最大日志文件数
 */
private int getLogFileNum() {
    if (null != mLog2fileService) {
        try {
            return mLog2fileService.getLogFileNum();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
    return 0;
}

键值拦截

一些支持遥控器操作的产品上,默认只允许管理员使用遥控器操作设备,普通用户只能操作『上下左右』键,当操作其它键时需要先按『NUM_LOCK』键,然后输入预设的密码(默认密码:『上上下下左右左右』)才可以解锁后使用。

API

// IKeyInterceptService.aidl
package com.ayst.androidx;

// Declare any non-default types here with import statements

interface IKeyInterceptService {
    void openKeyIntercept();
    void closeKeyIntercept();
    boolean isOpen();
}

使用
1.在 APP 源码 aidl/android/os/ 目录下新建 IKeyInterceptService.aidl,如下:

// IKeyInterceptService.aidl
package com.ayst.androidx;

// Declare any non-default types here with import statements

interface IKeyInterceptService {
    void openKeyIntercept();
    void closeKeyIntercept();
    boolean isOpen();
}

2.实现下面代码:

Intent intent = new Intent();
intent.setPackage("com.ayst.androidx");
intent.setAction("com.ayst.androidx.KEY_INTERCEPT_SERVICE");
mContext.bindService(intent, mKeyInterceptServiceConnection, Context.BIND_AUTO_CREATE);

private ServiceConnection mKeyInterceptServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.d(TAG, "IKeyInterceptService, onServiceConnected...");
        mIKeyInterceptService = IKeyInterceptService.Stub.asInterface(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.d(TAG, "IKeyInterceptService, onServiceDisconnected...");
        mIKeyInterceptService = null;
    }
};

/**
 * 打开、关闭键值拦截
 *
 * @param on
 */
public void toggleKeyIntercept(boolean on) {
    if (null != mIKeyInterceptService) {
        try {
            if (on) {
                mIKeyInterceptService.openKeyIntercept();
            } else {
                mIKeyInterceptService.closeKeyIntercept();
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

启用应用

一些产品为了方便作业人员安装测试设备,需要临时停用某些应用,有时会忘记恢复,从而导致后面客户应用无法运行的问题。

该方案在通过属性『ro.enableapps』配置开机后需要自动启用的应用包名,开机后运行Service重新启用之前停用的应用。

关键实现

使用 PackageManager 提供的方法:

/**
 * Set the enabled setting for an application
 * This setting will override any enabled state which may have been set by the application in
 * its manifest.  It also overrides the enabled state set in the manifest for any of the
 * application's components.  It does not override any enabled state set by
 * {@link #setComponentEnabledSetting} for any of the application's components.
 *
 * @param packageName The package name of the application to enable
 * @param newState The new enabled state for the application.
 * @param flags Optional behavior flags.
 */
public abstract void setApplicationEnabledSetting(String packageName,
        @EnabledState int newState, @EnabledFlags int flags);
public class AppEnableService extends Service {
    private static final String TAG = "AppEnableService";

    private String[] mEnableApps;
    private Thread mEnableThread;

    public AppEnableService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand...");

        mEnableApps = loadApps();

        if (null != mEnableApps && mEnableApps.length > 0) {
            mEnableThread = new Thread(mEnableRunnable);
            mEnableThread.start();
        } else {
            Log.w(TAG, "onStartCommand, There is no application to be enabled.");
        }

        return Service.START_REDELIVER_INTENT;
    }

    private String[] loadApps() {
        String value = AppUtils.getProperty("ro.enableapps", "");
        if (!TextUtils.isEmpty(value)) {
            return value.split(",");
        }

        return null;
    }

    private Runnable mEnableRunnable = new Runnable() {
        @Override
        public void run() {
            PackageManager pm = getPackageManager();
            for (String pkgName : mEnableApps) {
                if (!TextUtils.isEmpty(pkgName)
                && pkgName.contains(".")) {
                    pm.setApplicationEnabledSetting(
                            pkgName,
                            PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
                            0);

                    Log.i(TAG, pkgName + " Enabled.");
                }
            }
        }
    };
}

属性配置

通过属性『ro.enableapps』配置应用包名,支持同时配置多个包名,用逗号隔开。例如:

ro.enableapps=com.ayst.sample1,com.ayst.sample2

你可能感兴趣的:(Android,android,androidx)