Fix issue #3485923: Gmail crash

Allow application to try to recover if a surface OOM error
happens on the client side.

Change-Id: I0308bd99647a35e4bcac448340b7fc6330a828f6
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index d3d3792..6426635 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1020,7 +1020,8 @@
             data.enforceInterface(IActivityManager.descriptor);
             int[] pids = data.createIntArray();
             String reason = data.readString();
-            boolean res = killPids(pids, reason);
+            boolean secure = data.readInt() != 0;
+            boolean res = killPids(pids, reason, secure);
             reply.writeNoException();
             reply.writeInt(res ? 1 : 0);
             return true;
@@ -2636,12 +2637,13 @@
         mRemote.transact(NOTE_WAKEUP_ALARM_TRANSACTION, data, null, 0);
         data.recycle();
     }
-    public boolean killPids(int[] pids, String reason) throws RemoteException {
+    public boolean killPids(int[] pids, String reason, boolean secure) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeIntArray(pids);
         data.writeString(reason);
+        data.writeInt(secure ? 1 : 0);
         mRemote.transact(KILL_PIDS_TRANSACTION, data, reply, 0);
         boolean res = reply.readInt() != 0;
         data.recycle();
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index f42e8fb..61e6fc8 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -250,7 +250,7 @@
     
     public void noteWakeupAlarm(IIntentSender sender) throws RemoteException;
     
-    public boolean killPids(int[] pids, String reason) throws RemoteException;
+    public boolean killPids(int[] pids, String reason, boolean secure) throws RemoteException;
     
     // Special low-level communication with activity manager.
     public void startRunning(String pkg, String cls, String action,
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index d46dacc..b53aa21 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -108,14 +108,14 @@
      * 
      * @return True if the initialization was successful, false otherwise.
      */
-    abstract boolean initialize(SurfaceHolder holder);
+    abstract boolean initialize(SurfaceHolder holder) throws Surface.OutOfResourcesException;
     
     /**
      * Updates the hardware renderer for the specified surface.
      * 
      * @param holder The holder for the surface to hardware accelerate.
      */
-    abstract void updateSurface(SurfaceHolder holder);
+    abstract void updateSurface(SurfaceHolder holder) throws Surface.OutOfResourcesException;
 
     /**
      * Setup the hardware renderer for drawing. This is called for every
@@ -189,7 +189,7 @@
      * @param holder
      */
     void initializeIfNeeded(int width, int height, View.AttachInfo attachInfo,
-            SurfaceHolder holder) {
+            SurfaceHolder holder) throws Surface.OutOfResourcesException {
         if (isRequested()) {
             // We lost the gl context, so recreate it.
             if (!isEnabled()) {
@@ -366,7 +366,7 @@
         }
 
         @Override
-        boolean initialize(SurfaceHolder holder) {
+        boolean initialize(SurfaceHolder holder) throws Surface.OutOfResourcesException {
             if (isRequested() && !isEnabled()) {
                 initializeEgl();
                 mGl = createEglSurface(holder);
@@ -395,7 +395,7 @@
         }
         
         @Override
-        void updateSurface(SurfaceHolder holder) {
+        void updateSurface(SurfaceHolder holder) throws Surface.OutOfResourcesException {
             if (isRequested() && isEnabled()) {
                 createEglSurface(holder);
             }
@@ -446,7 +446,7 @@
             sEglContext = createContext(sEgl, sEglDisplay, sEglConfig);
         }
 
-        GL createEglSurface(SurfaceHolder holder) {
+        GL createEglSurface(SurfaceHolder holder) throws Surface.OutOfResourcesException {
             // Check preconditions.
             if (sEgl == null) {
                 throw new RuntimeException("egl not initialized");
@@ -494,7 +494,7 @@
              * the context is current and bound to a surface.
              */
             if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, sEglContext)) {
-                throw new RuntimeException("eglMakeCurrent failed "
+                throw new Surface.OutOfResourcesException("eglMakeCurrent failed "
                         + getEGLErrorString(sEgl.eglGetError()));
             }
             
@@ -516,7 +516,7 @@
 
         @Override
         void initializeIfNeeded(int width, int height, View.AttachInfo attachInfo,
-                SurfaceHolder holder) {
+                SurfaceHolder holder) throws Surface.OutOfResourcesException {
             if (isRequested()) {
                 checkEglErrors();
                 super.initializeIfNeeded(width, height, attachInfo, holder);
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 1218e81..990af083 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -84,6 +84,11 @@
             out Surface outSurface);
 
     /**
+     * Called by a client to report that it ran out of graphics memory.
+     */
+    boolean outOfMemory(IWindow window);
+
+    /**
      * Give the window manager a hint of the part of the window that is
      * completely transparent, allowing it to work with the surface flinger
      * to optimize compositing of this part of the window.
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 965c959..5468230 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -1066,7 +1066,20 @@
                         mPreviousTransparentRegion.setEmpty();
 
                         if (mAttachInfo.mHardwareRenderer != null) {
-                            hwInitialized = mAttachInfo.mHardwareRenderer.initialize(mHolder);
+                            try {
+                                hwInitialized = mAttachInfo.mHardwareRenderer.initialize(mHolder);
+                            } catch (Surface.OutOfResourcesException e) {
+                                Log.e(TAG, "OutOfResourcesException initializing HW surface", e);
+                                try {
+                                    if (!sWindowSession.outOfMemory(mWindow)) {
+                                        Slog.w(TAG, "No processes killed for memory; killing self");
+                                        Process.killProcess(Process.myPid());
+                                    }
+                                } catch (RemoteException ex) {
+                                }
+                                mLayoutRequested = true;    // ask wm for a new surface next time.
+                                return;
+                            }
                         }
                     }
                 } else if (!mSurface.isValid()) {
@@ -1081,7 +1094,20 @@
                 } else if (surfaceGenerationId != mSurface.getGenerationId() &&
                         mSurfaceHolder == null && mAttachInfo.mHardwareRenderer != null) {
                     fullRedrawNeeded = true;
-                    mAttachInfo.mHardwareRenderer.updateSurface(mHolder);
+                    try {
+                        mAttachInfo.mHardwareRenderer.updateSurface(mHolder);
+                    } catch (Surface.OutOfResourcesException e) {
+                        Log.e(TAG, "OutOfResourcesException updating HW surface", e);
+                        try {
+                            if (!sWindowSession.outOfMemory(mWindow)) {
+                                Slog.w(TAG, "No processes killed for memory; killing self");
+                                Process.killProcess(Process.myPid());
+                            }
+                        } catch (RemoteException ex) {
+                        }
+                        mLayoutRequested = true;    // ask wm for a new surface next time.
+                        return;
+                    }
                 }
             } catch (RemoteException e) {
             }
@@ -1569,14 +1595,24 @@
                 canvas.setDensity(mDensity);
             } catch (Surface.OutOfResourcesException e) {
                 Log.e(TAG, "OutOfResourcesException locking surface", e);
-                // TODO: we should ask the window manager to do something!
-                // for now we just do nothing
+                try {
+                    if (!sWindowSession.outOfMemory(mWindow)) {
+                        Slog.w(TAG, "No processes killed for memory; killing self");
+                        Process.killProcess(Process.myPid());
+                    }
+                } catch (RemoteException ex) {
+                }
                 mLayoutRequested = true;    // ask wm for a new surface next time.
                 return;
             } catch (IllegalArgumentException e) {
                 Log.e(TAG, "IllegalArgumentException locking surface", e);
-                // TODO: we should ask the window manager to do something!
-                // for now we just do nothing
+                try {
+                    if (!sWindowSession.outOfMemory(mWindow)) {
+                        Slog.w(TAG, "No processes killed for memory; killing self");
+                        Process.killProcess(Process.myPid());
+                    }
+                } catch (RemoteException ex) {
+                }
                 mLayoutRequested = true;    // ask wm for a new surface next time.
                 return;
             }
@@ -2033,8 +2069,22 @@
                     if (mAttachInfo.mHardwareRenderer != null &&
                             mSurface != null && mSurface.isValid()) {
                         mFullRedrawNeeded = true;
-                        mAttachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight,
-                                mAttachInfo, mHolder);
+                        try {
+                            mAttachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight,
+                                    mAttachInfo, mHolder);
+                        } catch (Surface.OutOfResourcesException e) {
+                            Log.e(TAG, "OutOfResourcesException locking surface", e);
+                            try {
+                                if (!sWindowSession.outOfMemory(mWindow)) {
+                                    Slog.w(TAG, "No processes killed for memory; killing self");
+                                    Process.killProcess(Process.myPid());
+                                }
+                            } catch (RemoteException ex) {
+                            }
+                            // Retry in a bit.
+                            sendMessageDelayed(obtainMessage(msg.what, msg.arg1, msg.arg2), 500);
+                            return;
+                        }
                     }
                 }
 
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index 436eff0..91ada6b 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -374,7 +374,7 @@
                                 done = true;
                             } else {
                                 // Eliminate system process here?
-                                ams.killPids(pids, "unmount media");
+                                ams.killPids(pids, "unmount media", true);
                                 // Confirm if file references have been freed.
                                 pids = getStorageUsers(path);
                                 if (pids == null || pids.length == 0) {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index e6dfb7f..2eafc1d 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -6184,7 +6184,7 @@
         }
     }
 
-    public boolean killPids(int[] pids, String pReason) {
+    public boolean killPids(int[] pids, String pReason, boolean secure) {
         if (Binder.getCallingUid() != Process.SYSTEM_UID) {
             throw new SecurityException("killPids only available to the system");
         }
@@ -6207,11 +6207,18 @@
                 }
             }
             
-            // If the worse oom_adj is somewhere in the hidden proc LRU range,
+            // If the worst oom_adj is somewhere in the hidden proc LRU range,
             // then constrain it so we will kill all hidden procs.
             if (worstType < EMPTY_APP_ADJ && worstType > HIDDEN_APP_MIN_ADJ) {
                 worstType = HIDDEN_APP_MIN_ADJ;
             }
+
+            // If this is not a secure call, don't let it kill processes that
+            // are important.
+            if (!secure && worstType < SECONDARY_SERVER_ADJ) {
+                worstType = SECONDARY_SERVER_ADJ;
+            }
+
             Slog.w(TAG, "Killing processes " + reason + " at adjustment " + worstType);
             for (int i=0; i<pids.length; i++) {
                 ProcessRecord proc = mPidsSelfLocked.get(pids[i]);
diff --git a/services/java/com/android/server/wm/Session.java b/services/java/com/android/server/wm/Session.java
index b9db177..0f09356 100644
--- a/services/java/com/android/server/wm/Session.java
+++ b/services/java/com/android/server/wm/Session.java
@@ -161,6 +161,10 @@
         return res;
     }
 
+    public boolean outOfMemory(IWindow window) {
+        return mService.outOfMemoryWindow(this, window);
+    }
+
     public void setTransparentRegion(IWindow window, Region region) {
         mService.setTransparentRegionWindow(this, window, region);
     }
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index b5d84e8..eed41a0 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -2717,6 +2717,22 @@
                 | (displayed ? WindowManagerImpl.RELAYOUT_FIRST_TIME : 0);
     }
 
+    public boolean outOfMemoryWindow(Session session, IWindow client) {
+        long origId = Binder.clearCallingIdentity();
+
+        try {
+            synchronized(mWindowMap) {
+                WindowState win = windowForClientLocked(session, client, false);
+                if (win == null) {
+                    return false;
+                }
+                return reclaimSomeSurfaceMemoryLocked(win, "from-client", false);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
     public void finishDrawingWindow(Session session, IWindow client) {
         final long origId = Binder.clearCallingIdentity();
         synchronized(mWindowMap) {
@@ -7443,7 +7459,7 @@
                                         + " pos=(" + w.mShownFrame.left
                                         + "," + w.mShownFrame.top + ")", e);
                                 if (!recoveringMemory) {
-                                    reclaimSomeSurfaceMemoryLocked(w, "position");
+                                    reclaimSomeSurfaceMemoryLocked(w, "position", true);
                                 }
                             }
                         }
@@ -7471,7 +7487,7 @@
                                 Slog.e(TAG, "Error resizing surface of " + w
                                         + " size=(" + width + "x" + height + ")", e);
                                 if (!recoveringMemory) {
-                                    reclaimSomeSurfaceMemoryLocked(w, "size");
+                                    reclaimSomeSurfaceMemoryLocked(w, "size", true);
                                 }
                             }
                         }
@@ -7618,7 +7634,7 @@
                             } catch (RuntimeException e) {
                                 Slog.w(TAG, "Error updating surface in " + w, e);
                                 if (!recoveringMemory) {
-                                    reclaimSomeSurfaceMemoryLocked(w, "update");
+                                    reclaimSomeSurfaceMemoryLocked(w, "update", true);
                                 }
                             }
                         }
@@ -8077,13 +8093,15 @@
             Slog.w(TAG, "Failure showing surface " + win.mSurface + " in " + win, e);
         }
 
-        reclaimSomeSurfaceMemoryLocked(win, "show");
+        reclaimSomeSurfaceMemoryLocked(win, "show", true);
 
         return false;
     }
 
-    void reclaimSomeSurfaceMemoryLocked(WindowState win, String operation) {
+    boolean reclaimSomeSurfaceMemoryLocked(WindowState win, String operation, boolean secure) {
         final Surface surface = win.mSurface;
+        boolean leakedSurface = false;
+        boolean killedApps = false;
 
         EventLog.writeEvent(EventLogTags.WM_NO_SURFACE_MEMORY, win.toString(),
                 win.mSession.mPid, operation);
@@ -8098,7 +8116,6 @@
             // window list to make sure we haven't left any dangling surfaces
             // around.
             int N = mWindows.size();
-            boolean leakedSurface = false;
             Slog.i(TAG, "Out of memory for surface!  Looking for leaks...");
             for (int i=0; i<N; i++) {
                 WindowState ws = mWindows.get(i);
@@ -8130,7 +8147,6 @@
                 }
             }
 
-            boolean killedApps = false;
             if (!leakedSurface) {
                 Slog.w(TAG, "No leaked surfaces; killing applicatons!");
                 SparseIntArray pidCandidates = new SparseIntArray();
@@ -8146,7 +8162,7 @@
                         pids[i] = pidCandidates.keyAt(i);
                     }
                     try {
-                        if (mActivityManager.killPids(pids, "Free memory")) {
+                        if (mActivityManager.killPids(pids, "Free memory", secure)) {
                             killedApps = true;
                         }
                     } catch (RemoteException e) {
@@ -8173,6 +8189,8 @@
         } finally {
             Binder.restoreCallingIdentity(callingIdentity);
         }
+
+        return leakedSurface || killedApps;
     }
 
     private boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java
index d0eec89..f8ff5f8 100644
--- a/services/java/com/android/server/wm/WindowState.java
+++ b/services/java/com/android/server/wm/WindowState.java
@@ -594,7 +594,7 @@
                         + " / " + this);
             } catch (Surface.OutOfResourcesException e) {
                 Slog.w(WindowManagerService.TAG, "OutOfResourcesException creating surface");
-                mService.reclaimSomeSurfaceMemoryLocked(this, "create");
+                mService.reclaimSomeSurfaceMemoryLocked(this, "create", true);
                 return null;
             } catch (Exception e) {
                 Slog.e(WindowManagerService.TAG, "Exception creating surface", e);
@@ -628,7 +628,7 @@
                     }
                 } catch (RuntimeException e) {
                     Slog.w(WindowManagerService.TAG, "Error creating surface in " + w, e);
-                    mService.reclaimSomeSurfaceMemoryLocked(this, "create-init");
+                    mService.reclaimSomeSurfaceMemoryLocked(this, "create-init", true);
                 }
                 mLastHidden = true;
             } finally {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
index 8422d48..ab8c4ec 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
@@ -85,6 +85,10 @@
         return 0;
     }
 
+    public boolean outOfMemory(IWindow window) throws RemoteException {
+        return false;
+    }
+
     public void getDisplayFrame(IWindow window, Rect outDisplayFrame) {
         // pass for now.
     }