Create end of animation callback for Activity

Activities cannot draw while their entering animations are active.
This change introduces a callback, onEnterAnimationComplete() so
that activities can know when their draws will be effective.

Fixes bug 13658460.

Change-Id: Ic48540cd4c7e37538f10cb2dc0852aa3f55d11e1
diff --git a/api/current.txt b/api/current.txt
index 569425f..4e0d311 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3392,6 +3392,7 @@
     method public android.view.View onCreateView(android.view.View, java.lang.String, android.content.Context, android.util.AttributeSet);
     method protected void onDestroy();
     method public void onDetachedFromWindow();
+    method public void onEnterAnimationComplete();
     method public boolean onGenericMotionEvent(android.view.MotionEvent);
     method public boolean onKeyDown(int, android.view.KeyEvent);
     method public boolean onKeyLongPress(int, android.view.KeyEvent);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index cac646d..2ebfa1d 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -5501,6 +5501,14 @@
     }
 
     /**
+     * Activities cannot draw during the period that their windows are animating in. In order
+     * to know when it is safe to begin drawing they can override this method which will be
+     * called when the entering animation has completed.
+     */
+    public void onEnterAnimationComplete() {
+    }
+
+    /**
      * Adjust the current immersive mode setting.
      *
      * Note that changing this value will have no effect on the activity's
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 318a520..311a8f55 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2231,6 +2231,14 @@
             reply.writeNoException();
             return true;
         }
+
+        case NOTIFY_ENTER_ANIMATION_COMPLETE_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            notifyEnterAnimationComplete(token);
+            reply.writeNoException();
+            return true;
+        }
         }
 
         return super.onTransact(code, data, reply, flags);
@@ -5146,5 +5154,18 @@
         reply.recycle();
     }
 
+    @Override
+    public void notifyEnterAnimationComplete(IBinder token) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        mRemote.transact(NOTIFY_ENTER_ANIMATION_COMPLETE_TRANSACTION, data, reply,
+                IBinder.FLAG_ONEWAY);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
     private IBinder mRemote;
 }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 48954f4..0bab7b2 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1163,6 +1163,10 @@
         public void scheduleBackgroundMediaPlayingChanged(IBinder token, boolean playing) {
             sendMessage(H.BACKGROUND_MEDIA_PLAYING_CHANGED, token, playing ? 1 : 0);
         }
+
+        public void scheduleEnterAnimationComplete(IBinder token) {
+            sendMessage(H.ENTER_ANIMATION_COMPLETE, token);
+        }
     }
 
     private class H extends Handler {
@@ -1215,6 +1219,7 @@
         public static final int ON_NEW_ACTIVITY_OPTIONS = 146;
         public static final int STOP_MEDIA_PLAYING = 147;
         public static final int BACKGROUND_MEDIA_PLAYING_CHANGED = 148;
+        public static final int ENTER_ANIMATION_COMPLETE = 149;
 
         String codeToString(int code) {
             if (DEBUG_MESSAGES) {
@@ -1267,6 +1272,7 @@
                     case ON_NEW_ACTIVITY_OPTIONS: return "ON_NEW_ACTIVITY_OPTIONS";
                     case STOP_MEDIA_PLAYING: return "STOP_MEDIA_PLAYING";
                     case BACKGROUND_MEDIA_PLAYING_CHANGED: return "BACKGROUND_MEDIA_PLAYING_CHANGED";
+                    case ENTER_ANIMATION_COMPLETE: return "ENTER_ANIMATION_COMPLETE";
                 }
             }
             return Integer.toString(code);
@@ -1491,6 +1497,9 @@
                 case BACKGROUND_MEDIA_PLAYING_CHANGED:
                     handleOnBackgroundMediaPlayingChanged((IBinder) msg.obj, msg.arg1 > 0);
                     break;
+                case ENTER_ANIMATION_COMPLETE:
+                    handleEnterAnimationComplete((IBinder) msg.obj);
+                    break;
             }
             if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
         }
@@ -2509,6 +2518,13 @@
         installContentProviders(mInitialApplication, Lists.newArrayList(info));
     }
 
+    private void handleEnterAnimationComplete(IBinder token) {
+        ActivityClientRecord r = mActivities.get(token);
+        if (r != null) {
+            r.activity.onEnterAnimationComplete();
+        }
+    }
+
     private static final ThreadLocal<Intent> sCurrentBroadcastIntent = new ThreadLocal<Intent>();
 
     /**
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index 0b4510fe..e9d4bd9 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -666,6 +666,15 @@
             reply.writeNoException();
             return true;
         }
+
+        case ENTER_ANIMATION_COMPLETE_TRANSACTION:
+        {
+            data.enforceInterface(IApplicationThread.descriptor);
+            IBinder token = data.readStrongBinder();
+            scheduleEnterAnimationComplete(token);
+            reply.writeNoException();
+            return true;
+        }
         }
 
         return super.onTransact(code, data, reply, flags);
@@ -1342,4 +1351,13 @@
         mRemote.transact(BACKGROUND_MEDIA_PLAYING_CHANGED_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
         data.recycle();
     }
+
+    @Override
+    public void scheduleEnterAnimationComplete(IBinder token) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        data.writeStrongBinder(token);
+        mRemote.transact(ENTER_ANIMATION_COMPLETE_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
 }
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 53c1408..5347f03 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -448,6 +448,7 @@
     public void mediaResourcesReleased(IBinder token) throws RemoteException;
 
     public void notifyLaunchTaskBehindComplete(IBinder token) throws RemoteException;
+    public void notifyEnterAnimationComplete(IBinder token) throws RemoteException;
 
     /*
      * Private non-Binder interfaces
@@ -758,4 +759,5 @@
     int MEDIA_RESOURCES_RELEASED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+227;
     int NOTIFY_LAUNCH_TASK_BEHIND_COMPLETE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+228;
     int START_ACTIVITY_FROM_RECENTS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 229;
+    int NOTIFY_ENTER_ANIMATION_COMPLETE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+230;
 }
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index 18faf0e..4a1fda4 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -147,6 +147,7 @@
     void updateTimePrefs(boolean is24Hour) throws RemoteException;
     void scheduleStopMediaPlaying(IBinder token) throws RemoteException;
     void scheduleBackgroundMediaPlayingChanged(IBinder token, boolean enabled) throws RemoteException;
+    void scheduleEnterAnimationComplete(IBinder token) throws RemoteException;
 
     String descriptor = "android.app.IApplicationThread";
 
@@ -203,4 +204,5 @@
     int UPDATE_TIME_PREFS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+51;
     int STOP_MEDIA_PLAYING_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+52;
     int BACKGROUND_MEDIA_PLAYING_CHANGED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+53;
+    int ENTER_ANIMATION_COMPLETE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+54;
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index fed68f9..314e906 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1158,6 +1158,7 @@
     static final int UPDATE_TIME = 41;
     static final int SYSTEM_USER_START_MSG = 42;
     static final int SYSTEM_USER_CURRENT_MSG = 43;
+    static final int ENTER_ANIMATION_COMPLETE_MSG = 44;
 
     static final int FIRST_ACTIVITY_STACK_MSG = 100;
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1813,6 +1814,18 @@
                 mSystemServiceManager.switchUser(msg.arg1);
                 break;
             }
+            case ENTER_ANIMATION_COMPLETE_MSG: {
+                synchronized (ActivityManagerService.this) {
+                    ActivityRecord r = ActivityRecord.forToken((IBinder) msg.obj);
+                    if (r != null && r.app != null && r.app.thread != null) {
+                        try {
+                            r.app.thread.scheduleEnterAnimationComplete(r.appToken);
+                        } catch (RemoteException e) {
+                        }
+                    }
+                }
+                break;
+            }
             }
         }
     };
@@ -5699,6 +5712,11 @@
     }
 
     @Override
+    public final void notifyEnterAnimationComplete(IBinder token) {
+        mHandler.sendMessage(mHandler.obtainMessage(ENTER_ANIMATION_COMPLETE_MSG, token));
+    }
+
+    @Override
     public String getCallingPackage(IBinder token) {
         synchronized (this) {
             ActivityRecord r = getCallingRecordLocked(token);
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index 874e105..9b69ce2 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -297,6 +297,13 @@
             mAppToken.mLaunchTaskBehind = false;
         } else {
             mAppToken.updateReportedVisibilityLocked();
+            if (mAppToken.mEnteringAnimation) {
+                mAppToken.mEnteringAnimation = false;
+                try {
+                    mService.mActivityManager.notifyEnterAnimationComplete(mAppToken.token);
+                } catch (RemoteException e) {
+                }
+            }
         }
 
         return false;
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 312689b..3fcd067 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -110,6 +110,7 @@
     boolean mDeferRemoval;
 
     boolean mLaunchTaskBehind;
+    boolean mEnteringAnimation;
 
     AppWindowToken(WindowManagerService _service, IApplicationToken _token,
             boolean _voiceInteraction) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 170ecbd..670ba55 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4450,6 +4450,7 @@
                 if (visible) {
                     mOpeningApps.add(wtoken);
                     wtoken.startingMoved = false;
+                    wtoken.mEnteringAnimation = true;
 
                     // If the token is currently hidden (should be the
                     // common case), then we need to set up to wait for
@@ -4472,6 +4473,7 @@
                     }
                 } else {
                     mClosingApps.add(wtoken);
+                    wtoken.mEnteringAnimation = false;
 
                     // If the token is currently visible (should be the
                     // common case), then set up to wait for it to be hidden.