diff --git a/app/build.gradle b/app/build.gradle index ea6b62e..cf749fa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,24 +13,25 @@ static def releaseTime() { android { namespace "com.aoleyun.sn" - compileSdkVersion 29 + compileSdkVersion 36 buildToolsVersion "36.0.0" defaultConfig { applicationId "com.aoleyun.sn" - versionCode 246 - versionName "1.6.1013" + versionCode 250 + versionName "1.6.1016" //There are no CERT files because If the mini sdk version is 23+, the AGP will ignore the V1 scheme signature. minSdkVersion 24 - targetSdkVersion 29 + targetSdkVersion 36 + multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" ndk { //选择要添加的对应 cpu 类型的 .so 库。 - abiFilters 'arm64-v8a', 'armeabi-v7a' - // 还可以添加 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64', 'mips', 'mips64' + abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86_64' + // 还可以添加 'armeabi', 'x86', 'mips', 'mips64' } lintOptions { @@ -322,9 +323,31 @@ android { v1SigningEnabled true v2SigningEnabled true } + + crosshatch { + storeFile file("keystore/AospCrosshatch.keystore") + storePassword "Fan1996" + keyAlias "AospCrosshatch" + keyPassword "Fan1996" + v1SigningEnabled true + v2SigningEnabled true + } } buildTypes { + CrosshatchDebug.initWith(debug) + CrosshatchDebug { + buildConfigField "String", "platform", '"AOSP"' + debuggable true + versionNameSuffix "_debug" + signingConfig signingConfigs.crosshatch + } + + CrosshatchRelease.initWith(release) + CrosshatchRelease { + buildConfigField "String", "platform", '"AOSP"' + signingConfig signingConfigs.crosshatch + } G122JDebug.initWith(debug) G122JDebug { @@ -884,7 +907,7 @@ dependencies { //阿里云推送 implementation 'com.aliyun.ams:alicloud-android-push:3.8.0' //百度地图 - implementation 'com.baidu.lbsyun:BaiduMapSDK_Location:9.1.8' + implementation 'com.baidu.lbsyun:BaiduMapSDK_Location:9.7.0' //MMKV implementation 'com.tencent:mmkv-static:2.4.0' //bugly diff --git a/app/keystore/AospCrosshatch.keystore b/app/keystore/AospCrosshatch.keystore new file mode 100644 index 0000000..a5be287 Binary files /dev/null and b/app/keystore/AospCrosshatch.keystore differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3cf1003..528488a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -107,6 +107,14 @@ + + + + + + + + + + android:exported="true" + android:foregroundServiceType="specialUse"> + diff --git a/app/src/main/java/com/aoleyun/sn/activity/SplashActivity.java b/app/src/main/java/com/aoleyun/sn/activity/SplashActivity.java index 522a30a..85e1e25 100644 --- a/app/src/main/java/com/aoleyun/sn/activity/SplashActivity.java +++ b/app/src/main/java/com/aoleyun/sn/activity/SplashActivity.java @@ -34,11 +34,9 @@ import com.aoleyun.sn.utils.FlowInfo; import com.aoleyun.sn.utils.GetFlowUtil; import com.aoleyun.sn.utils.JgyUtils; import com.aoleyun.sn.utils.NetworkUtils; -import com.aoleyun.sn.utils.SPUtils; import com.aoleyun.sn.utils.ServiceAliveUtils; import com.aoleyun.sn.utils.Utils; import com.blankj.utilcode.util.PathUtils; -import com.google.gson.Gson; import com.tencent.mmkv.MMKV; import com.zackratos.ultimatebarx.ultimatebarx.java.UltimateBarX; @@ -182,7 +180,6 @@ public class SplashActivity extends AppCompatActivity { // } // ActivityManager mAm = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); // mAm.killBackgroundProcesses("com.android.documentsui"); - Log.e(TAG, "debugTest: SPUtils all " + new Gson().toJson(SPUtils.getAll(this))); // Utils.getPublicIP(this); Utils.obtainWifiInfo(this); diff --git a/app/src/main/java/com/aoleyun/sn/activity/debug/DebugActivity.java b/app/src/main/java/com/aoleyun/sn/activity/debug/DebugActivity.java index c76356f..0df8e4f 100644 --- a/app/src/main/java/com/aoleyun/sn/activity/debug/DebugActivity.java +++ b/app/src/main/java/com/aoleyun/sn/activity/debug/DebugActivity.java @@ -120,7 +120,9 @@ public class DebugActivity extends BaseMvvmActivity() { @@ -589,7 +589,7 @@ public class MainActivity extends BaseMvvmActivityneedStartLogAtBootTime(), needStart=" + needStart); -// return needStart; -// } - - public class ServiceNullException extends Exception { private static final long serialVersionUID = -430298482097527072L; } diff --git a/app/src/main/java/com/aoleyun/sn/manager/ConnectManager.java b/app/src/main/java/com/aoleyun/sn/manager/ConnectManager.java index c93dfaf..1b83637 100644 --- a/app/src/main/java/com/aoleyun/sn/manager/ConnectManager.java +++ b/app/src/main/java/com/aoleyun/sn/manager/ConnectManager.java @@ -103,7 +103,7 @@ public class ConnectManager { } long nowTime = System.currentTimeMillis(); long lastTime = mMMKV.decodeLong(key + "_time"); -// long lastTime = (long) SPUtils.get(mContext, key, 0L); +// long lastTime = mMMKV.decodeLong(key, 0L); Log.e(TAG, "isNeedConnect: key = " + key + "\ttime = " + lastTime); long intervalTime = getConnectModeTime(connectMode); //防止一分钟内重复请求 diff --git a/app/src/main/java/com/aoleyun/sn/manager/MapManager.java b/app/src/main/java/com/aoleyun/sn/manager/MapManager.java index 1c8b817..62c6cc5 100644 --- a/app/src/main/java/com/aoleyun/sn/manager/MapManager.java +++ b/app/src/main/java/com/aoleyun/sn/manager/MapManager.java @@ -6,7 +6,7 @@ import android.provider.Settings; import android.util.Log; import com.aoleyun.sn.bean.ElectronicFenceInfo; -import com.aoleyun.sn.utils.SPUtils; +import com.aoleyun.sn.comm.CommonConfig; import com.baidu.location.BDAbstractLocationListener; import com.baidu.location.BDLocation; import com.baidu.location.LocationClient; @@ -29,10 +29,13 @@ import com.baidu.trace.model.CoordType; import com.baidu.trace.model.LatLng; import com.baidu.trace.model.OnTraceListener; import com.baidu.trace.model.PushMessage; +import com.tencent.mmkv.MMKV; public class MapManager { private String TAG = "MapManager"; + private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE); + @SuppressLint("StaticFieldLeak") private static MapManager sInstance; private Context mContext; @@ -65,14 +68,21 @@ public class MapManager { } public void initMap() { + LocationClient.setAgreePrivacy(true); + Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.ASSISTED_GPS_ENABLED, 1); Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "+gps"); Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "+network"); - mLocationClient = new LocationClient(mContext); - mLocationClient.setLocOption(getDefaultLocationClientOption()); - mLocationClient.registerLocationListener(mListener); - mLocationClient.stop(); - mLocationClient.start(); + //百度定位 SDK 9.7.0 起 LocationClient 构造方法声明了 throws Exception + try { + mLocationClient = new LocationClient(mContext); + mLocationClient.setLocOption(getDefaultLocationClientOption()); + mLocationClient.registerLocationListener(mListener); + mLocationClient.stop(); + mLocationClient.start(); + } catch (Exception e) { + Log.e(TAG, "initMap: LocationClient 创建失败: " + e.getMessage(), e); + } } public LocationClient getLocationClient() { @@ -131,28 +141,28 @@ public class MapManager { 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: " + (String) SPUtils.get(mContext, "MapAddress", "-")); - Log.e(TAG, "MapError: " + (String) SPUtils.get(mContext, "MapError", "-")); + Log.e(TAG, "MapAddress: " + mMMKV.decodeString("MapAddress", "-")); + Log.e(TAG, "MapError: " + mMMKV.decodeString("MapError", "-")); // Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "-network"); // Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "-gps"); // Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED, ""); @@ -214,7 +224,7 @@ public class MapManager { } } Log.e(TAG, "onLocationChanged: " + "定位失败"); - SPUtils.put(mContext, "MapError", sb); + mMMKV.encode("MapError", sb.toString()); Log.e(TAG, "onLocDiagnosticMessage: " + sb); // logMsg(sb.toString(), tag); } diff --git a/app/src/main/java/com/aoleyun/sn/network/NetInterfaceManager.java b/app/src/main/java/com/aoleyun/sn/network/NetInterfaceManager.java index c3518f1..06a1b7f 100644 --- a/app/src/main/java/com/aoleyun/sn/network/NetInterfaceManager.java +++ b/app/src/main/java/com/aoleyun/sn/network/NetInterfaceManager.java @@ -47,6 +47,7 @@ import com.aoleyun.sn.bean.DefaultApp; import com.aoleyun.sn.bean.DeveloperBean; import com.aoleyun.sn.bean.EBagCode; import com.aoleyun.sn.bean.ForceDownloadData; +import com.aoleyun.sn.bean.IconControlInfo; import com.aoleyun.sn.bean.LessonApp; import com.aoleyun.sn.bean.LessonJson; import com.aoleyun.sn.bean.LessonSetting; @@ -99,7 +100,6 @@ import com.aoleyun.sn.utils.JgyUtils; import com.aoleyun.sn.utils.MD5Util; import com.aoleyun.sn.utils.NetworkUtils; import com.aoleyun.sn.utils.PowerOffAlarmUtils; -import com.aoleyun.sn.utils.SPUtils; import com.aoleyun.sn.utils.SysSettingUtils; import com.aoleyun.sn.utils.TimeUtils; import com.aoleyun.sn.utils.URLUtils; @@ -132,6 +132,7 @@ import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collectors; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; @@ -717,10 +718,15 @@ public class NetInterfaceManager { return schedule(getSystemSettingApi().getBrowserBookmarks(HTTP_KEY, Utils.getSerial(mContext))); } + @Deprecated public Observable getDesktopIconObservable() { return schedule(getSystemSettingApi().getDesktopIcon(HTTP_KEY, Utils.getSerial(mContext))); } + public Observable>> getIconControlObservable() { + return schedule(getSystemSettingApi().getIconControl(HTTP_KEY, Utils.getSerial(mContext))); + } + public Observable getSystemSettingObservable() { return withCache(schedule(getSystemSettingApi().getSystemSettings(HTTP_KEY, Utils.getSerial(mContext))), UrlAddress.GET_FIRMWARE, @@ -1428,57 +1434,165 @@ public class NetInterfaceManager { }); } - public void getDesktopIcon(BehaviorSubject 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()); } }