lifecycle, onCompleteCallback callback) {
- getDesktopIconObservable()
+ /**
+ * 应用 getIconControl 接口下发的应用管控清单。
+ *
+ * 清单为空(接口失败或没有数据)表示接口没有任何管控:上次下发的禁用清单会被清空(应用恢复显示),
+ * 未被接口提及的应用恢复为本地默认禁用。
+ *
+ * 注意:应用图标禁用(AOLE_ACTION_HIDE_DESKTOP_ICON)与 getSnAppAttr 的在线禁用
+ * (AOLE_ACTION_DISABLE_APP)是两套独立数据,分开存放。
+ *
+ * @param disablePkgs 需要禁用的应用(control_type = 0)
+ * @param showPkgs 需要显示的应用(control_type = 1)
+ */
+ private void applyIconControl(List disablePkgs, List showPkgs) {
+ //旧的禁用清单
+ Set oldHidePkgs = ApkUtils.getDesktopHidePkgs(mContext);
+ //本次要禁用的包名(含不同机型同功能应用的包名映射),持久化后 showAllApp 会跳过这些应用,避免图标被重新显示
+ Set hidePkgs = expandAlias(disablePkgs);
+ //1、先保存接口数据:后续放开判断(isKeepDisableByOtherSource)要读到最新的显示清单,
+ // 否则接口要求显示的应用会被"本地默认禁用"挡住
+ String hideString = TextUtils.join(",", hidePkgs);
+ String showString = TextUtils.join(",", showPkgs);
+ Log.e("getIconControl", "applyIconControl: disable = " + hideString + ", show = " + showString);
+ Settings.Global.putString(mContext.getContentResolver(),
+ CommonConfig.AOLE_ACTION_HIDE_DESKTOP_ICON,
+ TextUtils.isEmpty(hideString) ? "null" : hideString);
+ Settings.Global.putString(mContext.getContentResolver(),
+ CommonConfig.AOLE_ACTION_SHOW_DESKTOP_ICON,
+ TextUtils.isEmpty(showString) ? "null" : showString);
+ //2、按清单禁用
+ for (String pkg : hidePkgs) {
+ JgyUtils.getInstance().hideApp(pkg);
+ }
+ //3、按清单显示(被其他管控来源要求禁用的除外)
+ for (String pack : showPkgs) {
+ showAppIfAllowed(pack);
+ }
+ //4、上次禁用、本次既没要求禁用也没要求显示的应用,恢复显示
+ for (String pkg : oldHidePkgs) {
+ if (hidePkgs.contains(pkg) || showPkgs.contains(pkg)) {
+ continue;
+ }
+ showAppIfAllowed(pkg);
+ }
+ //5、接口清单本次既没要求禁用也没要求显示的应用,恢复本地默认禁用
+ // (例如后台删除了某应用的管控记录,它应回到机型的默认禁用状态)
+ for (String pkg : ApkUtils.getEffectiveDefaultDisableApp(mContext)) {
+ if (hidePkgs.contains(pkg) || showPkgs.contains(pkg) || ApkUtils.isDisable(mContext, pkg)) {
+ continue;
+ }
+ Log.e("getIconControl", "applyIconControl: 恢复默认禁用: " + pkg);
+ JgyUtils.getInstance().hideApp(pkg);
+ }
+ }
+
+ /**
+ * 显示应用(被其他管控来源要求禁用的除外)
+ *
+ * @param pkg 包名
+ */
+ private void showAppIfAllowed(String pkg) {
+ if (ApkUtils.isKeepDisableByOtherSource(mContext, pkg, ApkUtils.SOURCE_DESKTOP_HIDE)) {
+ Log.e("getIconControl", "showAppIfAllowed: 被其他管控来源禁用,保持禁用: " + pkg);
+ return;
+ }
+ Log.e("getIconControl", "showAppIfAllowed: show: " + pkg);
+ JgyUtils.getInstance().showApp(pkg);
+ }
+
+ /**
+ * 展开包名映射:不同机型的同功能应用包名不同,禁用时需一起处理
+ *
+ * @param pkgs 接口下发的包名
+ * @return 实际需要禁用的包名
+ */
+ private Set expandAlias(List pkgs) {
+ Set hidePkgs = new HashSet<>();
+ for (String pack : pkgs) {
+ if (TextUtils.isEmpty(pack)) {
+ continue;
+ }
+ hidePkgs.add(pack);
+ if ("com.mediatek.camera".equals(pack)) {
+ hidePkgs.add("com.android.camera2");
+ }
+ if ("com.android.mms".equals(pack)) {
+ hidePkgs.add("com.android.messaging");
+ }
+ }
+ return hidePkgs;
+ }
+
+ public void getIconControl(BehaviorSubject lifecycle, onCompleteCallback callback) {
+ getIconControlObservable()
.observeOn(Schedulers.io())
.compose(RxLifecycle.bindUntilEvent(lifecycle, ActivityEvent.DESTROY))
- .subscribe(getDesktopIconObserver(callback));
+ .subscribe(getIconControlObserver(callback));
}
- public void getDesktopIcon(onCompleteCallback callback) {
- getDesktopIconObservable()
+ public void getIconControl(BehaviorSubject lifecycle) {
+ getIconControlObservable()
.observeOn(Schedulers.io())
- .subscribe(getDesktopIconObserver(callback));
+ .compose(RxLifecycle.bindUntilEvent(lifecycle, ActivityEvent.DESTROY))
+ .subscribe(getIconControlObserver(null));
}
- public void getDesktopIcon() {
- getDesktopIconObservable()
+ public void getIconControl() {
+ getIconControlObservable()
.observeOn(Schedulers.io())
- .subscribe(getDesktopIconObserver(null));
+ .subscribe(getIconControlObserver(null));
}
- public Observer getDesktopIconObserver(onCompleteCallback callback) {
- return new Observer() {
+ public Observer>> getIconControlObserver(onCompleteCallback callback) {
+ return new Observer>>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
- Log.e("getDesktopIcon", "onSubscribe: ");
+ Log.e("getIconControl", "onSubscribe: ");
}
@Override
- public void onNext(@NonNull BaseResponse response) {
- Log.e("getDesktopIcon", "onNext: " + response);
+ public void onNext(BaseResponse> listBaseResponse) {
+ Log.e("getIconControl", "onNext: " + listBaseResponse);
+ //先把不在管控清单里的应用恢复显示
ApkUtils.showAllApp(mContext);
- if (response.isSuccess()) {
- String data = response.data.toString();
- if (!TextUtils.isEmpty(data)) {
- List newList = Arrays.asList(data.split(","));//新的list
- PackageManager pm = mContext.getPackageManager();
- for (String pack : newList) {
- try {
- if ("com.mediatek.camera".equals(pack)) {
- JgyUtils.getInstance().hideApp("com.android.camera2");
+ List disablePkgs = new ArrayList<>();
+ List showPkgs = new ArrayList<>();
+ if (listBaseResponse.isSuccess() && listBaseResponse.data != null) {
+ List iconControlInfoList = listBaseResponse.data;
+ disablePkgs = iconControlInfoList.stream()
+ .filter(new Predicate() {
+ @Override
+ public boolean test(IconControlInfo iconControlInfo) {
+ return iconControlInfo.getControl_type() == 0;
}
- if ("com.android.mms".equals(pack)) {
- JgyUtils.getInstance().hideApp("com.android.messaging");
+ })
+ .map(new Function() {
+ @Override
+ public String apply(IconControlInfo iconControlInfo) {
+ return iconControlInfo.getApp_package();
}
- JgyUtils.getInstance().hideApp(pack);
- Log.e("getDesktopIcon", pack);
- } catch (Exception e) {
- Log.e("getDesktopIcon", "Exception: " + e.getMessage());
- }
- }
- }
+ }).collect(Collectors.toList());
+
+ showPkgs = iconControlInfoList.stream()
+ .filter(new Predicate() {
+ @Override
+ public boolean test(IconControlInfo iconControlInfo) {
+ return iconControlInfo.getControl_type() == 1;
+ }
+ })
+ .map(new Function() {
+ @Override
+ public String apply(IconControlInfo iconControlInfo) {
+ return iconControlInfo.getApp_package();
+ }
+ }).collect(Collectors.toList());
}
+ Log.e("getIconControl", "onNext: disablePkgs = " + disablePkgs + ", showPkgs = " + showPkgs);
+ //清单为空(接口失败或没有数据)时同样按"接口清单为空"处理:
+ //上次接口禁用的应用恢复显示,未被接口提及的应用恢复为本地默认禁用
+ applyIconControl(disablePkgs, showPkgs);
if (JgyUtils.getInstance().isAiuduPlatform()) {
ApkUtils.HideAiUDuApp();
}
@@ -1486,13 +1600,17 @@ public class NetInterfaceManager {
@Override
public void onError(@NonNull Throwable e) {
- Log.e("getDesktopIcon", "onError: " + e.getMessage());
- onComplete();
+ Log.e("getIconControl", "onError: " + e.getMessage());
+ //接口异常同样视为没有数据,禁用本机默认禁用的应用
+ ApkUtils.hideDefaultDisableApp(mContext);
+ if (callback != null) {
+ callback.onComplete();
+ }
}
@Override
public void onComplete() {
- Log.e("getDesktopIcon", "onComplete: ");
+ Log.e("getIconControl", "onComplete: ");
if (callback != null) {
callback.onComplete();
}
@@ -1618,18 +1736,19 @@ public class NetInterfaceManager {
}
private void setAppDisable(List appAttrList) {
- String appString = Settings.Global.getString(mContext.getContentResolver(), CommonConfig.AOLE_ACTION_DISABLE_APP);
- if (!TextUtils.isEmpty(appString)) {
- List pkgs = new ArrayList<>(Arrays.asList(appString.split(",")));
- for (String pkg : pkgs) {
- JgyUtils.getInstance().showApp(pkg);
- }
- }
+ //注意:这里只处理 getSnAppAttr 接口下发的禁用清单(AOLE_ACTION_DISABLE_APP),
+ //与 getIconControl 接口下发的禁用清单(AOLE_ACTION_HIDE_DESKTOP_ICON)是两套独立数据
+ List oldPkgs = ApkUtils.parsePkgList(
+ Settings.Global.getString(mContext.getContentResolver(), CommonConfig.AOLE_ACTION_DISABLE_APP));
if (appAttrList == null || appAttrList.size() == 0) {
+ //接口没有数据(失败或为空):只放开 AOLE_ACTION_DISABLE_APP 里记录的、
+ //且没有被其他管控来源要求禁用的应用
Log.e("getSnAppAttr", "onNext: appAttrList is empty");
+ showAppWithoutOtherSource(oldPkgs);
Settings.Global.putString(mContext.getContentResolver(), CommonConfig.AOLE_ACTION_DISABLE_APP, "null");
} else {
+ showAppWithoutOtherSource(oldPkgs);
for (AppAttr appAttr : appAttrList) {
if (appAttr.getIs_open() == 0) {
JgyUtils.getInstance().hideApp(appAttr.getApp_package());
@@ -1644,6 +1763,25 @@ public class NetInterfaceManager {
}
}
+ /**
+ * 放开在线禁用应用。
+ *
+ * 只放开在线禁用清单(AOLE_ACTION_DISABLE_APP)中的应用,若该应用同时被其他管控来源
+ * (本地默认禁用、桌面图标隐藏、爱优读隐藏等)要求禁用,则保持禁用不做放开。
+ *
+ * @param pkgs 旧的在线的禁用包名
+ */
+ private void showAppWithoutOtherSource(List pkgs) {
+ for (String pkg : pkgs) {
+ if (ApkUtils.isKeepDisableByOtherSource(mContext, pkg, ApkUtils.SOURCE_ONLINE_DISABLE)) {
+ Log.e("getSnAppAttr", "showApp: 被其他管控来源禁用,保持禁用: " + pkg);
+ continue;
+ }
+ Log.e("getSnAppAttr", "showApp: " + pkg);
+ JgyUtils.getInstance().showApp(pkg);
+ }
+ }
+
public void setSystemSetting(BehaviorSubject lifecycle, onCompleteCallback callback) {
getSystemSettingObservable()
.observeOn(Schedulers.io())
@@ -1674,7 +1812,7 @@ public class NetInterfaceManager {
@Override
public void onNext(@NonNull BaseResponse baseResponse) {
Log.e("setSystemSetting", "onNext: " + baseResponse);
- SPUtils.put(mContext, CommonConfig.JGY_FIRST_CONNECT, 1);
+ mMMKV.encode(CommonConfig.JGY_FIRST_CONNECT, 1);
if (baseResponse.isSuccess()) {
String data = GsonUtils.toJSONString(baseResponse.data);
Log.e("setSystemSetting", "onNext: " + data);
@@ -1723,7 +1861,7 @@ public class NetInterfaceManager {
Settings.System.putString(mContext.getContentResolver(), CommonConfig.ACTIVATIONBEAN_CODE_KEY, eBagCode.getEbagCode());
}
String ebagCode = eBagCode.getEbagCode();
- SPUtils.put(mContext, "ebagCode", ebagCode);
+ mMMKV.encode("ebagCode", ebagCode);
Settings.Global.putInt(mContext.getContentResolver(), CommonConfig.UIUI_ACTIVATION_KEY, 1);
JgyUtils.getInstance().checkLocalExternalFiles();
} else {
@@ -1864,11 +2002,11 @@ public class NetInterfaceManager {
private void setDefaultDesktop(String defaultApp) {
if (!TextUtils.isEmpty(defaultApp)) {
JgyUtils.getInstance().setDefaultDesktop(defaultApp);
-// SPUtils.put(mContext, "default_launcher", defaultApp);
+// mMMKV.encode(CommonConfig.DEFAULT_LAUNCHER_PACKAGE_NAME, defaultApp);
} else {
//如果默认桌面不为空则设置为默认桌面
String default_launcher = mMMKV.decodeString(CommonConfig.DEFAULT_LAUNCHER_PACKAGE_NAME, "");
-// String default_launcher = (String) SPUtils.get(mContext, "default_launcher", "");
+// String default_launcher = mMMKV.decodeString(CommonConfig.DEFAULT_LAUNCHER_PACKAGE_NAME, "");
Log.e("getDefaultApp", "onNext: " + default_launcher);
if (TextUtils.isEmpty(default_launcher)) {
if (JgyUtils.getInstance().checkAppPlatform() == JgyUtils.YXPD1Platform) {
@@ -1963,7 +2101,7 @@ public class NetInterfaceManager {
// int oldState = Settings.System.getInt(mContext.getContentResolver(), JGYActions.ACTION_QCH_UNLOCK_IPAD, JGYActions.FRAME_CODE_LOCKED);
// Log.e( "getLockedState", "qch_unlock_ipad: " + oldState);
//后台1是锁定,底层0是锁定
- SPUtils.put(mContext, CommonConfig.JGY_FIRST_CONNECT, 1);
+ mMMKV.encode(CommonConfig.JGY_FIRST_CONNECT, 1);
if (locked == JGYActions.NET_CODE_LOCKED) {
// if (oldState == JGYActions.FRAME_CODE_UNLOCKED) {
// Log.e( "getLockedState", "onNext: " + "state changed , reset devices");
@@ -2764,21 +2902,21 @@ public class NetInterfaceManager {
}
String white_ip = browserData.getWhite_ip();
if (!TextUtils.isEmpty(white_ip)) {
- SPUtils.put(mContext, "white_ip", white_ip);
+ mMMKV.encode("white_ip", white_ip);
} else {
- SPUtils.put(mContext, "white_ip", " ");
+ mMMKV.encode("white_ip", " ");
}
String black_ip = browserData.getBlack_ip();
if (!TextUtils.isEmpty(black_ip)) {
- SPUtils.put(mContext, "black_ip", black_ip);
+ mMMKV.encode("black_ip", black_ip);
} else {
- SPUtils.put(mContext, "black_ip", " ");
+ mMMKV.encode("black_ip", " ");
}
} else {
Settings.System.putString(mContext.getContentResolver(), "DeselectBrowserArray", homePage);
Settings.System.putString(mContext.getContentResolver(), "qch_webblack_url", " ");
- SPUtils.put(mContext, "white_ip", " ");
- SPUtils.put(mContext, "black_ip", " ");
+ mMMKV.encode("white_ip", " ");
+ mMMKV.encode("black_ip", " ");
}
}
@@ -3714,15 +3852,14 @@ public class NetInterfaceManager {
Intent intent = new Intent(LogcatService.LOGCAT_START_ACTION);
PendingIntent startPendingIntent = PendingIntent.getBroadcast(mContext,
REQUEST_CODE, intent,
- PendingIntent.FLAG_CANCEL_CURRENT);
- alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP,
- timestamp, startPendingIntent);
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ setExactAlarm(alarmManager, timestamp, startPendingIntent);
}
Intent intent = new Intent(LogcatService.LOGCAT_STOP_ACTION);
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext,
REQUEST_CODE, intent,
- PendingIntent.FLAG_CANCEL_CURRENT);
- alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP,
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ setExactAlarm(alarmManager,
timestamp + snRunLog.getDuration() * 1000, pendingIntent);
}
}
@@ -3740,6 +3877,23 @@ public class NetInterfaceManager {
});
}
+ /**
+ * 设置精确闹钟。
+ * Android 14(targetSdk 33+)起 SCHEDULE_EXACT_ALARM 默认被拒绝,
+ * 权限不可用时会抛 SecurityException,这里降级为非精确闹钟,保证日志上传策略依然生效。
+ */
+ private static void setExactAlarm(AlarmManager alarmManager, long triggerAtMillis, PendingIntent pendingIntent) {
+ if (alarmManager == null || pendingIntent == null) {
+ return;
+ }
+ try {
+ alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent);
+ } catch (SecurityException e) {
+ Log.e(TAG, "setExactAlarm: 精确闹钟不可用,降级为非精确闹钟: " + e.getMessage());
+ alarmManager.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent);
+ }
+ }
+
public static final String PublicIP = "PublicIP";
public static final String WHOIS_ADDR = "whois_ip_addr";
diff --git a/app/src/main/java/com/aoleyun/sn/network/UrlAddress.java b/app/src/main/java/com/aoleyun/sn/network/UrlAddress.java
index 260fed2..ae40b00 100644
--- a/app/src/main/java/com/aoleyun/sn/network/UrlAddress.java
+++ b/app/src/main/java/com/aoleyun/sn/network/UrlAddress.java
@@ -110,7 +110,10 @@ public class UrlAddress {
/*小程序的管控设置*/
public static final String GET_SN_SETTING = "And/control/getSnSetting";
/*获取隐藏桌面图标*/
+ @Deprecated
public static final String GET_HIDE_DESKTOPICON = "Icon";
+ /*获取隐藏桌面图标 新版*/
+ public static final String GET_ICON_CONTROL = "icon/control";
/*获取系统设置*/
public static final String GET_FIRMWARE = "firmware/get";
/*获取时间管控*/
diff --git a/app/src/main/java/com/aoleyun/sn/network/api/aoleyun/SystemSettingApi.java b/app/src/main/java/com/aoleyun/sn/network/api/aoleyun/SystemSettingApi.java
index f186fac..8330a74 100644
--- a/app/src/main/java/com/aoleyun/sn/network/api/aoleyun/SystemSettingApi.java
+++ b/app/src/main/java/com/aoleyun/sn/network/api/aoleyun/SystemSettingApi.java
@@ -5,6 +5,7 @@ import com.aoleyun.sn.bean.BaseResponse;
import com.aoleyun.sn.bean.BlacklistBean;
import com.aoleyun.sn.bean.BrowserBookmarks;
import com.aoleyun.sn.bean.BrowserData;
+import com.aoleyun.sn.bean.IconControlInfo;
import com.aoleyun.sn.bean.PoweroffBean;
import com.aoleyun.sn.bean.SnSetting;
import com.aoleyun.sn.bean.SnTimeControl;
@@ -42,11 +43,18 @@ public interface SystemSettingApi {
@FormUrlEncoded
@POST(UrlAddress.GET_HIDE_DESKTOPICON)
+ @Deprecated
Observable getDesktopIcon(
@Field("key") String key,
@Field("sn") String sn
);
+ @GET(UrlAddress.GET_ICON_CONTROL)
+ Observable>> getIconControl(
+ @Query("key") String key,
+ @Query("sn") String sn
+ );
+
@GET(UrlAddress.GET_SN_TIME_CONTROL)
@Deprecated
Observable> getSnTimeControl(
diff --git a/app/src/main/java/com/aoleyun/sn/push/PushManager.java b/app/src/main/java/com/aoleyun/sn/push/PushManager.java
index 5eb557b..b774014 100644
--- a/app/src/main/java/com/aoleyun/sn/push/PushManager.java
+++ b/app/src/main/java/com/aoleyun/sn/push/PushManager.java
@@ -51,7 +51,7 @@ import com.aoleyun.sn.utils.CmdUtil;
import com.aoleyun.sn.utils.ForegroundAppUtil;
import com.aoleyun.sn.utils.JgyUtils;
import com.aoleyun.sn.utils.MySQLData;
-import com.aoleyun.sn.utils.SPUtils;
+import com.aoleyun.sn.utils.ReceiverCompat;
import com.aoleyun.sn.utils.ServiceAliveUtils;
import com.aoleyun.sn.utils.SysSettingUtils;
import com.aoleyun.sn.utils.TimeUtils;
@@ -336,7 +336,7 @@ public class PushManager {
@Override
public void run() {
NetInterfaceManager.getInstance().getAppLimit();
- NetInterfaceManager.getInstance().getDesktopIcon();
+ NetInterfaceManager.getInstance().getIconControl();
NetInterfaceManager.getInstance().getDefaultDesktop();
NetInterfaceManager.getInstance().getAppAutoStartUpdateAndNet();
}
@@ -378,7 +378,7 @@ public class PushManager {
@Override
public void run() {
NetInterfaceManager.getInstance().getAppLimit();
- NetInterfaceManager.getInstance().getDesktopIcon();
+ NetInterfaceManager.getInstance().getIconControl();
NetInterfaceManager.getInstance().getDefaultDesktop();
NetInterfaceManager.getInstance().getAppAutoStartUpdateAndNet();
}
@@ -421,7 +421,7 @@ public class PushManager {
new Handler(Looper.getMainLooper()).postDelayed(() -> {
//后台发送时数据库未改变,有时候可能获取到的数据时上一次的
Log.e(TAG, "run: HIDE_DESKTOP_ICON ");
- NetInterfaceManager.getInstance().getDesktopIcon();
+ NetInterfaceManager.getInstance().getIconControl();
}, 2000);
break;
case DISABLE_APP_SLIDE:
@@ -457,28 +457,28 @@ public class PushManager {
case BDLocation.TypeOffLineLocation:// 离线定位结果
Log.e(TAG, "onLocationChanged: " + "定位成功");
Log.e(TAG, "onLocationChanged: " + location.getAddrStr() + location.getLocationDescribe());
- SPUtils.put(mContext, "MapAddress", location.getAddrStr() + location.getLocationDescribe());
- SPUtils.put(mContext, "longitude", location.getLongitude());
- SPUtils.put(mContext, "latitude", location.getLatitude());
- SPUtils.put(mContext, "MapError", "-");
+ mMMKV.encode("MapAddress", location.getAddrStr() + location.getLocationDescribe());
+ mMMKV.encode("longitude", String.valueOf(location.getLongitude()));
+ mMMKV.encode("latitude", String.valueOf(location.getLatitude()));
+ mMMKV.encode("MapError", "-");
break;
case BDLocation.TypeServerError:
Log.e(TAG, "onReceiveLocation: " + "服务端网络定位失败");
- SPUtils.put(mContext, "MapError", "服务端网络定位失败,可以反馈IMEI号和大体定位时间到loc-bugs@baidu.com,会有人追查原因");
+ mMMKV.encode("MapError", "服务端网络定位失败,可以反馈IMEI号和大体定位时间到loc-bugs@baidu.com,会有人追查原因");
break;
case BDLocation.TypeNetWorkException:
Log.e(TAG, "onReceiveLocation: " + "网络不同导致定位失败,请检查网络是否通畅");
- SPUtils.put(mContext, "MapError", "网络不同导致定位失败,请检查网络是否通畅");
+ mMMKV.encode("MapError", "网络不同导致定位失败,请检查网络是否通畅");
break;
case BDLocation.TypeCriteriaException:
Log.e(TAG, "onReceiveLocation: " + "无法获取有效定位依据导致定位失败");
- SPUtils.put(mContext, "MapError", "无法获取有效定位依据导致定位失败,一般是由于手机的原因,处于飞行模式下一般会造成这种结果,可以试着重启手机");
+ mMMKV.encode("MapError", "无法获取有效定位依据导致定位失败,一般是由于手机的原因,处于飞行模式下一般会造成这种结果,可以试着重启手机");
break;
default:
}
}
- Log.e(TAG, "MapAddress: " + SPUtils.get(mContext, "MapAddress", "-"));
- Log.e(TAG, "MapError: " + SPUtils.get(mContext, "MapError", "-"));
+ Log.e(TAG, "MapAddress: " + mMMKV.decodeString("MapAddress", "-"));
+ Log.e(TAG, "MapError: " + mMMKV.decodeString("MapError", "-"));
locationClient.stop();
}
@@ -524,7 +524,7 @@ public class PushManager {
}
}
Log.e(TAG, "onLocationChanged: " + "定位失败");
- SPUtils.put(mContext, "MapError", sb);
+ mMMKV.encode("MapError", sb.toString());
Log.e(TAG, "onLocDiagnosticMessage: " + sb);
}
});
@@ -783,7 +783,7 @@ public class PushManager {
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
- NetInterfaceManager.getInstance().getDesktopIcon();
+ NetInterfaceManager.getInstance().getIconControl();
NetInterfaceManager.getInstance().getDefaultDesktop();
NetInterfaceManager.getInstance().getAppAutoStartUpdateAndNet();
}
@@ -890,7 +890,8 @@ public class PushManager {
if (Build.VERSION.SDK_INT >= 21) {
return ((BatteryManager) mContext.getSystemService(Context.BATTERY_SERVICE)).getIntProperty(4);
}
- Intent intent = (new ContextWrapper(mContext)).registerReceiver(null, new IntentFilter("android.intent.action.BATTERY_CHANGED"));
+ Intent intent = ReceiverCompat.registerReceiver(new ContextWrapper(mContext), null,
+ new IntentFilter("android.intent.action.BATTERY_CHANGED"), ReceiverCompat.RECEIVER_NOT_EXPORTED);
return intent.getIntExtra("level", -1) * 100 / intent.getIntExtra("scale", -1);
}
@@ -1390,13 +1391,13 @@ public class PushManager {
String ebagCode = jsonObject.get("ebagCode").getAsString();
if (TextUtils.isEmpty(ebagCode)) {
Log.e(TAG, "setEbagCode: " + "ebagCode is empty");
- SPUtils.put(mContext, "ebagCode", "");
+ mMMKV.encode("ebagCode", "");
if (JgyUtils.getInstance().checkAppPlatform() != JgyUtils.YXPD1Platform) {
Settings.System.putString(mContext.getContentResolver(), CommonConfig.ACTIVATIONBEAN_CODE_KEY, "");
Settings.Global.putInt(mContext.getContentResolver(), CommonConfig.UIUI_ACTIVATION_KEY, 0);
}
} else {
- SPUtils.put(mContext, "ebagCode", ebagCode);
+ mMMKV.encode("ebagCode", ebagCode);
if (JgyUtils.getInstance().checkAppPlatform() != JgyUtils.YXPD1Platform) {
Settings.System.putString(mContext.getContentResolver(), CommonConfig.ACTIVATIONBEAN_CODE_KEY, ebagCode);
Settings.Global.putInt(mContext.getContentResolver(), CommonConfig.UIUI_ACTIVATION_KEY, 1);
@@ -1483,13 +1484,13 @@ public class PushManager {
if (is_log_type == 0) {
NetInterfaceManager.getInstance().uploadLogFile();
} else {
- SPUtils.put(mContext, CommonConfig.UPLOAD_LOG_TYPE, 1);
+ mMMKV.encode(CommonConfig.UPLOAD_LOG_TYPE, 1);
}
}
private void getBatteryInfo(Context context) {
IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
- Intent receiver = context.registerReceiver(null, filter);
+ Intent receiver = ReceiverCompat.registerReceiver(context, null, filter, ReceiverCompat.RECEIVER_NOT_EXPORTED);
String jsonString = BatteryUtils.getBatteryJson(context, receiver).toString();
Gson gson = new Gson();
Type type = new TypeToken() {
diff --git a/app/src/main/java/com/aoleyun/sn/receiver/BootReceiver.java b/app/src/main/java/com/aoleyun/sn/receiver/BootReceiver.java
index 6ea7ca0..de781e4 100644
--- a/app/src/main/java/com/aoleyun/sn/receiver/BootReceiver.java
+++ b/app/src/main/java/com/aoleyun/sn/receiver/BootReceiver.java
@@ -11,7 +11,6 @@ import com.aoleyun.sn.rlog.LogDBManager;
import com.aoleyun.sn.utils.BootManager;
import com.aoleyun.sn.utils.JgyUtils;
import com.aoleyun.sn.utils.PowerOffAlarmUtils;
-import com.aoleyun.sn.utils.SPUtils;
import com.aoleyun.sn.utils.TimeUtils;
import com.tencent.mmkv.MMKV;
@@ -80,9 +79,9 @@ public class BootReceiver extends BroadcastReceiver {
mMMKV.encode(ConnectManager.REBOOT_LAST_ONNECT_TIME, System.currentTimeMillis());
//设备重启后 AlarmManager 中的闹钟会被系统清空,重新安排定时关机
PowerOffAlarmUtils.schedule(context);
- if ((int) SPUtils.get(context, CommonConfig.FIRST_STARTUP, 0) == 0) {
+ if (mMMKV.decodeInt(CommonConfig.FIRST_STARTUP, 0) == 0) {
LogDBManager.getInstance().creatRebootLog("首次启动", TimeUtils.transferLongToDate(System.currentTimeMillis()));
- SPUtils.put(context, CommonConfig.FIRST_STARTUP, 1);
+ mMMKV.encode(CommonConfig.FIRST_STARTUP, 1);
} else {
LogDBManager.getInstance().creatRebootLog("设备重启", TimeUtils.transferLongToDate(System.currentTimeMillis()));
// Intent checkNetintent = new Intent(context, CheckNetActivity.class);
diff --git a/app/src/main/java/com/aoleyun/sn/receiver/NewAppReceiver.java b/app/src/main/java/com/aoleyun/sn/receiver/NewAppReceiver.java
index 4615d8d..a6ef586 100644
--- a/app/src/main/java/com/aoleyun/sn/receiver/NewAppReceiver.java
+++ b/app/src/main/java/com/aoleyun/sn/receiver/NewAppReceiver.java
@@ -119,7 +119,7 @@ public class NewAppReceiver extends BroadcastReceiver {
NetInterfaceManager.getInstance().getAppLimit();
// NetInterfaceManager.getInstance().getDefaultDesktop();
NetInterfaceManager.getInstance().getAppAutoStartUpdateAndNet();
- NetInterfaceManager.getInstance().getDesktopIcon();
+ NetInterfaceManager.getInstance().getIconControl();
NetInterfaceManager.getInstance().sendInstalledAppInfo(new NetInterfaceManager.onCompleteCallback() {
@Override
public void onComplete() {
@@ -161,7 +161,7 @@ public class NewAppReceiver extends BroadcastReceiver {
Log.e("setLauncher", "onNext: " + pkg);
JgyUtils.getInstance().checkDefaultDesktop(pkg);
String oldDesktop = JgyUtils.getInstance().getDefaultDesktop();
-// String oldDesktop = (String) SPUtils.get(mContext, "default_launcher", "");
+// String oldDesktop = mMMKV.decodeString(CommonConfig.DEFAULT_LAUNCHER_PACKAGE_NAME, "");
Log.e("setLauncher", "onNext: oldDesktop = " + oldDesktop);
if (pkg.equals(oldDesktop)) {
ApkUtils.openPackage(mContext, pkg);
diff --git a/app/src/main/java/com/aoleyun/sn/service/GuardService.java b/app/src/main/java/com/aoleyun/sn/service/GuardService.java
index 8ee1fa4..ce08d45 100644
--- a/app/src/main/java/com/aoleyun/sn/service/GuardService.java
+++ b/app/src/main/java/com/aoleyun/sn/service/GuardService.java
@@ -59,7 +59,7 @@ import com.aoleyun.sn.service.main.MainService;
import com.aoleyun.sn.utils.ApkUtils;
import com.aoleyun.sn.utils.CmdUtil;
import com.aoleyun.sn.utils.JgyUtils;
-import com.aoleyun.sn.utils.SPUtils;
+import com.aoleyun.sn.utils.ReceiverCompat;
import com.aoleyun.sn.utils.ServiceAliveUtils;
import com.aoleyun.sn.utils.TimeUtils;
import com.aoleyun.sn.utils.Utils;
@@ -72,6 +72,7 @@ import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import com.hjq.toast.Toaster;
+import com.tencent.mmkv.MMKV;
import java.io.BufferedReader;
import java.io.File;
@@ -101,6 +102,8 @@ import okhttp3.ResponseBody;
public class GuardService extends Service {
private final static String TAG = "GuardService";
+ private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
+
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
@@ -234,13 +237,13 @@ public class GuardService extends Service {
public void onDestroy() {
super.onDestroy();
if (null != mNewAppReceiver) {
- unregisterReceiver(mNewAppReceiver);
+ ReceiverCompat.unregisterReceiver(this, mNewAppReceiver);
}
if (null != presentReceiver) {
- unregisterReceiver(presentReceiver);
+ ReceiverCompat.unregisterReceiver(this, presentReceiver);
}
if (null != batteryReceiver) {
- unregisterReceiver(batteryReceiver);
+ ReceiverCompat.unregisterReceiver(this, batteryReceiver);
}
}
@@ -255,7 +258,8 @@ public class GuardService extends Service {
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
// filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addDataScheme("package");
- registerReceiver(mNewAppReceiver, filter);
+ //Android13+ 必须显式声明导出标志,否则注册时会抛 SecurityException
+ ReceiverCompat.registerReceiver(this, mNewAppReceiver, filter, ReceiverCompat.RECEIVER_NOT_EXPORTED);
}
private PresentReceiver presentReceiver;
@@ -275,7 +279,7 @@ public class GuardService extends Service {
filter.addAction("android.intent.action.FORCE_FACTORY_RESET");
filter.addAction(Intent.ACTION_SHUTDOWN);
filter.addAction(Intent.ACTION_REBOOT);
- registerReceiver(presentReceiver, filter);
+ ReceiverCompat.registerReceiver(this, presentReceiver, filter, ReceiverCompat.RECEIVER_NOT_EXPORTED);
}
public class PresentReceiver extends BroadcastReceiver {
@@ -338,8 +342,8 @@ public class GuardService extends Service {
break;
case Intent.ACTION_SHUTDOWN:
case Intent.ACTION_REBOOT:
- int restart_count = (int) SPUtils.get(context, "restart_count", 0);
- SPUtils.put(context, "restart_count", restart_count + 1);
+ int restart_count = mMMKV.decodeInt("restart_count", 0);
+ mMMKV.encode("restart_count", restart_count + 1);
// android.os.Process.killProcess(android.os.Process.myPid());
break;
case Intent.ACTION_FACTORY_RESET:
@@ -435,7 +439,7 @@ public class GuardService extends Service {
filter.addAction(Intent.ACTION_BATTERY_OKAY);
filter.addAction(Intent.ACTION_POWER_CONNECTED);
filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
- registerReceiver(batteryReceiver, filter);
+ ReceiverCompat.registerReceiver(this, batteryReceiver, filter, ReceiverCompat.RECEIVER_NOT_EXPORTED);
}
BatteryReceiver batteryReceiver;
diff --git a/app/src/main/java/com/aoleyun/sn/service/LogcatService.java b/app/src/main/java/com/aoleyun/sn/service/LogcatService.java
index 0719e09..6990da4 100644
--- a/app/src/main/java/com/aoleyun/sn/service/LogcatService.java
+++ b/app/src/main/java/com/aoleyun/sn/service/LogcatService.java
@@ -11,6 +11,7 @@ import android.util.Log;
import com.aoleyun.sn.bean.BaseResponse;
import com.aoleyun.sn.network.NetInterfaceManager;
+import com.aoleyun.sn.utils.ReceiverCompat;
import com.aoleyun.sn.utils.Utils;
import java.io.BufferedReader;
@@ -106,7 +107,8 @@ public class LogcatService extends Service {
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
filter.addAction(LOGCAT_START_ACTION);
filter.addAction(LOGCAT_STOP_ACTION);
- registerReceiver(receiver, filter);
+ //Android13+ 必须显式声明导出标志,否则注册时会抛 SecurityException
+ ReceiverCompat.registerReceiver(this, receiver, filter, ReceiverCompat.RECEIVER_NOT_EXPORTED);
}
Thread thread = null;
@@ -334,7 +336,7 @@ public class LogcatService extends Service {
public void onDestroy() {
super.onDestroy();
if (receiver != null) {
- unregisterReceiver(receiver);
+ ReceiverCompat.unregisterReceiver(this, receiver);
}
}
}
diff --git a/app/src/main/java/com/aoleyun/sn/service/RemoteService.java b/app/src/main/java/com/aoleyun/sn/service/RemoteService.java
index 0ed33fd..babc112 100644
--- a/app/src/main/java/com/aoleyun/sn/service/RemoteService.java
+++ b/app/src/main/java/com/aoleyun/sn/service/RemoteService.java
@@ -104,7 +104,7 @@ public class RemoteService extends Service {
// @Override
// public void setDefaultDesktop(String pkg) throws RemoteException {
// Log.e(TAG, "setDefaultDesktop: " + "pkg = " + pkg);
-//// SPUtils.put(RemoteService.this, "default_launcher", pkg);
+//// mMMKV.encode(CommonConfig.DEFAULT_LAUNCHER_PACKAGE_NAME, pkg);
// mMMKV.encode(CommonConfig.DEFAULT_LAUNCHER_PACKAGE_NAME, pkg);
// JgyUtils.getInstance().setDefaultDesktop(pkg);
// if (PackageNames.DESKTOP.equals(pkg)) {
diff --git a/app/src/main/java/com/aoleyun/sn/service/StepService.java b/app/src/main/java/com/aoleyun/sn/service/StepService.java
index 38c7bfd..e4b8cd5 100644
--- a/app/src/main/java/com/aoleyun/sn/service/StepService.java
+++ b/app/src/main/java/com/aoleyun/sn/service/StepService.java
@@ -18,6 +18,7 @@ import androidx.annotation.Nullable;
import com.aoleyun.sn.BuildConfig;
import com.aoleyun.sn.KeepAliveConnection;
import com.aoleyun.sn.utils.JgyUtils;
+import com.aoleyun.sn.utils.ReceiverCompat;
import com.aoleyun.sn.utils.ServiceAliveUtils;
import com.aoleyun.sn.utils.Utils;
import com.blankj.utilcode.util.NetworkUtils;
@@ -123,7 +124,8 @@ public class StepService extends Service implements NetworkUtils.OnNetworkStatus
filter.addAction(Intent.ACTION_REBOOT);
filter.addAction(Intent.ACTION_FACTORY_RESET);
filter.addAction(Intent.ACTION_MASTER_CLEAR);
- registerReceiver(screenLockReceiver, filter);
+ //Android13+ 必须显式声明导出标志,否则注册时会抛 SecurityException
+ ReceiverCompat.registerReceiver(this, screenLockReceiver, filter, ReceiverCompat.RECEIVER_NOT_EXPORTED);
}
private class ScreenLockReceiver extends BroadcastReceiver {
@@ -163,7 +165,7 @@ public class StepService extends Service implements NetworkUtils.OnNetworkStatus
NetworkUtils.unregisterNetworkStatusChangedListener(this);
closeConnect();
if (screenLockReceiver != null) {
- unregisterReceiver(screenLockReceiver);
+ ReceiverCompat.unregisterReceiver(this, screenLockReceiver);
}
}
diff --git a/app/src/main/java/com/aoleyun/sn/service/main/MainService.java b/app/src/main/java/com/aoleyun/sn/service/main/MainService.java
index d6942bb..7baf5b1 100644
--- a/app/src/main/java/com/aoleyun/sn/service/main/MainService.java
+++ b/app/src/main/java/com/aoleyun/sn/service/main/MainService.java
@@ -60,7 +60,7 @@ import com.aoleyun.sn.utils.CacheUtils;
import com.aoleyun.sn.utils.ForegroundAppUtil;
import com.aoleyun.sn.utils.JgyUtils;
import com.aoleyun.sn.utils.PowerOffAlarmUtils;
-import com.aoleyun.sn.utils.SPUtils;
+import com.aoleyun.sn.utils.ReceiverCompat;
import com.aoleyun.sn.utils.SysSettingUtils;
import com.aoleyun.sn.utils.TimeUtils;
import com.aoleyun.sn.utils.Utils;
@@ -74,6 +74,8 @@ import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import io.reactivex.rxjava3.core.Observable;
@@ -310,35 +312,22 @@ public class MainService extends BaseService implements NetworkUtils.OnNetworkSt
return mMainBinder;
}
+ /**
+ * 初始化任务后台线程(单线程串行,保证初始化顺序)
+ */
+ private static final ExecutorService sInitExecutor = Executors.newSingleThreadExecutor();
+
@Override
public void onCreate() {
super.onCreate();
+ //前台服务必须在 startForegroundService() 之后 5 秒内调用 startForeground(),
+ //放在 onCreate 最前面执行,避免后续初始化耗时或抛异常导致前台服务超时(ANR)
+ startForegroundIfNeeded();
Log.e(TAG, "onCreate: ");
mServiceModel = new MainServiceModel(this);
mServiceModel.setLifecycle(getLifecycleSubject());
initLiveData();
- if ("YX-T01".equalsIgnoreCase(Build.MODEL)) {
- JgyUtils.getInstance().setTongyiAppTop();
- }
- if (JgyUtils.getInstance().checkAppPlatform() == JgyUtils.YXPD1Platform) {
- Settings.System.putString(getContentResolver(), CommonConfig.ACTIVATIONBEAN_CODE_KEY, Utils.getSerial(this));
- if (ApkUtils.isAvailable(this, JgyUtils.JXW_LAUNCHER_PACKAGE_NAME)) {
- Intent intent2 = new Intent(Intent.ACTION_VIEW);
- intent2.setPackage(JgyUtils.JXW_LAUNCHER_PACKAGE_NAME);
- intent2.setClassName(JgyUtils.JXW_LAUNCHER_PACKAGE_NAME, JgyUtils.JXW_LAUNCHER_CLASS_NAME);
- try {
- startService(intent2);//或者bindService(intent, mConnection, Context.BIND_AUTO_CREATE); 都能启动
- } catch (Exception e) {
- Log.e(TAG, "onCreate: " + e.getMessage());
- }
- }
- }
-
- String launcherPkg = JgyUtils.getInstance().getDefaultDesktop();
- Log.e(TAG, "onCreate: " + launcherPkg);
- JgyUtils.getInstance().checkDefaultDesktop(launcherPkg);
-
TimeTask task = new TimeTask();
task.execute("ntp.aliyun.com");
@@ -357,34 +346,7 @@ public class MainService extends BaseService implements NetworkUtils.OnNetworkSt
Log.e(TAG, "onCreate: device not reboot");
}
- Settings.Global.putString(getContentResolver(), "AOLE_SERIAL", Utils.getSerial(MainService.this));
-
- PackageManager pm = getPackageManager();
- try {
- Log.e(TAG, "initView: " + pm.getApplicationEnabledSetting("com.teclast.update"));
- pm.setApplicationEnabledSetting("com.teclast.update", PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 0);
- Log.e(TAG, "onCreate: enable com.teclast.update");
- } catch (Exception e) {
- Log.e(TAG, "onCreate: setApplicationEnabledSetting = " + e.getMessage());
- }
- if ("V1.01_20230519".equalsIgnoreCase(Build.DISPLAY)) {
- Log.e(TAG, "onCreate: enable");
- Intent intent = new Intent();
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.setClassName("com.teclast.update", "com.teclast.update.GoogleOtaClient");
- try {
- startActivity(intent);
- } catch (Exception e) {
- Log.e(TAG, "onCreate: startActivity = " + e.getMessage());
- }
- }
-
- if (JgyUtils.getInstance().isAiuduPlatform()) {
-// if (mMMKV.decodeInt(CommonConfig.DEVICES_FRIST_START, 0) == 0) {
-// ApkUtils.openPackage(MainService.this, "com.books.wisdom");
-// }
- ApkUtils.HideAiUDuApp();
- }
+ //Settings 写入、包管理查询与启停等耗时操作统一放到后台线程(见 initOnBackground)
// RoleManager mRm = getSystemService(RoleManager.class);
@@ -399,11 +361,7 @@ public class MainService extends BaseService implements NetworkUtils.OnNetworkSt
// Log.e("RoleManager", "onRoleHoldersChanged: roleName = " + s + "userHandle = " + userHandle);
// }
// }, UserHandle.ALL);
- //根据要求卸载这两个应用
- if (JgyUtils.getInstance().getDeviceIsLocked()) {
- ApkUtils.UninstallAPP(this, "com.joytv.live");
- ApkUtils.UninstallAPP(this, "com.tencent.android.qqdownloader");
- }
+ //根据要求卸载这两个应用(包管理耗时操作,移到后台线程执行,见 initOnBackground)
// Log.e(TAG, "onCreate: " + Build.MODEL);
// if ("K106".equals(Build.MODEL)) {
// if (ApkUtils.isAvailable(MainService.this, JXW_LAUNCHER)) {
@@ -411,12 +369,7 @@ public class MainService extends BaseService implements NetworkUtils.OnNetworkSt
// }
// }
- try {
- JgyUtils.getInstance().hideApp("com.android.email");
- JgyUtils.getInstance().hideApp("com.android.browser");
- } catch (Exception e) {
- Log.e(TAG, "onCreate: " + e.getMessage());
- }
+ //隐藏系统邮件与浏览器(包管理耗时操作,移到后台线程执行,见 initOnBackground)
Observable.create(topAppSubscribe)
.throttleFirst(1, TimeUnit.MINUTES)
@@ -425,27 +378,12 @@ public class MainService extends BaseService implements NetworkUtils.OnNetworkSt
.throttleFirst(3, TimeUnit.HOURS)
.subscribe(timeChangedObserver);
- if ((int) SPUtils.get(this, CommonConfig.JGY_FIRST_CONNECT, 0) == 0) {
+ if (mMMKV.decodeInt(CommonConfig.JGY_FIRST_CONNECT, 0) == 0) {
Log.e(TAG, "onCreate: " + "Disable All Settings");
SysSettingUtils.setDisableSetting(this);
// mPresenter.getLockedState(CommonConfig.JGY_FIRST_CONNECT);
}
- int oldVersionCode = (int) SPUtils.get(this, CommonConfig.SN_VERSIONCODE, 0);
- if (oldVersionCode < BuildConfig.VERSION_CODE) {
- Log.e(TAG, "onCreate: " + "upgrade successful");
- try {
-// new CacheUtils().cleanApplicationUserData(this, "com.aoleyun.os");
- new CacheUtils().cleanApplicationUserData(this, "com.aoleyun.audos");
-
- } catch (Exception e) {
- Log.e(TAG, "onReceive: " + e.getMessage());
- e.printStackTrace();
- }
-// mPresenter.checkAoleyunUpdate();
- } else {
-
- }
- SPUtils.put(this, CommonConfig.SN_VERSIONCODE, BuildConfig.VERSION_CODE);
+ //版本升级后的用户数据清理为耗时 IO,移到后台线程执行(见 initOnBackground)
//根据本地保存的策略安排定时关机闹钟(服务重启、设备重启后重新计算)
PowerOffAlarmUtils.schedule(this);
@@ -454,25 +392,10 @@ public class MainService extends BaseService implements NetworkUtils.OnNetworkSt
timeChangedStart.onstar(System.currentTimeMillis());
setStatusbar();
setFloatingWindow();
- JgyUtils.getInstance().writeAppPackageList();
-
- if (JgyUtils.getInstance().checkAppPlatform() == JgyUtils.HL500Platform) {
- mNotificationManagerCompat = NotificationManagerCompat.from(this);
- createNotificationChannel();
- sendSimpleNotification();
- }
// aliyunPushInit();
- IActivityManager activityManager = ActivityManagerNative.getDefault();
- try {
- activityManager.setActivityController(new AoleyunActivityController(), false);
- } catch (RemoteException e) {
- Log.e(TAG, "setActivityController: " + e.getMessage());
- e.printStackTrace();
- }
- mMMKV.encode(CommonConfig.DEVICES_FRIST_START, 1);
Observable.create(killSubscribe)
.throttleLast(30, TimeUnit.SECONDS)
// .throttleLast(3, TimeUnit.SECONDS)
@@ -482,13 +405,7 @@ public class MainService extends BaseService implements NetworkUtils.OnNetworkSt
.throttleFirst(5, TimeUnit.MINUTES)
.subscribe(networkObserver);
- JgyUtils.getInstance().checkAoleyunApp();
-
- String allowAppList = Settings.System.getString(getContentResolver(), CommonConfig.SEEWO_APP_SOURCE_INSTALL_WHITELIST);
- if (TextUtils.isEmpty(allowAppList)) {
- Settings.System.putString(getContentResolver(), CommonConfig.SEEWO_APP_SOURCE_INSTALL_WHITELIST, String.join(",", JgyUtils.DEFAULT_APP_SOURCE_WHITE_LIST));
- }
-
+ //应用存在性检查与安装白名单初始化(Settings / 包管理读操作)移到后台线程(见 initOnBackground)
int reminder = mMMKV.decodeInt(CommonConfig.EYE_TO_SCREEN_DISTANCE_REMINDER, 0);
Log.e(TAG, "onCreate: reminder = " + reminder);
@@ -504,7 +421,156 @@ public class MainService extends BaseService implements NetworkUtils.OnNetworkSt
startService(new Intent(this, ShakeDetectionService.class));
}
+ //以上是必须在主线程完成的工作(startForeground、LiveData、广播注册、悬浮窗等),
+ //其余 Settings、包管理、文件 IO 等耗时操作统一切到后台线程执行,
+ //避免服务创建阶段长时间阻塞主线程造成 Service ANR
+ runOnBackground(new Runnable() {
+ @Override
+ public void run() {
+ initOnBackground();
+ }
+ });
+ mMMKV.encode(CommonConfig.DEVICES_FRIST_START, 1);
+ }
+
+ /**
+ * 前台服务声明。
+ * Android 14 起前台服务必须在清单中声明 foregroundServiceType(见 AndroidManifest),
+ * 并且 startForegroundService() 之后 5 秒内必须调用 startForeground(),
+ * 否则系统会抛出 ForegroundServiceDidNotStartInTimeException 并触发 ANR。
+ * 因此该方法在 onCreate 最前面执行,内部并做异常兜底。
+ */
+ private void startForegroundIfNeeded() {
+ if (JgyUtils.getInstance().checkAppPlatform() != JgyUtils.HL500Platform) {
+ return;
+ }
+ try {
+ mNotificationManagerCompat = NotificationManagerCompat.from(this);
+ createNotificationChannel();
+ sendSimpleNotification();
+ } catch (Exception e) {
+ Log.e(TAG, "startForegroundIfNeeded: " + e.getMessage());
+ }
+ }
+
+ /**
+ * 后台线程执行的初始化,只包含 Settings 读写、包管理、文件 IO 等非 UI 操作。
+ */
+ private void initOnBackground() {
+ try {
+ if (mMMKV.decodeInt(CommonConfig.DEVICES_FRIST_START, 0) == 0) {
+ if (JgyUtils.getInstance().checkAppPlatform() == JgyUtils.DX11Platform) {
+ Settings.System.putInt(getContentResolver(), CommonConfig.AOLE_ACTION_DEVELOPER_OPTIONS, 1);
+ }
+ if (!checkAoleyunApp()) {
+ Log.e(TAG, "onStartCommand: " + "checkAoleyunApp");
+// mPresenter.checkAoleyunUpdate();
+ }
+
+ try {
+ //本地默认禁用的应用统一在 ApkUtils.DEFAULT_DISABLE_APP_MAP 中按机型维护,
+ //showAllApp() 会跳过这些应用,避免被重新启用
+ ApkUtils.hideDefaultDisableApp(this);
+ } catch (Exception e) {
+ Log.e(TAG, "initOnBackground: " + e.getMessage());
+ }
+ }
+
+ if ("YX-T01".equalsIgnoreCase(Build.MODEL)) {
+ JgyUtils.getInstance().setTongyiAppTop();
+ }
+ if (JgyUtils.getInstance().checkAppPlatform() == JgyUtils.YXPD1Platform) {
+ Settings.System.putString(getContentResolver(), CommonConfig.ACTIVATIONBEAN_CODE_KEY, Utils.getSerial(this));
+ if (ApkUtils.isAvailable(this, JgyUtils.JXW_LAUNCHER_PACKAGE_NAME)) {
+ Intent intent2 = new Intent(Intent.ACTION_VIEW);
+ intent2.setPackage(JgyUtils.JXW_LAUNCHER_PACKAGE_NAME);
+ intent2.setClassName(JgyUtils.JXW_LAUNCHER_PACKAGE_NAME, JgyUtils.JXW_LAUNCHER_CLASS_NAME);
+ try {
+ startService(intent2);//或者bindService(intent, mConnection, Context.BIND_AUTO_CREATE); 都能启动
+ } catch (Exception e) {
+ Log.e(TAG, "initOnBackground: " + e.getMessage());
+ }
+ }
+ }
+
+ String launcherPkg = JgyUtils.getInstance().getDefaultDesktop();
+ Log.e(TAG, "initOnBackground: " + launcherPkg);
+ JgyUtils.getInstance().checkDefaultDesktop(launcherPkg);
+
+ Settings.Global.putString(getContentResolver(), "AOLE_SERIAL", Utils.getSerial(MainService.this));
+
+ PackageManager pm = getPackageManager();
+ try {
+ Log.e(TAG, "initOnBackground: " + pm.getApplicationEnabledSetting("com.teclast.update"));
+ pm.setApplicationEnabledSetting("com.teclast.update", PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 0);
+ Log.e(TAG, "initOnBackground: enable com.teclast.update");
+ } catch (Exception e) {
+ Log.e(TAG, "initOnBackground: setApplicationEnabledSetting = " + e.getMessage());
+ }
+ if ("V1.01_20230519".equalsIgnoreCase(Build.DISPLAY)) {
+ Log.e(TAG, "initOnBackground: enable");
+ Intent intent = new Intent();
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setClassName("com.teclast.update", "com.teclast.update.GoogleOtaClient");
+ try {
+ startActivity(intent);
+ } catch (Exception e) {
+ Log.e(TAG, "initOnBackground: startActivity = " + e.getMessage());
+ }
+ }
+
+ if (JgyUtils.getInstance().isAiuduPlatform()) {
+ ApkUtils.HideAiUDuApp();
+ }
+
+ //根据要求卸载这两个应用
+ if (JgyUtils.getInstance().getDeviceIsLocked()) {
+ ApkUtils.UninstallAPP(this, "com.joytv.live");
+ ApkUtils.UninstallAPP(this, "com.tencent.android.qqdownloader");
+ }
+
+ int oldVersionCode = mMMKV.decodeInt(CommonConfig.SN_VERSIONCODE, 0);
+ if (oldVersionCode < BuildConfig.VERSION_CODE) {
+ Log.e(TAG, "initOnBackground: " + "upgrade successful");
+ try {
+ new CacheUtils().cleanApplicationUserData(this, "com.aoleyun.audos");
+ } catch (Exception e) {
+ Log.e(TAG, "initOnBackground: " + e.getMessage());
+ }
+ }
+ mMMKV.encode(CommonConfig.SN_VERSIONCODE, BuildConfig.VERSION_CODE);
+
+ JgyUtils.getInstance().writeAppPackageList();
+
+ IActivityManager activityManager = ActivityManagerNative.getDefault();
+ try {
+ activityManager.setActivityController(new AoleyunActivityController(), false);
+ } catch (RemoteException e) {
+ Log.e(TAG, "setActivityController: " + e.getMessage());
+ }
+
+ JgyUtils.getInstance().checkAoleyunApp();
+
+ String allowAppList = Settings.System.getString(getContentResolver(), CommonConfig.SEEWO_APP_SOURCE_INSTALL_WHITELIST);
+ if (TextUtils.isEmpty(allowAppList)) {
+ Settings.System.putString(getContentResolver(), CommonConfig.SEEWO_APP_SOURCE_INSTALL_WHITELIST, String.join(",", JgyUtils.DEFAULT_APP_SOURCE_WHITE_LIST));
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "initOnBackground: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 在后台单线程中串行执行初始化任务
+ */
+ private void runOnBackground(Runnable runnable) {
+ try {
+ sInitExecutor.execute(runnable);
+ } catch (Exception e) {
+ Log.e(TAG, "runOnBackground: " + e.getMessage());
+ }
}
private void initLiveData() {
@@ -544,7 +610,7 @@ public class MainService extends BaseService implements NetworkUtils.OnNetworkSt
mServiceModel.setLogoImg();
mServiceModel.getAllAppList();
mServiceModel.getBrowserBookmarks();
- mServiceModel.getDesktopIcon();
+ mServiceModel.getIconControl();
mServiceModel.getAllInfoAndSetting();
mServiceModel.getAppAutoStartUpdateAndNet();
mServiceModel.setAppinsideWeb();
@@ -594,23 +660,23 @@ public class MainService extends BaseService implements NetworkUtils.OnNetworkSt
showFloatingWindow(tips);
}
screenlocked = true;
- SPUtils.put(MainService.this, "is_screen_lock", true);
- SPUtils.put(MainService.this, "screen_tips", tips);
+ mMMKV.encode("is_screen_lock", true);
+ mMMKV.encode("screen_tips", tips);
} else {
if (!timelocked) {
hideFloatingWindow();
}
screenlocked = false;
- SPUtils.put(MainService.this, "is_screen_lock", false);
- SPUtils.put(MainService.this, "screen_tips", "");
+ mMMKV.encode("is_screen_lock", false);
+ mMMKV.encode("screen_tips", "");
}
} else {
if (!timelocked) {
hideFloatingWindow();
}
screenlocked = false;
- SPUtils.put(MainService.this, "is_screen_lock", false);
- SPUtils.put(MainService.this, "screen_tips", "");
+ mMMKV.encode("is_screen_lock", false);
+ mMMKV.encode("screen_tips", "");
}
}
});
@@ -624,14 +690,20 @@ public class MainService extends BaseService implements NetworkUtils.OnNetworkSt
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, "onStartCommand: ");
- JgyUtils.getInstance().wakeUpAoleyunAPP();
- if (mMMKV.decodeInt(CommonConfig.DEVICES_FRIST_START, 0) == 0) {
- if (!checkAoleyunApp()) {
- Log.e(TAG, "onStartCommand: " + "checkAoleyunApp");
-// mPresenter.checkAoleyunUpdate();
- }
+ try {
+ JgyUtils.getInstance().wakeUpAoleyunAPP();
+ // checkUpdate 内部会做安装包 MD5 校验、文件读写与 APK 安装,属于耗时 IO,
+ // 必须放到子线程执行,否则会阻塞主线程导致 ANR,且安装异常会直接闪退
+ new Thread(() -> {
+ try {
+ NetInterfaceManager.getInstance().checkUpdate();
+ } catch (Throwable e) {
+ Log.e(TAG, "onStartCommand: checkUpdate 异常 " + e.getMessage(), e);
+ }
+ }, "check-update").start();
+ } catch (Throwable e) {
+ Log.e(TAG, "onStartCommand: 异常 " + e.getMessage(), e);
}
- NetInterfaceManager.getInstance().checkUpdate();
return START_STICKY;
}
@@ -687,7 +759,7 @@ public class MainService extends BaseService implements NetworkUtils.OnNetworkSt
private void setStatusbar() {
// StatusBarManager mStatusBarManager = (StatusBarManager) getApplicationContext().getSystemService(Context.STATUS_BAR_SERVICE);
// mStatusBarManager.disable(StatusBarManager.DISABLE_RECENT | StatusBarManager.DISABLE_HOME);
- String status = (String) SPUtils.get(this, JGYActions.ACTION_STATUSBAR_STATUS, "");
+ String status = mMMKV.decodeString(JGYActions.ACTION_STATUSBAR_STATUS, "");
Log.e(TAG, "setStatusbar: String: " + status);
if (TextUtils.isEmpty(status)) {
Log.e(TAG, "setStatusbar: " + "status is NULL");
@@ -709,25 +781,25 @@ public class MainService extends BaseService implements NetworkUtils.OnNetworkSt
private void unRegisterReceivers() {
if (null != lockScreenReceiver) {
- unregisterReceiver(lockScreenReceiver);
+ ReceiverCompat.unregisterReceiver(this, lockScreenReceiver);
}
if (null != mTimeChangedReceiver) {
- unregisterReceiver(mTimeChangedReceiver);
+ ReceiverCompat.unregisterReceiver(this, mTimeChangedReceiver);
}
if (null != mInstallReceiver) {
- unregisterReceiver(mInstallReceiver);
+ ReceiverCompat.unregisterReceiver(this, mInstallReceiver);
}
if (null != mWifiReceiver) {
- unregisterReceiver(mWifiReceiver);
+ ReceiverCompat.unregisterReceiver(this, mWifiReceiver);
}
if (null != mRefreshReceiver) {
- unregisterReceiver(mRefreshReceiver);
+ ReceiverCompat.unregisterReceiver(this, mRefreshReceiver);
}
if (null != screenLockReceiver) {
- unregisterReceiver(screenLockReceiver);
+ ReceiverCompat.unregisterReceiver(this, screenLockReceiver);
}
if (null != mDisplayReceiver) {
- unregisterReceiver(mDisplayReceiver);
+ ReceiverCompat.unregisterReceiver(this, mDisplayReceiver);
}
}
@@ -747,7 +819,8 @@ public class MainService extends BaseService implements NetworkUtils.OnNetworkSt
filter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
filter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
- registerReceiver(mWifiReceiver, filter);
+ //Android13+ 必须显式声明导出标志,否则注册时会抛 SecurityException
+ ReceiverCompat.registerReceiver(this, mWifiReceiver, filter, ReceiverCompat.RECEIVER_NOT_EXPORTED);
}
public class WifiReceiver extends BroadcastReceiver {
@@ -806,7 +879,7 @@ public class MainService extends BaseService implements NetworkUtils.OnNetworkSt
}
IntentFilter filter = new IntentFilter();
filter.addAction(RefreshReceiver.REFRESH_RECEIVER_ACTION);
- registerReceiver(mRefreshReceiver, filter);
+ ReceiverCompat.registerReceiver(this, mRefreshReceiver, filter, ReceiverCompat.RECEIVER_NOT_EXPORTED);
}
public class RefreshReceiver extends BroadcastReceiver {
@@ -833,7 +906,7 @@ public class MainService extends BaseService implements NetworkUtils.OnNetworkSt
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
- registerReceiver(screenLockReceiver, filter);
+ ReceiverCompat.registerReceiver(this, screenLockReceiver, filter, ReceiverCompat.RECEIVER_NOT_EXPORTED);
}
private class ScreenLockReceiver extends BroadcastReceiver {
@@ -944,7 +1017,8 @@ public class MainService extends BaseService implements NetworkUtils.OnNetworkSt
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
filter.addAction(LockScreenReceiver.action_lock);
filter.addAction(LockScreenReceiver.action_unlock);
- registerReceiver(lockScreenReceiver, filter);
+ //锁屏管控广播由兄弟管控应用下发,需保持可被外部发送
+ ReceiverCompat.registerReceiver(this, lockScreenReceiver, filter, ReceiverCompat.RECEIVER_EXPORTED);
}
}
@@ -1025,7 +1099,7 @@ public class MainService extends BaseService implements NetworkUtils.OnNetworkSt
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
filter.addAction(Intent.ACTION_TIME_TICK);
filter.addAction(TimeChangedReceiver.ACTION_UPDATE);
- registerReceiver(mTimeChangedReceiver, filter);
+ ReceiverCompat.registerReceiver(this, mTimeChangedReceiver, filter, ReceiverCompat.RECEIVER_NOT_EXPORTED);
}
public class TimeChangedReceiver extends BroadcastReceiver {
@@ -1070,7 +1144,7 @@ public class MainService extends BaseService implements NetworkUtils.OnNetworkSt
filter.addAction(NIGHT_DISPLAY_DISABLED);
filter.addAction(READ_MODEL_ENABLED);
filter.addAction(READ_MODEL_DISABLED);
- registerReceiver(mDisplayReceiver, filter);
+ ReceiverCompat.registerReceiver(this, mDisplayReceiver, filter, ReceiverCompat.RECEIVER_NOT_EXPORTED);
}
public static final String NIGHT_DISPLAY_ENABLED = "NIGHT_DISPLAY_ENABLED";
@@ -1108,7 +1182,7 @@ public class MainService extends BaseService implements NetworkUtils.OnNetworkSt
private static final String UPLOAD_LOG_TIME = "23:00:00";
private void checkUploadLogTime() {
- int is_log_type = (int) SPUtils.get(this, CommonConfig.UPLOAD_LOG_TYPE, 0);
+ int is_log_type = mMMKV.decodeInt(CommonConfig.UPLOAD_LOG_TYPE, 0);
if (is_log_type == 0) {
Log.e(TAG, "checkUploadLogTime: " + "is_log_type = " + is_log_type);
} else {
@@ -1197,7 +1271,9 @@ public class MainService extends BaseService implements NetworkUtils.OnNetworkSt
mInstallReceiver = new InstallReceiver();
}
IntentFilter intentFilter = new IntentFilter(INSTALLAPK_ACTION);
- registerReceiver(mInstallReceiver, intentFilter, INSTALLAPK_PREMISSIONS, null);
+ //安装广播由兄弟管控应用下发(受签名权限保护),需显式声明导出
+ ReceiverCompat.registerReceiver(this, mInstallReceiver, intentFilter,
+ ReceiverCompat.RECEIVER_EXPORTED, INSTALLAPK_PREMISSIONS);
}
private static final String INSTALLAPK_ACTION = "JGY_INSTALLAPK_ACTION";
diff --git a/app/src/main/java/com/aoleyun/sn/service/main/MainServiceModel.java b/app/src/main/java/com/aoleyun/sn/service/main/MainServiceModel.java
index f107cd9..220a2ef 100644
--- a/app/src/main/java/com/aoleyun/sn/service/main/MainServiceModel.java
+++ b/app/src/main/java/com/aoleyun/sn/service/main/MainServiceModel.java
@@ -20,7 +20,6 @@ import com.aoleyun.sn.comm.CommonConfig;
import com.aoleyun.sn.comm.JGYActions;
import com.aoleyun.sn.network.NetInterfaceManager;
import com.aoleyun.sn.utils.JgyUtils;
-import com.aoleyun.sn.utils.SPUtils;
import com.aoleyun.sn.utils.SysSettingUtils;
import com.aoleyun.sn.utils.URLUtils;
import com.aoleyun.sn.utils.Utils;
@@ -131,7 +130,7 @@ public class MainServiceModel extends ViewModel {
* 第一次连接
*/
public void getFirstConnect() {
- int first = (int) SPUtils.get(mContext, CommonConfig.JGY_FIRST_CONNECT, 0);
+ int first = mMMKV.decodeInt(CommonConfig.JGY_FIRST_CONNECT, 0);
Log.e(TAG, "getFirstConnect: " + "first_connect = " + first);
if (first == 0) {
Log.e(TAG, "setDisableSetting: ");
@@ -377,13 +376,12 @@ public class MainServiceModel extends ViewModel {
new URLUtils(mContext).setBrowserBlackList();
}
- public void getDesktopIcon() {
+ public void getIconControl() {
NetInterfaceManager.getInstance()
- .getDesktopIcon(getLifecycle(), new NetInterfaceManager.onCompleteCallback() {
+ .getIconControl(getLifecycle(), new NetInterfaceManager.onCompleteCallback() {
@Override
public void onComplete() {
- Log.e("getDesktopIcon", "onComplete: ");
-// JgyUtils.getInstance().hideSystemAPP();
+ Log.e("getIconControl", "onComplete: ");
}
});
}
diff --git a/app/src/main/java/com/aoleyun/sn/utils/ApkUtils.java b/app/src/main/java/com/aoleyun/sn/utils/ApkUtils.java
index 8076201..24eef41 100644
--- a/app/src/main/java/com/aoleyun/sn/utils/ApkUtils.java
+++ b/app/src/main/java/com/aoleyun/sn/utils/ApkUtils.java
@@ -46,9 +46,12 @@ import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Enumeration;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@@ -355,6 +358,49 @@ public class ApkUtils {
this.add("com.android.browser");
}};
+ /**
+ * 所有机型公共的默认禁用应用({@link #DEFAULT_DISABLE_APP_MAP} 的固定 key)
+ */
+ public static final String DEFAULT_DISABLE_APP_KEY = "default";
+
+ /**
+ * 设备型号前缀({@link #DEFAULT_DISABLE_APP_MAP} 中以该前缀 + Build.MODEL 作为 key)
+ */
+ public static final String DEFAULT_DISABLE_APP_MODEL_PREFIX = "MODEL:";
+
+ /**
+ * 本地默认禁用的应用(按机型区分,设备启动时由 MainService 禁用)。
+ *
+ * {@link #showAllApp(Context)} 不会再把这些应用重新启用,避免与本地默认禁用、
+ * 后台在线下发的禁用互相覆盖。
+ *
+ * key 支持两种写法,命中后会被叠加:
+ *
+ * 1、平台:"default" 表示所有机型;其它按 build.gradle 中 buildConfigField 配置的 platform
+ * (即 {@link BuildConfig#platform}),如 "QualcommA"、"MT6765"、"MTKG11"、"展锐";
+ *
+ * 2、设备型号:"MODEL:" 前缀 + {@link Build#MODEL},如 "MODEL:G1096JW"。
+ *
+ * 需要给某个机型单独配置时,在这里增加对应 key 与包名即可。
+ */
+ public static final Map> DEFAULT_DISABLE_APP_MAP = new HashMap>() {{
+ //所有机型默认禁用
+ put(DEFAULT_DISABLE_APP_KEY, new HashSet() {{
+ this.add("com.android.email");
+ this.add("com.android.browser");
+ }});
+ put(JgyUtils.Qualcomm_TAG, new HashSet() {{
+ this.add("org.chromium.chrome");
+ this.add("com.onyx.appmarket");
+ this.add("com.onyx.aiassistant");
+ this.add("com.onyx.android.production.test");
+ this.add("com.onyx.dict");
+ this.add("com.onyx.mail");
+ this.add("com.onyx.musicplayer");
+ this.add("com.onyx.voicerecorder");
+ }});
+ }};
+
/**
* 预置的可以卸载的系统应用,判断出来为第三方
*/
@@ -912,10 +958,15 @@ public class ApkUtils {
public static void installApp(Context context, String filePath) {
Log.e(TAG, "installApp: " + filePath);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- installAppatPie(context, filePath);
- } else {
- installApps(filePath);
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ installAppatPie(context, filePath);
+ } else {
+ installApps(filePath);
+ }
+ } catch (Throwable e) {
+ // 兜底:安装入口不向调用方抛异常,避免主线程/服务线程因安装失败而闪退
+ Log.e(TAG, "installApp: 安装异常 " + e.getMessage(), e);
}
}
@@ -966,35 +1017,87 @@ public class ApkUtils {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public static void installAppatPie(Context context, String apkFilePath) {
- File file = new File(apkFilePath);
- Log.e(TAG, "installAppatPie: 正在安装应用 " + apkFilePath);
- PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
- PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(PackageInstaller
- .SessionParams.MODE_FULL_INSTALL);
- sessionParams.setSize(file.length());
- int sessionId = createSession(packageInstaller, sessionParams);
- if (sessionId != -1) {
- boolean copySuccess = copyApkFile(packageInstaller, sessionId, apkFilePath);
- if (copySuccess) {
- Toaster.show("正在安装应用");
- install(packageInstaller, sessionId, context);
+ try {
+ File file = new File(apkFilePath);
+ Log.e(TAG, "installAppatPie: 正在安装应用 " + apkFilePath);
+ if (!file.exists() || file.length() <= 0) {
+ Log.e(TAG, "installAppatPie: 安装包不存在或为空 " + apkFilePath);
+ return;
}
+ PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
+ PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(PackageInstaller
+ .SessionParams.MODE_FULL_INSTALL);
+ sessionParams.setSize(file.length());
+ int sessionId = createSession(packageInstaller, sessionParams);
+ if (sessionId != -1) {
+ boolean copySuccess = copyApkFile(packageInstaller, sessionId, apkFilePath);
+ if (copySuccess) {
+ Toaster.show("正在安装应用");
+ install(packageInstaller, sessionId, context);
+ } else {
+ Log.e(TAG, "installAppatPie: 拷贝安装包失败,放弃本次会话");
+ abandonSession(packageInstaller, sessionId);
+ }
+ }
+ } catch (Throwable e) {
+ // 安装失败只记录日志,绝不向外抛出,否则会直接导致调用方(含主线程)进程闪退
+ Log.e(TAG, "installAppatPie: 安装异常 " + e.getMessage(), e);
}
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private static void install(PackageInstaller packageInstaller, int sessionId, Context context) {
+ PackageInstaller.Session session = null;
try {
- PackageInstaller.Session session = packageInstaller.openSession(sessionId);
- Intent intent = new Intent(context, InstallResultReceiver.class);
- PendingIntent pendingIntent = PendingIntent.getBroadcast(
- context,
- 1, intent,
- PendingIntent.FLAG_UPDATE_CURRENT
- );
- session.commit(pendingIntent.getIntentSender());
- } catch (IOException e) {
- e.printStackTrace();
+ session = packageInstaller.openSession(sessionId);
+ session.commit(createResultPendingIntent(context).getIntentSender());
+ } catch (Throwable e) {
+ Log.e(TAG, "install: 安装失败 " + e.getMessage(), e);
+ abandonSession(packageInstaller, sessionId);
+ } finally {
+ if (session != null) {
+ try {
+ session.close();
+ } catch (Throwable ignore) {
+ }
+ }
+ }
+ }
+
+ /**
+ * 创建 PackageInstaller 的安装结果回调 PendingIntent。
+ *
+ * Android 14(targetSdk 34+)起,系统会向该 PendingIntent 回填安装结果,
+ * 因此必须使用 FLAG_MUTABLE,否则 commit() 会抛出
+ * IllegalArgumentException: The commit() status receiver should come from a mutable PendingIntent,
+ * 该异常在旧代码中会直接冒泡到主线程造成进程闪退。
+ */
+ private static PendingIntent createResultPendingIntent(Context context) {
+ Intent intent = new Intent(context, InstallResultReceiver.class);
+ intent.setPackage(context.getPackageName());
+ int flags = PendingIntent.FLAG_UPDATE_CURRENT;
+ if (Build.VERSION.SDK_INT >= ANDROID_12) {
+ flags |= FLAG_MUTABLE;
+ }
+ return PendingIntent.getBroadcast(context, 1, intent, flags);
+ }
+
+ /**
+ * Android 12(API 31)。编译使用的 framework.jar 为设备提取版,不含 API 31 新增符号,
+ * 因此这里使用字面量常量代替 {@code Build.VERSION_CODES.S} 与 {@code PendingIntent.FLAG_MUTABLE}。
+ */
+ private static final int ANDROID_12 = 31;
+ /**
+ * PendingIntent.FLAG_MUTABLE(API 31 新增,值为 1 << 25)。
+ */
+ private static final int FLAG_MUTABLE = 1 << 25;
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ private static void abandonSession(PackageInstaller packageInstaller, int sessionId) {
+ try {
+ packageInstaller.abandonSession(sessionId);
+ } catch (Throwable e) {
+ Log.e(TAG, "abandonSession: " + e.getMessage());
}
}
@@ -1150,11 +1253,14 @@ public class ApkUtils {
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public static void uninstall(Context context, String packageName) {
- Intent broadcastIntent = new Intent(context, InstallResultReceiver.class);
- PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 1,
- broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT);
- PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
- packageInstaller.uninstall(packageName, pendingIntent.getIntentSender());
+ try {
+ // 卸载结果同样由系统回填到 PendingIntent 中,Android 14+ 必须为可变(Mutable)
+ PendingIntent pendingIntent = createResultPendingIntent(context);
+ PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
+ packageInstaller.uninstall(packageName, pendingIntent.getIntentSender());
+ } catch (Throwable e) {
+ Log.e(TAG, "uninstall: 卸载失败 " + packageName + " " + e.getMessage(), e);
+ }
}
public static boolean pmUninstall(String packageName) {
@@ -1242,23 +1348,35 @@ public class ApkUtils {
public static void showAllApp(Context context) {
PackageManager pm = context.getPackageManager();
- // 查询所有已经安装的应用程序
- List packages = pm.getInstalledPackages(PackageManager.COMPONENT_ENABLED_STATE_ENABLED | PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+ //本地默认禁用 + getSnAppAttr 在线禁用 + getIconControl 应用管控禁用,
+ //这三类应用要保持禁用状态,不能被重新启用
+ Set keepDisablePkgs = getKeepDisablePkgs(context);
+ Log.e(TAG, "showAllApp: keepDisablePkgs = " + keepDisablePkgs);
+ // 查询所有已经安装的应用程序(原来传的是 3,等价于 GET_ACTIVITIES|GET_RECEIVERS,会多加载无用元数据)
+ List packages = pm.getInstalledPackages(0);
+ boolean isAiuduPlatform = JgyUtils.getInstance().isAiuduPlatform();
for (PackageInfo packageInfo : packages) {
String pkg = packageInfo.packageName;
- Log.e(TAG, "showAllApp: " + pkg + " disable = " + (pm.getApplicationEnabledSetting(pkg) == PackageManager.COMPONENT_ENABLED_STATE_DISABLED));
if (canremove_systemapp.contains(pkg)
&& !show_canremove_systemapp.contains(pkg)) {
Log.e(TAG, "showAllApp: continue: " + pkg);
continue;
}
- if (JgyUtils.getInstance().isAiuduPlatform()) {
- if (AiudHideApp.contains(pkg)) {
- continue;
- }
+ if (isAiuduPlatform && AiudHideApp.contains(pkg)) {
+ continue;
+ }
+ if (keepDisablePkgs.contains(pkg)) {
+ Log.e(TAG, "showAllApp: keep disable: " + pkg);
+ continue;
}
try {
- Log.i(TAG, "showAllApp: show: " + pkg);
+ int state = pm.getApplicationEnabledSetting(pkg);
+ //已经是启用状态,无需重复设置(避免每次启动都做一遍无用的跨进程调用)
+ if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
+ || state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+ continue;
+ }
+ Log.e(TAG, "showAllApp: show: " + pkg + ", state = " + state);
pm.setApplicationEnabledSetting(pkg, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 0);
} catch (Exception e) {
Log.e(TAG, "showAllApp: " + e.getMessage());
@@ -1266,6 +1384,193 @@ public class ApkUtils {
}
}
+ /**
+ * 获取需要保持禁用状态的应用,包含:
+ *
+ * 1、当前生效的本地默认禁用应用({@link #getEffectiveDefaultDisableApp(Context)});
+ *
+ * 2、getSnAppAttr 接口下发的在线禁用应用;
+ *
+ * 3、getIconControl 接口下发的禁用应用。
+ *
+ * @param context 上下文
+ * @return 不允许被 {@link #showAllApp(Context)} 重新启用的包名集合
+ */
+ public static Set getKeepDisablePkgs(Context context) {
+ Set pkgs = new HashSet<>(getEffectiveDefaultDisableApp(context));
+ pkgs.addAll(getOnlineDisablePkgs(context));
+ pkgs.addAll(getDesktopHidePkgs(context));
+ return pkgs;
+ }
+
+ /**
+ * 获取当前机型本地默认禁用的应用。
+ *
+ * 结果由 {@link #DEFAULT_DISABLE_APP_MAP} 决定,按"所有机型 + 平台 + 设备型号"三级叠加,
+ * 见 {@link #DEFAULT_DISABLE_APP_MAP} 的说明。
+ *
+ * @param context 上下文
+ * @return 当前机型需要默认禁用的包名集合
+ */
+ public static Set getDefaultDisableApp(Context context) {
+ String platform = BuildConfig.platform;
+ String model = Build.MODEL;
+ Set pkgs = new HashSet<>();
+ //所有机型公共的默认禁用应用
+ pkgs.addAll(getDefaultDisableAppByKey(DEFAULT_DISABLE_APP_KEY));
+ //按平台区分的默认禁用应用
+ pkgs.addAll(getDefaultDisableAppByKey(platform));
+ //按设备型号区分的默认禁用应用
+ pkgs.addAll(getDefaultDisableAppByKey(DEFAULT_DISABLE_APP_MODEL_PREFIX + model));
+ Log.e(TAG, "getDefaultDisableApp: platform = " + platform + ", model = " + model + ", pkgs = " + pkgs);
+ return pkgs;
+ }
+
+ /**
+ * 按 key 取默认禁用应用清单
+ *
+ * @param key {@link #DEFAULT_DISABLE_APP_MAP} 的 key
+ * @return 对应的包名集合,没有配置时返回空集合
+ */
+ private static Set getDefaultDisableAppByKey(String key) {
+ Set pkgs = DEFAULT_DISABLE_APP_MAP.get(key);
+ return pkgs == null ? Collections.emptySet() : pkgs;
+ }
+
+ /**
+ * 读取 getSnAppAttr 接口下发的在线禁用清单
+ *
+ * @param context 上下文
+ * @return 在线禁用的包名集合
+ */
+ public static Set getOnlineDisablePkgs(Context context) {
+ String appString = Settings.Global.getString(context.getContentResolver(), CommonConfig.AOLE_ACTION_DISABLE_APP);
+ Log.e(TAG, "getOnlineDisablePkgs: " + appString);
+ return new HashSet<>(parsePkgList(appString));
+ }
+
+ /**
+ * 读取 getIconControl 接口下发的禁用应用清单
+ *
+ * @param context 上下文
+ * @return 被禁用的包名集合
+ */
+ public static Set getDesktopHidePkgs(Context context) {
+ String appString = Settings.Global.getString(context.getContentResolver(), CommonConfig.AOLE_ACTION_HIDE_DESKTOP_ICON);
+ Log.e(TAG, "getDesktopHidePkgs: " + appString);
+ return new HashSet<>(parsePkgList(appString));
+ }
+
+ /**
+ * 在线禁用来源(getSnAppAttr 接口下发)
+ */
+ public static final int SOURCE_ONLINE_DISABLE = 1;
+ /**
+ * 应用管控禁用来源(getIconControl 接口下发)
+ */
+ public static final int SOURCE_DESKTOP_HIDE = 1 << 1;
+
+ /**
+ * 读取 getIconControl 接口要求显示(control_type = 1)的应用清单
+ *
+ * @param context 上下文
+ * @return 接口要求显示的包名集合
+ */
+ public static Set getShowAppPkgs(Context context) {
+ String appString = Settings.Global.getString(context.getContentResolver(), CommonConfig.AOLE_ACTION_SHOW_DESKTOP_ICON);
+ Log.e(TAG, "getShowAppPkgs: " + appString);
+ return new HashSet<>(parsePkgList(appString));
+ }
+
+ /**
+ * 获取当前生效的默认禁用应用。
+ *
+ * = 机型配置的默认禁用({@link #getDefaultDisableApp(Context)})− getIconControl 接口明确要求显示的应用,
+ * 即后台下发的"显示"优先级高于本地默认禁用。
+ *
+ * @param context 上下文
+ * @return 当前生效的默认禁用包名集合
+ */
+ public static Set getEffectiveDefaultDisableApp(Context context) {
+ Set pkgs = new HashSet<>(getDefaultDisableApp(context));
+ pkgs.removeAll(getShowAppPkgs(context));
+ return pkgs;
+ }
+
+ /**
+ * 应用是否被"其他管控来源"要求保持禁用。
+ *
+ * 用于放开某个来源的禁用清单时,避免把其他来源(本地默认禁用、爱优读隐藏、预置可卸载应用,
+ * 以及未排除的在线禁用/应用管控禁用)要求禁用的应用一起放开。
+ *
+ * @param context 上下文
+ * @param pkg 包名
+ * @param excludeSources 需要排除的来源({@link #SOURCE_ONLINE_DISABLE}、
+ * {@link #SOURCE_DESKTOP_HIDE} 按位或),本人正在放开的来源需要排除
+ * @return true:该应用还被其他来源管控,不能放开
+ */
+ public static boolean isKeepDisableByOtherSource(Context context, String pkg, int excludeSources) {
+ if (TextUtils.isEmpty(pkg)) {
+ return false;
+ }
+ if (getEffectiveDefaultDisableApp(context).contains(pkg)) {
+ return true;
+ }
+ if (canremove_systemapp.contains(pkg) && !show_canremove_systemapp.contains(pkg)) {
+ return true;
+ }
+ if (JgyUtils.getInstance().isAiuduPlatform() && AiudHideApp.contains(pkg)) {
+ return true;
+ }
+ if ((excludeSources & SOURCE_ONLINE_DISABLE) == 0 && getOnlineDisablePkgs(context).contains(pkg)) {
+ return true;
+ }
+ return (excludeSources & SOURCE_DESKTOP_HIDE) == 0 && getDesktopHidePkgs(context).contains(pkg);
+ }
+
+ /**
+ * 禁用当前生效的默认禁用应用(已禁用的跳过,被接口要求显示的不禁用)
+ *
+ * @param context 上下文
+ */
+ public static void hideDefaultDisableApp(Context context) {
+ for (String pkg : getEffectiveDefaultDisableApp(context)) {
+ if (isDisable(context, pkg)) {
+ //已经是禁用状态(或未安装),无需重复禁用
+ continue;
+ }
+ Log.e(TAG, "hideDefaultDisableApp: hide: " + pkg);
+ JgyUtils.getInstance().hideApp(pkg);
+ }
+ }
+
+ /**
+ * 解析 Settings.Global 中以逗号分隔的包名清单
+ *
+ * @param appString 清单字符串
+ * @return 包名列表(已过滤空值、中括号以及后台无数据时写入的"null")
+ */
+ public static List parsePkgList(String appString) {
+ List pkgs = new ArrayList<>();
+ if (TextUtils.isEmpty(appString)) {
+ return pkgs;
+ }
+ try {
+ for (String pkg : appString.split(",")) {
+ pkg = pkg.replace("[", "").replace("]", "").trim();
+ if (TextUtils.isEmpty(pkg) || "null".equalsIgnoreCase(pkg)) {
+ continue;
+ }
+ if (!pkgs.contains(pkg)) {
+ pkgs.add(pkg);
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "parsePkgList: " + e.getMessage());
+ }
+ return pkgs;
+ }
+
public static void hideSystemSettingAPP(Context context, String pkage) {
int hide = 0;
//后台0显示,1隐藏
diff --git a/app/src/main/java/com/aoleyun/sn/utils/InstallResultReceiver.java b/app/src/main/java/com/aoleyun/sn/utils/InstallResultReceiver.java
index 3c1cfc7..65d36fb 100644
--- a/app/src/main/java/com/aoleyun/sn/utils/InstallResultReceiver.java
+++ b/app/src/main/java/com/aoleyun/sn/utils/InstallResultReceiver.java
@@ -42,11 +42,13 @@ public class InstallResultReceiver extends BroadcastReceiver {
// while (iterator.hasNext()) {
// Log.d("KEY", iterator.next());
// }
- String STATUS = intent.getStringExtra(PackageInstaller.EXTRA_STATUS);
+ //STATUS / SESSION_ID / LEGACY_STATUS 都是 int 类型 extra,
+ //使用 getStringExtra 读取会抛 ClassCastException(被 Bundle 吞掉并打印警告)
+ int STATUS = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
String PACKAGE_NAME = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME);
- String SESSION_ID = intent.getStringExtra(PackageInstaller.EXTRA_SESSION_ID);
+ int SESSION_ID = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
String STATUS_MESSAGE = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
- String LEGACY_STATUS = intent.getStringExtra("android.content.pm.extra.LEGACY_STATUS");
+ int LEGACY_STATUS = intent.getIntExtra("android.content.pm.extra.LEGACY_STATUS", -1);
// Log.e("fht", STATUS);
// Log.e("fht", PACKAGE_NAME);
// Log.e("fht", SESSION_ID);
diff --git a/app/src/main/java/com/aoleyun/sn/utils/JgyUtils.java b/app/src/main/java/com/aoleyun/sn/utils/JgyUtils.java
index bc4cc8b..e3b75e2 100644
--- a/app/src/main/java/com/aoleyun/sn/utils/JgyUtils.java
+++ b/app/src/main/java/com/aoleyun/sn/utils/JgyUtils.java
@@ -30,7 +30,9 @@ import android.os.BatteryManager;
import android.os.Build;
import android.os.Debug;
import android.os.Environment;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
@@ -515,7 +517,8 @@ public class JgyUtils {
BatteryManager batteryManager = (BatteryManager) mContext.getSystemService(Context.BATTERY_SERVICE);
return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
} else {
- Intent intent = new ContextWrapper(mContext).registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+ Intent intent = ReceiverCompat.registerReceiver(new ContextWrapper(mContext), null,
+ new IntentFilter(Intent.ACTION_BATTERY_CHANGED), ReceiverCompat.RECEIVER_NOT_EXPORTED);
return (intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100) /
intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
}
@@ -1088,7 +1091,7 @@ public class JgyUtils {
public void onBootSendNetwork() {
if (!MTK11Tag.equals(getAppPlatform())) {
- String oldBlackListString = (String) SPUtils.get(mContext, CommonConfig.AOLE_ACTION_NETWORK_DISALLOW, "");
+ String oldBlackListString = mMMKV.decodeString(CommonConfig.AOLE_ACTION_NETWORK_DISALLOW, "");
HashSet oldBlackListSet = new HashSet<>(Arrays.asList(oldBlackListString.split(",")));
Log.e(TAG, "setAppNetwork: oldBlackListSet: " + oldBlackListSet);
oldBlackListSet.removeIf(new Predicate() {
@@ -1318,7 +1321,7 @@ public class JgyUtils {
private List getOldWhitelist() {
- String whiteListString = (String) SPUtils.get(mContext, JGY_APPINSIDE_WHITELIST, "");
+ String whiteListString = mMMKV.decodeString(JGY_APPINSIDE_WHITELIST, "");
Log.e(TAG, "getOldWhitelist: " + whiteListString);
Gson gson = new Gson();
Type listType = new TypeToken>() {
@@ -1349,7 +1352,7 @@ public class JgyUtils {
}
});
String data = new Gson().toJson(appgrounds);
- SPUtils.put(mContext, JGY_APPINSIDE_WHITELIST, data);
+ mMMKV.encode(JGY_APPINSIDE_WHITELIST, data);
}
public void onBootSetAppInsideWeb() {
@@ -1507,7 +1510,7 @@ public class JgyUtils {
if (locked == JGYActions.FRAME_CODE_LOCKED) {
SysSettingUtils.setDisableSetting(mContext);
}
- SPUtils.put(mContext, JGYActions.ACTION_STATUSBAR_STATUS, "");
+ mMMKV.encode(JGYActions.ACTION_STATUSBAR_STATUS, "");
} else {
if (getAppPlatform().equals(JgyUtils.ZR6016Tag)) {
Log.e(TAG, "SettingSysData: ZR6016Tag");
@@ -2246,6 +2249,7 @@ public class JgyUtils {
/**
* @param state 1打开 0关闭
+ * dx11 state为1关闭,为0打开
*/
public void setDeveloperOptions(int state) {
Log.e(TAG, "setDeveloperOptions: state = " + state);
@@ -2428,11 +2432,11 @@ public class JgyUtils {
@Deprecated
public void cleanLauncherCache() {
Log.e(TAG, "cleanLauncherCache: Start");
- int cleaned = (int) SPUtils.get(mContext, "fristcleanLauncherCache", 0);
+ int cleaned = mMMKV.decodeInt("fristcleanLauncherCache", 0);
if (cleaned == 0) {
try {
new CacheUtils().cleanApplicationUserData(mContext, "com.android.launcher3");
- SPUtils.put(mContext, "fristcleanLauncherCache", 1);
+ mMMKV.encode("fristcleanLauncherCache", 1);
Log.e(TAG, "cleanLauncherCache: end");
} catch (Exception e) {
e.printStackTrace();
@@ -2677,6 +2681,16 @@ public class JgyUtils {
private static long lastTime;
+ /**
+ * 服务错峰启动间隔(毫秒),避免多个服务同时启动造成主线程串行阻塞
+ */
+ private static final long SERVICE_START_INTERVAL = 1500L;
+
+ /**
+ * 服务错峰启动用的主线程 Handler
+ */
+ private static final Handler sStartServiceHandler = new Handler(Looper.getMainLooper());
+
public static void startServices(Context context) {
Log.e(TAG, "startServices: " + context.getClass().getSimpleName());
if (System.currentTimeMillis() - lastTime < 10 * 60 * 1000) {
@@ -2692,8 +2706,30 @@ public class JgyUtils {
} else {
context.startService(new Intent(context, MainService.class));
}
- context.startService(new Intent(context, StepService.class));
- context.startService(new Intent(context, GuardService.class));
+ //三个服务同时启动会在开机阶段造成主线程串行阻塞(曾出现 Service 启动 200s 超时 ANR),
+ //这里按固定间隔错峰启动 StepService / GuardService
+ final Context appContext = context.getApplicationContext() != null
+ ? context.getApplicationContext() : context;
+ sStartServiceHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ appContext.startService(new Intent(appContext, StepService.class));
+ } catch (Exception e) {
+ Log.e(TAG, "startServices StepService: " + e.getMessage());
+ }
+ }
+ }, SERVICE_START_INTERVAL);
+ sStartServiceHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ appContext.startService(new Intent(appContext, GuardService.class));
+ } catch (Exception e) {
+ Log.e(TAG, "startServices GuardService: " + e.getMessage());
+ }
+ }
+ }, SERVICE_START_INTERVAL * 2);
// context.startService(new Intent(context, LogcatService.class));
}
}
@@ -2794,7 +2830,7 @@ public class JgyUtils {
public void checkDefaultDesktop(String pkg) {
String desktopPkg = getDefaultDesktop();
-// String desktopPkg = (String) SPUtils.get(mContext, "default_launcher", "");
+// String desktopPkg = mMMKV.decodeString(CommonConfig.DEFAULT_LAUNCHER_PACKAGE_NAME, "");
Log.e(TAG, "checkDefaultDesktop: pkg = " + pkg);
Log.e(TAG, "checkDefaultDesktop: desktopPkg = " + desktopPkg);
if (TextUtils.isEmpty(pkg)) {
@@ -2988,7 +3024,7 @@ public class JgyUtils {
// setDefaultLauncher(mContext, pkg, className);
Log.e(TAG, "setDefaultDesktop: setDefaultLauncher: pkg = " + pkg);
}
-// String oldDesktop = (String) SPUtils.get(mContext, "default_launcher", "");
+// String oldDesktop = mMMKV.decodeString(CommonConfig.DEFAULT_LAUNCHER_PACKAGE_NAME, "");
// if (Objects.equals(oldDesktop, pkg)) {
// Log.e(TAG, "setDefaultDesktop: " + "数据一致");
// return;
diff --git a/app/src/main/java/com/aoleyun/sn/utils/ReceiverCompat.java b/app/src/main/java/com/aoleyun/sn/utils/ReceiverCompat.java
new file mode 100644
index 0000000..16a6847
--- /dev/null
+++ b/app/src/main/java/com/aoleyun/sn/utils/ReceiverCompat.java
@@ -0,0 +1,122 @@
+package com.aoleyun.sn.utils;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Build;
+import android.util.Log;
+
+/**
+ * 动态注册广播兼容工具。
+ *
+ * Android 13(API 33)起,动态注册的接收器若包含非系统保护广播,必须显式声明导出标志;
+ * Android 14+ 的系统(含各厂商定制 ROM,例如本机 Android 16)会强制校验,缺失时直接抛出:
+ *
+ * java.lang.SecurityException: One of RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED
+ * should be specified when a receiver isn't being registered exclusively for system broadcasts
+ *
+ * 该异常会导致 Activity / Service 创建失败(启动闪退),并进一步造成前台服务
+ * {@code startForegroundService()} 超时 ANR。
+ *
+ * 说明:本工程通过 {@code app/libs/framework.jar} 提供系统隐藏 API(该 jar 为
+ * Android 10 / API 29 的 framework),其中已包含
+ * {@code registerReceiver(BroadcastReceiver, IntentFilter, int)} 等带 flags 的重载,
+ * 因此这里直接调用即可,无需反射;在低版本系统中未知标志位会被忽略,
+ * 行为与改造前的两参注册保持一致。
+ *
+ *
标志选择建议:
+ *
+ * - {@link #RECEIVER_NOT_EXPORTED}:只接收系统广播,或本应用
+ * (含共享 {@code android.uid.system} 的兄弟应用)发送的广播;
+ * - {@link #RECEIVER_EXPORTED}:需要接收其它(不同 UID 的)应用发送的广播,
+ * 与改造前两参 {@code registerReceiver} 的默认行为一致。
+ *
+ *
+ * @author LiGuangMin
+ */
+public final class ReceiverCompat {
+
+ private static final String TAG = "ReceiverCompat";
+
+ /**
+ * 对应 Context.RECEIVER_EXPORTED(API 33)。
+ */
+ public static final int RECEIVER_EXPORTED = 0x00000002;
+
+ /**
+ * 对应 Context.RECEIVER_NOT_EXPORTED(API 33)。
+ */
+ public static final int RECEIVER_NOT_EXPORTED = 0x00000004;
+
+ /**
+ * 带导出标志的注册重载从 API 33 开始对应用生效。
+ * 此处不使用 {@code Build.VERSION_CODES.TIRAMISU},因为 framework.jar 只到 API 29。
+ */
+ private static final int FLAGS_API = 33;
+
+ private ReceiverCompat() {
+ }
+
+ /**
+ * 兼容注册,等价于 {@code registerReceiver(receiver, filter)},
+ * 但在 API 33+ 上显式带上导出标志。
+ */
+ public static Intent registerReceiver(Context context, BroadcastReceiver receiver,
+ IntentFilter filter, int flags) {
+ return registerReceiver(context, receiver, filter, flags, null);
+ }
+
+ /**
+ * 安全注销广播接收器。
+ *
+ * 系统自带的 {@code unregisterReceiver()} 在"接收器未注册过"或"重复注销"时会抛出
+ * {@code IllegalArgumentException: Receiver not registered},直接导致进程崩溃;
+ * 而业务代码通常只能用字段是否为 null 来判断,字段非 null 并不代表注册成功
+ * (例如注册过程中抛异常、Service 被销毁等场景)。这里统一做容错处理。
+ *
+ * @param receiver 允许为 null
+ */
+ public static void unregisterReceiver(Context context, BroadcastReceiver receiver) {
+ if (context == null || receiver == null) {
+ return;
+ }
+ try {
+ context.unregisterReceiver(receiver);
+ } catch (Exception e) {
+ Log.e(TAG, "unregisterReceiver failed: " + e);
+ }
+ }
+
+ /**
+ * 兼容注册,等价于
+ * {@code registerReceiver(receiver, filter, broadcastPermission, scheduler)}。
+ *
+ * @param receiver 可以为 null,此时用于查询粘性广播
+ * @param flags {@link #RECEIVER_EXPORTED} 或 {@link #RECEIVER_NOT_EXPORTED}
+ * @param broadcastPermission 发送方必须持有的权限,可为 null
+ */
+ public static Intent registerReceiver(Context context, BroadcastReceiver receiver,
+ IntentFilter filter, int flags,
+ String broadcastPermission) {
+ if (context == null || filter == null) {
+ return null;
+ }
+ try {
+ if (Build.VERSION.SDK_INT >= FLAGS_API) {
+ return context.registerReceiver(receiver, filter, broadcastPermission, null, flags);
+ }
+ //Android 13 以下不支持导出标志,保持原有注册方式
+ return context.registerReceiver(receiver, filter, broadcastPermission, null);
+ } catch (Exception e) {
+ //兜底:极端情况下(例如厂商裁剪了重载)退回到传统注册,避免直接崩溃
+ Log.e(TAG, "registerReceiver failed, fallback to legacy: " + e);
+ try {
+ return context.registerReceiver(receiver, filter);
+ } catch (Exception inner) {
+ Log.e(TAG, "registerReceiver legacy failed: " + inner);
+ return null;
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/aoleyun/sn/utils/SPUtils.java b/app/src/main/java/com/aoleyun/sn/utils/SPUtils.java
deleted file mode 100644
index 9614380..0000000
--- a/app/src/main/java/com/aoleyun/sn/utils/SPUtils.java
+++ /dev/null
@@ -1,194 +0,0 @@
-package com.aoleyun.sn.utils;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.drawable.BitmapDrawable;
-import android.util.Base64;
-import android.widget.ImageView;
-
-import com.aoleyun.sn.comm.CommonConfig;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.Map;
-
-public class SPUtils {
- /**
- * 保存在手机里面的文件名
- */
- public static final String FILE_NAME = "share_data";
-
- /**
- * 保存数据的方法,我们需要拿到保存数据的具体类型,然后根据类型调用不同的保存方法
- */
- public static void put(Context context, String key, Object object) {
-
- SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
- Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = sp.edit();
-
- if (object instanceof String) {
- editor.putString(key, (String) object);
- } else if (object instanceof Integer) {
- editor.putInt(key, (Integer) object);
- } else if (object instanceof Boolean) {
- editor.putBoolean(key, (Boolean) object);
- } else if (object instanceof Float) {
- editor.putFloat(key, (Float) object);
- } else if (object instanceof Long) {
- editor.putLong(key, (Long) object);
- } else {
- editor.putString(key, object.toString());
- }
-
- SharedPreferencesCompat.apply(editor);
- }
-
- /**
- * 得到保存数据的方法,我们根据默认值得到保存的数据的具体类型,然后调用相对于的方法获取值
- */
- public static Object get(Context context, String key, Object defaultObject) {
- SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
- Context.MODE_PRIVATE);
-
- if (defaultObject instanceof String) {
- return sp.getString(key, (String) defaultObject);
- } else if (defaultObject instanceof Integer) {
- return sp.getInt(key, (Integer) defaultObject);
- } else if (defaultObject instanceof Boolean) {
- return sp.getBoolean(key, (Boolean) defaultObject);
- } else if (defaultObject instanceof Float) {
- return sp.getFloat(key, (Float) defaultObject);
- } else if (defaultObject instanceof Long) {
- return sp.getLong(key, (Long) defaultObject);
- }
-
- return null;
- }
-
- /**
- * 移除某个key值已经对应的值
- */
- public static void remove(Context context, String key) {
- SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
- Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = sp.edit();
- editor.remove(key);
- SharedPreferencesCompat.apply(editor);
- }
-
- /**
- * 清除所有数据
- */
- public static void clear(Context context) {
- SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
- Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = sp.edit();
- editor.clear();
- SharedPreferencesCompat.apply(editor);
- reductFirstEnter(context);
- }
-
- //还原状态firstEnter信息
- private static void reductFirstEnter(Context context) {
- put(context, CommonConfig.FLAG_FIRST_ENTER, CommonConfig.FLAG_FIRST_ENTER);
- }
-
-
- /**
- * 查询某个key是否已经存在
- */
- public static boolean contains(Context context, String key) {
- SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
- Context.MODE_PRIVATE);
- return sp.contains(key);
- }
-
- /**
- * 返回所有的键值对
- */
- public static Map getAll(Context context) {
- SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
- Context.MODE_PRIVATE);
- return sp.getAll();
- }
-
-
- /**
- * 保存图片到SharedPreferences
- *
- * @param mContext
- * @param imageView
- */
- public static void putImage(Context mContext, String key, ImageView imageView) {
- BitmapDrawable drawable = (BitmapDrawable) imageView.getDrawable();
- Bitmap bitmap = drawable.getBitmap();
- // 将Bitmap压缩成字节数组输出流
- ByteArrayOutputStream byStream = new ByteArrayOutputStream();
- bitmap.compress(Bitmap.CompressFormat.PNG, 80, byStream);
- // 利用Base64将我们的字节数组输出流转换成String
- byte[] byteArray = byStream.toByteArray();
- String imgString = new String(Base64.encodeToString(byteArray, Base64.DEFAULT));
- // 将String保存shareUtils
- SPUtils.put(mContext, key, imgString);
- }
-
- /**
- * 从SharedPreferences读取图片
- *
- * @param mContext
- * @param imageView
- */
- public static Bitmap getImage(Context mContext, String key, ImageView imageView) {
- String imgString = (String) SPUtils.get(mContext, key, "");
- if (!imgString.equals("")) {
- // 利用Base64将我们string转换
- byte[] byteArray = Base64.decode(imgString, Base64.DEFAULT);
- ByteArrayInputStream byStream = new ByteArrayInputStream(byteArray);
- // 生成bitmap
- return BitmapFactory.decodeStream(byStream);
- }
- return null;
- }
-
- /**
- * 创建一个解决SharedPreferencesCompat.apply方法的一个兼容类
- */
- private static class SharedPreferencesCompat {
- private static final Method sApplyMethod = findApplyMethod();
-
- /**
- * 反射查找apply的方法
- */
- @SuppressWarnings({"unchecked", "rawtypes"})
- private static Method findApplyMethod() {
- try {
- Class clz = SharedPreferences.Editor.class;
- return clz.getMethod("apply");
- } catch (NoSuchMethodException e) {
- }
-
- return null;
- }
-
- /**
- * 如果找到则使用apply执行,否则使用commit
- */
- public static void apply(SharedPreferences.Editor editor) {
- try {
- if (sApplyMethod != null) {
- sApplyMethod.invoke(editor);
- return;
- }
- } catch (IllegalArgumentException e) {
- } catch (IllegalAccessException e) {
- } catch (InvocationTargetException e) {
- }
- editor.commit();
- }
- }
-}
diff --git a/app/src/main/java/com/aoleyun/sn/utils/SysSettingUtils.java b/app/src/main/java/com/aoleyun/sn/utils/SysSettingUtils.java
index d76a931..6f203f4 100644
--- a/app/src/main/java/com/aoleyun/sn/utils/SysSettingUtils.java
+++ b/app/src/main/java/com/aoleyun/sn/utils/SysSettingUtils.java
@@ -37,6 +37,8 @@ import java.util.List;
public class SysSettingUtils {
private static final String TAG = "SysSettingUtils";
+ private static final MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
+
public SysSettingUtils() {
}
@@ -65,7 +67,7 @@ public class SysSettingUtils {
setAutoTime(context, jsonObject);
String navJsonString = jsonObject.get("setting_nav").getAsString();
JsonObject navJson = JsonParser.parseString(navJsonString).getAsJsonObject();
- SPUtils.put(context, JGYActions.ACTION_STATUSBAR_STATUS, navJsonString);
+ mMMKV.encode(JGYActions.ACTION_STATUSBAR_STATUS, navJsonString);
setStatusBar(context, navJson);
setBrowserInput(context, jsonObject);
// TODO: 2022/4/11 不是酷比定制的会报错,无法抛出异常
@@ -113,6 +115,9 @@ public class SysSettingUtils {
setNotification(context, 0);
setSettingsMenuShow(context);
setEyeProtectionModeEntrance(context, 1);
+ if (JgyUtils.getInstance().checkAppPlatform() == JgyUtils.DX11Platform) {
+ Settings.System.putInt(context.getContentResolver(), CommonConfig.AOLE_ACTION_DEVELOPER_OPTIONS, 1);
+ }
}
/**
@@ -501,7 +506,7 @@ public class SysSettingUtils {
AllwinnerCubeMdmManager.getInstance().setBluetoothEnable(setting_bht == 0);
}
- int bt_rename = changeNum(jsonObject.get("bluetooth_name_change").getAsInt());
+ int bt_rename = jsonObject.get("bluetooth_name_change").getAsInt();
Settings.System.putInt(context.getContentResolver(), CommonConfig.AOLE_ACTION_BT_RENAME_ALLOW, bt_rename);
}
@@ -1347,11 +1352,13 @@ public class SysSettingUtils {
if (power_saving_mode == 0) {
menus.add("battery");
}
-// // 安全 (默认允许)
-// int security = jsonObject.get("security").getAsInt();
-// if (security == 0) {
-// menus.add("security");
-// }
+
+ // 安全 (默认允许)
+ int security = jsonObject.get("security").getAsInt();
+ if (security == 0) {
+ menus.add("security");
+ }
+
// // 大屏特色功能 (默认允许)
// int large_screen_features = jsonObject.get("large_screen_features").getAsInt();
// if (large_screen_features == 0) {
@@ -1383,10 +1390,11 @@ public class SysSettingUtils {
menus.add("apps");
}
// // 荣耀账号登录 (默认禁止)
-// int honor_account_login = jsonObject.get("honor_account_login").getAsInt();
-// if (honor_account_login == 0) {
+ int honor_account_login = jsonObject.get("honor_account_login").getAsInt();
+ if (honor_account_login == 0) {
// menus.add("honor_id");
-// }
+ menus.add("account_id");
+ }
// // 智慧互联 (默认禁止)
// int smart_interconnect = jsonObject.get("smart_interconnect").getAsInt();
// if (smart_interconnect == 0) {
@@ -1412,12 +1420,21 @@ public class SysSettingUtils {
// if (app_startup_management == 0) {
// menus.add("apps_startup_management");
// }
+
// 应用管理 (默认禁止)
-// int app_management_control = jsonObject.get("app_management_control").getAsInt();
-// if (app_management_control == 0) {
+ int app_management_control = jsonObject.get("app_management_control").getAsInt();
+ if (app_management_control == 0) {
// menus.add("apps_management");
// menus.add("apps_honor_tips");
-// }
+ menus.add("apps");
+ }
+
+ // 儿童 (默认禁止)
+ int children_mode = jsonObject.get("children_mode").getAsInt();
+ if (children_mode == 0) {
+ menus.add("kids_mode");
+ }
+
Log.e(TAG, "setSettingsMenuShow: " + menus);
Settings.System.putString(context.getContentResolver(), CommonConfig.AOLE_SETTINGS_DISALLOW, String.join(",", menus));
}
diff --git a/app/src/main/java/com/aoleyun/sn/utils/TimeUtils.java b/app/src/main/java/com/aoleyun/sn/utils/TimeUtils.java
index ea9ef49..aeb5f13 100644
--- a/app/src/main/java/com/aoleyun/sn/utils/TimeUtils.java
+++ b/app/src/main/java/com/aoleyun/sn/utils/TimeUtils.java
@@ -11,7 +11,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import com.aoleyun.sn.bean.PoweroffBean;
+import com.aoleyun.sn.comm.CommonConfig;
import com.aoleyun.sn.service.main.MainService;
+import com.tencent.mmkv.MMKV;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
@@ -37,6 +39,8 @@ public class TimeUtils {
public static long dayTime = 60 * 60 * 24 * 1000;
public static long minuteTime = 60 * 1000;
+ private static final MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
+
public static String getNowTime() {
long nowTime = System.currentTimeMillis();
DateFormat df = ContralTime.getDf();
@@ -183,8 +187,8 @@ public class TimeUtils {
throw new RuntimeException("Time format error!");
}
try {
- SPUtils.put(context, START_TIME_KEY, time[0].trim());
- SPUtils.put(context, END_TIME_KEY, time[1].trim());
+ mMMKV.encode(START_TIME_KEY, time[0].trim());
+ mMMKV.encode(END_TIME_KEY, time[1].trim());
Date startDate = df.parse(time[0].trim());
Date endDate = df.parse(time[1].trim());
ContralTime contralTime = new ContralTime();
@@ -203,8 +207,8 @@ public class TimeUtils {
}
public static ContralTime getDefaltContralTime(Context context) {
- String startTime = (String) SPUtils.get(context, START_TIME_KEY, "00:00");
- String endTime = (String) SPUtils.get(context, END_TIME_KEY, "00:00");
+ String startTime = mMMKV.decodeString(START_TIME_KEY, "00:00");
+ String endTime = mMMKV.decodeString(END_TIME_KEY, "00:00");
if (null == startTime || null == endTime || ("00:00".equals(startTime) && "00:00".equals(endTime))) {
return null;
} else {
@@ -223,8 +227,8 @@ public class TimeUtils {
}
public static void setEmpty(Context context) {
- SPUtils.put(context, START_TIME_KEY, "00:00");
- SPUtils.put(context, END_TIME_KEY, "00:00");
+ mMMKV.encode(START_TIME_KEY, "00:00");
+ mMMKV.encode(END_TIME_KEY, "00:00");
Intent intent = new Intent();
intent.setAction(MainService.TimeChangedReceiver.ACTION_UPDATE);
context.sendBroadcast(intent);
diff --git a/app/src/main/java/com/aoleyun/sn/utils/URLUtils.java b/app/src/main/java/com/aoleyun/sn/utils/URLUtils.java
index 5bdd663..c56f27a 100644
--- a/app/src/main/java/com/aoleyun/sn/utils/URLUtils.java
+++ b/app/src/main/java/com/aoleyun/sn/utils/URLUtils.java
@@ -7,6 +7,9 @@ import android.text.TextUtils;
import android.util.Log;
import android.webkit.WebSettings;
+import com.aoleyun.sn.comm.CommonConfig;
+import com.tencent.mmkv.MMKV;
+
import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
@@ -37,6 +40,8 @@ public class URLUtils {
private long time1;
private long time2;
+ private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
+
public URLUtils(Context context) {
this.mContext = context;
}
@@ -45,12 +50,12 @@ public class URLUtils {
time1 = System.currentTimeMillis();
//会写入两次,第一次是不带前缀的,第二次会把实际的地址写入进去
String oldwhiteList = Settings.System.getString(mContext.getContentResolver(), "DeselectBrowserArray");
- String oldWhiteIP = (String) SPUtils.get(mContext, "white_ip", "");
+ String oldWhiteIP = mMMKV.decodeString("white_ip", "");
if (TextUtils.isEmpty(oldwhiteList) && TextUtils.isEmpty(oldWhiteIP)) {
Log.e(TAG, "getBrowserWhiteList: " + "oldwhiteList is empty");
Settings.System.putString(mContext.getContentResolver(), "DeselectBrowserArray", "Invalid");
} else {
- String white_ip = (String) SPUtils.get(mContext, "white_ip", "");
+ String white_ip = mMMKV.decodeString("white_ip", "");
if (!TextUtils.isEmpty(white_ip)) {
whiteIPList = new HashSet<>(Arrays.asList(white_ip.split(",")));
}
@@ -221,7 +226,7 @@ public class URLUtils {
if (TextUtils.isEmpty(qch_webblack_url)) {
Settings.System.putString(mContext.getContentResolver(), "qch_webblack_url", "Invalid");
} else {
- String black_ip = (String) SPUtils.get(mContext, "black_ip", "");
+ String black_ip = mMMKV.decodeString("black_ip", "");
if (!TextUtils.isEmpty(black_ip)) {
blackIPList = new HashSet<>(Arrays.asList(black_ip.split(",")));
}
diff --git a/app/src/main/java/com/aoleyun/sn/utils/Utils.java b/app/src/main/java/com/aoleyun/sn/utils/Utils.java
index 34aa8d0..71a95b8 100644
--- a/app/src/main/java/com/aoleyun/sn/utils/Utils.java
+++ b/app/src/main/java/com/aoleyun/sn/utils/Utils.java
@@ -14,7 +14,6 @@ import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -59,6 +58,7 @@ import com.aoleyun.sn.comm.CommonConfig;
import com.aoleyun.sn.manager.AllwinnerCubeMdmManager;
import com.arialyy.aria.core.Aria;
import com.blankj.utilcode.util.FileUtils;
+import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
@@ -67,6 +67,7 @@ import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.hjq.toast.Toaster;
+import com.tencent.mmkv.MMKV;
import java.io.BufferedReader;
import java.io.DataOutputStream;
@@ -115,6 +116,8 @@ import vendor.mediatek.hardware.nvram.V1_0.INvram;
public class Utils {
private static final String TAG = "Utils";
+ private static final MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
+
// MD5 设备地址标识
public static String getMAC(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // 如果当前设备系统大于等于6.0 使用下面的方法
@@ -597,22 +600,6 @@ public class Utils {
return intent;
}
- // 设置系统亮度模式
- public static void systemBrightness(Context context) {
- try {
- SharedPreferences mPrefs = context.getSharedPreferences("colorflykids", 0);
- boolean initSetting = mPrefs.getBoolean("init_setting", true);
- if (initSetting) {
- mPrefs.edit().putBoolean("init_setting", false).commit();
- }
- Settings.System.putInt(context.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE, 0);
- mPrefs.edit().putBoolean("first_init", false).commit();
-
- } catch (Exception err) {
- err.printStackTrace();
- }
- }
-
// 更新 版本比较
public static boolean isUpdate(String oldVersion, String newVersion) {
if (TextUtils.isEmpty(oldVersion) || TextUtils.isEmpty(newVersion) || oldVersion.equals(newVersion)) {
@@ -1205,47 +1192,57 @@ public class Utils {
*/
public static void ariaDownload(Context context, String url, JsonObject jsonObject) {
Log.e(TAG, "ariaDownload: " + jsonObject);
- String fileName = getFileNamefromURL(url);
- String urlMD5 = "";
- if (jsonObject.get("MD5") == null) {
- if (jsonObject.get("app_md5") != null) {
- urlMD5 = jsonObject.get("app_md5").getAsString();
- }
- } else {
- if (jsonObject.get("MD5") != null) {
- if (!jsonObject.get("MD5").isJsonNull()) {
- urlMD5 = jsonObject.get("MD5").getAsString();
- }
- }
- }
+ final String fileName = getFileNamefromURL(url);
+ final String urlMD5 = resolveUrlMD5(jsonObject);
Log.e("ariaDownload", "urlMD5=" + urlMD5);
- String p = JgyUtils.getInstance().getDownLoadPath();
- File file = new File(JgyUtils.getInstance().getDownLoadPath() + fileName);
- if (file.exists() && !file.isDirectory()) {
- String filenameMD5 = getMD5fromFileName(url);
- String fileMD5 = FileUtils.getFileMD5ToString(file);
- Log.e("ariaDownload", "filenameMD5=" + filenameMD5);
- Log.e("ariaDownload", "fileMD5=" + fileMD5);
- //后端还没有完成json字段的MD5,暂时用文件名获取
- if (fileMD5.equalsIgnoreCase(urlMD5)) {
- ApkUtils.installApp(context, file.getAbsolutePath());
- } else {
- Aria.download(context)
- .load(url) //读取下载地址
- .setFilePath(JgyUtils.getInstance().getDownLoadPath() + fileName)
- .ignoreFilePathOccupy()
- .setExtendField(jsonObject.toString())
- .create(); //启动下载}
+ final File file = new File(JgyUtils.getInstance().getDownLoadPath() + fileName);
+ // 校验安装包 MD5 需要完整读取文件,属于耗时 IO;安装 APK 也可能抛异常,
+ // 统一放到子线程执行,避免阻塞主线程(历史上这里曾导致主线程闪退/ANR)
+ new Thread(() -> {
+ try {
+ if (file.exists() && !file.isDirectory()) {
+ String filenameMD5 = getMD5fromFileName(url);
+ String fileMD5 = FileUtils.getFileMD5ToString(file);
+ Log.e("ariaDownload", "filenameMD5=" + filenameMD5);
+ Log.e("ariaDownload", "fileMD5=" + fileMD5);
+ //后端还没有完成json字段的MD5,暂时用文件名获取
+ if (fileMD5.equalsIgnoreCase(urlMD5)) {
+ ApkUtils.installApp(context, file.getAbsolutePath());
+ return;
+ }
+ }
+ } catch (Throwable e) {
+ Log.e("ariaDownload", "检查安装包异常: " + e.getMessage(), e);
}
- } else {
+ startAriaDownload(context, url, fileName, jsonObject);
+ }, "apk-install-check").start();
+ }
- Aria.download(context)
- .load(url) //读取下载地址
- .setFilePath(JgyUtils.getInstance().getDownLoadPath() + fileName)
- .ignoreFilePathOccupy()
- .setExtendField(jsonObject.toString())
- .create(); //启动下载}
+ /**
+ * 解析下载 JSON 中的 MD5 字段,兼容 MD5 / app_md5 两种命名。
+ */
+ private static String resolveUrlMD5(JsonObject jsonObject) {
+ String urlMD5 = "";
+ JsonElement md5Element = jsonObject.get("MD5");
+ if (md5Element == null || md5Element.isJsonNull()) {
+ md5Element = jsonObject.get("app_md5");
}
+ if (md5Element != null && !md5Element.isJsonNull()) {
+ urlMD5 = md5Element.getAsString();
+ }
+ return urlMD5;
+ }
+
+ /**
+ * 提交 Aria 下载任务。
+ */
+ private static void startAriaDownload(Context context, String url, String fileName, JsonObject jsonObject) {
+ Aria.download(context)
+ .load(url) //读取下载地址
+ .setFilePath(JgyUtils.getInstance().getDownLoadPath() + fileName)
+ .ignoreFilePathOccupy()
+ .setExtendField(jsonObject.toString())
+ .create(); //启动下载}
}
private void showToast() {
@@ -1485,7 +1482,7 @@ public class Utils {
jsonObject.addProperty("rom", rom);
jsonObject.addProperty("screen_rate", screen_rate);
jsonObject.addProperty("working_time", SystemClock.elapsedRealtime() / 1000);
- jsonObject.addProperty("restart_count", (int) SPUtils.get(context, "restart_count", 0));
+ jsonObject.addProperty("restart_count", mMMKV.decodeInt("restart_count", 0));
jsonObject.addProperty("storage_usable", getDataTotalSizeGb(context));
jsonObject.addProperty("storage_total", getDataTotalSizeGb(context));
String jsonString = jsonObject.toString();
@@ -1517,12 +1514,12 @@ public class Utils {
}
public static String getAddressJsonString(Context context) {
- String address = String.valueOf(SPUtils.get(context, "MapAddress", "-"));
+ String address = String.valueOf(mMMKV.decodeString("MapAddress", "-"));
if ("-".equals(address)) {
address = "定位失败";
}
- String longitude = String.valueOf(SPUtils.get(context, "longitude", "0"));
- String latitude = String.valueOf(SPUtils.get(context, "latitude", "0"));
+ String longitude = String.valueOf(mMMKV.decodeString("longitude", "0"));
+ String latitude = String.valueOf(mMMKV.decodeString("latitude", "0"));
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("address", address);
jsonObject.addProperty("longitude", longitude);
@@ -1616,7 +1613,8 @@ public class Utils {
if (Build.VERSION.SDK_INT >= 21) {
return ((BatteryManager) mContext.getSystemService(Context.BATTERY_SERVICE)).getIntProperty(4);
} else {
- Intent intent = (new ContextWrapper(mContext)).registerReceiver(null, new IntentFilter("android.intent.action.BATTERY_CHANGED"));
+ Intent intent = ReceiverCompat.registerReceiver(new ContextWrapper(mContext), null,
+ new IntentFilter("android.intent.action.BATTERY_CHANGED"), ReceiverCompat.RECEIVER_NOT_EXPORTED);
return intent.getIntExtra("level", -1) * 100 / intent.getIntExtra("scale", -1);
}
}
@@ -1624,7 +1622,7 @@ public class Utils {
public static int getBatteryPercentage(Context context) {
IntentFilter iFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
- Intent batteryStatus = context.registerReceiver(null, iFilter);
+ Intent batteryStatus = ReceiverCompat.registerReceiver(context, null, iFilter, ReceiverCompat.RECEIVER_NOT_EXPORTED);
int level = batteryStatus != null ? batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) : -1;
int scale = batteryStatus != null ? batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1) : -1;
@@ -1636,7 +1634,7 @@ public class Utils {
public static int getIsCharging(Context context) {
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
- Intent batteryStatus = context.registerReceiver(null, ifilter);
+ Intent batteryStatus = ReceiverCompat.registerReceiver(context, null, ifilter, ReceiverCompat.RECEIVER_NOT_EXPORTED);
// Are we charging / charged?
int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
diff --git a/app/src/main/java/com/aoleyun/sn/utils/XAPKUtils.java b/app/src/main/java/com/aoleyun/sn/utils/XAPKUtils.java
index 3224e83..47067e3 100644
--- a/app/src/main/java/com/aoleyun/sn/utils/XAPKUtils.java
+++ b/app/src/main/java/com/aoleyun/sn/utils/XAPKUtils.java
@@ -323,40 +323,81 @@ public class XAPKUtils {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public static void installAppatPie(Context context, List apkFilePath) {
- Log.e(TAG, "installApps: 正在安装应用 " + apkFilePath);
+ try {
+ Log.e(TAG, "installApps: 正在安装应用 " + apkFilePath);
// File file = new File(apkFilePath);
- PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
- PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(PackageInstaller
- .SessionParams.MODE_FULL_INSTALL);
- sessionParams.setSize(XAPKSize);
- int sessionId = createSession(packageInstaller, sessionParams);
- if (sessionId != -1) {
- for (String apkPath : apkFilePath) {
- copyApkFile(packageInstaller, sessionId, apkPath);
- }
+ PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
+ PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(PackageInstaller
+ .SessionParams.MODE_FULL_INSTALL);
+ sessionParams.setSize(XAPKSize);
+ int sessionId = createSession(packageInstaller, sessionParams);
+ if (sessionId != -1) {
+ for (String apkPath : apkFilePath) {
+ copyApkFile(packageInstaller, sessionId, apkPath);
+ }
// boolean copySuccess = ;
// if (copySuccess) {
- Toaster.show("正在安装应用");
- install(packageInstaller, sessionId, context);
+ Toaster.show("正在安装应用");
+ install(packageInstaller, sessionId, context);
// }
//
+ }
+ } catch (Throwable e) {
+ // 安装失败只记录日志,避免异常冒泡导致进程闪退
+ Log.e(TAG, "installAppatPie: 安装异常 " + e.getMessage(), e);
}
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private static void install(PackageInstaller packageInstaller, int sessionId, Context context) {
+ PackageInstaller.Session session = null;
try {
- PackageInstaller.Session session = packageInstaller.openSession(sessionId);
- Intent intent = new Intent(context, InstallResultReceiver.class);
- PendingIntent pendingIntent = PendingIntent.getBroadcast(
- context,
- 1, intent,
- PendingIntent.FLAG_UPDATE_CURRENT
- );
- session.commit(pendingIntent.getIntentSender());
- } catch (IOException e) {
- e.printStackTrace();
- Log.e(TAG, "install: " + e.getMessage());
+ session = packageInstaller.openSession(sessionId);
+ session.commit(createResultPendingIntent(context).getIntentSender());
+ } catch (Throwable e) {
+ Log.e(TAG, "install: 安装失败 " + e.getMessage(), e);
+ abandonSession(packageInstaller, sessionId);
+ } finally {
+ if (session != null) {
+ try {
+ session.close();
+ } catch (Throwable ignore) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Android 14(targetSdk 34+)起,PackageInstaller 的 commit 结果接收器必须是可变
+ * (Mutable)PendingIntent,系统需要向其中回填安装结果,否则 commit() 会抛出
+ * IllegalArgumentException 并导致进程闪退。
+ */
+ private static PendingIntent createResultPendingIntent(Context context) {
+ Intent intent = new Intent(context, InstallResultReceiver.class);
+ intent.setPackage(context.getPackageName());
+ int flags = PendingIntent.FLAG_UPDATE_CURRENT;
+ if (Build.VERSION.SDK_INT >= ANDROID_12) {
+ flags |= FLAG_MUTABLE;
+ }
+ return PendingIntent.getBroadcast(context, 1, intent, flags);
+ }
+
+ /**
+ * Android 12(API 31)。编译使用的 framework.jar 为设备提取版,不含 API 31 新增符号,
+ * 因此这里使用字面量常量代替 {@code Build.VERSION_CODES.S} 与 {@code PendingIntent.FLAG_MUTABLE}。
+ */
+ private static final int ANDROID_12 = 31;
+ /**
+ * PendingIntent.FLAG_MUTABLE(API 31 新增,值为 1 << 25)。
+ */
+ private static final int FLAG_MUTABLE = 1 << 25;
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ private static void abandonSession(PackageInstaller packageInstaller, int sessionId) {
+ try {
+ packageInstaller.abandonSession(sessionId);
+ } catch (Throwable e) {
+ Log.e(TAG, "abandonSession: " + e.getMessage());
}
}