Add lockTaskOnLaunch attribute.

The new AndroidManifest activity attribute, lockTaskOnLaunch attribute
is a boolean that puts the system in lockTask mode when true and if
the activity specified is the root of a privileged task.

This bug also fixes lockTask mode for root activities that finish
themselves. Previously finish was not allowed even if there were
activities left in the task that were still valid.

A NullPointerException for lock task toasts has also been fixed.

Fixes bug 19995702.

Change-Id: Iba6976b1a0cc5a22eb526db66d2e9af66445541f
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 4723c0d..24c026d 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -648,6 +648,12 @@
      */
     public boolean resizeable;
 
+    /**
+     * Value indicating if the activity is to be locked at startup.
+     * @hide
+     */
+    public boolean lockTaskOnLaunch;
+
     public ActivityInfo() {
     }
 
@@ -665,13 +671,15 @@
         uiOptions = orig.uiOptions;
         parentActivityName = orig.parentActivityName;
         maxRecents = orig.maxRecents;
+        resizeable = orig.resizeable;
+        lockTaskOnLaunch = orig.lockTaskOnLaunch;
     }
-    
+
     /**
      * Return the theme resource identifier to use for this activity.  If
      * the activity defines a theme, that is used; else, the application
      * theme is used.
-     * 
+     *
      * @return The theme associated with this activity.
      */
     public final int getThemeResource() {
@@ -709,7 +717,7 @@
         if (uiOptions != 0) {
             pw.println(prefix + " uiOptions=0x" + Integer.toHexString(uiOptions));
         }
-        pw.println(prefix + "resizeable=" + resizeable);
+        pw.println(prefix + "resizeable=" + resizeable + " lockTaskOnLaunch=" + lockTaskOnLaunch);
         super.dumpBack(pw, prefix);
     }
     
@@ -739,6 +747,7 @@
         dest.writeInt(persistableMode);
         dest.writeInt(maxRecents);
         dest.writeInt(resizeable ? 1 : 0);
+        dest.writeInt(lockTaskOnLaunch ? 1 : 0);
     }
 
     public static final Parcelable.Creator<ActivityInfo> CREATOR
@@ -767,5 +776,6 @@
         persistableMode = source.readInt();
         maxRecents = source.readInt();
         resizeable = (source.readInt() == 1);
+        lockTaskOnLaunch = (source.readInt() == 1);
     }
 }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 4b81fd4..3d5c2ed 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -3113,6 +3113,9 @@
                         R.styleable.AndroidManifestActivity_screenOrientation,
                         ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
             }
+
+            a.info.lockTaskOnLaunch =
+                    sa.getBoolean(R.styleable.AndroidManifestActivity_lockTaskOnLaunch, false);
         } else {
             a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
             a.info.configChanges = 0;
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index b0b4e3a..1b3a5e9 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1038,6 +1038,13 @@
          activity. -->
     <attr name="resizeableActivity" format="boolean" />
 
+    <!-- Tasks rooted at this activity will start up in lock-task mode. That means that they
+         cannot be navigated away from until they finish or explicitly release lock-task mode.
+         This only works for system privileged activities. An exception will be thrown for
+         non-privileged activities that use this attribute.
+         @hide -->
+    <attr name="lockTaskOnLaunch" format="boolean" />
+
     <!-- When set installer will extract native libraries. If set to false
          libraries in the apk must be stored and page-aligned.  -->
     <attr name="extractNativeLibs" format="boolean"/>
@@ -1191,7 +1198,7 @@
          features in your package (or other packages).  See the
          <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a>
          document for more information on permissions.
-         
+
          <p>This appears as a child tag of the root
          {@link #AndroidManifest manifest} tag. -->
     <declare-styleable name="AndroidManifestPermission" parent="AndroidManifest">
@@ -1210,15 +1217,15 @@
         <attr name="protectionLevel" />
         <attr name="permissionFlags" />
     </declare-styleable>
-    
+
     <!-- The <code>permission-group</code> tag declares a logical grouping of
          related permissions.
-         
+
          <p>Note that this tag does not declare a permission itself, only
          a namespace in which further permissions can be placed.  See
          the {@link #AndroidManifestPermission &lt;permission&gt;} tag for
          more information.
-         
+
          <p>This appears as a child tag of the root
          {@link #AndroidManifest manifest} tag. -->
     <declare-styleable name="AndroidManifestPermissionGroup" parent="AndroidManifest">
@@ -1236,7 +1243,7 @@
         <attr name="permissionGroupFlags" />
         <attr name="priority" />
     </declare-styleable>
-    
+
     <!-- The <code>permission-tree</code> tag declares the base of a tree of
          permission values: it declares that this package has ownership of
          the given permission name, as well as all names underneath it
@@ -1749,6 +1756,7 @@
         <attr name="relinquishTaskIdentity" />
         <attr name="resumeWhilePausing" />
         <attr name="resizeableActivity" />
+        <attr name="lockTaskOnLaunch" />
     </declare-styleable>
     
     <!-- The <code>activity-alias</code> tag declares a new
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 5c7daf2..754d7ebd 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2657,4 +2657,5 @@
   <!--IntentFilter auto verification -->
   <public type="attr" name="autoVerify" />
 
+  <public type="attr" name="lockTaskOnLaunch" />
 </resources>
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index dbf7def..c345d34 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3911,9 +3911,9 @@
             }
             // Do not allow task to finish in Lock Task mode.
             if (tr == mStackSupervisor.mLockTaskModeTask) {
-                if (rootR == r) {
+                if (rootR == r && tr.getTopActivity() == r) {
                     Slog.i(TAG, "Not finishing task in lock task mode");
-                    mStackSupervisor.showLockTaskToast();
+                    mStackSupervisor.showLockTaskToastLocked();
                     return false;
                 }
             }
@@ -4074,7 +4074,7 @@
                 // Do not allow task to finish in Lock Task mode.
                 if (r.task == mStackSupervisor.mLockTaskModeTask) {
                     if (rootR == r) {
-                        mStackSupervisor.showLockTaskToast();
+                        mStackSupervisor.showLockTaskToastLocked();
                         return false;
                     }
                 }
@@ -8238,7 +8238,7 @@
                 return;
             }
             if (mStackSupervisor.isLockTaskModeViolation(task)) {
-                mStackSupervisor.showLockTaskToast();
+                mStackSupervisor.showLockTaskToastLocked();
                 Slog.e(TAG, "moveTaskToFront: Attempt to violate Lock Task Mode");
                 return;
             }
@@ -8272,7 +8272,7 @@
                 if (taskId >= 0) {
                     if ((mStackSupervisor.mLockTaskModeTask != null)
                             && (mStackSupervisor.mLockTaskModeTask.taskId == taskId)) {
-                        mStackSupervisor.showLockTaskToast();
+                        mStackSupervisor.showLockTaskToastLocked();
                         return false;
                     }
                     return ActivityRecord.getStackLocked(token).moveTaskToBackLocked(taskId);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index f874244..d574571 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -154,9 +154,10 @@
     static final int CONTAINER_CALLBACK_VISIBILITY = FIRST_SUPERVISOR_STACK_MSG + 8;
     static final int LOCK_TASK_START_MSG = FIRST_SUPERVISOR_STACK_MSG + 9;
     static final int LOCK_TASK_END_MSG = FIRST_SUPERVISOR_STACK_MSG + 10;
-    static final int CONTAINER_CALLBACK_TASK_LIST_EMPTY = FIRST_SUPERVISOR_STACK_MSG + 11;
-    static final int CONTAINER_TASK_LIST_EMPTY_TIMEOUT = FIRST_SUPERVISOR_STACK_MSG + 12;
-    static final int LAUNCH_TASK_BEHIND_COMPLETE = FIRST_SUPERVISOR_STACK_MSG + 13;
+    static final int LOCK_TASK_SHOW_TOAST_MSG = FIRST_SUPERVISOR_STACK_MSG + 11;
+    static final int CONTAINER_CALLBACK_TASK_LIST_EMPTY = FIRST_SUPERVISOR_STACK_MSG + 12;
+    static final int CONTAINER_TASK_LIST_EMPTY_TIMEOUT = FIRST_SUPERVISOR_STACK_MSG + 13;
+    static final int LAUNCH_TASK_BEHIND_COMPLETE = FIRST_SUPERVISOR_STACK_MSG + 14;
 
     private final static String VIRTUAL_DISPLAY_BASE_NAME = "ActivityViewVirtualDisplay";
 
@@ -1884,7 +1885,7 @@
                         findTaskLocked(r) : findActivityLocked(intent, r.info);
                 if (intentActivity != null) {
                     if (isLockTaskModeViolation(intentActivity.task)) {
-                        showLockTaskToast();
+                        showLockTaskToastLocked();
                         Slog.e(TAG, "startActivityUnchecked: Attempt to violate Lock Task Mode");
                         return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
                     }
@@ -2144,6 +2145,20 @@
             } else {
                 r.setTask(reuseTask, taskToAffiliate);
             }
+            if (r.info.lockTaskOnLaunch) {
+                try {
+                    if (!AppGlobals.getPackageManager().isUidPrivileged(callingUid)) {
+                        Slog.e(TAG, "Non-privileged activity " + r +
+                                " using lockTaskOnLaunch attribute");
+                        throw new RuntimeException(
+                                "Non-privileged activity using lockTaskOnLaunch attribute.");
+                    }
+                } catch (RemoteException e) {
+                    // Unreachable. The package manager is in this process.
+                }
+                setLockTaskModeLocked(r.task, ActivityManager.LOCK_TASK_MODE_LOCKED,
+                        "lockTaskOnLaunch attribute");
+            }
             if (!movedHome) {
                 if ((launchFlags &
                         (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME))
@@ -3596,8 +3611,8 @@
         return list;
     }
 
-    void showLockTaskToast() {
-        mLockTaskNotify.showToast(mLockTaskModeState);
+    void showLockTaskToastLocked() {
+        mHandler.sendEmptyMessage(LOCK_TASK_SHOW_TOAST_MSG);
     }
 
     void setLockTaskModeLocked(TaskRecord task, int lockTaskModeState, String reason) {
@@ -3796,6 +3811,13 @@
                         mLockTaskModeState = ActivityManager.LOCK_TASK_MODE_NONE;
                     }
                 } break;
+                case LOCK_TASK_SHOW_TOAST_MSG: {
+                    if (mLockTaskNotify == null) {
+                        mLockTaskNotify = new LockTaskNotify(mService.mContext);
+                    }
+                    mLockTaskNotify.showToast(mLockTaskModeState);
+                    break;
+                }
                 case CONTAINER_CALLBACK_TASK_LIST_EMPTY: {
                     final ActivityContainer container = (ActivityContainer) msg.obj;
                     final IActivityContainerCallback callback = container.mCallback;