Allow a root activity to relinquish task identity

If a an acitivty with attribute android:relinquishTaskIdentity true
is the root activity of a task then the intent of that task will
be that of the first activity in the stack with
android:relinquishTaskIdentity set false.

The ability to set intent also includes the ability to set the
TaskDescription of the task.

Fixes bug 15675610.
Fixes bug 10428661.

Change-Id: Ib28a9eae3b9832eeeef9106adbebe344184ee5ae
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 1cde41f..9fff329 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -27,7 +27,6 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.graphics.Bitmap;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.Slog;
@@ -57,11 +56,12 @@
     private static final String ATTR_ONTOPOFHOME = "on_top_of_home";
     private static final String ATTR_LASTDESCRIPTION = "last_description";
     private static final String ATTR_LASTTIMEMOVED = "last_time_moved";
+    private static final String ATTR_NEVERRELINQUISH = "never_relinquish_identity";
 
     private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail";
 
     final int taskId;       // Unique identifier for this task.
-    final String affinity;  // The affinity name for this task, or null.
+    String affinity;        // The affinity name for this task, or null.
     final IVoiceInteractionSession voiceSession;    // Voice interaction session driving task
     final IVoiceInteractor voiceInteractor;         // Associated interactor to provide to app
     Intent intent;          // The original intent that started the task.
@@ -111,13 +111,15 @@
      * Display.DEFAULT_DISPLAY. */
     boolean mOnTopOfHome = false;
 
+    /** If original intent did not allow relinquishing task identity, save that information */
+    boolean mNeverRelinquishIdentity = true;
+
     final ActivityManagerService mService;
 
     TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent,
             IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) {
         mService = service;
         taskId = _taskId;
-        affinity = info.taskAffinity;
         voiceSession = _voiceSession;
         voiceInteractor = _voiceInteractor;
         setIntent(_intent, info);
@@ -128,7 +130,7 @@
             String _affinity, ComponentName _realActivity, ComponentName _origActivity,
             boolean _rootWasReset, boolean _askedCompatMode, int _taskType, boolean _onTopOfHome,
             int _userId, String _lastDescription, ArrayList<ActivityRecord> activities,
-            long lastTimeMoved) {
+            long lastTimeMoved, boolean neverRelinquishIdentity) {
         mService = service;
         taskId = _taskId;
         intent = _intent;
@@ -146,6 +148,7 @@
         lastDescription = _lastDescription;
         mActivities = activities;
         mLastTimeMoved = lastTimeMoved;
+        mNeverRelinquishIdentity = neverRelinquishIdentity;
     }
 
     void touchActiveTime() {
@@ -157,6 +160,14 @@
     }
 
     void setIntent(Intent _intent, ActivityInfo info) {
+        if (intent == null) {
+            mNeverRelinquishIdentity =
+                    (info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) == 0;
+        } else if (mNeverRelinquishIdentity) {
+            return;
+        }
+
+        affinity = info.taskAffinity;
         stringName = null;
 
         if (info.targetActivity == null) {
@@ -282,6 +293,7 @@
 
         mActivities.remove(newTop);
         mActivities.add(newTop);
+        updateEffectiveIntent();
 
         setFrontOfTask();
     }
@@ -311,6 +323,7 @@
             r.mActivityType = taskType;
         }
         mActivities.add(index, r);
+        updateEffectiveIntent();
         if (r.isPersistable()) {
             mService.notifyTaskPersisterLocked(this, false);
         }
@@ -322,6 +335,7 @@
             // Was previously in list.
             numFullscreen--;
         }
+        updateEffectiveIntent();
         if (r.isPersistable()) {
             mService.notifyTaskPersisterLocked(this, false);
         }
@@ -579,12 +593,19 @@
         // utility activities.
         int activityNdx;
         final int numActivities = mActivities.size();
+        final boolean relinquish = numActivities == 0 ? false :
+                (mActivities.get(0).info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) != 0;
         for (activityNdx = Math.min(numActivities, 1); activityNdx < numActivities;
                 ++activityNdx) {
             final ActivityRecord r = mActivities.get(activityNdx);
+            if (relinquish && (r.info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) == 0) {
+                // This will be the top activity for determining taskDescription. Pre-inc to
+                // overcome initial decrement below.
+                ++activityNdx;
+                break;
+            }
             if (r.intent != null &&
-                    (r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
-                            != 0) {
+                    (r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) {
                 break;
             }
         }
@@ -615,6 +636,27 @@
         }
     }
 
+    int findEffectiveRootIndex() {
+        int activityNdx;
+        final int topActivityNdx = mActivities.size() - 1;
+        for (activityNdx = 0; activityNdx < topActivityNdx; ++activityNdx) {
+            final ActivityRecord r = mActivities.get(activityNdx);
+            if (r.finishing) {
+                continue;
+            }
+            if ((r.info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) == 0) {
+                break;
+            }
+        }
+        return activityNdx;
+    }
+
+    void updateEffectiveIntent() {
+        final int effectiveRootIndex = findEffectiveRootIndex();
+        final ActivityRecord r = mActivities.get(effectiveRootIndex);
+        setIntent(r.intent, r.info);
+    }
+
     void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
         Slog.i(TAG, "Saving task=" + this);
 
@@ -634,6 +676,7 @@
         out.attribute(null, ATTR_TASKTYPE, String.valueOf(taskType));
         out.attribute(null, ATTR_ONTOPOFHOME, String.valueOf(mOnTopOfHome));
         out.attribute(null, ATTR_LASTTIMEMOVED, String.valueOf(mLastTimeMoved));
+        out.attribute(null, ATTR_NEVERRELINQUISH, String.valueOf(mNeverRelinquishIdentity));
         if (lastDescription != null) {
             out.attribute(null, ATTR_LASTDESCRIPTION, lastDescription.toString());
         }
@@ -684,6 +727,7 @@
         int userId = 0;
         String lastDescription = null;
         long lastTimeOnTop = 0;
+        boolean neverRelinquishIdentity = true;
         int taskId = -1;
         final int outerDepth = in.getDepth();
 
@@ -714,6 +758,8 @@
                 lastDescription = attrValue;
             } else if (ATTR_LASTTIMEMOVED.equals(attrName)) {
                 lastTimeOnTop = Long.valueOf(attrValue);
+            } else if (ATTR_NEVERRELINQUISH.equals(attrName)) {
+                neverRelinquishIdentity = Boolean.valueOf(attrValue);
             } else {
                 Slog.w(TAG, "TaskRecord: Unknown attribute=" + attrName);
             }
@@ -748,7 +794,7 @@
         final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent,
                 affinityIntent, affinity, realActivity, origActivity, rootHasReset,
                 askedCompatMode, taskType, onTopOfHome, userId, lastDescription, activities,
-                lastTimeOnTop);
+                lastTimeOnTop, neverRelinquishIdentity);
 
         for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) {
             final ActivityRecord r = activities.get(activityNdx);