blob: ed51a782692ec2a9e800d8a595ead9476f0b6f9d [file] [log] [blame]
/*
* 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 android.annotation.Nullable;
import android.app.ActivityManager.StackInfo;
import android.car.Car;
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.ICarPackageManager;
import android.car.drivingstate.CarUxRestrictions;
import android.car.drivingstate.ICarUxRestrictionsChangeListener;
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.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.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import com.android.car.CarLog;
import com.android.car.CarServiceBase;
import com.android.car.CarServiceUtils;
import com.android.car.CarUxRestrictionsManagerService;
import com.android.car.R;
import com.android.car.SystemActivityMonitoringService;
import com.android.car.SystemActivityMonitoringService.TopTaskInfoContainer;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class CarPackageManagerService extends ICarPackageManager.Stub implements CarServiceBase {
private static final boolean DBG_POLICY_SET = false;
private static final boolean DBG_POLICY_CHECK = false;
private static final boolean DBG_POLICY_ENFORCEMENT = false;
// 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 final Context mContext;
private final SystemActivityMonitoringService mSystemActivityMonitoringService;
private final PackageManager mPackageManager;
private final HandlerThread mHandlerThread;
private final PackageHandler mHandler;
// For dumpsys logging.
private final LinkedList<String> mBlockedActivityLogs = new LinkedList<>();
// Store the white list and black list strings from the resource file.
private String mConfiguredWhitelist;
private String mConfiguredBlacklist;
private final List<String> mAllowedAppInstallSources;
/**
* Hold policy set from policy service or client.
* Key: packageName of policy service
*/
@GuardedBy("this")
private final HashMap<String, ClientPolicy> mClientPolicies = new HashMap<>();
@GuardedBy("this")
private HashMap<String, AppBlockingPackageInfoWrapper> mActivityWhitelistMap = new HashMap<>();
// The list corresponding to the one configured in <activityBlacklist>
@GuardedBy("this")
private HashMap<String, AppBlockingPackageInfoWrapper> mActivityBlacklistMap = new HashMap<>();
@GuardedBy("this")
private LinkedList<AppBlockingPolicyProxy> mProxies;
@GuardedBy("this")
private final LinkedList<CarAppBlockingPolicy> mWaitingPolicies = new LinkedList<>();
private final CarUxRestrictionsManagerService mCarUxRestrictionsService;
private boolean mEnableActivityBlocking;
private final ComponentName mActivityBlockingActivity;
private final ActivityLaunchListener mActivityLaunchListener = new ActivityLaunchListener();
private final UxRestrictionsListener mUxRestrictionsListener;
// Information related to when the installed packages should be parsed for building a white and
// black list
private final List<String> mPackageManagerActions = Arrays.asList(
Intent.ACTION_PACKAGE_ADDED,
Intent.ACTION_PACKAGE_CHANGED,
Intent.ACTION_PACKAGE_DATA_CLEARED,
Intent.ACTION_PACKAGE_REMOVED,
Intent.ACTION_PACKAGE_REPLACED,
Intent.ACTION_PACKAGE_FULLY_REMOVED);
private final PackageParsingEventReceiver mPackageParsingEventReceiver =
new PackageParsingEventReceiver();
private final BootEventReceiver mBootEventReceiver = new BootEventReceiver();
// To track if the packages have been parsed for building white/black lists. If we haven't had
// received any intents (boot complete or package changed), then the white list is null leading
// to blocking everything. So, no blocking until we have had a chance to parse the packages.
private boolean mHasParsedPackages;
// To track if we received the boot complete intent.
private boolean mBootLockedIntentRx;
public CarPackageManagerService(Context context,
CarUxRestrictionsManagerService uxRestrictionsService,
SystemActivityMonitoringService systemActivityMonitoringService) {
mContext = context;
mCarUxRestrictionsService = uxRestrictionsService;
mSystemActivityMonitoringService = systemActivityMonitoringService;
mPackageManager = mContext.getPackageManager();
mUxRestrictionsListener = new UxRestrictionsListener(uxRestrictionsService);
mHandlerThread = new HandlerThread(CarLog.TAG_PACKAGE);
mHandlerThread.start();
mHandler = new PackageHandler(mHandlerThread.getLooper());
Resources res = context.getResources();
mEnableActivityBlocking = res.getBoolean(R.bool.enableActivityBlockingForSafety);
String blockingActivity = res.getString(R.string.activityBlockingActivity);
mActivityBlockingActivity = ComponentName.unflattenFromString(blockingActivity);
mAllowedAppInstallSources = Arrays.asList(
res.getStringArray(R.array.allowedAppInstallSources));
}
@Override
public void setAppBlockingPolicy(String packageName, CarAppBlockingPolicy policy, int flags) {
if (DBG_POLICY_SET) {
Log.i(CarLog.TAG_PACKAGE, "policy setting from binder call, client:" + packageName);
}
doSetAppBlockingPolicy(packageName, policy, flags, true /*setNow*/);
}
/**
* Restarts the requested task. If task with {@code taskId} does not exist, do nothing.
*/
@Override
public void restartTask(int taskId) {
mSystemActivityMonitoringService.restartTask(taskId);
}
private void doSetAppBlockingPolicy(String packageName, CarAppBlockingPolicy policy, int flags,
boolean setNow) {
if (mContext.checkCallingOrSelfPermission(Car.PERMISSION_CONTROL_APP_BLOCKING)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException(
"requires permission " + Car.PERMISSION_CONTROL_APP_BLOCKING);
}
CarServiceUtils.assertPackageName(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");
}
mHandler.requestUpdatingPolicy(packageName, policy, flags);
if (setNow) {
mHandler.requestPolicySetting();
if ((flags & CarPackageManager.FLAG_SET_POLICY_WAIT_FOR_CHANGE) != 0) {
synchronized (policy) {
try {
policy.wait();
} catch (InterruptedException e) {
}
}
}
}
}
@Override
public boolean isActivityDistractionOptimized(String packageName, String className) {
assertPackageAndClassName(packageName, className);
synchronized (this) {
if (DBG_POLICY_CHECK) {
Log.i(CarLog.TAG_PACKAGE, "isActivityDistractionOptimized"
+ dumpPoliciesLocked(false));
}
AppBlockingPackageInfo info = searchFromBlacklistsLocked(packageName);
if (info != null) {
return false;
}
return isActivityInWhitelistsLocked(packageName, className);
}
}
@Override
public boolean isServiceDistractionOptimized(String packageName, String className) {
if (packageName == null) {
throw new IllegalArgumentException("Package name null");
}
synchronized (this) {
if (DBG_POLICY_CHECK) {
Log.i(CarLog.TAG_PACKAGE, "isServiceDistractionOptimized"
+ dumpPoliciesLocked(false));
}
AppBlockingPackageInfo info = searchFromBlacklistsLocked(packageName);
if (info != null) {
return false;
}
info = searchFromWhitelistsLocked(packageName);
if (info != null) {
return true;
}
}
return false;
}
@Override
public boolean isActivityBackedBySafeActivity(ComponentName activityName) {
if (!mUxRestrictionsListener.isRestricted()) {
return true;
}
StackInfo info = mSystemActivityMonitoringService.getFocusedStackForTopActivity(
activityName);
if (info == null) { // not top in focused stack
return true;
}
if (info.taskNames.length <= 1) { // nothing below this.
return false;
}
ComponentName activityBehind = ComponentName.unflattenFromString(
info.taskNames[info.taskNames.length - 2]);
return isActivityDistractionOptimized(activityBehind.getPackageName(),
activityBehind.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("this")
private AppBlockingPackageInfo searchFromBlacklistsLocked(String packageName) {
for (ClientPolicy policy : mClientPolicies.values()) {
AppBlockingPackageInfoWrapper wrapper = policy.blacklistsMap.get(packageName);
if (wrapper != null && wrapper.isMatching) {
return wrapper.info;
}
}
AppBlockingPackageInfoWrapper wrapper = mActivityBlacklistMap.get(packageName);
return (wrapper != null) ? wrapper.info : null;
}
@GuardedBy("this")
private AppBlockingPackageInfo searchFromWhitelistsLocked(String packageName) {
for (ClientPolicy policy : mClientPolicies.values()) {
AppBlockingPackageInfoWrapper wrapper = policy.whitelistsMap.get(packageName);
if (wrapper != null && wrapper.isMatching) {
return wrapper.info;
}
}
AppBlockingPackageInfoWrapper wrapper = mActivityWhitelistMap.get(packageName);
return (wrapper != null) ? wrapper.info : null;
}
@GuardedBy("this")
private boolean isActivityInWhitelistsLocked(String packageName, String className) {
for (ClientPolicy policy : mClientPolicies.values()) {
if (isActivityInMapAndMatching(policy.whitelistsMap, packageName, className)) {
return true;
}
}
return isActivityInMapAndMatching(mActivityWhitelistMap, packageName, className);
}
private boolean isActivityInMapAndMatching(HashMap<String, AppBlockingPackageInfoWrapper> map,
String packageName, String className) {
AppBlockingPackageInfoWrapper wrapper = map.get(packageName);
if (wrapper == null || !wrapper.isMatching) {
if (DBG_POLICY_CHECK) {
Log.d(CarLog.TAG_PACKAGE, "Pkg not in whitelist:" + packageName);
}
return false;
}
return wrapper.info.isActivityCovered(className);
}
@Override
public void init() {
synchronized (this) {
mHandler.requestInit();
}
}
@Override
public void release() {
synchronized (this) {
mHandler.requestRelease();
// wait for release do be done. This guarantees that init is done.
try {
wait();
} catch (InterruptedException e) {
}
mHasParsedPackages = false;
mActivityWhitelistMap.clear();
mActivityBlacklistMap.clear();
mClientPolicies.clear();
if (mProxies != null) {
for (AppBlockingPolicyProxy proxy : mProxies) {
proxy.disconnect();
}
mProxies.clear();
}
wakeupClientsWaitingForPolicySetitngLocked();
}
mContext.unregisterReceiver(mPackageParsingEventReceiver);
mContext.unregisterReceiver(mBootEventReceiver);
mCarUxRestrictionsService.unregisterUxRestrictionsChangeListener(mUxRestrictionsListener);
mSystemActivityMonitoringService.registerActivityLaunchListener(null);
}
// run from HandlerThread
private void doHandleInit() {
startAppBlockingPolicies();
IntentFilter bootIntent = new IntentFilter();
bootIntent.addAction(Intent.ACTION_LOCKED_BOOT_COMPLETED);
mContext.registerReceiver(mBootEventReceiver, bootIntent);
IntentFilter pkgParseIntent = new IntentFilter();
for (String action : mPackageManagerActions) {
pkgParseIntent.addAction(action);
}
pkgParseIntent.addDataScheme("package");
mContext.registerReceiver(mPackageParsingEventReceiver, pkgParseIntent);
try {
mCarUxRestrictionsService.registerUxRestrictionsChangeListener(mUxRestrictionsListener);
} catch (IllegalArgumentException e) {
// can happen while mocking is going on while init is still done.
Log.w(CarLog.TAG_PACKAGE, "sensor subscription failed", e);
return;
}
mSystemActivityMonitoringService.registerActivityLaunchListener(
mActivityLaunchListener);
}
private void doParseInstalledPackages() {
generateActivityWhitelistMap();
generateActivityBlacklistMap();
synchronized (this) {
mHasParsedPackages = true;
}
mUxRestrictionsListener.checkIfTopActivityNeedsBlocking();
}
private synchronized void doHandleRelease() {
notifyAll();
}
@GuardedBy("this")
private void wakeupClientsWaitingForPolicySetitngLocked() {
for (CarAppBlockingPolicy waitingPolicy : mWaitingPolicies) {
synchronized (waitingPolicy) {
waitingPolicy.notifyAll();
}
}
mWaitingPolicies.clear();
}
private void doSetPolicy() {
synchronized (this) {
wakeupClientsWaitingForPolicySetitngLocked();
}
blockTopActivitiesIfNecessary();
}
private void doUpdatePolicy(String packageName, CarAppBlockingPolicy policy, int flags) {
if (DBG_POLICY_SET) {
Log.i(CarLog.TAG_PACKAGE, "setting policy from:" + packageName + ",policy:" + policy +
",flags:0x" + Integer.toHexString(flags));
}
AppBlockingPackageInfoWrapper[] blacklistWrapper = verifyList(policy.blacklists);
AppBlockingPackageInfoWrapper[] whitelistWrapper = verifyList(policy.whitelists);
synchronized (this) {
ClientPolicy clientPolicy = mClientPolicies.get(packageName);
if (clientPolicy == null) {
clientPolicy = new ClientPolicy();
mClientPolicies.put(packageName, clientPolicy);
}
if ((flags & CarPackageManager.FLAG_SET_POLICY_ADD) != 0) {
clientPolicy.addToBlacklists(blacklistWrapper);
clientPolicy.addToWhitelists(whitelistWrapper);
} else if ((flags & CarPackageManager.FLAG_SET_POLICY_REMOVE) != 0) {
clientPolicy.removeBlacklists(blacklistWrapper);
clientPolicy.removeWhitelists(whitelistWrapper);
} else { //replace.
clientPolicy.replaceBlacklists(blacklistWrapper);
clientPolicy.replaceWhitelists(whitelistWrapper);
}
if ((flags & CarPackageManager.FLAG_SET_POLICY_WAIT_FOR_CHANGE) != 0) {
mWaitingPolicies.add(policy);
}
if (DBG_POLICY_SET) {
Log.i(CarLog.TAG_PACKAGE, "policy set:" + dumpPoliciesLocked(false));
}
}
blockTopActivitiesIfNecessary();
}
private AppBlockingPackageInfoWrapper[] verifyList(AppBlockingPackageInfo[] list) {
if (list == null) {
return null;
}
LinkedList<AppBlockingPackageInfoWrapper> 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 = null;
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 ||
(!packageInfo.applicationInfo.isSystemApp() &&
!packageInfo.applicationInfo.isUpdatedSystemApp())) {
Signature[] signatires = packageInfo.signatures;
if (!isAnySignatureMatching(signatires, 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<Signature> setFromPackage = new ArraySet<Signature>();
for (Signature sig : fromPackage) {
setFromPackage.add(sig);
}
for (Signature sig : fromPolicy) {
if (setFromPackage.contains(sig)) {
return true;
}
}
return false;
}
/**
* Generate a map of whitelisted packages and activities of the form {pkgName, Whitelisted
* activities}. The whitelist information can come from a configuration XML resource or from
* the apps marking their activities as distraction optimized.
*/
private void generateActivityWhitelistMap() {
HashMap<String, AppBlockingPackageInfoWrapper> activityWhitelist = new HashMap<>();
mConfiguredWhitelist = mContext.getString(R.string.activityWhitelist);
if (mConfiguredWhitelist == null) {
if (DBG_POLICY_CHECK) {
Log.d(CarLog.TAG_PACKAGE, "Null whitelist in config");
}
return;
}
// Get the apps/activities that are whitelisted in the configuration XML resource
HashMap<String, Set<String>> configWhitelist = parseConfiglist(mConfiguredWhitelist);
if (configWhitelist == null) {
if (DBG_POLICY_CHECK) {
Log.w(CarLog.TAG_PACKAGE, "White list null. No apps whitelisted");
}
return;
}
// Add the blocking overlay activity to the whitelist, since that needs to run in a
// restricted state to communicate the reason an app was blocked.
Set<String> defaultActivity = new ArraySet<>();
if (mActivityBlockingActivity != null) {
defaultActivity.add(mActivityBlockingActivity.getClassName());
configWhitelist.put(mActivityBlockingActivity.getPackageName(), defaultActivity);
}
List<PackageInfo> packages = mPackageManager.getInstalledPackages(
PackageManager.GET_SIGNATURES | PackageManager.GET_ACTIVITIES
| PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
for (PackageInfo info : packages) {
if (info.applicationInfo == null) {
continue;
}
int flags = 0;
String[] activities = null;
if (info.applicationInfo.isSystemApp()
|| info.applicationInfo.isUpdatedSystemApp()) {
flags = AppBlockingPackageInfo.FLAG_SYSTEM_APP;
}
/* 1. Check if all or some of this app is in the <activityWhitelist>
in config.xml */
Set<String> configActivitiesForPackage = configWhitelist.get(info.packageName);
if (configActivitiesForPackage != null) {
if (DBG_POLICY_CHECK) {
Log.d(CarLog.TAG_PACKAGE, info.packageName + " whitelisted");
}
if (configActivitiesForPackage.size() == 0) {
// Whole Pkg has been whitelisted
flags |= AppBlockingPackageInfo.FLAG_WHOLE_ACTIVITY;
// Add all activities to the whitelist
activities = getActivitiesInPackage(info);
if (activities == null && DBG_POLICY_CHECK) {
Log.d(CarLog.TAG_PACKAGE, info.packageName + ": Activities null");
}
} else {
if (DBG_POLICY_CHECK) {
Log.d(CarLog.TAG_PACKAGE, "Partially Whitelisted. WL Activities:");
for (String a : configActivitiesForPackage) {
Log.d(CarLog.TAG_PACKAGE, a);
}
}
activities = configActivitiesForPackage.toArray(
new String[configActivitiesForPackage.size()]);
}
} else {
/* 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()
&& !info.applicationInfo.isSystemApp()
&& !info.applicationInfo.isUpdatedSystemApp()) {
try {
if (mAllowedAppInstallSources != null) {
String installerName = mPackageManager.getInstallerPackageName(
info.packageName);
if (installerName == null || (installerName != null
&& !mAllowedAppInstallSources.contains(installerName))) {
Log.w(CarLog.TAG_PACKAGE,
info.packageName + " not installed from permitted sources "
+ installerName == null ? "NULL" : installerName);
continue;
}
}
} catch (IllegalArgumentException e) {
Log.w(CarLog.TAG_PACKAGE, info.packageName + " not installed!");
continue;
}
}
try {
activities = CarAppMetadataReader.findDistractionOptimizedActivities(
mContext,
info.packageName);
} catch (NameNotFoundException e) {
Log.w(CarLog.TAG_PACKAGE, "Error reading metadata: " + info.packageName);
continue;
}
if (activities != null) {
// Some of the activities in this app are Distraction Optimized.
if (DBG_POLICY_CHECK) {
for (String activity : activities) {
Log.d(CarLog.TAG_PACKAGE,
"adding " + activity + " from " + info.packageName
+ " to whitelist");
}
}
}
}
// Nothing to add to whitelist
if (activities == null) {
continue;
}
Signature[] signatures;
signatures = info.signatures;
AppBlockingPackageInfo appBlockingInfo = new AppBlockingPackageInfo(
info.packageName, 0, 0, flags, signatures, activities);
AppBlockingPackageInfoWrapper wrapper = new AppBlockingPackageInfoWrapper(
appBlockingInfo, true);
activityWhitelist.put(info.packageName, wrapper);
}
synchronized (this) {
mActivityWhitelistMap.clear();
mActivityWhitelistMap.putAll(activityWhitelist);
}
}
private boolean isDebugBuild() {
return Build.IS_USERDEBUG || Build.IS_ENG;
}
/**
* Generate a map of blacklisted packages and activities of the form {pkgName, Blacklisted
* activities}. The blacklist information comes from a configuration XML resource.
*/
private void generateActivityBlacklistMap() {
HashMap<String, AppBlockingPackageInfoWrapper> activityBlacklist = new HashMap<>();
mConfiguredBlacklist = mContext.getString(R.string.activityBlacklist);
if (mConfiguredBlacklist == null) {
if (DBG_POLICY_CHECK) {
Log.d(CarLog.TAG_PACKAGE, "Null blacklist in config");
}
return;
}
Map<String, Set<String>> configBlacklist = parseConfiglist(mConfiguredBlacklist);
if (configBlacklist == null) {
if (DBG_POLICY_CHECK) {
Log.w(CarLog.TAG_PACKAGE, "Black list null. No apps blacklisted");
}
return;
}
for (String pkg : configBlacklist.keySet()) {
if (TextUtils.isEmpty(pkg)) {
// This means there is nothing to blacklist
Log.d(CarLog.TAG_PACKAGE, "Empty string in blacklist pkg");
continue;
}
int flags = 0;
PackageInfo pkgInfo;
String[] activities;
try {
pkgInfo = mPackageManager.getPackageInfo(
pkg, PackageManager.GET_ACTIVITIES
| PackageManager.GET_SIGNING_CERTIFICATES
| PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
} catch (NameNotFoundException e) {
Log.e(CarLog.TAG_PACKAGE, pkg + " not found to blacklist ", e);
continue;
}
if (pkgInfo.applicationInfo.isSystemApp()
|| pkgInfo.applicationInfo.isUpdatedSystemApp()) {
flags |= AppBlockingPackageInfo.FLAG_SYSTEM_APP;
}
Set<String> configActivities = configBlacklist.get(pkg);
if (configActivities.size() == 0) {
// whole package
flags |= AppBlockingPackageInfo.FLAG_WHOLE_ACTIVITY;
activities = getActivitiesInPackage(pkgInfo);
} else {
activities = configActivities.toArray(new String[configActivities.size()]);
}
if (activities == null) {
continue;
}
AppBlockingPackageInfo appBlockingInfo = new AppBlockingPackageInfo(pkg, 0, 0, flags,
pkgInfo.signatures, activities);
AppBlockingPackageInfoWrapper wrapper = new AppBlockingPackageInfoWrapper(
appBlockingInfo, true);
activityBlacklist.put(pkg, wrapper);
}
synchronized (this) {
mActivityBlacklistMap.clear();
mActivityBlacklistMap.putAll(activityBlacklist);
}
}
/**
* Parses the given resource and returns a map of packages and activities.
* Key is package name and value is list of activities. Empty list implies whole package is
* included.
*/
@Nullable
private HashMap<String, Set<String>> parseConfiglist(String configList) {
if (configList == null) {
return null;
}
HashMap<String, Set<String>> packageToActivityMap = new HashMap<>();
String[] entries = configList.split(PACKAGE_DELIMITER);
for (String entry : entries) {
String[] packageActivityPair = entry.split(PACKAGE_ACTIVITY_DELIMITER);
Set<String> 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 whitelisted.
if (newPackage || (activities.size() > 0)) {
activities.add(packageActivityPair[1]);
}
}
}
return packageToActivityMap;
}
@Nullable
private String[] getActivitiesInPackage(PackageInfo info) {
if (info == null || info.activities == null) {
return null;
}
List<String> activityList = new ArrayList<>();
for (ActivityInfo aInfo : info.activities) {
activityList.add(aInfo.name);
}
return activityList.toArray(new String[activityList.size()]);
}
/**
* 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<ResolveInfo> policyInfos = mPackageManager.queryIntentServices(policyIntent, 0);
if (policyInfos == null) { //no need to wait for service binding and retrieval.
mHandler.requestPolicySetting();
return;
}
LinkedList<AppBlockingPolicyProxy> 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;
}
Log.i(CarLog.TAG_PACKAGE, "found policy holding service:" + serviceInfo);
AppBlockingPolicyProxy proxy = new AppBlockingPolicyProxy(this, mContext,
serviceInfo);
proxy.connect();
proxies.add(proxy);
}
}
synchronized (this) {
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) {
boolean shouldSetPolicy = false;
synchronized (this) {
if (mProxies == null) {
proxy.disconnect();
return;
}
mProxies.remove(proxy);
if (mProxies.size() == 0) {
shouldSetPolicy = true;
mProxies = null;
}
}
try {
if (policy != null) {
if (DBG_POLICY_SET) {
Log.i(CarLog.TAG_PACKAGE, "policy setting from policy service:" +
proxy.getPackageName());
}
doSetAppBlockingPolicy(proxy.getPackageName(), policy, 0, false /*setNow*/);
}
} finally {
proxy.disconnect();
if (shouldSetPolicy) {
mHandler.requestPolicySetting();
}
}
}
@Override
public void dump(PrintWriter writer) {
synchronized (this) {
writer.println("*PackageManagementService*");
writer.println("mEnableActivityBlocking:" + mEnableActivityBlocking);
writer.println("mHasParsedPackages:" + mHasParsedPackages);
writer.println("mBootLockedIntentRx:" + mBootLockedIntentRx);
writer.println("ActivityRestricted:" + mUxRestrictionsListener.isRestricted());
writer.println(String.join("\n", mBlockedActivityLogs));
writer.print(dumpPoliciesLocked(true));
}
}
@GuardedBy("this")
private String dumpPoliciesLocked(boolean dumpAll) {
StringBuilder sb = new StringBuilder();
if (dumpAll) {
sb.append("**System white list**\n");
for (AppBlockingPackageInfoWrapper wrapper : mActivityWhitelistMap.values()) {
sb.append(wrapper.toString() + "\n");
}
sb.append("**System Black list**\n");
for (AppBlockingPackageInfoWrapper wrapper : mActivityBlacklistMap.values()) {
sb.append(wrapper.toString() + "\n");
}
}
sb.append("**Client Policies**\n");
for (Entry<String, ClientPolicy> entry : mClientPolicies.entrySet()) {
sb.append("Client:" + entry.getKey() + "\n");
sb.append(" whitelists:\n");
for (AppBlockingPackageInfoWrapper wrapper : entry.getValue().whitelistsMap.values()) {
sb.append(wrapper.toString() + "\n");
}
sb.append(" blacklists:\n");
for (AppBlockingPackageInfoWrapper wrapper : entry.getValue().blacklistsMap.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("**Whitelist string in resource**\n");
sb.append(mConfiguredWhitelist + "\n");
sb.append("**Blacklist string in resource**\n");
sb.append(mConfiguredBlacklist + "\n");
return sb.toString();
}
private void blockTopActivityIfNecessary(TopTaskInfoContainer topTask) {
boolean restricted = mUxRestrictionsListener.isRestricted();
if (!restricted) {
return;
}
doBlockTopActivityIfNotAllowed(topTask);
}
private void doBlockTopActivityIfNotAllowed(TopTaskInfoContainer topTask) {
if (topTask.topActivity == null) {
return;
}
boolean allowed = isActivityDistractionOptimized(
topTask.topActivity.getPackageName(),
topTask.topActivity.getClassName());
if (DBG_POLICY_ENFORCEMENT) {
Log.i(CarLog.TAG_PACKAGE, "new activity:" + topTask.toString() + " allowed:" + allowed);
}
if (allowed) {
return;
}
synchronized (this) {
if (!mEnableActivityBlocking) {
Log.d(CarLog.TAG_PACKAGE, "Current activity " + topTask.topActivity +
" not allowed, blocking disabled. Number of tasks in stack:"
+ topTask.stackInfo.taskIds.length);
return;
}
}
if (DBG_POLICY_CHECK) {
Log.i(CarLog.TAG_PACKAGE, "Current activity " + topTask.topActivity +
" not allowed, will block, number of tasks in stack:" +
topTask.stackInfo.taskIds.length);
}
StringBuilder blockedActivityLog = new StringBuilder();
Intent newActivityIntent = new Intent();
newActivityIntent.setComponent(mActivityBlockingActivity);
newActivityIntent.putExtra(
ActivityBlockingActivity.INTENT_KEY_BLOCKED_ACTIVITY,
topTask.topActivity.flattenToString());
blockedActivityLog.append("Blocked activity ")
.append(topTask.topActivity.flattenToShortString())
.append(". Task id ").append(topTask.taskId);
// If root activity of blocked task is DO, also pass its task id into blocking activity,
// which uses the id to display a button for restarting the blocked task.
for (int i = 0; i < topTask.stackInfo.taskIds.length; i++) {
// topTask.taskId is the task that should be blocked.
if (topTask.stackInfo.taskIds[i] == topTask.taskId) {
// stackInfo represents an ActivityStack. Its fields taskIds and taskNames
// are 1:1 mapped, where taskNames is the name of root activity in this task.
String taskRootActivity = topTask.stackInfo.taskNames[i];
ComponentName rootActivityName = ComponentName.unflattenFromString(
taskRootActivity);
if (isActivityDistractionOptimized(
rootActivityName.getPackageName(), rootActivityName.getClassName())) {
newActivityIntent.putExtra(
ActivityBlockingActivity.EXTRA_BLOCKED_TASK, topTask.taskId);
if (Log.isLoggable(CarLog.TAG_PACKAGE, Log.INFO)) {
Log.i(CarLog.TAG_PACKAGE, "Blocked task " + topTask.taskId
+ " has DO root activity " + taskRootActivity);
}
blockedActivityLog.append(". Root DO activity ")
.append(rootActivityName.flattenToShortString());
}
break;
}
}
addLog(blockedActivityLog.toString());
mSystemActivityMonitoringService.blockActivity(topTask, newActivityIntent);
}
private void blockTopActivitiesIfNecessary() {
boolean restricted = mUxRestrictionsListener.isRestricted();
if (!restricted) {
return;
}
List<TopTaskInfoContainer> topTasks = mSystemActivityMonitoringService.getTopTasks();
for (TopTaskInfoContainer topTask : topTasks) {
doBlockTopActivityIfNotAllowed(topTask);
}
}
/**
* 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 synchronized void setEnableActivityBlocking(boolean enable) {
if (!isDebugBuild()) {
Log.e(CarLog.TAG_PACKAGE, "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);
}
/**
* 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 CarAppMetadataReader.findDistractionOptimizedActivities(mContext, pkgName);
} catch (NameNotFoundException e) {
return null;
}
}
/**
* Append one line of log for dumpsys.
*
* <p>Maintains the size of log by {@link #LOG_SIZE} and appends tag and timestamp to the line.
*/
private void addLog(String log) {
while (mBlockedActivityLogs.size() >= LOG_SIZE) {
mBlockedActivityLogs.remove();
}
StringBuffer sb = new StringBuffer()
.append(CarLog.TAG_PACKAGE).append(':')
.append(DateFormat.format(
"MM-dd HH:mm:ss", System.currentTimeMillis())).append(": ")
.append(log);
mBlockedActivityLogs.add(sb.toString());
}
/**
* Reading policy and setting policy can take time. Run it in a separate handler thread.
*/
private class PackageHandler extends Handler {
private final int MSG_INIT = 0;
private final int MSG_PARSE_PKG = 1;
private final int MSG_SET_POLICY = 2;
private final int MSG_UPDATE_POLICY = 3;
private final int MSG_RELEASE = 4;
private PackageHandler(Looper looper) {
super(looper);
}
private void requestInit() {
Message msg = obtainMessage(MSG_INIT);
sendMessage(msg);
}
private void requestRelease() {
removeMessages(MSG_INIT);
removeMessages(MSG_SET_POLICY);
removeMessages(MSG_UPDATE_POLICY);
Message msg = obtainMessage(MSG_RELEASE);
sendMessage(msg);
}
private void requestPolicySetting() {
Message msg = obtainMessage(MSG_SET_POLICY);
sendMessage(msg);
}
private void requestUpdatingPolicy(String packageName, CarAppBlockingPolicy policy,
int flags) {
Pair<String, CarAppBlockingPolicy> pair = new Pair<>(packageName, policy);
Message msg = obtainMessage(MSG_UPDATE_POLICY, flags, 0, pair);
sendMessage(msg);
}
private void requestParsingInstalledPkgs(long delayMs) {
Message msg = obtainMessage(MSG_PARSE_PKG);
if (delayMs == 0) {
sendMessage(msg);
} else {
sendMessageDelayed(msg, delayMs);
}
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INIT:
doHandleInit();
break;
case MSG_PARSE_PKG:
removeMessages(MSG_PARSE_PKG);
doParseInstalledPackages();
break;
case MSG_SET_POLICY:
doSetPolicy();
break;
case MSG_UPDATE_POLICY:
Pair<String, CarAppBlockingPolicy> pair =
(Pair<String, CarAppBlockingPolicy>) msg.obj;
doUpdatePolicy(pair.first, pair.second, msg.arg1);
break;
case MSG_RELEASE:
doHandleRelease();
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<String, AppBlockingPackageInfoWrapper> whitelistsMap =
new HashMap<>();
private final HashMap<String, AppBlockingPackageInfoWrapper> blacklistsMap =
new HashMap<>();
private void replaceWhitelists(AppBlockingPackageInfoWrapper[] whitelists) {
whitelistsMap.clear();
addToWhitelists(whitelists);
}
private void addToWhitelists(AppBlockingPackageInfoWrapper[] whitelists) {
if (whitelists == null) {
return;
}
for (AppBlockingPackageInfoWrapper wrapper : whitelists) {
if (wrapper != null) {
whitelistsMap.put(wrapper.info.packageName, wrapper);
}
}
}
private void removeWhitelists(AppBlockingPackageInfoWrapper[] whitelists) {
if (whitelists == null) {
return;
}
for (AppBlockingPackageInfoWrapper wrapper : whitelists) {
if (wrapper != null) {
whitelistsMap.remove(wrapper.info.packageName);
}
}
}
private void replaceBlacklists(AppBlockingPackageInfoWrapper[] blacklists) {
blacklistsMap.clear();
addToBlacklists(blacklists);
}
private void addToBlacklists(AppBlockingPackageInfoWrapper[] blacklists) {
if (blacklists == null) {
return;
}
for (AppBlockingPackageInfoWrapper wrapper : blacklists) {
if (wrapper != null) {
blacklistsMap.put(wrapper.info.packageName, wrapper);
}
}
}
private void removeBlacklists(AppBlockingPackageInfoWrapper[] blacklists) {
if (blacklists == null) {
return;
}
for (AppBlockingPackageInfoWrapper wrapper : blacklists) {
if (wrapper != null) {
blacklistsMap.remove(wrapper.info.packageName);
}
}
}
}
private class ActivityLaunchListener
implements SystemActivityMonitoringService.ActivityLaunchListener {
@Override
public void onActivityLaunch(TopTaskInfoContainer topTask) {
blockTopActivityIfNecessary(topTask);
}
}
/**
* 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("this")
@Nullable
private CarUxRestrictions mCurrentUxRestrictions;
private final CarUxRestrictionsManagerService uxRestrictionsService;
public UxRestrictionsListener(CarUxRestrictionsManagerService service) {
uxRestrictionsService = service;
mCurrentUxRestrictions = uxRestrictionsService.getCurrentUxRestrictions();
}
@Override
public void onUxRestrictionsChanged(CarUxRestrictions restrictions) {
if (DBG_POLICY_CHECK) {
Log.d(CarLog.TAG_PACKAGE, "Received uxr restrictions: "
+ restrictions.isRequiresDistractionOptimization()
+ " : " + restrictions.getActiveRestrictions());
}
// We are not handling the restrictions until we know what is allowed and what is not.
// This is to handle some situations, where car service is ready and getting sensor
// data but we haven't received the boot complete intents.
if (!mHasParsedPackages) {
return;
}
synchronized (this) {
mCurrentUxRestrictions = new CarUxRestrictions(restrictions);
}
checkIfTopActivityNeedsBlocking();
}
private void checkIfTopActivityNeedsBlocking() {
boolean shouldCheck = false;
synchronized (this) {
if (mCurrentUxRestrictions != null
&& mCurrentUxRestrictions.isRequiresDistractionOptimization()) {
shouldCheck = true;
}
}
if (DBG_POLICY_CHECK) {
Log.d(CarLog.TAG_PACKAGE, "block?: " + shouldCheck);
}
if (shouldCheck) {
blockTopActivitiesIfNecessary();
}
}
private synchronized boolean isRestricted() {
// if current restrictions is null, try querying the service, once.
if (mCurrentUxRestrictions == null) {
mCurrentUxRestrictions = uxRestrictionsService.getCurrentUxRestrictions();
}
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;
}
}
/**
* Listens to the Boot intent to initiate parsing installed packages.
*/
private class BootEventReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null || intent.getAction() == null) {
return;
}
if (DBG_POLICY_CHECK) {
Log.d(CarLog.TAG_PACKAGE, "BootEventReceiver Received " + intent.getAction());
}
if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(intent.getAction())) {
mHandler.requestParsingInstalledPkgs(0);
mBootLockedIntentRx = true;
}
}
}
/**
* Listens to the package install/uninstall events to know when to initiate parsing
* installed packages.
*/
private class PackageParsingEventReceiver extends BroadcastReceiver {
private static final long PACKAGE_PARSING_DELAY_MS = 500;
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null || intent.getAction() == null) {
return;
}
if (DBG_POLICY_CHECK) {
Log.d(CarLog.TAG_PACKAGE,
"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);
mHandler.requestParsingInstalledPkgs(PACKAGE_PARSING_DELAY_MS);
}
}
private boolean isPackageManagerAction(String action) {
return mPackageManagerActions.indexOf(action) != -1;
}
/**
* Convenience log function to log what changed. Logs only when more debug logs
* are needed - DBG_POLICY_CHECK needs to be true
*/
private void logEventChange(Intent intent) {
if (!DBG_POLICY_CHECK || intent == null) {
return;
}
String packageName = intent.getData().getSchemeSpecificPart();
Log.d(CarLog.TAG_PACKAGE, "Pkg Changed:" + packageName);
String action = intent.getAction();
if (action == null) {
return;
}
if (action.equals(Intent.ACTION_PACKAGE_CHANGED)) {
Log.d(CarLog.TAG_PACKAGE, "Changed components");
String[] cc = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
if (cc != null) {
for (String c : cc) {
Log.d(CarLog.TAG_PACKAGE, c);
}
}
} else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)
|| action.equals(Intent.ACTION_PACKAGE_ADDED)) {
Log.d(CarLog.TAG_PACKAGE, action + " Replacing?: " + intent.getBooleanExtra(
Intent.EXTRA_REPLACING, false));
}
}
}
}