Merge "Allow device initializers to set a preferred setup activity." into mnc-dev
diff --git a/api/current.txt b/api/current.txt
index eb69ed6..df77416 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5781,6 +5781,7 @@
     method public void setPasswordQuality(android.content.ComponentName, int);
     method public boolean setPermittedAccessibilityServices(android.content.ComponentName, java.util.List<java.lang.String>);
     method public boolean setPermittedInputMethods(android.content.ComponentName, java.util.List<java.lang.String>);
+    method public void setPreferredSetupActivity(android.content.ComponentName, android.content.ComponentName);
     method public void setProfileEnabled(android.content.ComponentName);
     method public void setProfileName(android.content.ComponentName, java.lang.String);
     method public void setRecommendedGlobalProxy(android.content.ComponentName, android.net.ProxyInfo);
diff --git a/api/system-current.txt b/api/system-current.txt
index b8c7fa2..e10a8b2 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5886,6 +5886,7 @@
     method public void setPasswordQuality(android.content.ComponentName, int);
     method public boolean setPermittedAccessibilityServices(android.content.ComponentName, java.util.List<java.lang.String>);
     method public boolean setPermittedInputMethods(android.content.ComponentName, java.util.List<java.lang.String>);
+    method public void setPreferredSetupActivity(android.content.ComponentName, android.content.ComponentName);
     method public void setProfileEnabled(android.content.ComponentName);
     method public void setProfileName(android.content.ComponentName, java.lang.String);
     method public void setRecommendedGlobalProxy(android.content.ComponentName, android.net.ProxyInfo);
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index b11c509..a71a258 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2521,6 +2521,15 @@
             return true;
         }
 
+        case UPDATE_PREFERRED_SETUP_ACTIVITY_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            ComponentName preferredActivity = ComponentName.readFromParcel(data);
+            int userId = data.readInt();
+            updatePreferredSetupActivity(preferredActivity, userId);
+            reply.writeNoException();
+            return true;
+        }
+
         case GET_PACKAGE_PROCESS_STATE_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             String pkg = data.readString();
@@ -5821,6 +5830,20 @@
     }
 
     @Override
+    public void updatePreferredSetupActivity(ComponentName preferredActivity, int userId)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        ComponentName.writeToParcel(preferredActivity, data);
+        data.writeInt(userId);
+        mRemote.transact(UPDATE_PREFERRED_SETUP_ACTIVITY_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
+    @Override
     public int getPackageProcessState(String packageName) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 00558fe..5829fbe 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -496,6 +496,8 @@
             throws RemoteException;
     public void updateLockTaskPackages(int userId, String[] packages) throws RemoteException;
     public void updateDeviceOwner(String packageName) throws RemoteException;
+    public void updatePreferredSetupActivity(ComponentName preferredActivity, int userId)
+            throws RemoteException;
 
     public int getPackageProcessState(String packageName) throws RemoteException;
 
@@ -839,4 +841,5 @@
     int GET_PACKAGE_PROCESS_STATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+293;
     int SHOW_LOCK_TASK_ESCAPE_MESSAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+294;
     int UPDATE_DEVICE_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+295;
+    int UPDATE_PREFERRED_SETUP_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+296;
 }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 47133d4..ed814c3 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -4325,4 +4325,21 @@
             }
         }
     }
+
+    /**
+     * Called by a device initializer to set the activity to be launched on device boot or after a
+     * user switch during user setup. This activity will be started regardless of the priority of
+     * other 'home' activities. Once user setup is complete, the preferred setup activity will be
+     * ignored.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param activity The Activity to be started by default during user setup.
+     */
+    public void setPreferredSetupActivity(ComponentName admin, ComponentName activity) {
+        try {
+            mService.setPreferredSetupActivity(admin, activity);
+        } catch (RemoteException re) {
+            Log.w(TAG, "Failed talking with device policy service", re);
+        }
+    }
 }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 087fc88..a678c51 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -216,6 +216,8 @@
     String getDeviceInitializer();
     ComponentName getDeviceInitializerComponent();
 
+    void setPreferredSetupActivity(in ComponentName admin, in ComponentName activity);
+
     void setUserIcon(in ComponentName admin, in Bitmap icon);
 
     void sendDeviceInitializerStatus(int statusCode, String description);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 4970e0f..15d7367 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -442,6 +442,12 @@
      */
     String mDeviceOwnerName;
 
+    /**
+     * Preferred activities to start on boot/user switch, as set by DevicePolicyManager. Indexed
+     * by userId.
+     */
+    SparseArray<ComponentName> mPreferredSetupActivities = new SparseArray<>();
+
     public class PendingAssistExtras extends Binder implements Runnable {
         public final ActivityRecord activity;
         public final Bundle extras;
@@ -3334,15 +3340,22 @@
         ComponentName comp = intent.getComponent();
         try {
             if (comp != null) {
+                // Factory test.
                 ai = AppGlobals.getPackageManager().getActivityInfo(comp, flags, userId);
             } else {
-                ResolveInfo info = AppGlobals.getPackageManager().resolveIntent(
-                        intent,
-                        intent.resolveTypeIfNeeded(mContext.getContentResolver()),
-                            flags, userId);
+                ComponentName preferredComponent = mPreferredSetupActivities.get(userId);
+                if (preferredComponent != null) {
+                    ai = AppGlobals.getPackageManager().getActivityInfo(
+                            preferredComponent, flags, userId);
+                } else {
+                    ResolveInfo info = AppGlobals.getPackageManager().resolveIntent(
+                            intent,
+                            intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                                flags, userId);
 
-                if (info != null) {
-                    ai = info.activityInfo;
+                    if (info != null) {
+                        ai = info.activityInfo;
+                    }
                 }
             }
         } catch (RemoteException e) {
@@ -8574,6 +8587,22 @@
     }
 
     @Override
+    public void updatePreferredSetupActivity(ComponentName preferredActivity, int userId) {
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
+            throw new SecurityException(
+                    "updatePreferredSetupActivity called from non-system process");
+        }
+        synchronized (this) {
+            if (preferredActivity == null) {
+                mPreferredSetupActivities.delete(userId);
+            } else {
+                mPreferredSetupActivities.put(userId, preferredActivity);
+            }
+        }
+    }
+
+    @Override
     public void updateDeviceOwner(String packageName) {
         final int callingUid = Binder.getCallingUid();
         if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2b88158..31d7f74 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -181,6 +181,7 @@
 
     private static final String ATTR_PERMISSION_PROVIDER = "permission-provider";
     private static final String ATTR_SETUP_COMPLETE = "setup-complete";
+    private static final String ATTR_PREFERRED_SETUP_ACTIVITY = "setup-activity";
 
     private static final String ATTR_DELEGATED_CERT_INSTALLER = "delegated-cert-installer";
 
@@ -315,6 +316,8 @@
 
         boolean doNotAskCredentialsOnBoot = false;
 
+        ComponentName mPreferredSetupActivity;
+
         public DevicePolicyData(int userHandle) {
             mUserHandle = userHandle;
         }
@@ -1410,7 +1413,12 @@
                 out.attribute(null, ATTR_DELEGATED_CERT_INSTALLER,
                         policy.mDelegatedCertInstallerPackage);
             }
-
+            if (policy.mPreferredSetupActivity != null) {
+                out.attribute(null, ATTR_PREFERRED_SETUP_ACTIVITY,
+                        policy.mPreferredSetupActivity.flattenToString());
+            } else {
+                out.attribute(null, ATTR_PREFERRED_SETUP_ACTIVITY, "");
+            }
 
             final int N = policy.mAdminList.size();
             for (int i=0; i<N; i++) {
@@ -1531,6 +1539,12 @@
             }
             policy.mDelegatedCertInstallerPackage = parser.getAttributeValue(null,
                     ATTR_DELEGATED_CERT_INSTALLER);
+            String preferredSetupActivity =
+                    parser.getAttributeValue(null, ATTR_PREFERRED_SETUP_ACTIVITY);
+            if (preferredSetupActivity != null) {
+                policy.mPreferredSetupActivity =
+                        ComponentName.unflattenFromString(preferredSetupActivity);
+            }
 
             type = parser.next();
             int outerDepth = parser.getDepth();
@@ -1654,6 +1668,7 @@
         if (!policy.mStatusBarEnabledState) {
             setStatusBarEnabledStateInternal(policy.mStatusBarEnabledState, userHandle);
         }
+        updatePreferredSetupActivityLocked(userHandle);
     }
 
     private void updateLockTaskPackagesLocked(List<String> packages, int userId) {
@@ -4632,6 +4647,43 @@
     }
 
     @Override
+    public void setPreferredSetupActivity(ComponentName who, ComponentName activity) {
+        if (!mHasFeature) {
+            return;
+        }
+        Preconditions.checkNotNull(who, "ComponentName is null");
+        synchronized (this) {
+            ActiveAdmin activeAdmin =
+                    getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            if (!isDeviceInitializer(activeAdmin.info.getPackageName())) {
+                throw new SecurityException(
+                        "This method can only be called by device initializers");
+            }
+            int userHandle = UserHandle.getCallingUserId();
+            DevicePolicyData userData = getUserData(userHandle);
+            userData.mPreferredSetupActivity = activity;
+            saveSettingsLocked(userHandle);
+            updatePreferredSetupActivityLocked(userHandle);
+        }
+    }
+
+    private void updatePreferredSetupActivityLocked(int userHandle) {
+        if (!mHasFeature) {
+            return;
+        }
+        IActivityManager am = ActivityManagerNative.getDefault();
+        long ident = Binder.clearCallingIdentity();
+        try {
+            am.updatePreferredSetupActivity(
+                    getUserData(userHandle).mPreferredSetupActivity, userHandle);
+        } catch (RemoteException e) {
+            // Not gonna happen.
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
     public void setApplicationRestrictions(ComponentName who, String packageName, Bundle settings) {
         Preconditions.checkNotNull(who, "ComponentName is null");
         final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId());
@@ -6000,6 +6052,9 @@
                 if (!policy.mUserSetupComplete) {
                     policy.mUserSetupComplete = true;
                     synchronized (this) {
+                        // Clear the preferred setup activity.
+                        policy.mPreferredSetupActivity = null;
+                        updatePreferredSetupActivityLocked(userHandle);
                         // The DeviceInitializer was whitelisted but now should be removed.
                         removeDeviceInitializerFromLockTaskPackages(userHandle);
                         saveSettingsLocked(userHandle);