知识储备
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、完