Catching TransactionTooLargeException when binding widget.

A widget can potentially bypass binder size limit with remote views when
using partual updates (as the merging happens on the system service). In
such a case, show the widget in error state.

Bug: 32365088
Change-Id: Id1c4425958a06463273731464e31f05908c35ec7
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
index d3e5350..657b024 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -20,10 +20,8 @@
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
-import android.os.DeadObjectException;
-import android.os.TransactionTooLargeException;
+import android.util.SparseArray;
 import android.view.LayoutInflater;
-import android.view.View;
 
 import java.util.ArrayList;
 
@@ -36,6 +34,7 @@
 public class LauncherAppWidgetHost extends AppWidgetHost {
 
     private final ArrayList<Runnable> mProviderChangeListeners = new ArrayList<Runnable>();
+    private final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>();
 
     private Launcher mLauncher;
 
@@ -45,9 +44,11 @@
     }
 
     @Override
-    protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
+    protected LauncherAppWidgetHostView onCreateView(Context context, int appWidgetId,
             AppWidgetProviderInfo appWidget) {
-        return new LauncherAppWidgetHostView(context);
+        LauncherAppWidgetHostView view = new LauncherAppWidgetHostView(context);
+        mViews.put(appWidgetId, view);
+        return view;
     }
 
     @Override
@@ -55,15 +56,13 @@
         try {
             super.startListening();
         } catch (Exception e) {
-            if (e.getCause() instanceof TransactionTooLargeException ||
-                    e.getCause() instanceof DeadObjectException) {
-                // We're willing to let this slide. The exception is being caused by the list of
-                // RemoteViews which is being passed back. The startListening relationship will
-                // have been established by this point, and we will end up populating the
-                // widgets upon bind anyway. See issue 14255011 for more context.
-            } else {
+            if (!Utilities.isBinderSizeError(e)) {
                 throw new RuntimeException(e);
             }
+            // We're willing to let this slide. The exception is being caused by the list of
+            // RemoteViews which is being passed back. The startListening relationship will
+            // have been established by this point, and we will end up populating the
+            // widgets upon bind anyway. See issue 14255011 for more context.
         }
     }
 
@@ -98,7 +97,24 @@
             lahv.updateLastInflationOrientation();
             return lahv;
         } else {
-            return super.createView(context, appWidgetId, appWidget);
+            try {
+                return super.createView(context, appWidgetId, appWidget);
+            } catch (Exception e) {
+                if (!Utilities.isBinderSizeError(e)) {
+                    throw new RuntimeException(e);
+                }
+
+                // If the exception was thrown while fetching the remote views, let the view stay.
+                // This will ensure that if the widget posts a valid update later, the view
+                // will update.
+                LauncherAppWidgetHostView view = mViews.get(appWidgetId);
+                if (view == null) {
+                    view = onCreateView(mLauncher, appWidgetId, appWidget);
+                }
+                view.setAppWidget(appWidgetId, appWidget);
+                view.switchToErrorView();
+                return  view;
+            }
         }
     }
 
@@ -114,4 +130,16 @@
         // launcher spans accordingly.
         info.initSpans();
     }
+
+    @Override
+    public void deleteAppWidgetId(int appWidgetId) {
+        super.deleteAppWidgetId(appWidgetId);
+        mViews.remove(appWidgetId);
+    }
+
+    @Override
+    protected void clearViews() {
+        super.clearViews();
+        mViews.clear();
+    }
 }