Make immersive mode public & imply update locking

Activity.setImmersive(boolean) / android:immersive="bool" are now public.
In addition, if the foreground activity is immersive then an update lock
will be held on its behalf.  This lets applications such as movie players
suppress the display of intrusive notifications, OTA-availability dialogs,
and the like while they are displaying content that ought not to be
rudely interrupted.

The update lock aspect of this mode is *advisory*, not binding -- the
update mechanism is not actually constrained; it simply uses this information
in deciding whether/when to prompt the user.  It's more a guideline than
a rule.

Bug 7681380

Change-Id: I3c412a84cbf3933e3bf0168f2c71c54a86e4b7e5
diff --git a/api/current.txt b/api/current.txt
index 345b862..10c6e2c1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2683,6 +2683,7 @@
     method public final boolean isChild();
     method public boolean isDestroyed();
     method public boolean isFinishing();
+    method public boolean isImmersive();
     method public boolean isTaskRoot();
     method public final deprecated android.database.Cursor managedQuery(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
     method public boolean moveTaskToBack(boolean);
@@ -2770,6 +2771,7 @@
     method public final void setFeatureDrawableResource(int, int);
     method public final void setFeatureDrawableUri(int, android.net.Uri);
     method public void setFinishOnTouchOutside(boolean);
+    method public void setImmersive(boolean);
     method public void setIntent(android.content.Intent);
     method public final void setProgress(int);
     method public final void setProgressBarIndeterminate(boolean);
@@ -6330,6 +6332,7 @@
     field public static final int FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS = 256; // 0x100
     field public static final int FLAG_FINISH_ON_TASK_LAUNCH = 2; // 0x2
     field public static final int FLAG_HARDWARE_ACCELERATED = 512; // 0x200
+    field public static final int FLAG_IMMERSIVE = 2048; // 0x800
     field public static final int FLAG_MULTIPROCESS = 1; // 0x1
     field public static final int FLAG_NO_HISTORY = 128; // 0x80
     field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 5dc9da2..4f3f245 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -4818,8 +4818,8 @@
      * <code>android:immersive</code> but may be changed at runtime by
      * {@link #setImmersive}.
      *
+     * @see #setImmersive(boolean)
      * @see android.content.pm.ActivityInfo#FLAG_IMMERSIVE
-     * @hide
      */
     public boolean isImmersive() {
         try {
@@ -4831,7 +4831,7 @@
 
     /**
      * Adjust the current immersive mode setting.
-     * 
+     *
      * Note that changing this value will have no effect on the activity's
      * {@link android.content.pm.ActivityInfo} structure; that is, if
      * <code>android:immersive</code> is set to <code>true</code>
@@ -4840,9 +4840,8 @@
      * always have its {@link android.content.pm.ActivityInfo#FLAG_IMMERSIVE
      * FLAG_IMMERSIVE} bit set.
      *
-     * @see #isImmersive
+     * @see #isImmersive()
      * @see android.content.pm.ActivityInfo#FLAG_IMMERSIVE
-     * @hide
      */
     public void setImmersive(boolean i) {
         try {
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index e2ca1dd..8f3b62d 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -161,7 +161,6 @@
      */
     public static final int FLAG_SHOW_ON_LOCK_SCREEN = 0x0400;
     /**
-     * @hide
      * Bit in {@link #flags} corresponding to an immersive activity
      * that wishes not to be interrupted by notifications.
      * Applications that hide the system notification bar with
@@ -174,7 +173,14 @@
      * {@link #FLAG_IMMERSIVE} set, however, will not be interrupted; the
      * notification may be shown in some other way (such as a small floating
      * "toast" window).
-     * {@see android.app.Notification#FLAG_HIGH_PRIORITY}
+     *
+     * Note that this flag will always reflect the Activity's
+     * <code>android:immersive</code> manifest definition, even if the Activity's
+     * immersive state is changed at runtime via
+     * {@link android.app.Activity#setImmersive(boolean)}.
+     *
+     * @see android.app.Notification#FLAG_HIGH_PRIORITY
+     * @see android.app.Activity#setImmersive(boolean)
      */
     public static final int FLAG_IMMERSIVE = 0x0800;
     /**
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 49dc220..2ba9a10 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -115,6 +115,7 @@
 import android.os.StrictMode;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.UpdateLock;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.format.Time;
@@ -191,6 +192,7 @@
     static final boolean DEBUG_POWER = localLOGV || false;
     static final boolean DEBUG_POWER_QUICK = DEBUG_POWER || false;
     static final boolean DEBUG_MU = localLOGV || false;
+    static final boolean DEBUG_IMMERSIVE = localLOGV || false;
     static final boolean VALIDATE_TOKENS = false;
     static final boolean SHOW_ACTIVITY_START_TIME = true;
     
@@ -827,6 +829,12 @@
     long mLastWriteTime = 0;
 
     /**
+     * Used to retain an update lock when the foreground activity is in
+     * immersive mode.
+     */
+    final UpdateLock mUpdateLock = new UpdateLock("immersive");
+
+    /**
      * Set to true after the system has finished booting.
      */
     boolean mBooted = false;
@@ -895,6 +903,7 @@
     static final int REPORT_USER_SWITCH_MSG = 34;
     static final int CONTINUE_USER_SWITCH_MSG = 35;
     static final int USER_SWITCH_TIMEOUT_MSG = 36;
+    static final int IMMERSIVE_MODE_LOCK_MSG = 37;
 
     static final int FIRST_ACTIVITY_STACK_MSG = 100;
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1356,6 +1365,21 @@
                 timeoutUserSwitch((UserStartedState)msg.obj, msg.arg1, msg.arg2);
                 break;
             }
+            case IMMERSIVE_MODE_LOCK_MSG: {
+                final boolean nextState = (msg.arg1 != 0);
+                if (mUpdateLock.isHeld() != nextState) {
+                    if (DEBUG_IMMERSIVE) {
+                        final ActivityRecord r = (ActivityRecord) msg.obj;
+                        Slog.d(TAG, "Applying new update lock state '" + nextState + "' for " + r);
+                    }
+                    if (nextState) {
+                        mUpdateLock.acquire();
+                    } else {
+                        mUpdateLock.release();
+                    }
+                }
+                break;
+            }
             }
         }
     };
@@ -1822,9 +1846,20 @@
             if (r != null) {
                 mWindowManager.setFocusedApp(r.appToken, true);
             }
+            applyUpdateLockStateLocked(r);
         }
     }
 
+    final void applyUpdateLockStateLocked(ActivityRecord r) {
+        // Modifications to the UpdateLock state are done on our handler, outside
+        // the activity manager's locks.  The new state is determined based on the
+        // state *now* of the relevant activity record.  The object is passed to
+        // the handler solely for logging detail, not to be consulted/modified.
+        final boolean nextState = r != null && r.immersive;
+        mHandler.sendMessage(
+                mHandler.obtainMessage(IMMERSIVE_MODE_LOCK_MSG, (nextState) ? 1 : 0, 0, r));
+    }
+
     private final void updateLruProcessInternalLocked(ProcessRecord app, int bestPos) {
         // put it on the LRU to keep track of when it should be exited.
         int lrui = mLruProcesses.indexOf(app);
@@ -7421,11 +7456,19 @@
 
     public void setImmersive(IBinder token, boolean immersive) {
         synchronized(this) {
-            ActivityRecord r = mMainStack.isInStackLocked(token);
+            final ActivityRecord r = mMainStack.isInStackLocked(token);
             if (r == null) {
                 throw new IllegalArgumentException();
             }
             r.immersive = immersive;
+
+            // update associated state if we're frontmost
+            if (r == mFocusedActivity) {
+                if (DEBUG_IMMERSIVE) {
+                    Slog.d(TAG, "Frontmost changed immersion: "+ r);
+                }
+                applyUpdateLockStateLocked(r);
+            }
         }
     }