StatusBar: Catch OOM caused by third-party icons

Bug: 31825355
Test: runtest -x packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
Change-Id: I3e2a8c3da43a572a026ea0bbe1d39234035a4801
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 399b0d2..6283148 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -256,9 +256,16 @@
         if (mIcon == null) {
             return false;
         }
-        Drawable drawable = getIcon(mIcon);
+        Drawable drawable;
+        try {
+            drawable = getIcon(mIcon);
+        } catch (OutOfMemoryError e) {
+            Log.w(TAG, "OOM while inflating " + mIcon.icon + " for slot " + mSlot);
+            return false;
+        }
+
         if (drawable == null) {
-            Log.w(TAG, "No icon for slot " + mSlot);
+            Log.w(TAG, "No icon for slot " + mSlot + "; " + mIcon.icon);
             return false;
         }
         if (withClear) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
index 7d9e073..68f9cb05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
@@ -16,36 +16,74 @@
 
 package com.android.systemui.statusbar;
 
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNull;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.graphics.drawable.Icon;
-import android.os.Debug;
 import android.os.UserHandle;
+import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
-import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 import org.junit.runner.RunWith;
-
-import static junit.framework.Assert.assertNull;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import org.mockito.ArgumentMatcher;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class StatusBarIconViewTest extends SysuiTestCase {
 
+    @Rule
+    public ExpectedException mThrown = ExpectedException.none();
+
     private StatusBarIconView mIconView;
     private StatusBarIcon mStatusBarIcon = mock(StatusBarIcon.class);
 
+    private PackageManager mPackageManagerSpy;
+    private Context mContext;
+    private Resources mMockResources;
+
     @Before
-    public void setUp() {
-        mIconView = new StatusBarIconView(getContext(), "slot", null);
-        mStatusBarIcon = new StatusBarIcon(UserHandle.ALL, getContext().getPackageName(),
-                Icon.createWithResource(getContext(), R.drawable.ic_android), 0, 0, "");
+    public void setUp() throws Exception {
+        // Set up context such that asking for "mockPackage" resources returns mMockResources.
+        mMockResources = mock(Resources.class);
+        mPackageManagerSpy = spy(getContext().getPackageManager());
+        doReturn(mMockResources).when(mPackageManagerSpy)
+                .getResourcesForApplicationAsUser(eq("mockPackage"), anyInt());
+        doReturn(mMockResources).when(mPackageManagerSpy)
+                .getResourcesForApplication(eq("mockPackage"));
+        doReturn(mMockResources).when(mPackageManagerSpy).getResourcesForApplication(argThat(
+                (ArgumentMatcher<ApplicationInfo>) o -> "mockPackage".equals(o.packageName)));
+        mContext = new ContextWrapper(getContext()) {
+            @Override
+            public PackageManager getPackageManager() {
+                return mPackageManagerSpy;
+            }
+        };
+
+        mIconView = new StatusBarIconView(mContext, "test_slot", null);
+        mStatusBarIcon = new StatusBarIcon(UserHandle.ALL, "mockPackage",
+                Icon.createWithResource(mContext, R.drawable.ic_android), 0, 0, "");
     }
 
     @Test
@@ -55,4 +93,11 @@
         assertNull(mIconView.getTag(R.id.icon_is_grayscale));
     }
 
+    @Test
+    public void testSettingOomingIconDoesNotThrowOom() {
+        when(mMockResources.getDrawable(anyInt(), any())).thenThrow(new OutOfMemoryError("mocked"));
+        mStatusBarIcon.icon = Icon.createWithResource("mockPackage", R.drawable.ic_android);
+
+        assertFalse(mIconView.set(mStatusBarIcon));
+    }
 }
\ No newline at end of file