/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.car.pm; import static android.Manifest.permission.QUERY_ALL_PACKAGES; import static android.car.Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY; import static android.car.content.pm.CarPackageManager.BLOCKING_INTENT_EXTRA_BLOCKED_ACTIVITY_NAME; import static android.car.content.pm.CarPackageManager.BLOCKING_INTENT_EXTRA_BLOCKED_TASK_ID; import static android.car.content.pm.CarPackageManager.BLOCKING_INTENT_EXTRA_DISPLAY_ID; import static android.car.content.pm.CarPackageManager.BLOCKING_INTENT_EXTRA_IS_ROOT_ACTIVITY_DO; import static android.car.content.pm.CarPackageManager.BLOCKING_INTENT_EXTRA_ROOT_ACTIVITY_NAME; import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING; import static com.android.car.CarServiceUtils.checkCalledByPackage; import static com.android.car.CarServiceUtils.getHandlerThread; import static com.android.car.CarServiceUtils.isEventOfType; import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.PendingIntent; import android.app.TaskInfo; import android.car.Car; import android.car.CarOccupantZoneManager; import android.car.CarVersion; import android.car.ICarOccupantZoneCallback; import android.car.builtin.app.ActivityManagerHelper; import android.car.builtin.app.TaskInfoHelper; import android.car.builtin.content.pm.PackageManagerHelper; import android.car.builtin.os.BuildHelper; import android.car.builtin.os.ServiceManagerHelper; import android.car.builtin.util.Slogf; import android.car.content.pm.AppBlockingPackageInfo; import android.car.content.pm.CarAppBlockingPolicy; import android.car.content.pm.CarAppBlockingPolicyService; import android.car.content.pm.CarPackageManager; import android.car.content.pm.ICarBlockingUiCommandListener; import android.car.content.pm.ICarPackageManager; import android.car.drivingstate.CarUxRestrictions; import android.car.drivingstate.ICarUxRestrictionsChangeListener; import android.car.hardware.power.CarPowerPolicy; import android.car.hardware.power.CarPowerPolicyFilter; import android.car.hardware.power.ICarPowerPolicyListener; import android.car.hardware.power.PowerComponent; import android.car.user.CarUserManager.UserLifecycleListener; import android.car.user.UserLifecycleEventFilter; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.Signature; import android.content.res.Resources; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import android.util.Pair; import android.util.SparseArray; import android.util.SparseIntArray; import android.util.SparseLongArray; import android.util.proto.ProtoOutputStream; import android.view.Display; import android.view.accessibility.AccessibilityEvent; import com.android.car.CarLocalServices; import com.android.car.CarLog; import com.android.car.CarOccupantZoneService; import com.android.car.CarServiceBase; import com.android.car.CarServiceHelperWrapper; import com.android.car.CarUxRestrictionsManagerService; import com.android.car.R; import com.android.car.am.CarActivityService; import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; import com.android.car.internal.util.DebugUtils; import com.android.car.internal.util.IndentingPrintWriter; import com.android.car.internal.util.LocalLog; import com.android.car.internal.util.Sets; import com.android.car.power.CarPowerManagementService; import com.android.car.user.CarUserService; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; /** * Package manager service for cars. */ public final class CarPackageManagerService extends ICarPackageManager.Stub implements CarServiceBase { @VisibleForTesting static final String TAG = CarLog.tagFor(CarPackageManagerService.class); static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG); // Delimiters to parse packages and activities in the configuration XML resource. private static final String PACKAGE_DELIMITER = ","; private static final String PACKAGE_ACTIVITY_DELIMITER = "/"; private static final int LOG_SIZE = 20; private static final String[] WINDOW_DUMP_ARGUMENTS = new String[]{"windows"}; private static final String PROPERTY_RO_DRIVING_SAFETY_REGION = "ro.android.car.drivingsafetyregion"; private static final int ABA_LAUNCH_TIMEOUT_MS = 1_000; private final Context mContext; private final CarActivityService mActivityService; private final PackageManager mPackageManager; private final ActivityManager mActivityManager; private final IBinder mWindowManagerBinder; private final HandlerThread mHandlerThread = getHandlerThread( getClass().getSimpleName()); private final PackageHandler mHandler = new PackageHandler(mHandlerThread.getLooper(), this); private final Object mLock = new Object(); // For dumpsys logging. private final LocalLog mBlockedActivityLogs = new LocalLog(LOG_SIZE); private final BlockingUiCommandListenerMediator mBlockingUiCommandListenerMediator; // Store the allowlist and blocklist strings from the resource file. private String mConfiguredAllowlist; private String mConfiguredSystemAllowlist; private String mConfiguredBlocklist; @GuardedBy("mLock") private Map> mConfiguredAllowlistMap; @GuardedBy("mLock") private Map> mConfiguredBlocklistMap; private final List mAllowedAppInstallSources; @GuardedBy("mLock") private final SparseArray mTopActivityWithDialogPerDisplay = new SparseArray<>(); /** * Hold policy set from policy service or client. * Key: packageName of policy service */ @GuardedBy("mLock") private final HashMap mClientPolicies = new HashMap<>(); @GuardedBy("mLock") private HashMap mActivityAllowlistMap = new HashMap<>(); @GuardedBy("mLock") private HashSet mActivityDenylistPackages = new HashSet(); @GuardedBy("mLock") private LinkedList mProxies; @GuardedBy("mLock") private final LinkedList mWaitingPolicies = new LinkedList<>(); @GuardedBy("mLock") private String mCurrentDrivingSafetyRegion = CarPackageManager.DRIVING_SAFETY_REGION_ALL; // Package name + '/' + className format @GuardedBy("mLock") private final ArraySet mTempAllowedActivities = new ArraySet<>(); private final CarUxRestrictionsManagerService mCarUxRestrictionsService; private final CarOccupantZoneService mCarOccupantZoneService; private final boolean mEnableActivityBlocking; private final ComponentName mActivityBlockingActivity; // Memorize the target of ABA to defend bypassing it with launching two Activities continuously. private final SparseArray mBlockingActivityTargets = new SparseArray<>(); private final SparseLongArray mBlockingActivityLaunchTimes = new SparseLongArray(); private final boolean mPreventTemplatedAppsFromShowingDialog; private final String mTemplateActivityClassName; private final ActivityListener mActivityListener = new ActivityListener(); // K: (logical) display id of a physical display, V: UXR change listener of this display. // For multi-display, monitor UXR change on each display. @GuardedBy("mLock") private final SparseArray mUxRestrictionsListeners = new SparseArray<>(); // K: (logical) display id of a display, V: Task info of the blocking ui of this display. // For multi-display, monitor blocking ui task information for each display. @GuardedBy("mLock") private final SparseArray mBlockingUiTaskInfoPerDisplay = new SparseArray<>(); private final VendorServiceController mVendorServiceController; // Information related to when the installed packages should be parsed for building a allow and // block list private final Set mPackageManagerActions = Sets.newArraySet( Intent.ACTION_PACKAGE_ADDED, Intent.ACTION_PACKAGE_CHANGED, Intent.ACTION_PACKAGE_REMOVED, Intent.ACTION_PACKAGE_REPLACED); private final PackageParsingEventReceiver mPackageParsingEventReceiver = new PackageParsingEventReceiver(); /** * Mapping between the task ID and the last known display ID. */ @GuardedBy("mLock") private final SparseIntArray mLastKnownDisplayIdForTask = new SparseIntArray(); private final UserLifecycleListener mUserLifecycleListener = event -> { if (!isEventOfType(TAG, event, USER_LIFECYCLE_EVENT_TYPE_SWITCHING)) { return; } synchronized (mLock) { resetTempAllowedActivitiesLocked(); } }; private final ICarPowerPolicyListener mDisplayPowerPolicyListener = new ICarPowerPolicyListener.Stub() { @Override public void onPolicyChanged(CarPowerPolicy policy, CarPowerPolicy accumulatedPolicy) { if (!policy.isComponentEnabled(PowerComponent.DISPLAY)) { synchronized (mLock) { resetTempAllowedActivitiesLocked(); } } } }; public CarPackageManagerService(Context context, CarUxRestrictionsManagerService uxRestrictionsService, CarActivityService activityService, CarOccupantZoneService carOccupantZoneService) { mContext = context; mCarUxRestrictionsService = uxRestrictionsService; mActivityService = activityService; mCarOccupantZoneService = carOccupantZoneService; mPackageManager = mContext.getPackageManager(); mActivityManager = mContext.getSystemService(ActivityManager.class); mWindowManagerBinder = ServiceManagerHelper.getService(Context.WINDOW_SERVICE); Resources res = context.getResources(); mEnableActivityBlocking = res.getBoolean(R.bool.enableActivityBlockingForSafety); String blockingActivity = res.getString(R.string.activityBlockingActivity); mActivityBlockingActivity = ComponentName.unflattenFromString(blockingActivity); if (mEnableActivityBlocking && mActivityBlockingActivity == null) { Slogf.wtf(TAG, "mActivityBlockingActivity can't be null when enabled"); } mAllowedAppInstallSources = Arrays.asList( res.getStringArray(R.array.allowedAppInstallSources)); mVendorServiceController = new VendorServiceController( mContext, mHandler.getLooper()); mPreventTemplatedAppsFromShowingDialog = res.getBoolean(R.bool.config_preventTemplatedAppsFromShowingDialog); mTemplateActivityClassName = res.getString(R.string.config_template_activity_class_name); mBlockingUiCommandListenerMediator = new BlockingUiCommandListenerMediator(); } @Override public void setAppBlockingPolicy(String packageName, CarAppBlockingPolicy policy, int flags) { if (DBG) { Slogf.d(TAG, "policy setting from binder call, client:" + packageName); } doSetAppBlockingPolicy(packageName, policy, flags); } /** * Restarts the requested task. If task with {@code taskId} does not exist, do nothing. */ @Override public void restartTask(int taskId) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.REAL_GET_TASKS) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException( "requires permission " + android.Manifest.permission.REAL_GET_TASKS); } mActivityService.restartTask(taskId); } @Override public String getCurrentDrivingSafetyRegion() { assertAppBlockingOrDrivingStatePermission(); synchronized (mLock) { return mCurrentDrivingSafetyRegion; } } private String getComponentNameString(String packageName, String className) { return packageName + '/' + className; } @Override public void controlOneTimeActivityBlockingBypassingAsUser(String packageName, String activityClassName, boolean bypass, @UserIdInt int userId) { assertAppBlockingPermission(); if (!callerCanQueryPackage(packageName)) { throw new SecurityException("cannot query other package"); } try { // Read this to check the validity of pkg / activity name. Not checking this can allow // bad apps to be allowed later. CarAppMetadataReader.getSupportedDrivingSafetyRegionsForActivityAsUser(mContext, packageName, activityClassName, userId); } catch (NameNotFoundException e) { throw new ServiceSpecificException(CarPackageManager.ERROR_CODE_NO_PACKAGE, e.getMessage()); } String componentName = getComponentNameString(packageName, activityClassName); synchronized (mLock) { if (bypass) { mTempAllowedActivities.add(componentName); } else { mTempAllowedActivities.remove(componentName); } } if (!bypass) { // block top activities if bypassing is disabled. mHandler.post(this::blockTopActivitiesOnAllDisplaysIfNecessary); } } @GuardedBy("mLock") private void resetTempAllowedActivitiesLocked() { mTempAllowedActivities.clear(); } @Override public List getSupportedDrivingSafetyRegionsForActivityAsUser(String packageName, String activityClassName, @UserIdInt int userId) { assertAppBlockingOrDrivingStatePermission(); if (!callerCanQueryPackage(packageName)) { throw new SecurityException("cannot query other package"); } long token = Binder.clearCallingIdentity(); try { return CarAppMetadataReader.getSupportedDrivingSafetyRegionsForActivityAsUser(mContext, packageName, activityClassName, userId); } catch (NameNotFoundException e) { throw new ServiceSpecificException(CarPackageManager.ERROR_CODE_NO_PACKAGE, e.getMessage()); } finally { Binder.restoreCallingIdentity(token); } } private void assertAppBlockingOrDrivingStatePermission() { if (mContext.checkCallingOrSelfPermission(Car.PERMISSION_CONTROL_APP_BLOCKING) != PackageManager.PERMISSION_GRANTED && mContext.checkCallingOrSelfPermission( Car.PERMISSION_CAR_DRIVING_STATE) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException( "requires permission " + Car.PERMISSION_CONTROL_APP_BLOCKING + " or " + Car.PERMISSION_CAR_DRIVING_STATE); } } private void assertAppBlockingPermission() { if (mContext.checkCallingOrSelfPermission(Car.PERMISSION_CONTROL_APP_BLOCKING) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException( "requires permission " + Car.PERMISSION_CONTROL_APP_BLOCKING); } } private void doSetAppBlockingPolicy(String packageName, CarAppBlockingPolicy policy, int flags) { assertAppBlockingPermission(); checkCalledByPackage(mContext, packageName); if (policy == null) { throw new IllegalArgumentException("policy cannot be null"); } if ((flags & CarPackageManager.FLAG_SET_POLICY_ADD) != 0 && (flags & CarPackageManager.FLAG_SET_POLICY_REMOVE) != 0) { throw new IllegalArgumentException( "Cannot set both FLAG_SET_POLICY_ADD and FLAG_SET_POLICY_REMOVE flag"); } synchronized (mLock) { if ((flags & CarPackageManager.FLAG_SET_POLICY_WAIT_FOR_CHANGE) != 0) { mWaitingPolicies.add(policy); } } mHandler.requestUpdatingPolicy(packageName, policy, flags); if ((flags & CarPackageManager.FLAG_SET_POLICY_WAIT_FOR_CHANGE) != 0) { synchronized (mLock) { try { while (mWaitingPolicies.contains(policy)) { mLock.wait(); } } catch (InterruptedException e) { // Pass it over binder call throw new IllegalStateException( "Interrupted while waiting for policy completion", e); } } } } @Override public boolean isActivityDistractionOptimized(String packageName, String className) { if (!callerCanQueryPackage(packageName)) return false; assertPackageAndClassName(packageName, className); synchronized (mLock) { if (DBG) { Slogf.d(TAG, "isActivityDistractionOptimized" + dumpPoliciesLocked(false)); } if (mTempAllowedActivities.contains(getComponentNameString(packageName, className))) { return true; } for (int i = mTopActivityWithDialogPerDisplay.size() - 1; i >= 0; i--) { ComponentName activityWithDialog = mTopActivityWithDialogPerDisplay.get( mTopActivityWithDialogPerDisplay.keyAt(i)); if (activityWithDialog.getClassName().equals(className) && activityWithDialog.getPackageName().equals(packageName)) { return false; } } if (searchFromClientPolicyBlocklistsLocked(packageName)) { return false; } if (isActivityInClientPolicyAllowlistsLocked(packageName, className)) { return true; } // Check deny and allow list boolean packageBlocked = mActivityDenylistPackages.contains(packageName); AppBlockingPackageInfoWrapper infoWrapper = mActivityAllowlistMap.get(packageName); if (!packageBlocked && infoWrapper == null) { // Update cache updateActivityAllowlistAndDenylistMap(packageName); packageBlocked = mActivityDenylistPackages.contains(packageName); infoWrapper = mActivityAllowlistMap.get(packageName); } return !packageBlocked && isActivityInMapAndMatching(infoWrapper, packageName, className); } } @VisibleForTesting boolean callerCanQueryPackage(String packageName) { int callingUid = Binder.getCallingUid(); if (hasPermissionGranted(QUERY_ALL_PACKAGES, callingUid)) { return true; } String[] packages = mPackageManager.getPackagesForUid(callingUid); if (packages != null && packages.length > 0) { for (int i = 0; i < packages.length; i++) { if (Objects.equals(packageName, packages[i])) { return true; } } } Slogf.w(TAG, QUERY_ALL_PACKAGES + " permission is needed to query other packages."); return false; } private static boolean hasPermissionGranted(String permission, int uid) { return ActivityManagerHelper.checkComponentPermission(permission, uid, /* owningUid= */ Process.INVALID_UID, /* exported= */ true) == PackageManager.PERMISSION_GRANTED; } @Override public boolean isPendingIntentDistractionOptimized(PendingIntent pendingIntent) { if (!pendingIntent.isActivity()) { Slogf.d(TAG, "isPendingIntentDistractionOptimized: Activity not set on the " + "pending intent."); return false; } List infos = pendingIntent.queryIntentComponents( PackageManager.MATCH_DEFAULT_ONLY); if (infos.isEmpty()) { Slogf.d(TAG, "isPendingIntentDistractionOptimized: No intent component found for " + "the pending intent."); return false; } if (infos.size() > 1) { Slogf.d(TAG, "isPendingIntentDistractionOptimized: More than one intent component" + " found for the pending intent. Considering the first one."); } ActivityInfo activityInfo = infos.get(0).activityInfo; return isActivityDistractionOptimized(activityInfo.packageName, activityInfo.name); } @Override public boolean isServiceDistractionOptimized(String packageName, String className) { if (!callerCanQueryPackage(packageName)) return false; if (packageName == null) { throw new IllegalArgumentException("Package name null"); } synchronized (mLock) { if (DBG) { Slogf.d(TAG, "isServiceDistractionOptimized" + dumpPoliciesLocked(false)); } if (searchFromClientPolicyBlocklistsLocked(packageName)) { return false; } if (searchFromClientPolicyAllowlistsLocked(packageName)) { return true; } // Check deny and allow list boolean packageBlocked = mActivityDenylistPackages.contains(packageName); AppBlockingPackageInfoWrapper infoWrapper = mActivityAllowlistMap.get(packageName); if (!packageBlocked && infoWrapper == null) { // Update cache updateActivityAllowlistAndDenylistMap(packageName); packageBlocked = mActivityDenylistPackages.contains(packageName); infoWrapper = mActivityAllowlistMap.get(packageName); } return !packageBlocked && infoWrapper != null && infoWrapper.info != null; } } @Override public boolean isActivityBackedBySafeActivity(ComponentName activityName) { if (activityName == null) return false; if (!callerCanQueryPackage(activityName.getPackageName())) return false; TaskInfo info = mActivityService.getTaskInfoForTopActivity(activityName); if (DBG) { Slogf.d(TAG, "isActivityBackedBySafeActivity: info=%s", TaskInfoHelper.toString(info)); } if (info == null) { // not top in focused stack return true; } if (!isUxRestrictedOnDisplay(TaskInfoHelper.getDisplayId(info))) { return true; } if (info.baseActivity == null || info.baseActivity.equals(activityName)) { // nothing below this. return false; } return isActivityDistractionOptimized(info.baseActivity.getPackageName(), info.baseActivity.getClassName()); } public Looper getLooper() { return mHandlerThread.getLooper(); } private void assertPackageAndClassName(String packageName, String className) { if (packageName == null) { throw new IllegalArgumentException("Package name null"); } if (className == null) { throw new IllegalArgumentException("Class name null"); } } @GuardedBy("mLock") private boolean searchFromClientPolicyBlocklistsLocked(String packageName) { for (ClientPolicy policy : mClientPolicies.values()) { AppBlockingPackageInfoWrapper wrapper = policy.mBlocklistsMap.get(packageName); if (wrapper != null && wrapper.isMatching && wrapper.info != null) { return true; } } return false; } @GuardedBy("mLock") private boolean searchFromClientPolicyAllowlistsLocked(String packageName) { for (ClientPolicy policy : mClientPolicies.values()) { AppBlockingPackageInfoWrapper wrapper = policy.mAllowlistsMap.get(packageName); if (wrapper != null && wrapper.isMatching && wrapper.info != null) { return true; } } return false; } @GuardedBy("mLock") private boolean isActivityInClientPolicyAllowlistsLocked(String packageName, String className) { for (ClientPolicy policy : mClientPolicies.values()) { if (isActivityInMapAndMatching(policy.mAllowlistsMap.get(packageName), packageName, className)) { return true; } } return false; } private boolean isActivityInMapAndMatching(AppBlockingPackageInfoWrapper wrapper, String packageName, String className) { if (wrapper == null || !wrapper.isMatching) { if (DBG) { Slogf.d(TAG, "Pkg not in allowlist:" + packageName); } return false; } return wrapper.info.isActivityCovered(className); } @Override public void init() { String safetyRegion = SystemProperties.get(PROPERTY_RO_DRIVING_SAFETY_REGION, ""); synchronized (mLock) { setDrivingSafetyRegionWithCheckLocked(safetyRegion); mHandler.requestInit(); } UserLifecycleEventFilter userSwitchingEventFilter = new UserLifecycleEventFilter.Builder() .addEventType(USER_LIFECYCLE_EVENT_TYPE_SWITCHING).build(); CarLocalServices.getService(CarUserService.class).addUserLifecycleListener( userSwitchingEventFilter, mUserLifecycleListener); CarLocalServices.getService(CarPowerManagementService.class).addPowerPolicyListener( new CarPowerPolicyFilter.Builder().setComponents(PowerComponent.DISPLAY).build(), mDisplayPowerPolicyListener); } @Override public void release() { CarLocalServices.getService(CarPowerManagementService.class).removePowerPolicyListener( mDisplayPowerPolicyListener); CarLocalServices.getService(CarUserService.class).removeUserLifecycleListener( mUserLifecycleListener); synchronized (mLock) { mHandler.requestRelease(); // wait for release do be done. This guarantees that init is done. try { mLock.wait(); } catch (InterruptedException e) { Slogf.e(TAG, "Interrupted wait during release"); Thread.currentThread().interrupt(); } mActivityAllowlistMap.clear(); mActivityDenylistPackages.clear(); mClientPolicies.clear(); mBlockingUiTaskInfoPerDisplay.clear(); if (mProxies != null) { for (AppBlockingPolicyProxy proxy : mProxies) { proxy.disconnect(); } mProxies.clear(); } mWaitingPolicies.clear(); resetTempAllowedActivitiesLocked(); mLock.notifyAll(); } mContext.unregisterReceiver(mPackageParsingEventReceiver); mActivityService.unregisterActivityListener(mActivityListener); synchronized (mLock) { for (int i = 0; i < mUxRestrictionsListeners.size(); i++) { UxRestrictionsListener listener = mUxRestrictionsListeners.valueAt(i); mCarUxRestrictionsService.unregisterUxRestrictionsChangeListener(listener); } } } @GuardedBy("mLock") private void setDrivingSafetyRegionWithCheckLocked(String region) { if (region.isEmpty()) { mCurrentDrivingSafetyRegion = CarPackageManager.DRIVING_SAFETY_REGION_ALL; } else { mCurrentDrivingSafetyRegion = region; } } /** * Reset driving stat and all dynamically added allow list so that region information for * all packages are reset. This also resets one time allow list. */ public void resetDrivingSafetyRegion(@NonNull String region) { synchronized (mLock) { setDrivingSafetyRegionWithCheckLocked(region); resetTempAllowedActivitiesLocked(); mActivityAllowlistMap.clear(); mActivityDenylistPackages.clear(); } } // run from HandlerThread private void doHandleInit() { startAppBlockingPolicies(); IntentFilter pkgParseIntent = new IntentFilter(); for (String action : mPackageManagerActions) { pkgParseIntent.addAction(action); } pkgParseIntent.addDataScheme("package"); mContext.registerReceiverForAllUsers(mPackageParsingEventReceiver, pkgParseIntent, /* broadcastPermission= */ null, /* scheduler= */ null, Context.RECEIVER_NOT_EXPORTED); // Listen to UxR changes on all displays. List occupantZoneInfos = mCarOccupantZoneService.getAllOccupantZones(); synchronized (mLock) { for (int i = 0; i < occupantZoneInfos.size(); i++) { CarOccupantZoneManager.OccupantZoneInfo occupantZoneInfo = occupantZoneInfos.get(i); int zoneId = occupantZoneInfo.zoneId; int[] displayIds = mCarOccupantZoneService.getAllDisplaysForOccupantZone(zoneId); for (int j = 0; j < displayIds.length; j++) { int displayId = displayIds[j]; UxRestrictionsListener listener = new UxRestrictionsListener( mCarUxRestrictionsService, displayId); mUxRestrictionsListeners.put(displayId, listener); mCarUxRestrictionsService.registerUxRestrictionsChangeListener( listener, displayId); } } } // Update UxR listeners upon display changes. mCarOccupantZoneService.registerCallback(mOccupantZoneCallback); mVendorServiceController.init(); mActivityService.registerActivityListener(mActivityListener); } private final ICarOccupantZoneCallback mOccupantZoneCallback = new ICarOccupantZoneCallback.Stub() { @Override public void onOccupantZoneConfigChanged(int flags) throws RemoteException { // Respond to display changes. if ((flags & CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_DISPLAY) == 0) { return; } if (DBG) { String flagString = DebugUtils.flagsToString( CarOccupantZoneManager.class, "ZONE_CONFIG_CHANGE_FLAG_", flags); Slogf.d(TAG, "onOccupantZoneConfigChanged: display zone change flag=%s", flagString); } ArraySet updatedDisplayIds = new ArraySet<>(); List occupantZoneInfos = mCarOccupantZoneService.getAllOccupantZones(); synchronized (mLock) { for (int i = 0; i < occupantZoneInfos.size(); i++) { CarOccupantZoneManager.OccupantZoneInfo occupantZoneInfo = occupantZoneInfos.get(i); int zoneId = occupantZoneInfo.zoneId; int[] displayIds = mCarOccupantZoneService.getAllDisplaysForOccupantZone(zoneId); for (int j = 0; j < displayIds.length; j++) { int displayId = displayIds[j]; updatedDisplayIds.add(displayId); // Register UxR listener for newly added displays. if (mUxRestrictionsListeners.get(displayId) == null) { Slogf.d(TAG, "adding UxR listener for display %d", displayId); UxRestrictionsListener listener = new UxRestrictionsListener( mCarUxRestrictionsService, displayId); mUxRestrictionsListeners.put(displayId, listener); mCarUxRestrictionsService.registerUxRestrictionsChangeListener( listener, displayId); } } } // Remove UxR listener for removed displays. for (int i = 0; i < mUxRestrictionsListeners.size(); i++) { int displayId = mUxRestrictionsListeners.keyAt(i); if (!updatedDisplayIds.contains(displayId)) { UxRestrictionsListener listener = mUxRestrictionsListeners.valueAt(i); mCarUxRestrictionsService.unregisterUxRestrictionsChangeListener( listener); mUxRestrictionsListeners.remove(displayId); } } } } }; private void doParseInstalledPackage(String packageName) { // Delete the package from allowlist and denylist mapping synchronized (mLock) { mActivityDenylistPackages.remove(packageName); mActivityAllowlistMap.remove(packageName); } // Generate allowlist and denylist mapping for the package updateActivityAllowlistAndDenylistMap(packageName); mHandler.post(this::blockTopActivitiesOnAllDisplaysIfNecessary); } private void doHandleRelease() { synchronized (mLock) { mVendorServiceController.release(); mLock.notifyAll(); } } private void doUpdatePolicy(String packageName, CarAppBlockingPolicy policy, int flags) { if (DBG) { Slogf.d(TAG, "setting policy from:" + packageName + ",policy:" + policy + ",flags:0x" + Integer.toHexString(flags)); } AppBlockingPackageInfoWrapper[] blocklistWrapper = verifyList(policy.blacklists); AppBlockingPackageInfoWrapper[] allowlistWrapper = verifyList(policy.whitelists); synchronized (mLock) { ClientPolicy clientPolicy = mClientPolicies.get(packageName); if (clientPolicy == null) { clientPolicy = new ClientPolicy(); mClientPolicies.put(packageName, clientPolicy); } if ((flags & CarPackageManager.FLAG_SET_POLICY_ADD) != 0) { clientPolicy.addToBlocklists(blocklistWrapper); clientPolicy.addToAllowlists(allowlistWrapper); } else if ((flags & CarPackageManager.FLAG_SET_POLICY_REMOVE) != 0) { clientPolicy.removeBlocklists(blocklistWrapper); clientPolicy.removeAllowlists(allowlistWrapper); } else { //replace. clientPolicy.replaceBlocklists(blocklistWrapper); clientPolicy.replaceAllowlists(allowlistWrapper); } if ((flags & CarPackageManager.FLAG_SET_POLICY_WAIT_FOR_CHANGE) != 0) { mWaitingPolicies.remove(policy); mLock.notifyAll(); } if (DBG) { Slogf.d(TAG, "policy set:" + dumpPoliciesLocked(false)); } } mHandler.post(this::blockTopActivitiesOnAllDisplaysIfNecessary); } @Nullable private AppBlockingPackageInfoWrapper[] verifyList(@Nullable AppBlockingPackageInfo[] list) { if (list == null) { return null; } LinkedList wrappers = new LinkedList<>(); for (int i = 0; i < list.length; i++) { AppBlockingPackageInfo info = list[i]; if (info == null) { continue; } boolean isMatching = isInstalledPackageMatching(info); wrappers.add(new AppBlockingPackageInfoWrapper(info, isMatching)); } return wrappers.toArray(new AppBlockingPackageInfoWrapper[wrappers.size()]); } boolean isInstalledPackageMatching(AppBlockingPackageInfo info) { PackageInfo packageInfo; try { packageInfo = mPackageManager.getPackageInfo(info.packageName, PackageManager.GET_SIGNATURES); } catch (NameNotFoundException e) { return false; } if (packageInfo == null) { return false; } // if it is system app and client specified the flag, do not check signature if ((info.flags & AppBlockingPackageInfo.FLAG_SYSTEM_APP) == 0 || (!PackageManagerHelper.isSystemApp(packageInfo.applicationInfo) && !PackageManagerHelper.isUpdatedSystemApp(packageInfo.applicationInfo))) { Signature[] signatures = packageInfo.signatures; if (!isAnySignatureMatching(signatures, info.signatures)) { return false; } } int version = packageInfo.versionCode; if (info.minRevisionCode == 0) { if (info.maxRevisionCode == 0) { // all versions return true; } else { // only max version matters return info.maxRevisionCode > version; } } else { // min version matters if (info.maxRevisionCode == 0) { return info.minRevisionCode < version; } else { return (info.minRevisionCode < version) && (info.maxRevisionCode > version); } } } /** * Any signature from policy matching with package's signatures is treated as matching. */ boolean isAnySignatureMatching(Signature[] fromPackage, Signature[] fromPolicy) { if (fromPackage == null) { return false; } if (fromPolicy == null) { return false; } ArraySet setFromPackage = new ArraySet(); for (Signature sig : fromPackage) { setFromPackage.add(sig); } for (Signature sig : fromPolicy) { if (setFromPackage.contains(sig)) { return true; } } return false; } private AppBlockingPackageInfoWrapper getPackageInfoWrapperForUser(String packageName, @UserIdInt int userId, Map> configAllowlist, Map> configBlocklist) { PackageInfo info; try { info = PackageManagerHelper.getPackageInfoAsUser(mPackageManager, packageName, PackageManager.GET_SIGNATURES | PackageManager.GET_ACTIVITIES | PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_DISABLED_COMPONENTS, userId); } catch (NameNotFoundException e) { Slogf.w(TAG, packageName + " not installed! User Id: " + userId); return null; } if (info == null || info.applicationInfo == null) { return null; } int flags = 0; Set activities = new ArraySet<>(); if (PackageManagerHelper.isSystemApp(info.applicationInfo) || PackageManagerHelper.isUpdatedSystemApp(info.applicationInfo)) { flags = AppBlockingPackageInfo.FLAG_SYSTEM_APP; } /* 1. Check if all or some of this app is in the or in config.xml */ Set configActivitiesForPackage = configAllowlist.get(info.packageName); if (configActivitiesForPackage != null) { if (DBG) { Slogf.d(TAG, info.packageName + " allowlisted"); } if (configActivitiesForPackage.isEmpty()) { // Whole Pkg has been allowlisted flags |= AppBlockingPackageInfo.FLAG_WHOLE_ACTIVITY; // Add all activities to the allowlist List activitiesForPackage = getActivitiesInPackage(info); if (activitiesForPackage != null) { activities.addAll(activitiesForPackage); } else { if (DBG) { Slogf.d(TAG, info.packageName + ": Activities null"); } } } else { if (DBG) { Slogf.d(TAG, "Partially Allowlisted. WL Activities: " + configActivitiesForPackage); } activities.addAll(configActivitiesForPackage); } } /* 2. If app is not listed in the config.xml check their Manifest meta-data to see if they have any Distraction Optimized(DO) activities. For non system apps, we check if the app install source was a permittable source. This prevents side-loaded apps to fake DO. Bypass the check for debug builds for development convenience. */ if (!isDebugBuild() && !PackageManagerHelper.isSystemApp(info.applicationInfo) && !PackageManagerHelper.isUpdatedSystemApp(info.applicationInfo)) { try { if (mAllowedAppInstallSources != null) { String installerName = mPackageManager.getInstallerPackageName( info.packageName); if (installerName == null || (installerName != null && !mAllowedAppInstallSources.contains(installerName))) { Slogf.w(TAG, info.packageName + " not installed from permitted sources " + (installerName == null ? "NULL" : installerName)); return null; } } } catch (IllegalArgumentException e) { Slogf.w(TAG, info.packageName + " not installed!"); return null; } } try { String[] doActivities = findDistractionOptimizedActivitiesAsUser(info.packageName, userId); if (doActivities != null) { // Some of the activities in this app are Distraction Optimized. if (DBG) { for (String activity : doActivities) { Slogf.d(TAG, "adding " + activity + " from " + info.packageName + " to allowlist"); } } activities.addAll(Arrays.asList(doActivities)); } } catch (NameNotFoundException e) { Slogf.w(TAG, "Error reading metadata: " + info.packageName); return null; } // Nothing to add to allowlist if (activities.isEmpty()) { return null; } /* 3. Check if parsed activity is in in config.xml. Anything in blocklist should not be allowlisted, either as D.O. or by config. */ if (configBlocklist.containsKey(info.packageName)) { Set configBlocklistActivities = configBlocklist.get(info.packageName); if (configBlocklistActivities.isEmpty()) { // Whole package should be blocklisted. return null; } activities.removeAll(configBlocklistActivities); } Signature[] signatures; signatures = info.signatures; AppBlockingPackageInfo appBlockingInfo = new AppBlockingPackageInfo(info.packageName, /* minRevisionCode = */ 0, /* maxRevisionCode = */ 0, flags, signatures, activities.toArray(new String[activities.size()])); AppBlockingPackageInfoWrapper wrapper = new AppBlockingPackageInfoWrapper( appBlockingInfo, true); return wrapper; } /** * Update map of allowlisted packages and activities of the form {pkgName, Allowlisted * activities} and set of denylisted packages. The information can come from a configuration XML * resource or from the apps marking their activities as distraction optimized. */ private void updateActivityAllowlistAndDenylistMap(String packageName) { int userId = mActivityManager.getCurrentUser(); Slogf.d(TAG, "Updating allowlist and denylist mapping for package: " + packageName + " for UserId: " + userId); // Get the apps/activities that are allowlisted in the configuration XML resources. Map> configAllowlist = generateConfigAllowlist(); Map> configBlocklist = generateConfigBlocklist(); AppBlockingPackageInfoWrapper wrapper = getPackageInfoWrapperForUser(packageName, userId, configAllowlist, configBlocklist); if (wrapper == null && userId != UserHandle.SYSTEM.getIdentifier()) { Slogf.d(TAG, "Updating allowlist and denylist mapping for package: " + packageName + " for UserId: " + UserHandle.SYSTEM.getIdentifier()); // check package for system user, in case package is disabled for current user wrapper = getPackageInfoWrapperForUser(packageName, UserHandle.SYSTEM.getIdentifier(), configAllowlist, configBlocklist); } synchronized (mLock) { if (wrapper != null) { if (DBG) { Slogf.d(TAG, "Package: " + packageName + " added in allowlist."); } mActivityAllowlistMap.put(packageName, wrapper); } else { if (DBG) { Slogf.d(TAG, "Package: " + packageName + " added in denylist."); } mActivityDenylistPackages.add(packageName); } } } private Map> generateConfigAllowlist() { synchronized (mLock) { if (mConfiguredAllowlistMap != null) return mConfiguredAllowlistMap; Map> configAllowlist = new HashMap<>(); mConfiguredAllowlist = mContext.getString(R.string.activityAllowlist); if (mConfiguredAllowlist == null) { Slogf.w(TAG, "Allowlist is null."); } parseConfigList(mConfiguredAllowlist, configAllowlist); mConfiguredSystemAllowlist = mContext.getString(R.string.systemActivityAllowlist); if (mConfiguredSystemAllowlist == null) { Slogf.w(TAG, "System allowlist is null."); } parseConfigList(mConfiguredSystemAllowlist, configAllowlist); // Add the blocking overlay activity to the allowlist, since that needs to run in a // restricted state to communicate the reason an app was blocked. Set defaultActivity = new ArraySet<>(); if (mActivityBlockingActivity != null) { defaultActivity.add(mActivityBlockingActivity.getClassName()); configAllowlist.put(mActivityBlockingActivity.getPackageName(), defaultActivity); } mConfiguredAllowlistMap = configAllowlist; return configAllowlist; } } private Map> generateConfigBlocklist() { synchronized (mLock) { if (mConfiguredBlocklistMap != null) return mConfiguredBlocklistMap; Map> configBlocklist = new HashMap<>(); mConfiguredBlocklist = mContext.getString(R.string.activityDenylist); if (mConfiguredBlocklist == null) { if (DBG) { Slogf.d(TAG, "Null blocklist in config"); } } parseConfigList(mConfiguredBlocklist, configBlocklist); mConfiguredBlocklistMap = configBlocklist; return configBlocklist; } } private boolean isDebugBuild() { return BuildHelper.isUserDebugBuild() || BuildHelper.isEngBuild(); } /** * Parses the given resource and updates the input map of packages and activities. * * Key is package name and value is list of activities. Empty set implies whole package is * included. * * When there are multiple entries regarding one package, the entry with * greater scope wins. Namely if there were 2 entries such that one allowlists * an activity, and the other allowlists the entire package of the activity, * the package is allowlisted, regardless of input order. */ @VisibleForTesting /* package */ void parseConfigList(String configList, @NonNull Map> packageToActivityMap) { if (configList == null) { return; } String[] entries = configList.split(PACKAGE_DELIMITER); for (String entry : entries) { String[] packageActivityPair = entry.split(PACKAGE_ACTIVITY_DELIMITER); Set activities = packageToActivityMap.get(packageActivityPair[0]); boolean newPackage = false; if (activities == null) { activities = new ArraySet<>(); newPackage = true; packageToActivityMap.put(packageActivityPair[0], activities); } if (packageActivityPair.length == 1) { // whole package activities.clear(); } else if (packageActivityPair.length == 2) { // add class name only when the whole package is not allowlisted. if (newPackage || (activities.size() > 0)) { activities.add(packageActivityPair[1]); } } } } @Nullable private List getActivitiesInPackage(PackageInfo info) { if (info == null || info.activities == null) { return null; } List activityList = new ArrayList<>(); for (ActivityInfo aInfo : info.activities) { activityList.add(aInfo.name); } return activityList; } /** * Checks if there are any {@link CarAppBlockingPolicyService} and creates a proxy to * bind to them and retrieve the {@link CarAppBlockingPolicy} */ @VisibleForTesting public void startAppBlockingPolicies() { Intent policyIntent = new Intent(); policyIntent.setAction(CarAppBlockingPolicyService.SERVICE_INTERFACE); List policyInfos = mPackageManager.queryIntentServices(policyIntent, 0); if (policyInfos == null) { //no need to wait for service binding and retrieval. return; } LinkedList proxies = new LinkedList<>(); for (ResolveInfo resolveInfo : policyInfos) { ServiceInfo serviceInfo = resolveInfo.serviceInfo; if (serviceInfo == null) { continue; } if (serviceInfo.isEnabled()) { if (mPackageManager.checkPermission(Car.PERMISSION_CONTROL_APP_BLOCKING, serviceInfo.packageName) != PackageManager.PERMISSION_GRANTED) { continue; } Slogf.i(TAG, "found policy holding service:" + serviceInfo); AppBlockingPolicyProxy proxy = new AppBlockingPolicyProxy(this, mContext, serviceInfo); proxy.connect(); proxies.add(proxy); } } synchronized (mLock) { mProxies = proxies; } } public void onPolicyConnectionAndSet(AppBlockingPolicyProxy proxy, CarAppBlockingPolicy policy) { doHandlePolicyConnection(proxy, policy); } public void onPolicyConnectionFailure(AppBlockingPolicyProxy proxy) { doHandlePolicyConnection(proxy, null); } private void doHandlePolicyConnection(AppBlockingPolicyProxy proxy, CarAppBlockingPolicy policy) { synchronized (mLock) { if (mProxies == null) { proxy.disconnect(); return; } mProxies.remove(proxy); if (mProxies.size() == 0) { mProxies = null; } } try { if (policy != null) { if (DBG) { Slogf.d(TAG, "policy setting from policy service:" + proxy.getPackageName()); } doSetAppBlockingPolicy(proxy.getPackageName(), policy, 0); } } finally { proxy.disconnect(); } } @Override @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) public void dump(IndentingPrintWriter writer) { synchronized (mLock) { writer.println("*CarPackageManagerService*"); writer.println("mEnableActivityBlocking:" + mEnableActivityBlocking); writer.println("mPreventTemplatedAppsFromShowingDialog:" + mPreventTemplatedAppsFromShowingDialog); writer.println("mTemplateActivityClassName:" + mTemplateActivityClassName); List restrictions = new ArrayList<>(mUxRestrictionsListeners.size()); for (int i = 0; i < mUxRestrictionsListeners.size(); i++) { int displayId = mUxRestrictionsListeners.keyAt(i); UxRestrictionsListener listener = mUxRestrictionsListeners.valueAt(i); restrictions.add(String.format("Display %d is %s", displayId, (listener.isRestricted() ? "restricted" : "unrestricted"))); } writer.println("Display Restrictions:\n" + String.join("\n", restrictions)); writer.println(" Blocked activity log:"); mBlockedActivityLogs.dump(writer); writer.print(dumpPoliciesLocked(true)); writer.print("mCurrentDrivingSafetyRegion:"); writer.println(mCurrentDrivingSafetyRegion); writer.print("mTempAllowedActivities:"); writer.println(mTempAllowedActivities); writer.println("Car service overlay packages property name: " + PackageManagerHelper.PROPERTY_CAR_SERVICE_OVERLAY_PACKAGES); writer.println("Car service overlay packages: " + SystemProperties.get( PackageManagerHelper.PROPERTY_CAR_SERVICE_OVERLAY_PACKAGES, /* default= */ null)); mVendorServiceController.dump(writer); mBlockingUiCommandListenerMediator.dump(writer); } } @Override @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) public void dumpProto(ProtoOutputStream proto) {} @GuardedBy("mLock") private String dumpPoliciesLocked(boolean dumpAll) { StringBuilder sb = new StringBuilder(); if (dumpAll) { sb.append("**System allowlist**\n"); for (AppBlockingPackageInfoWrapper wrapper : mActivityAllowlistMap.values()) { sb.append(wrapper.toString() + "\n"); } } sb.append("**Client Policies**\n"); for (Entry entry : mClientPolicies.entrySet()) { sb.append("Client:" + entry.getKey() + "\n"); sb.append(" allowlists:\n"); for (AppBlockingPackageInfoWrapper wrapper : entry.getValue().mAllowlistsMap.values()) { sb.append(wrapper.toString() + "\n"); } sb.append(" blocklists:\n"); for (AppBlockingPackageInfoWrapper wrapper : entry.getValue().mBlocklistsMap.values()) { sb.append(wrapper.toString() + "\n"); } } sb.append("**Unprocessed policy services**\n"); if (mProxies != null) { for (AppBlockingPolicyProxy proxy : mProxies) { sb.append(proxy.toString() + "\n"); } } sb.append("**Allowlist string in resource**\n"); sb.append(mConfiguredAllowlist + "\n"); sb.append("**System allowlist string in resource**\n"); sb.append(mConfiguredSystemAllowlist + "\n"); sb.append("**Blocklist string in resource**\n"); sb.append(mConfiguredBlocklist + "\n"); sb.append("**Allowlist map from resource**\n"); sb.append(mConfiguredAllowlistMap + "\n"); sb.append("**Blocklist from resource**\n"); sb.append(mConfiguredBlocklist + "\n"); return sb.toString(); } /** * Returns whether UX restrictions is required for the given display. * * Non-physical display will use restrictions for {@link Display#DEFAULT_DISPLAY}. */ private boolean isUxRestrictedOnDisplay(int displayId) { UxRestrictionsListener listenerForTopTaskDisplay; synchronized (mLock) { if (mUxRestrictionsListeners.indexOfKey(displayId) < 0) { Slogf.w(TAG, "Cannot find UxR listener for display %d, using UxR on default display", displayId); listenerForTopTaskDisplay = mUxRestrictionsListeners.get(Display.DEFAULT_DISPLAY); if (listenerForTopTaskDisplay == null) { // This should never happen. Slogf.e(TAG, "Missing listener for default display."); return true; } } else { listenerForTopTaskDisplay = mUxRestrictionsListeners.get(displayId); } } return listenerForTopTaskDisplay.isRestricted(); } /** * Blocks top activities on all displays if necessary. */ private void blockTopActivitiesOnAllDisplaysIfNecessary() { List visibleTasks = mActivityService.getVisibleTasksInternal(); ArrayList restrictedDisplayIds = new ArrayList<>(); synchronized (mLock) { for (int i = 0; i < mUxRestrictionsListeners.size(); i++) { int displayId = mUxRestrictionsListeners.keyAt(i); UxRestrictionsListener listener = mUxRestrictionsListeners.valueAt(i); if (listener.isRestricted()) { if (DBG) { Slogf.d(TAG, "Display %d is in a UxRestricted state.", displayId); } restrictedDisplayIds.add(displayId); } else { if (DBG) { Slogf.d(TAG, "Display %d is not in a UxRestricted state, not blocking.", displayId); } } } } // Block activities on a display if it is in a Ux restricted state. for (int i = 0; i < restrictedDisplayIds.size(); i++) { int displayId = restrictedDisplayIds.get(i); if (DBG) { Slogf.d(TAG, "Initiating activity blocking on display %d.", displayId); } blockTopActivitiesOnDisplayIfNecessary(visibleTasks, displayId); } } /** * Blocks top activities on the given display if necessary. */ private void blockTopActivitiesOnDisplayIfNecessary(List visibleTasks, int displayId) { for (TaskInfo topTask : visibleTasks) { if (topTask == null) { Slogf.e(TAG, "Top tasks contains null."); continue; } int displayIdOfTask = TaskInfoHelper.getDisplayId(topTask); if (displayIdOfTask != displayId) { // Only block activities on the given display. Skip if it's not the given // display. continue; } boolean blocked = blockTopActivity(topTask); if (blocked) { if (DBG) { Slogf.d(TAG, "Display %d has already been blocked.", displayIdOfTask); } break; } } } /** * Blocks the top activity if it's on a Ux restricted display. * * @return {@code true} if the {@code topTask} was blocked, {@code false} otherwise. */ private boolean blockTopActivityIfNecessary(TaskInfo topTask) { int displayId = TaskInfoHelper.getDisplayId(topTask); synchronized (mLock) { if (!Objects.equals(mActivityBlockingActivity, topTask.topActivity) && mTopActivityWithDialogPerDisplay.contains(displayId) && !topTask.topActivity.equals( mTopActivityWithDialogPerDisplay.get(displayId))) { // Clear top activity-with-dialog if the activity has changed on this display. mTopActivityWithDialogPerDisplay.remove(displayId); } } if (isUxRestrictedOnDisplay(displayId)) { return doBlockTopActivityIfNotAllowed(displayId, topTask); } return false; } /** * Blocks the top activity if not allowed. * * @return {@code true} if the {@code topTask} was blocked, {@code false} otherwise. */ private boolean blockTopActivity(TaskInfo topTask) { int displayId = TaskInfoHelper.getDisplayId(topTask); synchronized (mLock) { if (!Objects.equals(mActivityBlockingActivity, topTask.topActivity) && mTopActivityWithDialogPerDisplay.contains(displayId) && !topTask.topActivity.equals( mTopActivityWithDialogPerDisplay.get(displayId))) { // Clear top activity-with-dialog if the activity has changed on this display. mTopActivityWithDialogPerDisplay.remove(displayId); } } return doBlockTopActivityIfNotAllowed(displayId, topTask); } /** * @return {@code true} if the {@code topTask} was blocked, {@code false} otherwise. */ private boolean doBlockTopActivityIfNotAllowed(int displayId, TaskInfo topTask) { if (topTask.topActivity == null) { return false; } if (topTask.topActivity.equals(mActivityBlockingActivity)) { mBlockingActivityLaunchTimes.put(displayId, 0); mBlockingActivityTargets.put(displayId, null); // If topTask is already ActivityBlockingActivity, treat it as already blocked. return true; } boolean allowed = isActivityAllowed(topTask); if (DBG) { Slogf.d(TAG, "new activity:" + topTask.toString() + " allowed:" + allowed); } if (allowed) { return false; } if (!mEnableActivityBlocking) { Slogf.d(TAG, "Current activity " + topTask.topActivity + " not allowed, blocking disabled."); return false; } if (DBG) { Slogf.d(TAG, "Current activity " + topTask.topActivity + " not allowed, will block."); } // TaskMonitor based on TaskOrganizer reflects only the actually launched tasks, // (TaskStackChangeListener reflects the internal state of ActivityTaskManagerService) // So it takes some time to recognize the ActivityBlockingActivity is shown. // This guard is to prevent from launching ABA repeatedly until it is shown. ComponentName blockingActivityTarget = mBlockingActivityTargets.get(displayId); if (topTask.topActivity.equals(blockingActivityTarget)) { long blockingActivityLaunchTime = mBlockingActivityLaunchTimes.get(displayId); if (SystemClock.uptimeMillis() - blockingActivityLaunchTime < ABA_LAUNCH_TIMEOUT_MS) { Slogf.d(TAG, "Waiting for BlockingActivity to be shown: displayId=%d", displayId); return false; } } // Figure out the root task of blocked task. ComponentName rootTaskActivityName = topTask.baseActivity; boolean isRootDO = false; if (rootTaskActivityName != null) { isRootDO = isActivityDistractionOptimized( rootTaskActivityName.getPackageName(), rootTaskActivityName.getClassName()); } Intent newActivityIntent = createBlockingActivityIntent( mActivityBlockingActivity, TaskInfoHelper.getDisplayId(topTask), topTask.topActivity.flattenToShortString(), topTask.taskId, rootTaskActivityName.flattenToString(), isRootDO); // Intent contains all info to debug what is blocked - log into both logcat and dumpsys. String log = "Starting blocking activity with intent: " + newActivityIntent.toUri(0); if (Slogf.isLoggable(TAG, Log.INFO)) { Slogf.i(TAG, log); } mBlockedActivityLogs.log(log); mBlockingActivityLaunchTimes.put(displayId, SystemClock.uptimeMillis()); mBlockingActivityTargets.put(displayId, topTask.topActivity); mActivityService.blockActivity(topTask, newActivityIntent); return true; } private boolean isActivityAllowed(TaskInfo topTaskInfoContainer) { ComponentName activityName = topTaskInfoContainer.topActivity; boolean isDistractionOptimized = isActivityDistractionOptimized( activityName.getPackageName(), activityName.getClassName()); if (!isDistractionOptimized) { return false; } return !(mPreventTemplatedAppsFromShowingDialog && isTemplateActivity(activityName) && isActivityShowingADialogOnDisplay(activityName, TaskInfoHelper.getDisplayId(topTaskInfoContainer))); } private boolean isTemplateActivity(ComponentName activityName) { // TODO(b/191263486): Finalise on how to detect the templated activities. return activityName.getClassName().equals(mTemplateActivityClassName); } private boolean isActivityShowingADialogOnDisplay(ComponentName activityName, int displayId) { String output = dumpWindows(); List appWindows = WindowDumpParser.getParsedAppWindows(output, activityName.getPackageName()); // TODO(b/192354699): Handle case where an activity can have multiple instances on the same // display. int totalAppWindows = appWindows.size(); String firstActivityRecord = null; int numTopActivityAppWindowsOnDisplay = 0; for (int i = 0; i < totalAppWindows; i++) { WindowDumpParser.Window appWindow = appWindows.get(i); if (appWindow.getDisplayId() != displayId) { continue; } if (TextUtils.isEmpty(appWindow.getActivityRecord())) { continue; } if (firstActivityRecord == null) { firstActivityRecord = appWindow.getActivityRecord(); } if (firstActivityRecord.equals(appWindow.getActivityRecord())) { numTopActivityAppWindowsOnDisplay++; } } Slogf.d(TAG, "Top activity = " + activityName); Slogf.d(TAG, "Number of app widows of top activity = " + numTopActivityAppWindowsOnDisplay); boolean isShowingADialog = numTopActivityAppWindowsOnDisplay > 1; synchronized (mLock) { if (isShowingADialog) { mTopActivityWithDialogPerDisplay.put(displayId, activityName); } else { mTopActivityWithDialogPerDisplay.remove(displayId); } } return isShowingADialog; } private String dumpWindows() { try { ParcelFileDescriptor[] fileDescriptors = ParcelFileDescriptor.createSocketPair(); mWindowManagerBinder.dump( fileDescriptors[0].getFileDescriptor(), WINDOW_DUMP_ARGUMENTS); fileDescriptors[0].close(); StringBuilder outputBuilder = new StringBuilder(); try (BufferedReader reader = new BufferedReader( new FileReader(fileDescriptors[1].getFileDescriptor()))) { String line; while ((line = reader.readLine()) != null) { outputBuilder.append(line).append("\n"); } } fileDescriptors[1].close(); return outputBuilder.toString(); } catch (IOException | RemoteException e) { throw new RuntimeException(e); } } /** * Creates an intent to start blocking activity. * * @param blockingActivity the activity to launch * @param blockedActivity the activity being blocked * @param blockedTaskId the blocked task id, which contains the blocked activity * @param taskRootActivity root activity of the blocked task * @param isRootDo denotes if the root activity is distraction optimised * @return an intent to launch the blocking activity. */ private static Intent createBlockingActivityIntent(ComponentName blockingActivity, int displayId, String blockedActivity, int blockedTaskId, String taskRootActivity, boolean isRootDo) { Intent newActivityIntent = new Intent(); newActivityIntent.setFlags( Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); newActivityIntent.setComponent(blockingActivity); newActivityIntent.putExtra( BLOCKING_INTENT_EXTRA_DISPLAY_ID, displayId); newActivityIntent.putExtra( BLOCKING_INTENT_EXTRA_BLOCKED_ACTIVITY_NAME, blockedActivity); newActivityIntent.putExtra( BLOCKING_INTENT_EXTRA_BLOCKED_TASK_ID, blockedTaskId); newActivityIntent.putExtra( BLOCKING_INTENT_EXTRA_ROOT_ACTIVITY_NAME, taskRootActivity); newActivityIntent.putExtra( BLOCKING_INTENT_EXTRA_IS_ROOT_ACTIVITY_DO, isRootDo); return newActivityIntent; } /** * Enable/Disable activity blocking by correspondingly enabling/disabling broadcasting UXR * changes in {@link CarUxRestrictionsManagerService}. This is only available in * engineering builds for development convenience. */ @Override public void setEnableActivityBlocking(boolean enable) { if (!isDebugBuild()) { Slogf.e(TAG, "Cannot enable/disable activity blocking"); return; } // Check if the caller has the same signature as that of the car service. if (mPackageManager.checkSignatures(Process.myUid(), Binder.getCallingUid()) != PackageManager.SIGNATURE_MATCH) { throw new SecurityException( "Caller " + mPackageManager.getNameForUid(Binder.getCallingUid()) + " does not have the right signature"); } mCarUxRestrictionsService.setUxRChangeBroadcastEnabled(enable); } @Override public CarVersion getTargetCarVersion(String packageName) { return getTargetCarVersion(Binder.getCallingUserHandle(), packageName); } @Override public CarVersion getSelfTargetCarVersion(String packageName) { checkCalledByPackage(mContext, packageName); return getTargetCarVersion(Binder.getCallingUserHandle(), packageName); } /** * Public, as it's also used by {@code ICarImpl}. */ public CarVersion getTargetCarVersion(UserHandle user, String packageName) { Context context = mContext.createContextAsUser(user, /* flags= */ 0); return getTargetCarVersion(context, packageName); } /** * Used by {@code CarShellCommand} as well. */ @Nullable public static CarVersion getTargetCarVersion(Context context, String packageName) { String permission = android.Manifest.permission.QUERY_ALL_PACKAGES; if (context.checkCallingOrSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { Slogf.w(TAG, "getTargetCarVersion(%s): UID %d doesn't have %s permission", packageName, Binder.getCallingUid(), permission); throw new SecurityException("requires permission " + permission); } ApplicationInfo info = null; try { info = context.getPackageManager().getApplicationInfo(packageName, PackageManager.ApplicationInfoFlags.of(PackageManager.GET_META_DATA)); } catch (NameNotFoundException e) { if (DBG) { Slogf.d(TAG, "getTargetCarVersion(%s, %s): not found: %s", context.getUser(), packageName, e); } throw new ServiceSpecificException(CarPackageManager.ERROR_CODE_NO_PACKAGE, e.getMessage()); } return CarVersionParser.getTargetCarVersion(info); } /** * Get the distraction optimized activities for the given package. * * @param pkgName Name of the package * @return Array of the distraction optimized activities in the package */ @Nullable public String[] getDistractionOptimizedActivities(String pkgName) { try { return findDistractionOptimizedActivitiesAsUser(pkgName, mActivityManager.getCurrentUser()); } catch (NameNotFoundException e) { return null; } } @Override public boolean requiresDisplayCompat(String packageName) { if (!callerCanQueryPackage(packageName)) { throw new SecurityException("requires permission " + QUERY_ALL_PACKAGES); } int callingUid = Binder.getCallingUid(); if (!hasPermissionGranted(PERMISSION_MANAGE_DISPLAY_COMPATIBILITY, callingUid)) { throw new SecurityException("requires permission " + PERMISSION_MANAGE_DISPLAY_COMPATIBILITY); } return CarServiceHelperWrapper.getInstance().requiresDisplayCompat( Objects.requireNonNull(packageName, "packageName cannot be Null")); } private String[] findDistractionOptimizedActivitiesAsUser(String pkgName, int userId) throws NameNotFoundException { String regionString; synchronized (mLock) { regionString = mCurrentDrivingSafetyRegion; } return CarAppMetadataReader.findDistractionOptimizedActivitiesAsUser(mContext, pkgName, userId, regionString); } /** * Reading policy and setting policy can take time. Run it in a separate handler thread. */ private static final class PackageHandler extends Handler { private static final String TAG = CarLog.tagFor(CarPackageManagerService.class); private static final int MSG_INIT = 0; private static final int MSG_PARSE_PKG = 1; private static final int MSG_UPDATE_POLICY = 2; private static final int MSG_RELEASE = 3; private final WeakReference mService; private PackageHandler(Looper looper, CarPackageManagerService service) { super(looper); mService = new WeakReference(service); } private void requestInit() { Message msg = obtainMessage(MSG_INIT); sendMessage(msg); } private void requestRelease() { removeMessages(MSG_UPDATE_POLICY); Message msg = obtainMessage(MSG_RELEASE); sendMessage(msg); } private void requestUpdatingPolicy(String packageName, CarAppBlockingPolicy policy, int flags) { Pair pair = new Pair<>(packageName, policy); Message msg = obtainMessage(MSG_UPDATE_POLICY, flags, 0, pair); sendMessage(msg); } private void requestParsingInstalledPkg(String packageName) { Message msg = obtainMessage(MSG_PARSE_PKG, packageName); sendMessage(msg); } @Override public void handleMessage(Message msg) { CarPackageManagerService service = mService.get(); if (service == null) { Slogf.i(TAG, "handleMessage null service"); return; } switch (msg.what) { case MSG_INIT: service.doHandleInit(); break; case MSG_PARSE_PKG: service.doParseInstalledPackage((String) msg.obj); break; case MSG_UPDATE_POLICY: Pair pair = (Pair) msg.obj; service.doUpdatePolicy(pair.first, pair.second, msg.arg1); break; case MSG_RELEASE: service.doHandleRelease(); break; default: break; } } } private static class AppBlockingPackageInfoWrapper { private final AppBlockingPackageInfo info; /** * Whether the current info is matching with the target package in system. Mismatch can * happen for version out of range or signature mismatch. */ private boolean isMatching; private AppBlockingPackageInfoWrapper(AppBlockingPackageInfo info, boolean isMatching) { this.info = info; this.isMatching = isMatching; } @Override public String toString() { return "AppBlockingPackageInfoWrapper [info=" + info + ", isMatching=" + isMatching + "]"; } } /** * Client policy holder per each client. Should be accessed with CarpackageManagerService.this * held. */ private static class ClientPolicy { private final HashMap mAllowlistsMap = new HashMap<>(); private final HashMap mBlocklistsMap = new HashMap<>(); private void replaceAllowlists(AppBlockingPackageInfoWrapper[] allowlists) { mAllowlistsMap.clear(); addToAllowlists(allowlists); } private void addToAllowlists(AppBlockingPackageInfoWrapper[] allowlists) { if (allowlists == null) { return; } for (AppBlockingPackageInfoWrapper wrapper : allowlists) { if (wrapper != null) { mAllowlistsMap.put(wrapper.info.packageName, wrapper); } } } private void removeAllowlists(AppBlockingPackageInfoWrapper[] allowlists) { if (allowlists == null) { return; } for (AppBlockingPackageInfoWrapper wrapper : allowlists) { if (wrapper != null) { mAllowlistsMap.remove(wrapper.info.packageName); } } } private void replaceBlocklists(AppBlockingPackageInfoWrapper[] blocklists) { mBlocklistsMap.clear(); addToBlocklists(blocklists); } private void addToBlocklists(AppBlockingPackageInfoWrapper[] blocklists) { if (blocklists == null) { return; } for (AppBlockingPackageInfoWrapper wrapper : blocklists) { if (wrapper != null) { mBlocklistsMap.put(wrapper.info.packageName, wrapper); } } } private void removeBlocklists(AppBlockingPackageInfoWrapper[] blocklists) { if (blocklists == null) { return; } for (AppBlockingPackageInfoWrapper wrapper : blocklists) { if (wrapper != null) { mBlocklistsMap.remove(wrapper.info.packageName); } } } } private class ActivityListener implements CarActivityService.ActivityListener { @Override public void onActivityCameOnTop(TaskInfo topTask) { if (topTask == null) { Slogf.e(TAG, "Received callback with null top task."); return; } boolean isBlockingActivity = Objects.equals(mActivityBlockingActivity, topTask.topActivity); synchronized (mLock) { if (isBlockingActivity) { // This is to keep track of the blocking ui taskInfo. mBlockingUiTaskInfoPerDisplay.put(TaskInfoHelper.getDisplayId(topTask), topTask); } mLastKnownDisplayIdForTask.put(topTask.taskId, TaskInfoHelper.getDisplayId(topTask)); } blockTopActivityIfNecessary(topTask); } @Override public void onActivityChangedInBackstack(TaskInfo taskInfo) { if (taskInfo == null) { Slogf.e(TAG, "Received callback with null task info."); return; } int lastKnownDisplayId; synchronized (mLock) { // Only update the array if display Id for the task is valid since display Id can // often be invalid when the task has vanished. if (TaskInfoHelper.getDisplayId(taskInfo) != Display.INVALID_DISPLAY) { mLastKnownDisplayIdForTask.put(taskInfo.taskId, TaskInfoHelper.getDisplayId(taskInfo)); } lastKnownDisplayId = mLastKnownDisplayIdForTask.get(taskInfo.taskId); } if (DBG) { Slogf.i(TAG, "Callback for task %s on display id %d.", taskInfo.taskId, lastKnownDisplayId); } // Only finish the blocking ui if it is visible and there is some change in the // backstack for the activity that is being blocked. This is because the blocked // activity could have crashed due to which there is a need for blocking ui to finish. if (isBlockingUiVisible(lastKnownDisplayId) && isBlockedActivityTarget( lastKnownDisplayId, taskInfo)) { mHandler.post(() -> finishBlockingUi(taskInfo)); synchronized (mLock) { mBlockingUiTaskInfoPerDisplay.delete(lastKnownDisplayId); } } } } /** * Checks if blocking ui is visible on that {@code displayId}. * * @param displayId the display id of the {@link TaskInfo}. * @return {@code true} if the blocking ui is visible, {@code false} otherwise. */ private boolean isBlockingUiVisible(int displayId) { synchronized (mLock) { return mBlockingUiTaskInfoPerDisplay.get(displayId) != null && TaskInfoHelper.isVisible( mBlockingUiTaskInfoPerDisplay.get(displayId)); } } /** * Check if this stack change came from the blocked {@code taskInfo} in * {@code mBlockingActivityTargets} for {@code displayId}. Ignore other activities since they * cannot can cause a visibility change for the blocked activity target. * * @param displayId the display id of the {@link TaskInfo}. * @param taskInfo {@link TaskInfo} due to which the task stack changed. * @return {@code true} if this stack change came from blocked {@code taskInfo} for * {@code displayId}, {@code false} otherwise. */ private boolean isBlockedActivityTarget(int displayId, TaskInfo taskInfo) { return mBlockingActivityTargets.get(displayId) == taskInfo.topActivity; } /** * Registers the {@link ICarBlockingUiCommandListener} listening for the commands to control the * blocking ui. * * @param listener listener to register. * @param displayId display Id with which the listener is associated. */ public void registerBlockingUiCommandListener(ICarBlockingUiCommandListener listener, int displayId) { ensurePermission(); mBlockingUiCommandListenerMediator.registerBlockingUiCommandListener(listener, displayId); } /** * Unregisters the {@link ICarBlockingUiCommandListener}. * * @param listener listener to unregister. */ public void unregisterBlockingUiCommandListener(ICarBlockingUiCommandListener listener) { ensurePermission(); mBlockingUiCommandListenerMediator.unregisterBlockingUiCommandListener(listener); } /** * Broadcast the finish command to listeners. * * @param taskInfo the {@link TaskInfo} due to which finish is broadcast to the listeners. */ public void finishBlockingUi(TaskInfo taskInfo) { ensurePermission(); int displayId = getLastKnownDisplayIdForTask(taskInfo.taskId); mBlockingUiCommandListenerMediator.finishBlockingUi(taskInfo, displayId); cleanUpLastKnownDisplayIdForTask(taskInfo); } /** * Returns the last known display Id for the given {@link TaskInfo}. */ private int getLastKnownDisplayIdForTask(int taskId) { synchronized (mLock) { return mLastKnownDisplayIdForTask.get(taskId); } } /** * Removes the task from {@code mLastKnownDisplayIdForTask}. */ private void cleanUpLastKnownDisplayIdForTask(TaskInfo taskInfo) { synchronized (mLock) { // This can happen when the task has not been removed from mLastKnownDisplayIdForTask // when the task vanishes in onTaskVanished. mLastKnownDisplayIdForTask.delete(taskInfo.taskId); } } /** * Get number of registered callbacks for the display ID. * * @param displayId display Id with which the listener is associated. * @return number of registered callbacks for the given {@code displayId}. */ @VisibleForTesting public int getCarBlockingUiCommandListenerRegisteredCallbacksForDisplay(int displayId) { return mBlockingUiCommandListenerMediator .getCarBlockingUiCommandListenerRegisteredCallbacksForDisplay(displayId); } /** Ensure permission is granted. */ private void ensurePermission() { if (mContext.checkCallingOrSelfPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException( "requires permission " + Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY); } } /** * Listens to the UX restrictions from {@link CarUxRestrictionsManagerService} and initiates * checking if the foreground Activity should be blocked. */ private class UxRestrictionsListener extends ICarUxRestrictionsChangeListener.Stub { @GuardedBy("mLock") @Nullable private CarUxRestrictions mCurrentUxRestrictions; private final CarUxRestrictionsManagerService uxRestrictionsService; private final int mDisplayId; UxRestrictionsListener(CarUxRestrictionsManagerService service, int displayId) { uxRestrictionsService = service; mDisplayId = displayId; } @Override public void onUxRestrictionsChanged(CarUxRestrictions restrictions) { if (DBG) { Slogf.d(TAG, "Received uxr restrictions: Requires DO? %b : %d on display %d", restrictions.isRequiresDistractionOptimization(), restrictions.getActiveRestrictions(), mDisplayId); } // Check if top activities need blocking on this display. boolean shouldCheck = false; synchronized (mLock) { mCurrentUxRestrictions = new CarUxRestrictions(restrictions); shouldCheck = mCurrentUxRestrictions.isRequiresDistractionOptimization(); } if (DBG) { Slogf.d(TAG, "Should check top tasks for blocking on display %d?: " + shouldCheck, mDisplayId); } if (shouldCheck) { // Each UxRestrictionsListener is only responsible for blocking activities on their // own display. mHandler.post(() -> blockTopActivitiesOnDisplayIfNecessary( mActivityService.getVisibleTasksInternal(), mDisplayId)); } } private boolean isRestricted() { // If current restrictions is null, try querying the service, once. synchronized (mLock) { if (mCurrentUxRestrictions == null) { mCurrentUxRestrictions = uxRestrictionsService.getCurrentUxRestrictions( mDisplayId); } if (mCurrentUxRestrictions != null) { return mCurrentUxRestrictions.isRequiresDistractionOptimization(); } } // If restriction information is still not available (could happen during bootup), // return not restricted. This maintains parity with previous implementation but needs // a revisit as we test more. return false; } } /** * Called when a window change event is received by the {@link CarSafetyAccessibilityService}. */ @VisibleForTesting void onWindowChangeEvent(@NonNull AccessibilityEvent event) { boolean receivedFromActivityBlockingActivity = event.getPackageName() != null && event.getClassName() != null && mActivityBlockingActivity.getPackageName().contentEquals( event.getPackageName()) && mActivityBlockingActivity.getClassName().contentEquals( event.getClassName()); if (!receivedFromActivityBlockingActivity) { int displayId = event.getDisplayId(); if (isUxRestrictedOnDisplay(displayId)) { if (DBG) { Slogf.d(TAG, "onWindowChange event from package %s on Ux restricted display %d," + " checking activity blocking", event.getPackageName(), displayId); } // Schedule activity blocking with mHandler to ensure there is no concurrent // activity blocking. mHandler.post(() -> blockTopActivitiesOnDisplayIfNecessary( mActivityService.getVisibleTasksInternal(), displayId)); } } else { Slogf.d(TAG, "Discarded onWindowChangeEvent received from " + "ActivityBlockingActivity"); } } /** * Listens to the package install/uninstall events to know when to initiate parsing * installed packages. */ private class PackageParsingEventReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (intent == null || intent.getAction() == null) { return; } if (DBG) { Slogf.d(TAG, "PackageParsingEventReceiver Received " + intent.getAction()); } String action = intent.getAction(); if (isPackageManagerAction(action)) { // send a delayed message so if we received multiple related intents, we parse // only once. logEventChange(intent); String packageName = getPackageName(intent); mHandler.requestParsingInstalledPkg(packageName); } } private String getPackageName(Intent intent) { // For mPackageManagerActions, data should contain package name. String dataString = intent.getDataString(); if (dataString == null) return null; String scheme = intent.getScheme(); if (!Objects.equals(scheme, "package")) return null; String[] splitData = intent.getDataString().split(":"); if (splitData.length < 2) return null; return splitData[1]; } private boolean isPackageManagerAction(String action) { return mPackageManagerActions.contains(action); } /** * Convenience log function to log what changed. Logs only when more debug logs * are needed - DBG needs to be true */ private void logEventChange(Intent intent) { if (intent == null) { return; } if (DBG) { String packageName = intent.getData().getSchemeSpecificPart(); Slogf.d(TAG, "Pkg Changed:" + packageName); String action = intent.getAction(); if (action == null) { return; } if (action.equals(Intent.ACTION_PACKAGE_CHANGED)) { Slogf.d(TAG, "Changed components"); String[] cc = intent .getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST); if (cc != null) { for (String c : cc) { Slogf.d(TAG, c); } } } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED) || action.equals(Intent.ACTION_PACKAGE_ADDED)) { Slogf.d(TAG, action + " Replacing?: " + intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)); } } } } }