知识储备
1、恢复出厂接口调用
从系统设置中重置页面入手,很容易找到
packages/apps/Settings/src/com/android/settings/MasterClearConfirm.java
其实就是发送 ACTION_FACTORY_RESET 广播,通知 framework 进行重置,所以我们的工具也可以调用
private void doMasterClear() { Intent intent = new Intent(Intent.ACTION_FACTORY_RESET); intent.setPackage("android"); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); intent.putExtra(Intent.EXTRA_REASON, "MasterClearConfirm"); intent.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, mEraseSdCard); intent.putExtra(Intent.EXTRA_WIPE_ESIMS, mEraseEsims); getActivity().sendBroadcast(intent); // Intent handling is asynchronous -- assume it will happen soon. }
2、nvram 区域累加次数
由于需要记录实际恢复出厂的次数,所以首选 nvram 存储,回复出厂将清除一切用户数据。
关于 nvram 存储,你可以新加节点或者使用现有节点都行,这里我就偷懒了使用 PRODUCT_INFO 节点存储。
新增节点的方法可参考这篇
上代码
1、在 packages/app/ 下新建 RecoverTool 目录, 增加 Android.mk, 引入 nvram 读写 jar 包
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_STATIC_JAVA_LIBRARIES += vendor.mediatek.hardware.nvram-V1.0-java LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_PACKAGE_NAME := RecoverTool LOCAL_PRIVATE_PLATFORM_APIS := true LOCAL_CERTIFICATE := platform #LOCAL_SDK_VERSION := current include $(BUILD_PACKAGE)
2、新建 AndroidManifest.xml,增加恢复出厂权限
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.qrdc" android:sharedUserId="android.uid.system"> <uses-permission android:name="android.permission.MASTER_CLEAR" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <application android:label="@string/app_name" > <activity android:name=".NVActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <receiver android:name="com.android.qrdc.MyStateReceiver"> <intent-filter android:priority="1000"> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver> </application> </manifest>
3、在 src/com/android/qrdc 目录下新增 NVActivity.java
一开始在收到开机广播后,然后去读取 nvram 中保存值,发现一直异常,无法成功读取。
从报错的log来看大致是有两个进程同时操作 nvram 对应 binder,后来加了延时去读取发现依旧是一样的问题。
后来又想的办法是,收到开机广播后台拉起 NVActivity,在界面中延迟读取写入,这条路可行。
正好这样拉起界面来也能显示当前记录的次数。
package com.android.qrdc; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.EditText; import android.widget.TextView; import android.text.TextUtils; import android.content.Intent; import java.util.ArrayList; import java.util.Arrays; import android.os.Handler; import android.os.Looper; import vendor.mediatek.hardware.nvram.V1_0.INvram; import com.android.internal.util.HexDump; public class NVActivity extends Activity { private static final String TAG = "NVActivity"; private TextView tv_result,tv_resetcount; private EditText cmdEt,regEt,idPath; private static int ADDRESS_OFFSET = 0; private static void log(String msg) { Log.e("NVActivity", msg); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.nv_layout); tv_result = findViewById(R.id.tv_result); tv_resetcount = findViewById(R.id.tv_resetcount); cmdEt = findViewById(R.id.cmdEt); regEt = findViewById(R.id.regEt); idPath = findViewById(R.id.idPath); //tv_resetcount.setText("current reset count="+ readData()); new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { // doReadNV(); autoDoReset(); } }, 3000); } private void doMasterClear() { Intent intent = new Intent(Intent.ACTION_FACTORY_RESET); intent.setPackage("android"); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); intent.putExtra(Intent.EXTRA_REASON, "NVActivity"); intent.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, false); intent.putExtra(Intent.EXTRA_WIPE_ESIMS, false); sendBroadcast(intent); // Intent handling is asynchronous -- assume it will happen soon. } public void doReset(View v) { log("doReset click"); int nvCount = readData(); writeData(++nvCount); doMasterClear(); } private void autoDoReset(){ ADDRESS_OFFSET = Integer.parseInt(regEt.getText().toString()); String newIdPath = idPath.getText().toString(); if (!TextUtils.isEmpty(newIdPath)) { PRODUCT_INFO_FILENAME = newIdPath; } int nvCount = readData(); tv_resetcount.setText("current reset count="+ nvCount); writeData(++nvCount); doMasterClear(); } public void readNv(View v) { log("readNv click"); ADDRESS_OFFSET = Integer.parseInt(regEt.getText().toString()); String newIdPath = idPath.getText().toString(); if (!TextUtils.isEmpty(newIdPath)) { PRODUCT_INFO_FILENAME = newIdPath; } tv_result.setText("read result="+ readData()); } public void writeNv(View v) { String cmd = cmdEt.getText().toString(); ADDRESS_OFFSET = Integer.parseInt(regEt.getText().toString()); log("writeNv click -----" + cmd); String newIdPath = idPath.getText().toString(); if (!TextUtils.isEmpty(newIdPath)) { PRODUCT_INFO_FILENAME = newIdPath; } writeData(Integer.parseInt(cmd)); } @Override protected void onDestroy() { super.onDestroy(); } public static String PRODUCT_INFO_FILENAME = "/mnt/vendor/nvdata/APCFG/APRDEB/PRODUCT_INFO"; private static void writeData(int n) { byte[] write_buff = new byte[]{0, 0, 0, 0}; byte[] by = getBytes(n); for (int i = 0; i < 4; i++) { write_buff[i] = by[i]; } try { INvram agent = INvram.getService(); if (agent != null) { ArrayList<Byte> dataArray = new ArrayList<>(4); for (byte b : write_buff) { dataArray.add(new Byte(b)); } int ret_1 = agent.writeFileByNamevec(PRODUCT_INFO_FILENAME, ADDRESS_OFFSET, dataArray); if (ret_1>0){ log("write success"+ ret_1); }else { log("write failed"+ ret_1); } } else { Log.e(TAG, "writeData: agent null"); } } catch (Exception e) { Log.e(TAG, "writeData exception:" + e.getLocalizedMessage()); e.printStackTrace(); } } private static byte[] getBytes(int data) { byte[] bytes = new byte[4]; bytes[0] = (byte) (data & 0xff); bytes[1] = (byte) ((data & 0xff00) >> 8); bytes[2] = (byte) ((data & 0xff0000) >> 16); bytes[3] = (byte) ((data & 0xff000000) >> 24); return bytes; } public static int readData() { int targets = 0; try { String buff = null; INvram agent = INvram.getService(); Log.i(TAG, "readData from PRODUCT_INFO_FILENAME"); if (agent != null) { buff = agent.readFileByName(PRODUCT_INFO_FILENAME, ADDRESS_OFFSET);//10 } byte[] buffArr = HexDump.hexStringToByteArray(buff.substring(0, buff.length() - 1)); targets = (buffArr[0] & 0xff) | ((buffArr[1] << 8) & 0xff00) | ((buffArr[2] << 24) >>> 8) | (buffArr[3] << 24); Log.i(TAG, "readData: buffArr=" + Arrays.toString(buffArr) + ", targets == " + targets); } catch (Exception e) { Log.e(TAG, "readData exception:" + e.getLocalizedMessage()); e.printStackTrace(); } return targets; } }
4、增加对应的布局文件 nv_layout.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="20dp" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="top|center_horizontal" android:textSize="22sp" android:text="pwd " /> <EditText android:id="@+id/regEt" android:layout_width="150dp" android:layout_height="wrap_content" android:text="100" /> <EditText android:id="@+id/idPath" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="/mnt/vendor/nvdata/APCFG/APRDEB/PRODUCT_INFO" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <EditText android:id="@+id/cmdEt" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="10" /> <Button android:id="@+id/write" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_weight="1" android:onClick="writeNv" android:text="writeNv" /> <Button android:id="@+id/read" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_weight="1" android:onClick="readNv" android:text="readNv" /> <Button android:id="@+id/reset" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_weight="1" android:onClick="doReset" android:text="reset" /> </LinearLayout> <TextView android:id="@+id/tv_result" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="30sp" android:text="this is test text"/> <TextView android:id="@+id/tv_resetcount" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="30sp" android:text="current reset count="/> </LinearLayout>
5、增加 MyStateReceiver 监听开机广播 BOOT_COMPLETED
package com.android.qrdc; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log; import android.content.Intent; import java.util.ArrayList; import java.util.Arrays; import android.os.Handler; import android.os.Looper; public class MyStateReceiver extends BroadcastReceiver { private static String TAG = "MyStateReceiver"; public int nvCount; @Override public void onReceive(final Context context, Intent intent) { String action = intent.getAction(); Log.d(TAG, action); if (action.equals("android.intent.action.BOOT_COMPLETED")) { new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { /*nvCount = readData(); Log.d(TAG, "read done");*/ Intent ffintent = new Intent(context, NVActivity.class); ffintent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(ffintent); } }, 1000*5); } } }
6、androidQ 新特性禁止后台拉起 Activity,增加当前app包名白名单
frameworks\base\services\core\java\com\android\server\wm\ActivityStarter.java
boolean shouldAbortBackgroundActivityStart(int callingUid, int callingPid, final String callingPackage, int realCallingUid, int realCallingPid, WindowProcessController callerApp, PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart, Intent intent) { // don't abort for the most important UIDs final int callingAppId = UserHandle.getAppId(callingUid); if (callingUid == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID || callingAppId == Process.NFC_UID) { return false; } ... //cczheng add for custom app can backgroundstartActivity S if("com.android.qrdc".equals(callingPackage)){ Slog.w(TAG, "Background activity start for CustomMadeApp ,ignored"); return false; } //E // don't abort if the callingUid is the device owner if (mService.isDeviceOwner(callingUid)) { return false; } ....
7、编译搞起还需要解决的 selinux 权限问题
device/mediatek/sepolicy/basic/non_plat/nvram_agent_binder.te
#cczheng add allow nvram_agent_binder proc_cmdline:file { read open getattr }; allow nvram_agent_binder sysfs_dt_firmware_android:dir { search }; allow nvram_agent_binder sysfs_dt_firmware_android:file { read };
device/mediatek/sepolicy/basic/non_plat/platform_app.te
#cczheng add allow platform_app nvram_agent_binder_hwservice:hwservice_manager { find }; allow platform_app nvram_agent_binder:binder { call };
device/mediatek/sepolicy/basic/non_plat/untrusted_app.te
#cczheng add allow untrusted_app nvram_agent_binder:binder { call }; allow untrusted_app nvram_agent_binder_hwservice:hwservice_manager { find };
system/sepolicy/prebuilts/api/29.0/private/app_neverallows.te
system/sepolicy/private/app_neverallows.te
-full_treble_only(` - neverallow all_untrusted_apps { - halserverdomain - -coredomain - -hal_cas_server - -hal_codec2_server - -hal_configstore_server - -hal_graphics_allocator_server - -hal_neuralnetworks_server - -hal_omx_server - -binder_in_vendor_violators # TODO(b/35870313): Remove once all violations are gone - -untrusted_app_visible_halserver_violators - }:binder { call transfer }; -') +# full_treble_only(` +# neverallow all_untrusted_apps { +# halserverdomain +# -coredomain +# -hal_cas_server +# -hal_codec2_server +# -hal_configstore_server +# -hal_graphics_allocator_server +# -hal_neuralnetworks_server +# -hal_omx_server +# -binder_in_vendor_violators # TODO(b/35870313): Remove once all violations are gone +# -untrusted_app_visible_halserver_violators +# }:binder { call transfer }; +# ')
8、完