add app blocking api
- launches CarAppBlockingPolicyService and read Policy on startup
- also provides setAppBlockingPolicy API
- not working fully due to missng framework api but only sets internal
state for now.
- also fixed some build warnings
- also renamed all permisisons to android.support.car
- remaining TODO:
monitor app install / uninstall and re-activate policy
plumb framework side policy setting
bug: 25261583
Change-Id: I6da09468c95a28f2e3d7d59e6e91279ca018239b
diff --git a/service/src/com/android/car/CarLog.java b/service/src/com/android/car/CarLog.java
index fd5e015..143a77f 100644
--- a/service/src/com/android/car/CarLog.java
+++ b/service/src/com/android/car/CarLog.java
@@ -22,6 +22,7 @@
public static final String TAG_HAL = "CAR.HAL";
public static final String TAG_HVAC = "CAR.HVAC";
public static final String TAG_INFO = "CAR.INFO";
+ public static final String TAG_PACKAGE = "CAR.PACKAGE";
public static final String TAG_POWER = "CAR.POWER";
public static final String TAG_RADIO = "CAR.RADIO";
public static final String TAG_SENSOR = "CAR.SENSOR";
diff --git a/service/src/com/android/car/CarPackageManagerService.java b/service/src/com/android/car/CarPackageManagerService.java
deleted file mode 100644
index 45ccc24..0000000
--- a/service/src/com/android/car/CarPackageManagerService.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.support.car.content.pm.CarPackageManager;
-import android.support.car.content.pm.ICarPackageManager;
-
-import java.io.PrintWriter;
-
-public class CarPackageManagerService extends ICarPackageManager.Stub implements CarServiceBase {
- private static final int VERSION = 1;
-
- public CarPackageManagerService(Context context) {
- //TODO
- }
-
- @Override
- public int getVersion() {
- return VERSION;
- }
-
- @Override
- public void init() {
- //TODO
- }
-
- @Override
- public void release() {
- //TODO
- }
-
- @Override
- public void dump(PrintWriter writer) {
- //TODO
- }
-}
diff --git a/service/src/com/android/car/CarServiceUtils.java b/service/src/com/android/car/CarServiceUtils.java
index 7344af5..3070ff9 100644
--- a/service/src/com/android/car/CarServiceUtils.java
+++ b/service/src/com/android/car/CarServiceUtils.java
@@ -16,7 +16,44 @@
package com.android.car;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Binder;
+import android.util.Log;
+
/** Utility class */
public class CarServiceUtils {
+ private static final String PACKAGE_NOT_FOUND = "Package not found:";
+
+ /**
+ * Check if package name passed belongs to UID for the current binder call.
+ * @param context
+ * @param packageName
+ */
+ public static void assertPakcageName(Context context, String packageName)
+ throws IllegalArgumentException, SecurityException {
+ if (packageName == null) {
+ throw new IllegalArgumentException("Package name null");
+ }
+ ApplicationInfo appInfo = null;
+ try {
+ appInfo = context.getPackageManager().getApplicationInfo(packageName,
+ 0);
+ } catch (NameNotFoundException e) {
+ String msg = PACKAGE_NOT_FOUND + packageName;
+ Log.w(CarLog.TAG_SERVICE, msg, e);
+ throw new SecurityException(msg, e);
+ }
+ if (appInfo == null) {
+ throw new SecurityException(PACKAGE_NOT_FOUND + packageName);
+ }
+ int uid = Binder.getCallingUid();
+ if (uid != appInfo.uid) {
+ throw new SecurityException("Wrong package name:" + packageName +
+ ", The package does not belong to caller's uid:" + uid);
+ }
+ }
}
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index abea6f7..6a478ed 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -29,6 +29,7 @@
import com.android.car.CarSystem;
import com.android.car.hal.VehicleHal;
+import com.android.car.pm.CarPackageManagerService;
import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
diff --git a/service/src/com/android/car/pm/AppBlockingPolicyProxy.java b/service/src/com/android/car/pm/AppBlockingPolicyProxy.java
new file mode 100644
index 0000000..b9ea0f8
--- /dev/null
+++ b/service/src/com/android/car/pm/AppBlockingPolicyProxy.java
@@ -0,0 +1,168 @@
+/*
+ * 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.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ServiceInfo;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.support.car.content.pm.CarAppBlockingPolicy;
+import android.support.car.content.pm.ICarAppBlockingPolicy;
+import android.support.car.content.pm.ICarAppBlockingPolicySetter;
+import android.util.Log;
+
+import com.android.car.CarLog;
+import com.android.internal.annotations.GuardedBy;
+
+public class AppBlockingPolicyProxy implements ServiceConnection {
+
+ private final CarPackageManagerService mService;
+ private final Context mContext;
+ private final ServiceInfo mServiceInfo;
+ private final ICarAppBlockingPolicySetterImpl mSetter;
+
+ @GuardedBy("this")
+ private ICarAppBlockingPolicy mPolicyService = null;
+
+ /**
+ * policy not set within this time after binding will be treated as failure and will be
+ * ignored.
+ */
+ private static final long TIMEOUT_MS = 5000;
+ private static final int MAX_CRASH_RETRY = 2;
+ @GuardedBy("this")
+ private int mCrashCount = 0;
+ @GuardedBy("this")
+ private boolean mBound = false;
+
+ private final Handler mHandler;
+ private final Runnable mTimeoutRunnable = new Runnable() {
+ @Override
+ public void run() {
+ Log.w(CarLog.TAG_PACKAGE, "Timeout for policy setting for service:" + mServiceInfo);
+ disconnect();
+ mService.onPolicyConnectionFailure(AppBlockingPolicyProxy.this);
+ }
+ };
+
+ public AppBlockingPolicyProxy(CarPackageManagerService service, Context context,
+ ServiceInfo serviceInfo) {
+ mService = service;
+ mContext = context;
+ mServiceInfo = serviceInfo;
+ mSetter = new ICarAppBlockingPolicySetterImpl();
+ mHandler = new Handler(mService.getLooper());
+ }
+
+ public String getPackageName() {
+ return mServiceInfo.packageName;
+ }
+
+ public void connect() {
+ Intent intent = new Intent();
+ intent.setClassName(mServiceInfo.packageName, mServiceInfo.name);
+ mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT,
+ UserHandle.SYSTEM);
+ synchronized (this) {
+ mBound = true;
+ }
+ mHandler.postDelayed(mTimeoutRunnable, TIMEOUT_MS);
+ }
+
+ public void disconnect() {
+ synchronized (this) {
+ if (!mBound) {
+ return;
+ }
+ mBound = false;
+ mPolicyService = null;
+ }
+ mHandler.removeCallbacks(mTimeoutRunnable);
+ try {
+ mContext.unbindService(this);
+ } catch (IllegalArgumentException e) {
+ Log.w(CarLog.TAG_PACKAGE, "unbind", e);
+ }
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ ICarAppBlockingPolicy policy = null;
+ boolean failed = false;
+ synchronized (this) {
+ mPolicyService = ICarAppBlockingPolicy.Stub.asInterface(service);
+ policy = mPolicyService;
+ if (policy == null) {
+ failed = true;
+ }
+ }
+ if (failed) {
+ Log.w(CarLog.TAG_PACKAGE, "Policy service connected with null binder:" + name);
+ mService.onPolicyConnectionFailure(this);
+ return;
+ }
+ try {
+ mPolicyService.setAppBlockingPolicySetter(mSetter);
+ } catch (RemoteException e) {
+ // let retry handle this
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ boolean failed = false;
+ synchronized (this) {
+ mCrashCount++;
+ if (mCrashCount > MAX_CRASH_RETRY) {
+ mPolicyService = null;
+ failed = true;
+ }
+ }
+ if (failed) {
+ Log.w(CarLog.TAG_PACKAGE, "Policy service keep crashing, giving up:" + name);
+ mService.onPolicyConnectionFailure(this);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "AppBlockingPolicyProxy [mServiceInfo=" + mServiceInfo + ", mCrashCount="
+ + mCrashCount + "]";
+ }
+
+ private class ICarAppBlockingPolicySetterImpl extends ICarAppBlockingPolicySetter.Stub {
+ private static final int VERSION = 1;
+ @Override
+ public int getVersion() {
+ return VERSION;
+ }
+
+ @Override
+ public void setAppBlockingPolicy(CarAppBlockingPolicy policy) {
+ mHandler.removeCallbacks(mTimeoutRunnable);
+ if (policy == null) {
+ Log.w(CarLog.TAG_PACKAGE, "setAppBlockingPolicy null policy from policy service:" +
+ mServiceInfo);
+ }
+ mService.onPolicyConnectionAndSet(AppBlockingPolicyProxy.this, policy);
+ }
+ }
+}
diff --git a/service/src/com/android/car/pm/CarAppMetadataReader.java b/service/src/com/android/car/pm/CarAppMetadataReader.java
new file mode 100644
index 0000000..b9973e2
--- /dev/null
+++ b/service/src/com/android/car/pm/CarAppMetadataReader.java
@@ -0,0 +1,151 @@
+/*
+ * 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.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.util.Log;
+
+import com.android.car.CarLog;
+
+import java.io.IOException;
+import java.util.LinkedList;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+/**
+ * Read meta data containing car application info.
+ */
+public class CarAppMetadataReader {
+ /** Name of the meta-data attribute for the automotive application XML resource */
+ private static final String METADATA_ATTRIBUTE = "android.support.car.application";
+ /** Name of the tag to declare automotive usage */
+ private static final String USES_TAG = "uses";
+ /** Name of the attribute to name the usage type */
+ private static final String NAME_ATTRIBUTE = "name";
+
+ private static final String NAME_ATTR_TYPE_SERVICE = "service";
+ private static final String NAME_ATTR_TYPE_ACTIVITY = "activity";
+
+ private static final String CLASS_ATTRIBUTE = "class";
+
+ public static CarAppMetadataInfo parseMetadata(Context context, String packageName) {
+ int metadataId = 0;
+ Resources resources = null;
+ try {
+ metadataId = getMetadataId(context, packageName);
+ if (metadataId == 0) {
+ return null;
+ }
+ PackageManager pm = context.getPackageManager();
+ resources = pm.getResourcesForApplication(packageName);
+ } catch (NameNotFoundException e) {
+ Log.w(CarLog.TAG_PACKAGE, "Cannot read mta data, package:" + packageName, e);
+ return null;
+ }
+
+ // Try to open the XML resource
+ XmlResourceParser parser = null;
+ try {
+ parser = resources.getXml(metadataId);
+ } catch (Resources.NotFoundException e) {
+ Log.w(CarLog.TAG_PACKAGE, "Resource not found [" + packageName + "]");
+ return null;
+ }
+
+ boolean useService = false;
+ boolean useAllActivities = false;
+ LinkedList<String> activities = null;
+ // Now, read the XML and pull out the appropriate "uses" attribute
+ int eventType;
+ try {
+ eventType = parser.next();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG && USES_TAG.equals(parser.getName())) {
+ String nameAttribute =
+ parser.getAttributeValue(null /*namespace*/, NAME_ATTRIBUTE);
+ if (nameAttribute == null) {
+ Log.w(CarLog.TAG_PACKAGE, "Bad metadata," + METADATA_ATTRIBUTE +
+ " for pavkage:" + packageName);
+ return null;
+ }
+ switch (nameAttribute) {
+ case NAME_ATTR_TYPE_SERVICE:
+ useService = true;
+ break;
+ case NAME_ATTR_TYPE_ACTIVITY: {
+ String classAttribute = parser.getAttributeValue(null /*namespace*/,
+ CLASS_ATTRIBUTE);
+ if (classAttribute == null) { // all activities
+ useAllActivities = true;
+ } else {
+ if (activities == null) {
+ activities = new LinkedList<>();
+ }
+ activities.add(classAttribute);
+ }
+ } break;
+ }
+ }
+ eventType = parser.next();
+ }
+ } catch (XmlPullParserException | IOException e) {
+ Log.w(CarLog.TAG_PACKAGE, "Resource not parsable [" + packageName + "]");
+ return null;
+ }
+ return new CarAppMetadataInfo(useService, useAllActivities, (activities == null)? null :
+ (String[]) activities.toArray());
+ }
+
+ /**
+ * Returns the resource ID of the automotive application XML
+ * @throws NameNotFoundException
+ */
+ private static int getMetadataId(Context context, String packageName)
+ throws NameNotFoundException {
+ final PackageManager pm = context.getPackageManager();
+ ApplicationInfo appInfo;
+ appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA /*flags*/);
+ if (appInfo.metaData == null) {
+ return 0;
+ }
+ return appInfo.metaData.getInt(METADATA_ATTRIBUTE, 0);
+ }
+
+ /**
+ * App's metada for car application
+ */
+ public static class CarAppMetadataInfo {
+ /** has "service" use */
+ public final boolean useService;
+ /** has "activity" use with no specific class */
+ public final boolean useAllActivities;
+ /** has "activity" uses with specific classes */
+ public String[] activities;
+
+ private CarAppMetadataInfo(boolean useService, boolean useAllActivities,
+ String[] activities) {
+ this.useService = useService;
+ this.useAllActivities = useAllActivities;
+ this.activities = activities;
+ }
+ }
+}
diff --git a/service/src/com/android/car/pm/CarPackageManagerService.java b/service/src/com/android/car/pm/CarPackageManagerService.java
new file mode 100644
index 0000000..e6aa991
--- /dev/null
+++ b/service/src/com/android/car/pm/CarPackageManagerService.java
@@ -0,0 +1,654 @@
+/*
+ * 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.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.Signature;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.support.car.Car;
+import android.support.car.content.pm.AppBlockingPackageInfo;
+import android.support.car.content.pm.CarAppBlockingPolicy;
+import android.support.car.content.pm.CarAppBlockingPolicyService;
+import android.support.car.content.pm.CarPackageManager;
+import android.support.car.content.pm.ICarPackageManager;
+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.pm.CarAppMetadataReader.CarAppMetadataInfo;
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map.Entry;
+
+//TODO monitor app installing and refresh policy
+
+public class CarPackageManagerService extends ICarPackageManager.Stub implements CarServiceBase {
+ private static final int VERSION = 1;
+ static final boolean DBG_POLICY_SET = true;
+ static final boolean DBG_POLICY_CHECK = false;
+
+ private final Context mContext;
+ private final PackageManager mPackageManager;
+
+ private final HandlerThread mHandlerThread;
+ private final PackageHandler mHandler;
+
+ /**
+ * 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> mSystemWhitelists = new HashMap<>();
+ @GuardedBy("this")
+ private LinkedList<AppBlockingPolicyProxy> mProxies;
+ @GuardedBy("this")
+ private boolean mReleased = true;
+
+ @GuardedBy("this")
+ private final LinkedList<CarAppBlockingPolicy> mWaitingPolicies = new LinkedList<>();
+
+ public CarPackageManagerService(Context context) {
+ mContext = context;
+ mPackageManager = mContext.getPackageManager();
+ mHandlerThread = new HandlerThread(CarLog.TAG_PACKAGE);
+ mHandlerThread.start();
+ mHandler = new PackageHandler(mHandlerThread.getLooper());
+ }
+
+ @Override
+ public int getVersion() {
+ return VERSION;
+ }
+
+ @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*/);
+ }
+
+ 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.assertPakcageName(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 isActivityAllowedWhileDriving(String packageName, String className) {
+ assertPackageAndClassName(packageName, className);
+ synchronized (this) {
+ if (DBG_POLICY_CHECK) {
+ Log.i(CarLog.TAG_PACKAGE, "isActivityAllowedWhileDriving" +
+ dumpPoliciesLocked(false));
+ }
+ AppBlockingPackageInfo info = searchFromBlacklistsLocked(packageName);
+ if (info != null) {
+ return false;
+ }
+ return isActivityInWhitelistsLocked(packageName, className);
+ }
+ }
+
+ @Override
+ public boolean isServiceAllowedWhileDriving(String packageName, String className) {
+ if (packageName == null) {
+ throw new IllegalArgumentException("Package name null");
+ }
+ synchronized (this) {
+ if (DBG_POLICY_CHECK) {
+ Log.i(CarLog.TAG_PACKAGE, "isServiceAllowedWhileDriving" +
+ dumpPoliciesLocked(false));
+ }
+ AppBlockingPackageInfo info = searchFromBlacklistsLocked(packageName);
+ if (info != null) {
+ return false;
+ }
+ info = searchFromWhitelistsLocked(packageName);
+ if (info != null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ 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");
+ }
+ }
+
+ private AppBlockingPackageInfo searchFromBlacklistsLocked(String packageName) {
+ for (ClientPolicy policy : mClientPolicies.values()) {
+ AppBlockingPackageInfoWrapper wrapper = policy.blacklistsMap.get(packageName);
+ if (wrapper != null && wrapper.isMatching) {
+ return wrapper.info;
+ }
+ }
+ return null;
+ }
+
+ 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 = mSystemWhitelists.get(packageName);
+ return (wrapper != null) ? wrapper.info : null;
+ }
+
+ private boolean isActivityInWhitelistsLocked(String packageName, String className) {
+ for (ClientPolicy policy : mClientPolicies.values()) {
+ if (isActivityInMapAndMatching(policy.whitelistsMap, packageName, className)) {
+ return true;
+ }
+ }
+ return isActivityInMapAndMatching(mSystemWhitelists, packageName, className);
+ }
+
+ private boolean isActivityInMapAndMatching(HashMap<String, AppBlockingPackageInfoWrapper> map,
+ String packageName, String className) {
+ AppBlockingPackageInfoWrapper wrapper = map.get(packageName);
+ if (wrapper == null || !wrapper.isMatching) {
+ return false;
+ }
+ return wrapper.info.isActivityCovered(className);
+ }
+
+ @Override
+ public void init() {
+ synchronized (this) {
+ mReleased = false;
+ mHandler.requestInit();
+ }
+ }
+
+ @Override
+ public void release() {
+ synchronized (this) {
+ mReleased = true;
+ mHandler.requestRelease();
+ mSystemWhitelists.clear();
+ mClientPolicies.clear();
+ if (mProxies != null) {
+ for (AppBlockingPolicyProxy proxy : mProxies) {
+ proxy.disconnect();
+ }
+ mProxies.clear();
+ }
+ wakeupClientsWaitingForPolicySetitngLocked();
+ }
+ }
+
+ // run from HandlerThread
+ private void doHandleInit() {
+ startAppBlockingPolicies();
+ generateSystemWhitelists();
+ }
+
+ private void wakeupClientsWaitingForPolicySetitngLocked() {
+ for (CarAppBlockingPolicy waitingPolicy : mWaitingPolicies) {
+ synchronized (waitingPolicy) {
+ waitingPolicy.notifyAll();
+ }
+ }
+ mWaitingPolicies.clear();
+ }
+
+ private void doSetPolicy() {
+ //TODO should set policy to AMS
+ // waiting for framework API to be ready
+ synchronized (this) {
+ wakeupClientsWaitingForPolicySetitngLocked();
+ }
+ }
+
+ 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));
+ }
+ }
+ }
+
+ 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;
+ }
+
+ private void generateSystemWhitelists() {
+ HashMap<String, AppBlockingPackageInfoWrapper> systemWhitelists = new HashMap<>();
+ // trust all system apps for services and trust all activities with car app meta-data.
+ List<PackageInfo> packages = mPackageManager.getInstalledPackages(0);
+ for (PackageInfo info : packages) {
+ if (info.applicationInfo != null && (info.applicationInfo.isSystemApp() ||
+ info.applicationInfo.isUpdatedSystemApp())) {
+ CarAppMetadataInfo metadataInfo = CarAppMetadataReader.parseMetadata(mContext,
+ info.packageName);
+ if (metadataInfo == null) {
+ AppBlockingPackageInfo appBlockingInfo = new AppBlockingPackageInfo(
+ info.packageName, 0, 0, AppBlockingPackageInfo.FLAG_SYSTEM_APP, null,
+ null);
+ AppBlockingPackageInfoWrapper wrapper = new AppBlockingPackageInfoWrapper(
+ appBlockingInfo, true);
+ systemWhitelists.put(info.packageName, wrapper);
+ } else {
+ int flags = AppBlockingPackageInfo.FLAG_SYSTEM_APP;
+ if (metadataInfo.useAllActivities) {
+ flags |= AppBlockingPackageInfo.FLAG_WHOLE_ACTIVITY;
+ }
+ AppBlockingPackageInfo appBlockingInfo = new AppBlockingPackageInfo(
+ info.packageName, 0, 0, flags, null,
+ metadataInfo.useAllActivities ? null : metadataInfo.activities);
+ }
+ }
+ }
+ synchronized (this) {
+ mSystemWhitelists.putAll(systemWhitelists);
+ }
+ }
+
+ private 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.print(dumpPoliciesLocked(true));
+ }
+ }
+
+ private String dumpPoliciesLocked(boolean dumpAll) {
+ StringBuilder sb = new StringBuilder();
+ if (dumpAll) {
+ sb.append("**System white list**\n");
+ for (AppBlockingPackageInfoWrapper wrapper : mSystemWhitelists.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");
+ }
+ }
+ return 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_SET_POLICY = 1;
+ private final int MSG_UPDATE_POLICY = 2;
+
+ 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);
+ }
+
+ 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);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_INIT:
+ doHandleInit();
+ 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;
+ }
+ }
+ }
+
+ 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);
+ }
+ }
+ }
+ }
+}