Merge "Persistent connection to DO/PO service." into oc-dev
diff --git a/Android.mk b/Android.mk
index eb649c9..634272b 100644
--- a/Android.mk
+++ b/Android.mk
@@ -102,6 +102,7 @@
 	core/java/android/app/IUserSwitchObserver.aidl \
 	core/java/android/app/IWallpaperManager.aidl \
 	core/java/android/app/IWallpaperManagerCallback.aidl \
+	core/java/android/app/admin/IDeviceAdminService.aidl \
 	core/java/android/app/admin/IDevicePolicyManager.aidl \
 	core/java/android/app/trust/IStrongAuthTracker.aidl \
 	core/java/android/app/trust/ITrustManager.aidl \
diff --git a/api/current.txt b/api/current.txt
index d9f6a6a..bf35894 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6232,6 +6232,11 @@
     field public static final java.lang.String EXTRA_LOCK_TASK_PACKAGE = "android.app.extra.LOCK_TASK_PACKAGE";
   }
 
+  public class DeviceAdminService extends android.app.Service {
+    ctor public DeviceAdminService();
+    method public final android.os.IBinder onBind(android.content.Intent);
+  }
+
   public class DevicePolicyManager {
     method public void addCrossProfileIntentFilter(android.content.ComponentName, android.content.IntentFilter, int);
     method public boolean addCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String);
@@ -6398,6 +6403,7 @@
     method public void wipeData(int);
     field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
     field public static final java.lang.String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
+    field public static final java.lang.String ACTION_DEVICE_ADMIN_SERVICE = "android.app.action.DEVICE_ADMIN_SERVICE";
     field public static final java.lang.String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED";
     field public static final java.lang.String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED";
     field public static final java.lang.String ACTION_PROVISIONING_SUCCESSFUL = "android.app.action.PROVISIONING_SUCCESSFUL";
diff --git a/api/system-current.txt b/api/system-current.txt
index 1c8325c..ec6c609 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6435,6 +6435,11 @@
     field public static final java.lang.String EXTRA_LOCK_TASK_PACKAGE = "android.app.extra.LOCK_TASK_PACKAGE";
   }
 
+  public class DeviceAdminService extends android.app.Service {
+    ctor public DeviceAdminService();
+    method public final android.os.IBinder onBind(android.content.Intent);
+  }
+
   public class DevicePolicyManager {
     method public void addCrossProfileIntentFilter(android.content.ComponentName, android.content.IntentFilter, int);
     method public boolean addCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String);
@@ -6622,6 +6627,7 @@
     field public static final java.lang.String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
     field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
     field public static final java.lang.String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
+    field public static final java.lang.String ACTION_DEVICE_ADMIN_SERVICE = "android.app.action.DEVICE_ADMIN_SERVICE";
     field public static final java.lang.String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED";
     field public static final java.lang.String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED";
     field public static final java.lang.String ACTION_PROVISIONING_SUCCESSFUL = "android.app.action.PROVISIONING_SUCCESSFUL";
diff --git a/api/test-current.txt b/api/test-current.txt
index 98db0a4..421cccf 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6251,6 +6251,11 @@
     field public static final java.lang.String EXTRA_LOCK_TASK_PACKAGE = "android.app.extra.LOCK_TASK_PACKAGE";
   }
 
+  public class DeviceAdminService extends android.app.Service {
+    ctor public DeviceAdminService();
+    method public final android.os.IBinder onBind(android.content.Intent);
+  }
+
   public class DevicePolicyManager {
     method public void addCrossProfileIntentFilter(android.content.ComponentName, android.content.IntentFilter, int);
     method public boolean addCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String);
@@ -6426,6 +6431,7 @@
     field public static final java.lang.String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
     field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
     field public static final java.lang.String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
+    field public static final java.lang.String ACTION_DEVICE_ADMIN_SERVICE = "android.app.action.DEVICE_ADMIN_SERVICE";
     field public static final java.lang.String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED";
     field public static final java.lang.String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED";
     field public static final java.lang.String ACTION_PROVISIONING_SUCCESSFUL = "android.app.action.PROVISIONING_SUCCESSFUL";
diff --git a/core/java/android/app/admin/DeviceAdminService.java b/core/java/android/app/admin/DeviceAdminService.java
new file mode 100644
index 0000000..cd0b1bf
--- /dev/null
+++ b/core/java/android/app/admin/DeviceAdminService.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 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 android.app.admin;
+
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * Base class for a service that device owner/profile owners can optionally have.
+ *
+ * <p>The system searches for it with an intent filter with the
+ * {@link DevicePolicyManager#ACTION_DEVICE_ADMIN_SERVICE} action, and tries to keep a bound
+ * connection as long as the hosting user is running, so that the device/profile owner is always
+ * considered to be in the foreground.
+ *
+ * <p>Device/profile owners can use
+ * {@link android.content.pm.PackageManager#setComponentEnabledSetting(ComponentName, int, int)}
+ * to disable/enable its own service.  For example, when a device/profile owner no longer needs
+ * to be in the foreground, it can (and should) disable its service.
+ *
+ * <p>The service must not be exported.
+ *
+ * <p>TODO: Describe how the system handles crashes in DO/PO.
+ */
+public class DeviceAdminService extends Service {
+    private final IDeviceAdminServiceImpl mImpl;
+
+    public DeviceAdminService() {
+        mImpl = new IDeviceAdminServiceImpl();
+    }
+
+    @Override
+    public final IBinder onBind(Intent intent) {
+        return mImpl.asBinder();
+    }
+
+    private class IDeviceAdminServiceImpl extends IDeviceAdminService.Stub {
+    }
+
+    // So far, we have no methods in this class.
+}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 6d8d5e9..2f0a630 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1515,6 +1515,16 @@
     public @interface ProvisioningPreCondition {}
 
     /**
+     * Service action: Action for a service that device owner and profile owner can optionally
+     * own.  If a device owner or a profile owner has such a service, the system tries to keep
+     * a bound connection to it, in order to keep their process always running.
+     * The service must not be exported.
+     */
+    @SdkConstant(SdkConstantType.SERVICE_ACTION)
+    public static final String ACTION_DEVICE_ADMIN_SERVICE
+            = "android.app.action.DEVICE_ADMIN_SERVICE";
+
+    /**
      * Return true if the given administrator component is currently active (enabled) in the system.
      *
      * @param admin The administrator component to check for.
diff --git a/core/java/android/app/admin/IDeviceAdminService.aidl b/core/java/android/app/admin/IDeviceAdminService.aidl
new file mode 100644
index 0000000..5276ed5
--- /dev/null
+++ b/core/java/android/app/admin/IDeviceAdminService.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 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 android.app.admin;
+
+/**
+ * @hide
+ */
+interface IDeviceAdminService {
+}
diff --git a/services/core/java/com/android/server/am/PersistentConnection.java b/services/core/java/com/android/server/am/PersistentConnection.java
new file mode 100644
index 0000000..c34c097
--- /dev/null
+++ b/services/core/java/com/android/server/am/PersistentConnection.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2017 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.server.am;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+
+/**
+ * Connects to a given service component on a given user.
+ *
+ * - Call {@link #connect()} to create a connection.
+ * - Call {@link #disconnect()} to disconnect.  Make sure to disconnect when the user stops.
+ *
+ * Add onConnected/onDisconnected callbacks as needed.
+ */
+public abstract class PersistentConnection<T> {
+    private final Object mLock = new Object();
+
+    private final String mTag;
+    private final Context mContext;
+    private final Handler mHandler;
+    private final int mUserId;
+    private final ComponentName mComponentName;
+
+    @GuardedBy("mLock")
+    private boolean mStarted;
+
+    @GuardedBy("mLock")
+    private boolean mIsConnected;
+
+    @GuardedBy("mLock")
+    private T mService;
+
+    private final ServiceConnection mServiceConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            synchronized (mLock) {
+                Slog.i(mTag, "Connected: " + mComponentName.flattenToShortString()
+                        + " u" + mUserId);
+
+                mIsConnected = true;
+                mService = asInterface(service);
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            synchronized (mLock) {
+                Slog.i(mTag, "Disconnected: " + mComponentName.flattenToShortString()
+                        + " u" + mUserId);
+
+                cleanUpConnectionLocked();
+            }
+        }
+    };
+
+    public PersistentConnection(@NonNull String tag, @NonNull Context context,
+            @NonNull Handler handler, int userId, @NonNull ComponentName componentName) {
+        mTag = tag;
+        mContext = context;
+        mHandler = handler;
+        mUserId = userId;
+        mComponentName = componentName;
+    }
+
+    public final ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    /**
+     * @return whether connected.
+     */
+    public final boolean isConnected() {
+        synchronized (mLock) {
+            return mIsConnected;
+        }
+    }
+
+    /**
+     * @return the service binder interface.
+     */
+    public final T getServiceBinder() {
+        synchronized (mLock) {
+            return mService;
+        }
+    }
+
+    /**
+     * Connects to the service.
+     */
+    public final void connect() {
+        synchronized (mLock) {
+            if (mStarted) {
+                return;
+            }
+            mStarted = true;
+
+            final Intent service = new Intent().setComponent(mComponentName);
+
+            final boolean success = mContext.bindServiceAsUser(service, mServiceConnection,
+                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+                    mHandler, UserHandle.of(mUserId));
+
+            if (!success) {
+                Slog.e(mTag, "Binding: " + service.getComponent() + " u" + mUserId
+                        + " failed.");
+            }
+        }
+    }
+
+    private void cleanUpConnectionLocked() {
+        mIsConnected = false;
+        mService = null;
+    }
+
+    /**
+     * Disconnect from the service.
+     */
+    public final void disconnect() {
+        synchronized (mLock) {
+            if (!mStarted) {
+                return;
+            }
+            Slog.i(mTag, "Stopping: " + mComponentName.flattenToShortString() + " u" + mUserId);
+            mStarted = false;
+            mContext.unbindService(mServiceConnection);
+
+            cleanUpConnectionLocked();
+        }
+    }
+
+    /** Must be implemented by a subclass to convert an {@link IBinder} to a stub. */
+    protected abstract T asInterface(IBinder binder);
+
+    public void dump(String prefix, PrintWriter pw) {
+        synchronized (mLock) {
+            pw.print(prefix);
+            pw.print(mComponentName.flattenToShortString());
+            pw.print(mStarted ? "  [started]" : "  [not started]");
+            pw.print(mIsConnected ? "  [connected]" : "  [not connected]");
+            pw.println();
+        }
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java
new file mode 100644
index 0000000..97fa9d5
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2017 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.server.devicepolicy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.IDeviceAdminService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BackgroundThread;
+import com.android.server.am.PersistentConnection;
+
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Manages connections to persistent services in owner packages.
+ */
+public class DeviceAdminServiceController {
+    static final String TAG = DevicePolicyManagerService.LOG_TAG;
+
+    static final boolean DEBUG = false; // DO NOT MERGE WITH TRUE.
+
+    final Object mLock = new Object();
+    final Context mContext;
+
+    private final DevicePolicyManagerService mService;
+    private final DevicePolicyManagerService.Injector mInjector;
+
+    private final Handler mHandler; // needed?
+
+    static void debug(String format, Object... args) {
+        if (!DEBUG) {
+            return;
+        }
+        Slog.d(TAG, String.format(format, args));
+    }
+
+    private class DevicePolicyServiceConnection
+            extends PersistentConnection<IDeviceAdminService> {
+        public DevicePolicyServiceConnection(int userId, @NonNull ComponentName componentName) {
+            super(TAG, mContext, mHandler, userId, componentName);
+        }
+
+        @Override
+        protected IDeviceAdminService asInterface(IBinder binder) {
+            return IDeviceAdminService.Stub.asInterface(binder);
+        }
+    }
+
+    /**
+     * User-ID -> {@link PersistentConnection}.
+     */
+    @GuardedBy("mLock")
+    private final SparseArray<DevicePolicyServiceConnection> mConnections = new SparseArray<>();
+
+    public DeviceAdminServiceController(DevicePolicyManagerService service) {
+        mService = service;
+        mInjector = service.mInjector;
+        mContext = mInjector.mContext;
+        mHandler = new Handler(BackgroundThread.get().getLooper());
+    }
+
+    /**
+     * Find a service that handles {@link DevicePolicyManager#ACTION_DEVICE_ADMIN_SERVICE}
+     * in a given package.
+     */
+    @Nullable
+    private ServiceInfo findService(@NonNull String packageName, int userId) {
+        final Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_ADMIN_SERVICE);
+        intent.setPackage(packageName);
+
+        try {
+            final ParceledListSlice<ResolveInfo> pls = mInjector.getIPackageManager()
+                    .queryIntentServices(intent, null, /* flags=*/ 0, userId);
+            if (pls == null) {
+                return null;
+            }
+            final List<ResolveInfo> list = pls.getList();
+            if (list.size() == 0) {
+                return null;
+            }
+            // Note if multiple services are found, that's an error, even if only one of them
+            // is exported.
+            if (list.size() > 1) {
+                Log.e(TAG, "More than one DeviceAdminService's found in package "
+                        + packageName
+                        + ".  They'll all be ignored.");
+                return null;
+            }
+            final ServiceInfo si = list.get(0).serviceInfo;
+            if (si.exported) {
+                Log.e(TAG, "DeviceAdminService must not be exported: '"
+                        + si.getComponentName().flattenToShortString()
+                        + "' will be ignored.");
+                return null;
+            }
+            return si;
+        } catch (RemoteException e) {
+        }
+        return null;
+    }
+
+    /**
+     * Find a service that handles {@link DevicePolicyManager#ACTION_DEVICE_ADMIN_SERVICE}
+     * in an owner package and connect to it.
+     */
+    public void startServiceForOwner(@NonNull String packageName, int userId,
+            @NonNull String actionForLog) {
+        final long token = mInjector.binderClearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                final ServiceInfo service = findService(packageName, userId);
+                if (service == null) {
+                    debug("Owner package %s on u%d has no service.",
+                            packageName, userId);
+                    disconnectServiceOnUserLocked(userId, actionForLog);
+                    return;
+                }
+                // See if it's already running.
+                final PersistentConnection<IDeviceAdminService> existing =
+                        mConnections.get(userId);
+                if (existing != null) {
+                    if (existing.getComponentName().equals(service.getComponentName())) {
+                        return;
+                    }
+                    disconnectServiceOnUserLocked(userId, actionForLog);
+                }
+
+                debug("Owner package %s on u%d has service %s for %s",
+                        packageName, userId,
+                        service.getComponentName().flattenToShortString(), actionForLog);
+
+                final DevicePolicyServiceConnection conn =
+                        new DevicePolicyServiceConnection(
+                                userId, service.getComponentName());
+                mConnections.put(userId, conn);
+                conn.connect();
+            }
+        } finally {
+            mInjector.binderRestoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Stop an owner service on a given user.
+     */
+    public void stopServiceForOwner(int userId, @NonNull String actionForLog) {
+        final long token = mInjector.binderClearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                disconnectServiceOnUserLocked(userId, actionForLog);
+            }
+        } finally {
+            mInjector.binderRestoreCallingIdentity(token);
+        }
+    }
+
+    private void disconnectServiceOnUserLocked(int userId, @NonNull String actionForLog) {
+        final DevicePolicyServiceConnection conn = mConnections.get(userId);
+        if (conn != null) {
+            debug("Stopping service for u%d if already running for %s.",
+                    userId, actionForLog);
+            conn.disconnect();
+            mConnections.remove(userId);
+        }
+    }
+
+    public void dump(String prefix, PrintWriter pw) {
+        synchronized (mLock) {
+            if (mConnections.size() == 0) {
+                return;
+            }
+            pw.println();
+            pw.print(prefix); pw.println("Owner Services:");
+            for (int i = 0; i < mConnections.size(); i++) {
+                final int userId = mConnections.keyAt(i);
+                pw.print(prefix); pw.print("  "); pw.print("User: "); pw.println(userId);
+
+                final DevicePolicyServiceConnection con = mConnections.valueAt(i);
+                con.dump(prefix + "    ", pw);
+            }
+        }
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 6f49324..bfa1b99 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -140,7 +140,6 @@
 import android.provider.ContactsContract.QuickContact;
 import android.provider.ContactsInternal;
 import android.provider.Settings;
-import android.security.Credentials;
 import android.security.IKeyChainAliasCallback;
 import android.security.IKeyChainService;
 import android.security.KeyChain;
@@ -194,7 +193,6 @@
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
 import java.text.DateFormat;
-import java.text.NumberFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -365,6 +363,7 @@
     final UserManagerInternal mUserManagerInternal;
     final TelephonyManager mTelephonyManager;
     private final LockPatternUtils mLockPatternUtils;
+    private final DeviceAdminServiceController mDeviceAdminServiceController;
 
     /**
      * Contains (package-user) pairs to remove. An entry (p, u) implies that removal of package p
@@ -459,7 +458,17 @@
 
         @Override
         public void onStartUser(int userHandle) {
-            mService.onStartUser(userHandle);
+            mService.handleStartUser(userHandle);
+        }
+
+        @Override
+        public void onUnlockUser(int userHandle) {
+            mService.handleUnlockUser(userHandle);
+        }
+
+        @Override
+        public void onStopUser(int userHandle) {
+            mService.handleStopUser(userHandle);
         }
     }
 
@@ -1420,7 +1429,7 @@
         }
     }
 
-    private void handlePackagesChanged(String packageName, int userHandle) {
+    private void handlePackagesChanged(@Nullable String packageName, int userHandle) {
         boolean removedAdmin = false;
         if (VERBOSE_LOG) Slog.d(LOG_TAG, "Handling package changes for user " + userHandle);
         DevicePolicyData policy = getUserData(userHandle);
@@ -1434,9 +1443,9 @@
                     if (packageName == null || packageName.equals(adminPackage)) {
                         if (mIPackageManager.getPackageInfo(adminPackage, 0, userHandle) == null
                                 || mIPackageManager.getReceiverInfo(aa.info.getComponent(),
-                                        PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
-                                        userHandle) == null) {
+                                PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+                                userHandle) == null) {
                             removedAdmin = true;
                             policy.mAdminList.remove(i);
                             policy.mAdminMap.remove(aa.info.getComponent());
@@ -1461,6 +1470,13 @@
                 }
             }
 
+            // If it's an owner package, we may need to refresh the bound connection.
+            final ComponentName owner = getOwnerComponent(userHandle);
+            if ((packageName != null) && (owner != null)
+                    && (owner.getPackageName().equals(packageName))) {
+                startOwnerService(userHandle, "package-broadcast");
+            }
+
             // Persist updates if the removed package was an admin or delegate.
             if (removedAdmin || removedDelegate) {
                 saveSettingsLocked(policy.mUserHandle);
@@ -1791,6 +1807,8 @@
         // Needed when mHasFeature == false, because it controls the certificate warning text.
         mCertificateMonitor = new CertificateMonitor(this, mInjector, mBackgroundHandler);
 
+        mDeviceAdminServiceController = new DeviceAdminServiceController(this);
+
         if (!mHasFeature) {
             // Skip the rest of the initialization
             return;
@@ -2943,7 +2961,7 @@
         loadOwners();
         cleanUpOldUsers();
         ensureUnknownSourcesRestrictionForProfileOwners();
-        onStartUser(UserHandle.USER_SYSTEM);
+        handleStartUser(UserHandle.USER_SYSTEM);
 
         // Register an observer for watching for user setup complete and settings changes.
         mSetupContentObserver.register();
@@ -2990,10 +3008,32 @@
         }
     }
 
-    private void onStartUser(int userId) {
+    void handleStartUser(int userId) {
         updateScreenCaptureDisabledInWindowManager(userId,
                 getScreenCaptureDisabled(null, userId));
         pushUserRestrictions(userId);
+
+        startOwnerService(userId, "start-user");
+    }
+
+    void handleUnlockUser(int userId) {
+        startOwnerService(userId, "unlock-user");
+    }
+
+    void handleStopUser(int userId) {
+        stopOwnerService(userId, "stop-user");
+    }
+
+    private void startOwnerService(int userId, String actionForLog) {
+        final ComponentName owner = getOwnerComponent(userId);
+        if (owner != null) {
+            mDeviceAdminServiceController.startServiceForOwner(
+                    owner.getPackageName(), userId, actionForLog);
+        }
+    }
+
+    private void stopOwnerService(int userId, String actionForLog) {
+        mDeviceAdminServiceController.stopServiceForOwner(userId, actionForLog);
     }
 
     private void cleanUpOldUsers() {
@@ -5078,7 +5118,7 @@
      * @param callerPackage the name of the calling package. Required if {@code who} is
      *            {@code null}.
      * @param reqPolicy the policy used in the API whose access permission is being checked.
-     * @param scoppe the delegation scope corresponding to the API being checked.
+     * @param scope the delegation scope corresponding to the API being checked.
      * @throws SecurityException if {@code who} is given and is not an owner for {@code reqPolicy};
      *            or when {@code who} is {@code null} and {@code callerPackage} is not a delegate
      *            of {@code scope}.
@@ -6460,6 +6500,9 @@
             } finally {
                 mInjector.binderRestoreCallingIdentity(ident);
             }
+            mDeviceAdminServiceController.startServiceForOwner(
+                    admin.getPackageName(), userId, "set-device-owner");
+
             Slog.i(LOG_TAG, "Device owner set: " + admin + " on user " + userId);
             return true;
         }
@@ -6615,6 +6658,8 @@
     }
 
     private void clearDeviceOwnerLocked(ActiveAdmin admin, int userId) {
+        mDeviceAdminServiceController.stopServiceForOwner(userId, "clear-device-owner");
+
         if (admin != null) {
             admin.disableCamera = false;
             admin.userRestrictions = null;
@@ -6692,6 +6737,8 @@
             } finally {
                 mInjector.binderRestoreCallingIdentity(id);
             }
+            mDeviceAdminServiceController.startServiceForOwner(
+                    who.getPackageName(), userHandle, "set-profile-owner");
             return true;
         }
     }
@@ -6723,6 +6770,8 @@
     }
 
     public void clearProfileOwnerLocked(ActiveAdmin admin, int userId) {
+        mDeviceAdminServiceController.stopServiceForOwner(userId, "clear-profile-owner");
+
         if (admin != null) {
             admin.disableCamera = false;
             admin.userRestrictions = null;
@@ -7275,6 +7324,7 @@
         synchronized (this) {
             pw.println("Current Device Policy Manager state:");
             mOwners.dump("  ", pw);
+            mDeviceAdminServiceController.dump("  ", pw);
             int userCount = mUserData.size();
             for (int u = 0; u < userCount; u++) {
                 DevicePolicyData policy = getUserData(mUserData.keyAt(u));
@@ -9624,6 +9674,21 @@
         return null;
     }
 
+    /**
+     * Return device owner or profile owner set on a given user.
+     */
+    private @Nullable ComponentName getOwnerComponent(int userId) {
+        synchronized (this) {
+            if (mOwners.getDeviceOwnerUserId() == userId) {
+                return mOwners.getDeviceOwnerComponent();
+            }
+            if (mOwners.hasProfileOwner(userId)) {
+                return mOwners.getProfileOwnerComponent(userId);
+            }
+        }
+        return null;
+    }
+
     private int checkManagedUserProvisioningPreCondition(int callingUserId) {
         if (!hasFeatureManagedUsers()) {
             return CODE_MANAGED_USERS_NOT_SUPPORTED;