AndroidX 做为一个后台 Service 应用,开机自动运行,配合系统做一些定制化功能,并且对外提供 API。
主要功能:
● 硬件看门狗代理
● USB Host/Device 切换
● 4G 网络保活
● 系统日志写入文件
● 键值拦截
● 启用应用
项目地址:https://github.com/aystshen/AndroidX
硬件看门狗代理主要负责下面几项工作:
● 对外提供看门狗 API,比如:打开关闭看门狗、设置超时时长、获取超时时长、判断看门狗是否打开。
● 定时发送看门狗心跳(喂狗)。
● 恢复看门狗状态(因进入 OTA 升级和用户关机时,会临时关闭看门狗,当升级完成或重新开机需要恢复看门狗状态)。
// 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 功能,使同一个 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。
// 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 模块重启工作。
// 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
// 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』键,然后输入预设的密码(默认密码:『上上下下左右左右』)才可以解锁后使用。
// 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