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);
+                }
+            }
+        }
+    }
+}