Wait for animation complete before completing boot

Defer the boot process in ActivityManagerService,
WindowManagerService and PowerManagerService until the boot
animation has completed.

Fixes bug 16309312.

Change-Id: Ic5e0d627ca4ded3e211c5d2afece89da40d34642
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 394b183..677fcef 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2295,6 +2295,13 @@
             reply.writeNoException();
             return true;
         }
+
+        case BOOT_ANIMATION_COMPLETE_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            bootAnimationComplete();
+            reply.writeNoException();
+            return true;
+        }
         }
 
         return super.onTransact(code, data, reply, flags);
@@ -5301,5 +5308,16 @@
         reply.recycle();
     }
 
+    @Override
+    public void bootAnimationComplete() throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        mRemote.transact(BOOT_ANIMATION_COMPLETE_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
     private IBinder mRemote;
 }
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 9483680..8fa1fd53 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -327,6 +327,8 @@
     public void notifyActivityDrawn(IBinder token) throws RemoteException;
     public ActivityOptions getActivityOptions(IBinder token) throws RemoteException;
 
+    public void bootAnimationComplete() throws RemoteException;
+
     public void setImmersive(IBinder token, boolean immersive) throws RemoteException;
     public boolean isImmersive(IBinder token) throws RemoteException;
     public boolean isTopActivityImmersive() throws RemoteException;
@@ -772,4 +774,5 @@
     int GET_APP_TASK_THUMBNAIL_SIZE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+234;
     int RELEASE_ACTIVITY_INSTANCE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+235;
     int RELEASE_SOME_ACTIVITIES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+236;
+    int BOOT_ANIMATION_COMPLETE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+237;
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a9a4732..5845ad0 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -888,6 +888,8 @@
     boolean mProcessesReady = false;
     boolean mSystemReady = false;
     boolean mBooting = false;
+    boolean mCallFinishBooting = false;
+    boolean mBootAnimationComplete = false;
     boolean mWaitingUpdate = false;
     boolean mDidUpdate = false;
     boolean mOnBattery = false;
@@ -6195,6 +6197,14 @@
     }
 
     final void finishBooting() {
+        synchronized (this) {
+            if (!mBootAnimationComplete) {
+                mCallFinishBooting = true;
+                return;
+            }
+            mCallFinishBooting = false;
+        }
+
         // Register receivers to handle package update events
         mPackageMonitor.register(mContext, Looper.getMainLooper(), false);
 
@@ -6253,6 +6263,18 @@
         }
     }
 
+    @Override
+    public void bootAnimationComplete() {
+        final boolean callFinishBooting;
+        synchronized (this) {
+            callFinishBooting = mCallFinishBooting;
+            mBootAnimationComplete = true;
+        }
+        if (callFinishBooting) {
+            finishBooting();
+        }
+    }
+
     final void ensureBootCompleted() {
         boolean booting;
         boolean enableScreen;
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 5f97a00..7808800 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -91,8 +91,6 @@
     private static final int MSG_SANDMAN = 2;
     // Message: Sent when the screen on blocker is released.
     private static final int MSG_SCREEN_ON_BLOCKER_RELEASED = 3;
-    // Message: Sent to poll whether the boot animation has terminated.
-    private static final int MSG_CHECK_IF_BOOT_ANIMATION_FINISHED = 4;
 
     // Dirty bit: mWakeLocks changed
     private static final int DIRTY_WAKE_LOCKS = 1 << 0;
@@ -154,12 +152,6 @@
     // provider populates the actual default value (R.integer.def_screen_off_timeout).
     private static final int DEFAULT_SCREEN_OFF_TIMEOUT = 15 * 1000;
 
-    // The name of the boot animation service in init.rc.
-    private static final String BOOT_ANIMATION_SERVICE = "bootanim";
-
-    // Poll interval in milliseconds for watching boot animation finished.
-    private static final int BOOT_ANIMATION_POLL_INTERVAL = 200;
-
     // Power hints defined in hardware/libhardware/include/hardware/power.h.
     private static final int POWER_HINT_INTERACTION = 2;
     private static final int POWER_HINT_LOW_POWER = 5;
@@ -478,14 +470,15 @@
 
     @Override
     public void onBootPhase(int phase) {
-        if (phase == PHASE_BOOT_COMPLETED) {
-            // This is our early signal that the system thinks it has finished booting.
-            // However, the boot animation may still be running for a few more seconds
-            // since it is ultimately in charge of when it terminates.
-            // Defer transitioning into the boot completed state until the animation exits.
-            // We do this so that the screen does not start to dim prematurely before
-            // the user has actually had a chance to interact with the device.
-            startWatchingForBootAnimationFinished();
+        synchronized (mLock) {
+            if (phase == PHASE_BOOT_COMPLETED) {
+                final long now = SystemClock.uptimeMillis();
+                mBootCompleted = true;
+                mDirty |= DIRTY_BOOT_COMPLETED;
+                userActivityNoUpdateLocked(
+                        now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
+                updatePowerStateLocked();
+            }
         }
     }
 
@@ -2076,38 +2069,6 @@
         updatePowerStateLocked();
     }
 
-    private void startWatchingForBootAnimationFinished() {
-        mHandler.sendEmptyMessage(MSG_CHECK_IF_BOOT_ANIMATION_FINISHED);
-    }
-
-    private void checkIfBootAnimationFinished() {
-        if (DEBUG) {
-            Slog.d(TAG, "Check if boot animation finished...");
-        }
-
-        if (SystemService.isRunning(BOOT_ANIMATION_SERVICE)) {
-            mHandler.sendEmptyMessageDelayed(MSG_CHECK_IF_BOOT_ANIMATION_FINISHED,
-                    BOOT_ANIMATION_POLL_INTERVAL);
-            return;
-        }
-
-        synchronized (mLock) {
-            if (!mBootCompleted) {
-                Slog.i(TAG, "Boot animation finished.");
-                handleBootCompletedLocked();
-            }
-        }
-    }
-
-    private void handleBootCompletedLocked() {
-        final long now = SystemClock.uptimeMillis();
-        mBootCompleted = true;
-        mDirty |= DIRTY_BOOT_COMPLETED;
-        userActivityNoUpdateLocked(
-                now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
-        updatePowerStateLocked();
-    }
-
     private void shutdownOrRebootInternal(final boolean shutdown, final boolean confirm,
             final String reason, boolean wait) {
         if (mHandler == null || !mSystemReady) {
@@ -2528,9 +2489,6 @@
                 case MSG_SCREEN_ON_BLOCKER_RELEASED:
                     handleScreenOnBlockerReleased();
                     break;
-                case MSG_CHECK_IF_BOOT_ANIMATION_FINISHED:
-                    checkIfBootAnimationFinished();
-                    break;
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 10ab6b5..54af851 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -21,6 +21,7 @@
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import android.app.AppOpsManager;
 import android.os.Build;
+import android.os.SystemService;
 import android.util.ArraySet;
 import android.util.TimeUtils;
 import android.view.IWindowId;
@@ -272,6 +273,12 @@
     // Default input dispatching timeout in nanoseconds.
     static final long DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS = 5000 * 1000000L;
 
+    // Poll interval in milliseconds for watching boot animation finished.
+    private static final int BOOT_ANIMATION_POLL_INTERVAL = 200;
+
+    // The name of the boot animation service in init.rc.
+    private static final String BOOT_ANIMATION_SERVICE = "bootanim";
+
     /** Minimum value for attachStack and resizeStack weight value */
     public static final float STACK_WEIGHT_MIN = 0.2f;
 
@@ -458,6 +465,7 @@
     boolean mSystemBooted = false;
     boolean mForceDisplayEnabled = false;
     boolean mShowingBootMessages = false;
+    boolean mBootAnimationStopped = false;
 
     String mLastANRState;
 
@@ -5607,17 +5615,70 @@
         performEnableScreen();
     }
 
+    private boolean checkWaitingForWindowsLocked() {
+
+        boolean haveBootMsg = false;
+        boolean haveApp = false;
+        // if the wallpaper service is disabled on the device, we're never going to have
+        // wallpaper, don't bother waiting for it
+        boolean haveWallpaper = false;
+        boolean wallpaperEnabled = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_enableWallpaperService)
+                && !mOnlyCore;
+        boolean haveKeyguard = true;
+        // TODO(multidisplay): Expand to all displays?
+        final WindowList windows = getDefaultWindowListLocked();
+        final int N = windows.size();
+        for (int i=0; i<N; i++) {
+            WindowState w = windows.get(i);
+            if (w.isVisibleLw() && !w.mObscured && !w.isDrawnLw()) {
+                return true;
+            }
+            if (w.isDrawnLw()) {
+                if (w.mAttrs.type == TYPE_BOOT_PROGRESS) {
+                    haveBootMsg = true;
+                } else if (w.mAttrs.type == TYPE_APPLICATION) {
+                    haveApp = true;
+                } else if (w.mAttrs.type == TYPE_WALLPAPER) {
+                    haveWallpaper = true;
+                } else if (w.mAttrs.type == TYPE_STATUS_BAR) {
+                    haveKeyguard = mPolicy.isKeyguardDrawnLw();
+                }
+            }
+        }
+
+        if (DEBUG_SCREEN_ON || DEBUG_BOOT) {
+            Slog.i(TAG, "******** booted=" + mSystemBooted + " msg=" + mShowingBootMessages
+                    + " haveBoot=" + haveBootMsg + " haveApp=" + haveApp
+                    + " haveWall=" + haveWallpaper + " wallEnabled=" + wallpaperEnabled
+                    + " haveKeyguard=" + haveKeyguard);
+        }
+
+        // If we are turning on the screen to show the boot message,
+        // don't do it until the boot message is actually displayed.
+        if (!mSystemBooted && !haveBootMsg) {
+            return true;
+        }
+
+        // If we are turning on the screen after the boot is completed
+        // normally, don't do so until we have the application and
+        // wallpaper.
+        if (mSystemBooted && ((!haveApp && !haveKeyguard) ||
+                (wallpaperEnabled && !haveWallpaper))) {
+            return true;
+        }
+
+        return false;
+    }
+
     public void performEnableScreen() {
         synchronized(mWindowMap) {
-            if (DEBUG_BOOT) {
-                RuntimeException here = new RuntimeException("here");
-                here.fillInStackTrace();
-                Slog.i(TAG, "performEnableScreen: mDisplayEnabled=" + mDisplayEnabled
-                        + " mForceDisplayEnabled=" + mForceDisplayEnabled
-                        + " mShowingBootMessages=" + mShowingBootMessages
-                        + " mSystemBooted=" + mSystemBooted
-                        + " mOnlyCore=" + mOnlyCore, here);
-            }
+            if (DEBUG_BOOT) Slog.i(TAG, "performEnableScreen: mDisplayEnabled=" + mDisplayEnabled
+                    + " mForceDisplayEnabled=" + mForceDisplayEnabled
+                    + " mShowingBootMessages=" + mShowingBootMessages
+                    + " mSystemBooted=" + mSystemBooted
+                    + " mOnlyCore=" + mOnlyCore,
+                    new RuntimeException("here").fillInStackTrace());
             if (mDisplayEnabled) {
                 return;
             }
@@ -5625,94 +5686,64 @@
                 return;
             }
 
-            if (!mForceDisplayEnabled) {
-                // Don't enable the screen until all existing windows
-                // have been drawn.
-                boolean haveBootMsg = false;
-                boolean haveApp = false;
-                // if the wallpaper service is disabled on the device, we're never going to have
-                // wallpaper, don't bother waiting for it
-                boolean haveWallpaper = false;
-                boolean wallpaperEnabled = mContext.getResources().getBoolean(
-                        com.android.internal.R.bool.config_enableWallpaperService)
-                        && !mOnlyCore;
-                boolean haveKeyguard = true;
-                // TODO(multidisplay): Expand to all displays?
-                final WindowList windows = getDefaultWindowListLocked();
-                final int N = windows.size();
-                for (int i=0; i<N; i++) {
-                    WindowState w = windows.get(i);
-                    if (w.isVisibleLw() && !w.mObscured && !w.isDrawnLw()) {
-                        return;
+            // Don't enable the screen until all existing windows have been drawn.
+            if (!mForceDisplayEnabled && checkWaitingForWindowsLocked()) {
+                return;
+            }
+
+            if (!mBootAnimationStopped) {
+                // Do this one time.
+                try {
+                    IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger");
+                    if (surfaceFlinger != null) {
+                        //Slog.i(TAG, "******* TELLING SURFACE FLINGER WE ARE BOOTED!");
+                        Parcel data = Parcel.obtain();
+                        data.writeInterfaceToken("android.ui.ISurfaceComposer");
+                        surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHED
+                                data, null, 0);
+                        data.recycle();
                     }
-                    if (w.isDrawnLw()) {
-                        if (w.mAttrs.type == TYPE_BOOT_PROGRESS) {
-                            haveBootMsg = true;
-                        } else if (w.mAttrs.type == TYPE_APPLICATION) {
-                            haveApp = true;
-                        } else if (w.mAttrs.type == TYPE_WALLPAPER) {
-                            haveWallpaper = true;
-                        } else if (w.mAttrs.type == TYPE_STATUS_BAR) {
-                            haveKeyguard = mPolicy.isKeyguardDrawnLw();
-                        }
-                    }
+                } catch (RemoteException ex) {
+                    Slog.e(TAG, "Boot completed: SurfaceFlinger is dead!");
                 }
+                mBootAnimationStopped = true;
+            }
 
-                if (DEBUG_SCREEN_ON || DEBUG_BOOT) {
-                    Slog.i(TAG, "******** booted=" + mSystemBooted + " msg=" + mShowingBootMessages
-                            + " haveBoot=" + haveBootMsg + " haveApp=" + haveApp
-                            + " haveWall=" + haveWallpaper + " wallEnabled=" + wallpaperEnabled
-                            + " haveKeyguard=" + haveKeyguard);
-                }
-
-                // If we are turning on the screen to show the boot message,
-                // don't do it until the boot message is actually displayed.
-                if (!mSystemBooted && !haveBootMsg) {
-                    return;
-                }
-
-                // If we are turning on the screen after the boot is completed
-                // normally, don't do so until we have the application and
-                // wallpaper.
-                if (mSystemBooted && ((!haveApp && !haveKeyguard) ||
-                        (wallpaperEnabled && !haveWallpaper))) {
-                    return;
-                }
+            if (!mForceDisplayEnabled && !checkBootAnimationCompleteLocked()) {
+                if (DEBUG_BOOT) Slog.i(TAG, "performEnableScreen: Waiting for anim complete");
+                return;
             }
 
             mDisplayEnabled = true;
             if (DEBUG_SCREEN_ON || DEBUG_BOOT) Slog.i(TAG, "******************** ENABLING SCREEN!");
-            if (false) {
-                StringWriter sw = new StringWriter();
-                PrintWriter pw = new FastPrintWriter(sw, false, 1024);
-                this.dump(null, pw, null);
-                pw.flush();
-                Slog.i(TAG, sw.toString());
-            }
-            try {
-                IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger");
-                if (surfaceFlinger != null) {
-                    //Slog.i(TAG, "******* TELLING SURFACE FLINGER WE ARE BOOTED!");
-                    Parcel data = Parcel.obtain();
-                    data.writeInterfaceToken("android.ui.ISurfaceComposer");
-                    surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHED
-                                            data, null, 0);
-                    data.recycle();
-                }
-            } catch (RemoteException ex) {
-                Slog.e(TAG, "Boot completed: SurfaceFlinger is dead!");
-            }
 
             // Enable input dispatch.
             mInputMonitor.setEventDispatchingLw(mEventDispatchingEnabled);
         }
 
+        try {
+            mActivityManager.bootAnimationComplete();
+        } catch (RemoteException e) {
+        }
+
         mPolicy.enableScreenAfterBoot();
 
         // Make sure the last requested orientation has been applied.
         updateRotationUnchecked(false, false);
     }
 
+    private boolean checkBootAnimationCompleteLocked() {
+        if (SystemService.isRunning(BOOT_ANIMATION_SERVICE)) {
+            mH.removeMessages(H.CHECK_IF_BOOT_ANIMATION_FINISHED);
+            mH.sendEmptyMessageDelayed(H.CHECK_IF_BOOT_ANIMATION_FINISHED,
+                    BOOT_ANIMATION_POLL_INTERVAL);
+            if (DEBUG_BOOT) Slog.i(TAG, "checkBootAnimationComplete: Waiting for anim complete");
+            return false;
+        }
+        if (DEBUG_BOOT) Slog.i(TAG, "checkBootAnimationComplete: Animation complete!");
+        return true;
+    }
+
     public void showBootMessage(final CharSequence msg, final boolean always) {
         boolean first = false;
         synchronized(mWindowMap) {
@@ -7457,6 +7488,8 @@
         public static final int SHOW_CIRCULAR_DISPLAY_MASK = 35;
         public static final int SHOW_EMULATOR_DISPLAY_OVERLAY = 36;
 
+        public static final int CHECK_IF_BOOT_ANIMATION_FINISHED = 37;
+
         @Override
         public void handleMessage(Message msg) {
             if (DEBUG_WINDOW_TRACE) {
@@ -7941,6 +7974,17 @@
                     }
                 }
                 break;
+                case CHECK_IF_BOOT_ANIMATION_FINISHED: {
+                    final boolean bootAnimationComplete;
+                    synchronized (mWindowMap) {
+                        if (DEBUG_BOOT) Slog.i(TAG, "CHECK_IF_BOOT_ANIMATION_FINISHED:");
+                        bootAnimationComplete = checkBootAnimationCompleteLocked();
+                    }
+                    if (bootAnimationComplete) {
+                        performEnableScreen();
+                    }
+                }
+                break;
             }
             if (DEBUG_WINDOW_TRACE) {
                 Slog.v(TAG, "handleMessage: exit");