Prevent crash in AppWidgetHost that can be triggered by an AppWidget

-> When the RemoteViewsFactory violates the getViewTypeCount() contract
   we detect it, and prevent the AdapterView from crashing
-> Also made RemoteViewsCache a static inner class, since we may need
   that down the road, and there's no reason it shouldn't be

Change-Id: I872a255167aac94513e522924179de61286b995a
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index 46ec923..aa95397 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -246,7 +246,7 @@
      * A FrameLayout which contains a loading view, and manages the re/applying of RemoteViews when
      * they are loaded.
      */
-    private class RemoteViewsFrameLayout extends FrameLayout {
+    private static class RemoteViewsFrameLayout extends FrameLayout {
         public RemoteViewsFrameLayout(Context context) {
             super(context);
         }
@@ -301,7 +301,7 @@
          * Notifies each of the RemoteViewsFrameLayouts associated with a particular position that
          * the associated RemoteViews has loaded.
          */
-        public void notifyOnRemoteViewsLoaded(int position, RemoteViews view, int typeId) {
+        public void notifyOnRemoteViewsLoaded(int position, RemoteViews view) {
             if (view == null) return;
 
             final Integer pos = position;
@@ -331,7 +331,7 @@
     /**
      * The meta-data associated with the cache in it's current state.
      */
-    private class RemoteViewsMetaData {
+    private static class RemoteViewsMetaData {
         int count;
         int viewTypeCount;
         boolean hasStableIds;
@@ -390,14 +390,23 @@
             }
         }
 
+        public boolean isViewTypeInRange(int typeId) {
+            int mappedType = getMappedViewType(typeId);
+            if (mappedType >= viewTypeCount) {
+                return false;
+            } else {
+                return true;
+            }
+        }
+
         private RemoteViewsFrameLayout createLoadingView(int position, View convertView,
-                ViewGroup parent) {
+                ViewGroup parent, Object lock, LayoutInflater layoutInflater) {
             // Create and return a new FrameLayout, and setup the references for this position
             final Context context = parent.getContext();
             RemoteViewsFrameLayout layout = new RemoteViewsFrameLayout(context);
 
             // Create a new loading view
-            synchronized (mCache) {
+            synchronized (lock) {
                 boolean customLoadingViewAvailable = false;
 
                 if (mUserLoadingView != null) {
@@ -425,7 +434,7 @@
                             mFirstViewHeight = firstView.getMeasuredHeight();
                             mFirstView = null;
                         } catch (Exception e) {
-                            float density = mContext.getResources().getDisplayMetrics().density;
+                            float density = context.getResources().getDisplayMetrics().density;
                             mFirstViewHeight = (int)
                                     Math.round(sDefaultLoadingViewHeight * density);
                             mFirstView = null;
@@ -434,7 +443,7 @@
                     }
 
                     // Compose the loading view text
-                    TextView loadingTextView = (TextView) mLayoutInflater.inflate(
+                    TextView loadingTextView = (TextView) layoutInflater.inflate(
                             com.android.internal.R.layout.remote_views_adapter_default_loading_view,
                             layout, false);
                     loadingTextView.setHeight(mFirstViewHeight);
@@ -451,7 +460,7 @@
     /**
      * The meta-data associated with a single item in the cache.
      */
-    private class RemoteViewsIndexMetaData {
+    private static class RemoteViewsIndexMetaData {
         int typeId;
         long itemId;
         boolean isRequested;
@@ -462,10 +471,11 @@
 
         public void set(RemoteViews v, long id, boolean requested) {
             itemId = id;
-            if (v != null)
+            if (v != null) {
                 typeId = v.getLayoutId();
-            else
+            } else {
                 typeId = 0;
+            }
             isRequested = requested;
         }
     }
@@ -473,7 +483,7 @@
     /**
      *
      */
-    private class FixedSizeRemoteViewsCache {
+    private static class FixedSizeRemoteViewsCache {
         private static final String TAG = "FixedSizeRemoteViewsCache";
 
         // The meta data related to all the RemoteViews, ie. count, is stable, etc.
@@ -861,21 +871,36 @@
                     "returned from RemoteViewsFactory.");
             return;
         }
-        synchronized (mCache) {
-            // Cache the RemoteViews we loaded
-            mCache.insert(position, remoteViews, itemId, isRequested);
 
-            // Notify all the views that we have previously returned for this index that
-            // there is new data for it.
-            final RemoteViews rv = remoteViews;
-            final int typeId = mCache.getMetaDataAt(position).typeId;
-            if (notifyWhenLoaded) {
-                mMainQueue.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        mRequestedViews.notifyOnRemoteViewsLoaded(position, rv, typeId);
-                    }
-                });
+        int layoutId = remoteViews.getLayoutId();
+        RemoteViewsMetaData metaData = mCache.getMetaData();
+        boolean viewTypeInRange;
+        synchronized (metaData) {
+            viewTypeInRange = metaData.isViewTypeInRange(layoutId);
+        }
+        synchronized (mCache) {
+            if (viewTypeInRange) {
+                // Cache the RemoteViews we loaded
+                mCache.insert(position, remoteViews, itemId, isRequested);
+
+                // Notify all the views that we have previously returned for this index that
+                // there is new data for it.
+                final RemoteViews rv = remoteViews;
+                if (notifyWhenLoaded) {
+                    mMainQueue.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            mRequestedViews.notifyOnRemoteViewsLoaded(position, rv);
+                        }
+                    });
+                }
+            } else {
+                // We need to log an error here, as the the view type count specified by the
+                // factory is less than the number of view types returned. We don't return this
+                // view to the AdapterView, as this will cause an exception in the hosting process,
+                // which contains the associated AdapterView.
+                Log.e(TAG, "Error: widget's RemoteViewsFactory returns more view types than " +
+                        " indicated by getViewTypeCount() ");
             }
         }
     }
@@ -1010,7 +1035,8 @@
                     RemoteViewsFrameLayout loadingView = null;
                     final RemoteViewsMetaData metaData = mCache.getMetaData();
                     synchronized (metaData) {
-                        loadingView = metaData.createLoadingView(position, convertView, parent);
+                        loadingView = metaData.createLoadingView(position, convertView, parent,
+                                mCache, mLayoutInflater);
                     }
                     return loadingView;
                 } finally {
@@ -1022,7 +1048,8 @@
                 RemoteViewsFrameLayout loadingView = null;
                 final RemoteViewsMetaData metaData = mCache.getMetaData();
                 synchronized (metaData) {
-                    loadingView = metaData.createLoadingView(position, convertView, parent);
+                    loadingView = metaData.createLoadingView(position, convertView, parent,
+                            mCache, mLayoutInflater);
                 }
 
                 mRequestedViews.add(position, loadingView);