blob: e58f08a799655f1a10db1d199ac965184a85b4dc [file] [log] [blame]
Winson Chung499cb9f2010-07-16 11:18:17 -07001/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
Sunny Goyalc12d31c2018-11-12 16:29:18 -080019import static android.widget.RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID;
20import static android.widget.RemoteViews.EXTRA_REMOTEADAPTER_ON_LIGHT_BACKGROUND;
21
Sunny Goyale1273eb2017-10-18 00:17:20 -070022import android.annotation.WorkerThread;
23import android.app.IServiceConnection;
Sunny Goyala086f042016-02-04 15:30:48 -080024import android.appwidget.AppWidgetHostView;
Winson Chung81f39eb2011-01-11 18:05:01 -080025import android.appwidget.AppWidgetManager;
Artur Satayeved5a6ae2019-12-10 17:47:54 +000026import android.compat.annotation.UnsupportedAppUsage;
Sunny Goyale1273eb2017-10-18 00:17:20 -070027import android.content.ComponentName;
Winson Chung499cb9f2010-07-16 11:18:17 -070028import android.content.Context;
29import android.content.Intent;
Sunny Goyale1273eb2017-10-18 00:17:20 -070030import android.content.ServiceConnection;
Sunny Goyal758e88b2019-06-06 16:26:38 -070031import android.content.pm.ActivityInfo;
Sunny Goyaldd60f4d2017-10-18 15:22:42 -070032import android.content.pm.ApplicationInfo;
Sunny Goyal758e88b2019-06-06 16:26:38 -070033import android.content.res.Configuration;
Winson Chung499cb9f2010-07-16 11:18:17 -070034import android.os.Handler;
35import android.os.HandlerThread;
36import android.os.IBinder;
37import android.os.Looper;
Winson Chung81f39eb2011-01-11 18:05:01 -080038import android.os.Message;
Adam Cohen2625fea2011-03-23 17:24:30 -070039import android.os.RemoteException;
Winson Chungfbc35902010-09-09 16:45:06 -070040import android.util.Log;
Sunny Goyal4ea54842016-02-03 16:29:47 -080041import android.util.SparseArray;
42import android.util.SparseBooleanArray;
43import android.util.SparseIntArray;
Winson Chunga5f6f802010-09-29 09:24:21 -070044import android.view.LayoutInflater;
Winson Chung499cb9f2010-07-16 11:18:17 -070045import android.view.View;
Adam Cohen181d2e32011-01-17 12:40:29 -080046import android.view.View.MeasureSpec;
Winson Chung84bbb022011-02-21 13:57:45 -080047import android.view.ViewGroup;
Adam Cohena6a4cbc2012-09-26 17:36:40 -070048import android.widget.RemoteViews.OnClickHandler;
Winson Chung499cb9f2010-07-16 11:18:17 -070049
50import com.android.internal.widget.IRemoteViewsFactory;
51
Aurimas Liutikas99441c52016-10-11 16:48:32 -070052import java.lang.ref.WeakReference;
53import java.util.Arrays;
54import java.util.HashMap;
55import java.util.LinkedList;
Sunny Goyal5c022632016-02-17 16:30:41 -080056import java.util.concurrent.Executor;
57
Winson Chung499cb9f2010-07-16 11:18:17 -070058/**
Sunny Goyale1273eb2017-10-18 00:17:20 -070059 * An adapter to a RemoteViewsService which fetches and caches RemoteViews to be later inflated as
60 * child views.
61 *
62 * The adapter runs in the host process, typically a Launcher app.
63 *
64 * It makes a service connection to the {@link RemoteViewsService} running in the
65 * AppWidgetsProvider's process. This connection is made on a background thread (and proxied via
66 * the platform to get the bind permissions) and all interaction with the service is done on the
67 * background thread.
68 *
69 * On first bind, the adapter will load can cache the RemoteViews locally. Afterwards the
70 * connection is only made when new RemoteViews are required.
71 * @hide
Winson Chung499cb9f2010-07-16 11:18:17 -070072 */
Winson Chung81f39eb2011-01-11 18:05:01 -080073public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback {
Jim Miller3e510532013-03-05 16:24:45 -080074
Winson Chungfbc35902010-09-09 16:45:06 -070075 private static final String TAG = "RemoteViewsAdapter";
Winson Chung499cb9f2010-07-16 11:18:17 -070076
Jim Miller3e510532013-03-05 16:24:45 -080077 // The max number of items in the cache
Sunny Goyale1273eb2017-10-18 00:17:20 -070078 private static final int DEFAULT_CACHE_SIZE = 40;
Winson Chung81f39eb2011-01-11 18:05:01 -080079 // The delay (in millis) to wait until attempting to unbind from a service after a request.
80 // This ensures that we don't stay continually bound to the service and that it can be destroyed
81 // if we need the memory elsewhere in the system.
Sunny Goyale1273eb2017-10-18 00:17:20 -070082 private static final int UNBIND_SERVICE_DELAY = 5000;
Adam Cohenb7ffea62011-07-14 14:45:07 -070083
84 // Default height for the default loading view, in case we cannot get inflate the first view
Sunny Goyale1273eb2017-10-18 00:17:20 -070085 private static final int DEFAULT_LOADING_VIEW_HEIGHT = 50;
Winson Chung499cb9f2010-07-16 11:18:17 -070086
Adam Cohen335c3b62012-07-24 17:18:16 -070087 // We cache the FixedSizeRemoteViewsCaches across orientation. These are the related data
Jim Miller3e510532013-03-05 16:24:45 -080088 // structures;
Sunny Goyal4ea54842016-02-03 16:29:47 -080089 private static final HashMap<RemoteViewsCacheKey, FixedSizeRemoteViewsCache>
90 sCachedRemoteViewsCaches = new HashMap<>();
Amith Yamasaniac2e6dd2012-12-03 13:33:10 -080091 private static final HashMap<RemoteViewsCacheKey, Runnable>
Sunny Goyal4ea54842016-02-03 16:29:47 -080092 sRemoteViewsCacheRemoveRunnables = new HashMap<>();
Amith Yamasaniac2e6dd2012-12-03 13:33:10 -080093
Adam Cohen335c3b62012-07-24 17:18:16 -070094 private static HandlerThread sCacheRemovalThread;
95 private static Handler sCacheRemovalQueue;
96
97 // We keep the cache around for a duration after onSaveInstanceState for use on re-inflation.
98 // If a new RemoteViewsAdapter with the same intent / widget id isn't constructed within this
99 // duration, the cache is dropped.
100 private static final int REMOTE_VIEWS_CACHE_DURATION = 5000;
101
Sunny Goyale1273eb2017-10-18 00:17:20 -0700102 private final Context mContext;
103 private final Intent mIntent;
104 private final int mAppWidgetId;
Sunny Goyalc12d31c2018-11-12 16:29:18 -0800105 private final boolean mOnLightBackground;
Sunny Goyale1273eb2017-10-18 00:17:20 -0700106 private final Executor mAsyncViewLoadExecutor;
107
108 private OnClickHandler mRemoteViewsOnClickHandler;
Mathew Inwood978c6e22018-08-21 15:58:55 +0100109 @UnsupportedAppUsage
Sunny Goyale1273eb2017-10-18 00:17:20 -0700110 private final FixedSizeRemoteViewsCache mCache;
111 private int mVisibleWindowLowerBound;
112 private int mVisibleWindowUpperBound;
113
114 // The set of requested views that are to be notified when the associated RemoteViews are
115 // loaded.
116 private RemoteViewsFrameLayoutRefSet mRequestedViews;
117
Mathew Inwood978c6e22018-08-21 15:58:55 +0100118 @UnsupportedAppUsage
Sunny Goyale1273eb2017-10-18 00:17:20 -0700119 private final HandlerThread mWorkerThread;
120 // items may be interrupted within the normally processed queues
121 private final Handler mMainHandler;
122 private final RemoteServiceHandler mServiceHandler;
123 private final RemoteAdapterConnectionCallback mCallback;
124
Adam Cohen335c3b62012-07-24 17:18:16 -0700125 // Used to indicate to the AdapterView that it can use this Adapter immediately after
126 // construction (happens when we have a cached FixedSizeRemoteViewsCache).
127 private boolean mDataReady = false;
128
Winson Chung499cb9f2010-07-16 11:18:17 -0700129 /**
Sunny Goyaldd60f4d2017-10-18 15:22:42 -0700130 * USed to dedupe {@link RemoteViews#mApplication} so that we do not hold on to
131 * multiple copies of the same ApplicationInfo object.
132 */
133 private ApplicationInfo mLastRemoteViewAppInfo;
134
135 /**
Winson Chung499cb9f2010-07-16 11:18:17 -0700136 * An interface for the RemoteAdapter to notify other classes when adapters
137 * are actually connected to/disconnected from their actual services.
138 */
139 public interface RemoteAdapterConnectionCallback {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800140 /**
141 * @return whether the adapter was set or not.
142 */
Sunny Goyal5c022632016-02-17 16:30:41 -0800143 boolean onRemoteAdapterConnected();
Winson Chung499cb9f2010-07-16 11:18:17 -0700144
Sunny Goyal5c022632016-02-17 16:30:41 -0800145 void onRemoteAdapterDisconnected();
Adam Cohen2148d432011-07-28 14:59:54 -0700146
147 /**
148 * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
149 * connected yet.
150 */
Sunny Goyal5c022632016-02-17 16:30:41 -0800151 void deferNotifyDataSetChanged();
152
153 void setRemoteViewsAdapter(Intent intent, boolean isAsync);
154 }
155
156 public static class AsyncRemoteAdapterAction implements Runnable {
157
158 private final RemoteAdapterConnectionCallback mCallback;
159 private final Intent mIntent;
160
161 public AsyncRemoteAdapterAction(RemoteAdapterConnectionCallback callback, Intent intent) {
162 mCallback = callback;
163 mIntent = intent;
164 }
165
166 @Override
167 public void run() {
168 mCallback.setRemoteViewsAdapter(mIntent, true);
169 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700170 }
171
Sunny Goyale1273eb2017-10-18 00:17:20 -0700172 static final int MSG_REQUEST_BIND = 1;
173 static final int MSG_NOTIFY_DATA_SET_CHANGED = 2;
174 static final int MSG_LOAD_NEXT_ITEM = 3;
175 static final int MSG_UNBIND_SERVICE = 4;
176
177 private static final int MSG_MAIN_HANDLER_COMMIT_METADATA = 1;
178 private static final int MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED = 2;
179 private static final int MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED = 3;
180 private static final int MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED = 4;
181 private static final int MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED = 5;
182
Winson Chung499cb9f2010-07-16 11:18:17 -0700183 /**
Sunny Goyale1273eb2017-10-18 00:17:20 -0700184 * Handler for various interactions with the {@link RemoteViewsService}.
Winson Chung499cb9f2010-07-16 11:18:17 -0700185 */
Sunny Goyale1273eb2017-10-18 00:17:20 -0700186 private static class RemoteServiceHandler extends Handler implements ServiceConnection {
187
188 private final WeakReference<RemoteViewsAdapter> mAdapter;
189 private final Context mContext;
190
Winson Chung499cb9f2010-07-16 11:18:17 -0700191 private IRemoteViewsFactory mRemoteViewsFactory;
Winson Chung499cb9f2010-07-16 11:18:17 -0700192
Sunny Goyale1273eb2017-10-18 00:17:20 -0700193 // The last call to notifyDataSetChanged didn't succeed, try again on next service bind.
194 private boolean mNotifyDataSetChangedPending = false;
195 private boolean mBindRequested = false;
196
197 RemoteServiceHandler(Looper workerLooper, RemoteViewsAdapter adapter, Context context) {
198 super(workerLooper);
199 mAdapter = new WeakReference<>(adapter);
200 mContext = context;
Winson Chung499cb9f2010-07-16 11:18:17 -0700201 }
202
Sunny Goyale1273eb2017-10-18 00:17:20 -0700203 @Override
204 public void onServiceConnected(ComponentName name, IBinder service) {
205 // This is called on the same thread.
Winson Chung16c8d8a2011-01-20 16:19:33 -0800206 mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
Sunny Goyale1273eb2017-10-18 00:17:20 -0700207 enqueueDeferredUnbindServiceMessage();
Winson Chung16c8d8a2011-01-20 16:19:33 -0800208
Sunny Goyale1273eb2017-10-18 00:17:20 -0700209 RemoteViewsAdapter adapter = mAdapter.get();
210 if (adapter == null) {
211 return;
212 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800213
Sunny Goyale1273eb2017-10-18 00:17:20 -0700214 if (mNotifyDataSetChangedPending) {
215 mNotifyDataSetChangedPending = false;
216 Message msg = Message.obtain(this, MSG_NOTIFY_DATA_SET_CHANGED);
217 handleMessage(msg);
218 msg.recycle();
219 } else {
220 if (!sendNotifyDataSetChange(false)) {
221 return;
Winson Chung499cb9f2010-07-16 11:18:17 -0700222 }
Sunny Goyale1273eb2017-10-18 00:17:20 -0700223
224 // Request meta data so that we have up to date data when calling back to
225 // the remote adapter callback
226 adapter.updateTemporaryMetaData(mRemoteViewsFactory);
227 adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_COMMIT_METADATA);
228 adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED);
229 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700230 }
231
Sunny Goyale1273eb2017-10-18 00:17:20 -0700232 @Override
233 public void onServiceDisconnected(ComponentName name) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700234 mRemoteViewsFactory = null;
Sunny Goyale1273eb2017-10-18 00:17:20 -0700235 RemoteViewsAdapter adapter = mAdapter.get();
236 if (adapter != null) {
237 adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED);
238 }
239 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700240
Sunny Goyale1273eb2017-10-18 00:17:20 -0700241 @Override
242 public void handleMessage(Message msg) {
243 RemoteViewsAdapter adapter = mAdapter.get();
Jim Miller3e510532013-03-05 16:24:45 -0800244
Sunny Goyale1273eb2017-10-18 00:17:20 -0700245 switch (msg.what) {
246 case MSG_REQUEST_BIND: {
247 if (adapter == null || mRemoteViewsFactory != null) {
248 enqueueDeferredUnbindServiceMessage();
Winson Chung16c8d8a2011-01-20 16:19:33 -0800249 }
Sunny Goyale1273eb2017-10-18 00:17:20 -0700250 if (mBindRequested) {
251 return;
252 }
253 int flags = Context.BIND_AUTO_CREATE
254 | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE;
255 final IServiceConnection sd = mContext.getServiceDispatcher(this, this, flags);
256 Intent intent = (Intent) msg.obj;
257 int appWidgetId = msg.arg1;
Sunny Goyale3306242018-12-06 08:58:27 -0800258 try {
259 mBindRequested = AppWidgetManager.getInstance(mContext)
260 .bindRemoteViewsService(mContext, appWidgetId, intent, sd, flags);
261 } catch (Exception e) {
262 Log.e(TAG, "Failed to bind remoteViewsService: " + e.getMessage());
263 }
Sunny Goyale1273eb2017-10-18 00:17:20 -0700264 return;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800265 }
Sunny Goyale1273eb2017-10-18 00:17:20 -0700266 case MSG_NOTIFY_DATA_SET_CHANGED: {
267 enqueueDeferredUnbindServiceMessage();
268 if (adapter == null) {
269 return;
270 }
271 if (mRemoteViewsFactory == null) {
272 mNotifyDataSetChangedPending = true;
273 adapter.requestBindService();
274 return;
275 }
276 if (!sendNotifyDataSetChange(true)) {
277 return;
278 }
279
280 // Flush the cache so that we can reload new items from the service
281 synchronized (adapter.mCache) {
282 adapter.mCache.reset();
283 }
284
285 // Re-request the new metadata (only after the notification to the factory)
286 adapter.updateTemporaryMetaData(mRemoteViewsFactory);
287 int newCount;
288 int[] visibleWindow;
289 synchronized (adapter.mCache.getTemporaryMetaData()) {
290 newCount = adapter.mCache.getTemporaryMetaData().count;
291 visibleWindow = adapter.getVisibleWindow(newCount);
292 }
293
294 // Pre-load (our best guess of) the views which are currently visible in the
295 // AdapterView. This mitigates flashing and flickering of loading views when a
296 // widget notifies that its data has changed.
297 for (int position : visibleWindow) {
298 // Because temporary meta data is only ever modified from this thread
299 // (ie. mWorkerThread), it is safe to assume that count is a valid
300 // representation.
301 if (position < newCount) {
302 adapter.updateRemoteViews(mRemoteViewsFactory, position, false);
303 }
304 }
305
306 // Propagate the notification back to the base adapter
307 adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_COMMIT_METADATA);
308 adapter.mMainHandler.sendEmptyMessage(
309 MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED);
310 return;
311 }
312
313 case MSG_LOAD_NEXT_ITEM: {
314 if (adapter == null || mRemoteViewsFactory == null) {
315 return;
316 }
317 removeMessages(MSG_UNBIND_SERVICE);
318 // Get the next index to load
319 final int position = adapter.mCache.getNextIndexToLoad();
320 if (position > -1) {
321 // Load the item, and notify any existing RemoteViewsFrameLayouts
322 adapter.updateRemoteViews(mRemoteViewsFactory, position, true);
323
324 // Queue up for the next one to load
325 sendEmptyMessage(MSG_LOAD_NEXT_ITEM);
326 } else {
327 // No more items to load, so queue unbind
328 enqueueDeferredUnbindServiceMessage();
329 }
330 return;
331 }
332 case MSG_UNBIND_SERVICE: {
333 unbindNow();
334 return;
335 }
336 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700337 }
338
Sunny Goyale1273eb2017-10-18 00:17:20 -0700339 protected void unbindNow() {
340 if (mBindRequested) {
341 mBindRequested = false;
342 mContext.unbindService(this);
343 }
344 mRemoteViewsFactory = null;
Winson Chung499cb9f2010-07-16 11:18:17 -0700345 }
346
Sunny Goyale1273eb2017-10-18 00:17:20 -0700347 private boolean sendNotifyDataSetChange(boolean always) {
348 try {
349 if (always || !mRemoteViewsFactory.isCreated()) {
350 mRemoteViewsFactory.onDataSetChanged();
351 }
352 return true;
353 } catch (RemoteException | RuntimeException e) {
354 Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
355 return false;
356 }
357 }
358
359 private void enqueueDeferredUnbindServiceMessage() {
360 removeMessages(MSG_UNBIND_SERVICE);
361 sendEmptyMessageDelayed(MSG_UNBIND_SERVICE, UNBIND_SERVICE_DELAY);
Winson Chung499cb9f2010-07-16 11:18:17 -0700362 }
363 }
364
365 /**
Winson Chung3ec9a452010-09-23 16:40:28 -0700366 * A FrameLayout which contains a loading view, and manages the re/applying of RemoteViews when
367 * they are loaded.
Winson Chung499cb9f2010-07-16 11:18:17 -0700368 */
Sunny Goyalb880d162016-02-24 14:38:59 -0800369 static class RemoteViewsFrameLayout extends AppWidgetHostView {
Sunny Goyala086f042016-02-04 15:30:48 -0800370 private final FixedSizeRemoteViewsCache mCache;
371
Sunny Goyaldd60f4d2017-10-18 15:22:42 -0700372 public int cacheIndex = -1;
373
Sunny Goyala086f042016-02-04 15:30:48 -0800374 public RemoteViewsFrameLayout(Context context, FixedSizeRemoteViewsCache cache) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700375 super(context);
Sunny Goyala086f042016-02-04 15:30:48 -0800376 mCache = cache;
Winson Chung499cb9f2010-07-16 11:18:17 -0700377 }
378
379 /**
Winson Chung3ec9a452010-09-23 16:40:28 -0700380 * Updates this RemoteViewsFrameLayout depending on the view that was loaded.
381 * @param view the RemoteViews that was loaded. If null, the RemoteViews was not loaded
382 * successfully.
Sunny Goyal5c022632016-02-17 16:30:41 -0800383 * @param forceApplyAsync when true, the host will always try to inflate the view
384 * asynchronously (for eg, when we are already showing the loading
385 * view)
Winson Chung499cb9f2010-07-16 11:18:17 -0700386 */
Sunny Goyal5c022632016-02-17 16:30:41 -0800387 public void onRemoteViewsLoaded(RemoteViews view, OnClickHandler handler,
388 boolean forceApplyAsync) {
Sunny Goyala086f042016-02-04 15:30:48 -0800389 setOnClickHandler(handler);
Sunny Goyal5c022632016-02-17 16:30:41 -0800390 applyRemoteViews(view, forceApplyAsync || ((view != null) && view.prefersAsyncApply()));
Sunny Goyala086f042016-02-04 15:30:48 -0800391 }
392
Sunny Goyal5c022632016-02-17 16:30:41 -0800393 /**
394 * Creates a default loading view. Uses the size of the first row as a guide for the
395 * size of the loading view.
396 */
Sunny Goyala086f042016-02-04 15:30:48 -0800397 @Override
398 protected View getDefaultView() {
Sunny Goyal5c022632016-02-17 16:30:41 -0800399 int viewHeight = mCache.getMetaData().getLoadingTemplate(getContext()).defaultHeight;
400 // Compose the loading view text
401 TextView loadingTextView = (TextView) LayoutInflater.from(getContext()).inflate(
402 com.android.internal.R.layout.remote_views_adapter_default_loading_view,
403 this, false);
404 loadingTextView.setHeight(viewHeight);
405 return loadingTextView;
Sunny Goyala086f042016-02-04 15:30:48 -0800406 }
407
408 @Override
409 protected Context getRemoteContext() {
410 return null;
411 }
412
413 @Override
414 protected View getErrorView() {
415 // Use the default loading view as the error view.
416 return getDefaultView();
Winson Chung3ec9a452010-09-23 16:40:28 -0700417 }
418 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700419
Winson Chung3ec9a452010-09-23 16:40:28 -0700420 /**
421 * Stores the references of all the RemoteViewsFrameLayouts that have been returned by the
422 * adapter that have not yet had their RemoteViews loaded.
423 */
Sunny Goyaldd60f4d2017-10-18 15:22:42 -0700424 private class RemoteViewsFrameLayoutRefSet
425 extends SparseArray<LinkedList<RemoteViewsFrameLayout>> {
Winson Chung3ec9a452010-09-23 16:40:28 -0700426
427 /**
428 * Adds a new reference to a RemoteViewsFrameLayout returned by the adapter.
429 */
430 public void add(int position, RemoteViewsFrameLayout layout) {
Sunny Goyaldd60f4d2017-10-18 15:22:42 -0700431 LinkedList<RemoteViewsFrameLayout> refs = get(position);
Winson Chung3ec9a452010-09-23 16:40:28 -0700432
433 // Create the list if necessary
Sunny Goyal4ea54842016-02-03 16:29:47 -0800434 if (refs == null) {
Sunny Goyaldd60f4d2017-10-18 15:22:42 -0700435 refs = new LinkedList<>();
436 put(position, refs);
Winson Chung499cb9f2010-07-16 11:18:17 -0700437 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700438
439 // Add the references to the list
Sunny Goyaldd60f4d2017-10-18 15:22:42 -0700440 layout.cacheIndex = position;
Winson Chung3ec9a452010-09-23 16:40:28 -0700441 refs.add(layout);
Winson Chung499cb9f2010-07-16 11:18:17 -0700442 }
443
Winson Chung3ec9a452010-09-23 16:40:28 -0700444 /**
445 * Notifies each of the RemoteViewsFrameLayouts associated with a particular position that
446 * the associated RemoteViews has loaded.
447 */
Adam Cohena5a06872012-07-11 15:23:10 -0700448 public void notifyOnRemoteViewsLoaded(int position, RemoteViews view) {
Winson Chung61ac7e32010-09-28 10:08:31 -0700449 if (view == null) return;
450
Sunny Goyaldd60f4d2017-10-18 15:22:42 -0700451 // Remove this set from the original mapping
452 final LinkedList<RemoteViewsFrameLayout> refs = removeReturnOld(position);
Sunny Goyal4ea54842016-02-03 16:29:47 -0800453 if (refs != null) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700454 // Notify all the references for that position of the newly loaded RemoteViews
Winson Chung3ec9a452010-09-23 16:40:28 -0700455 for (final RemoteViewsFrameLayout ref : refs) {
Sunny Goyal5c022632016-02-17 16:30:41 -0800456 ref.onRemoteViewsLoaded(view, mRemoteViewsOnClickHandler, true);
Winson Chung499cb9f2010-07-16 11:18:17 -0700457 }
458 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700459 }
460
Winson Chung3ec9a452010-09-23 16:40:28 -0700461 /**
Adam Cohenff067192012-12-05 18:14:39 -0800462 * We need to remove views from this set if they have been recycled by the AdapterView.
463 */
464 public void removeView(RemoteViewsFrameLayout rvfl) {
Sunny Goyaldd60f4d2017-10-18 15:22:42 -0700465 if (rvfl.cacheIndex < 0) {
466 return;
Adam Cohenff067192012-12-05 18:14:39 -0800467 }
Sunny Goyaldd60f4d2017-10-18 15:22:42 -0700468 final LinkedList<RemoteViewsFrameLayout> refs = get(rvfl.cacheIndex);
469 if (refs != null) {
470 refs.remove(rvfl);
471 }
472 rvfl.cacheIndex = -1;
Winson Chung3ec9a452010-09-23 16:40:28 -0700473 }
474 }
475
476 /**
477 * The meta-data associated with the cache in it's current state.
478 */
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700479 private static class RemoteViewsMetaData {
Winson Chung3ec9a452010-09-23 16:40:28 -0700480 int count;
481 int viewTypeCount;
482 boolean hasStableIds;
Winson Chung3ec9a452010-09-23 16:40:28 -0700483
484 // Used to determine how to construct loading views. If a loading view is not specified
485 // by the user, then we try and load the first view, and use its height as the height for
486 // the default loading view.
Sunny Goyal5c022632016-02-17 16:30:41 -0800487 LoadingViewTemplate loadingTemplate;
Winson Chung3ec9a452010-09-23 16:40:28 -0700488
489 // A mapping from type id to a set of unique type ids
Sunny Goyal4ea54842016-02-03 16:29:47 -0800490 private final SparseIntArray mTypeIdIndexMap = new SparseIntArray();
Winson Chung3ec9a452010-09-23 16:40:28 -0700491
492 public RemoteViewsMetaData() {
493 reset();
Winson Chung499cb9f2010-07-16 11:18:17 -0700494 }
495
Winson Chung16c8d8a2011-01-20 16:19:33 -0800496 public void set(RemoteViewsMetaData d) {
497 synchronized (d) {
498 count = d.count;
499 viewTypeCount = d.viewTypeCount;
500 hasStableIds = d.hasStableIds;
Sunny Goyal5c022632016-02-17 16:30:41 -0800501 loadingTemplate = d.loadingTemplate;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800502 }
503 }
504
Winson Chung3ec9a452010-09-23 16:40:28 -0700505 public void reset() {
506 count = 0;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800507
Winson Chung3ec9a452010-09-23 16:40:28 -0700508 // by default there is at least one dummy view type
509 viewTypeCount = 1;
510 hasStableIds = true;
Sunny Goyal5c022632016-02-17 16:30:41 -0800511 loadingTemplate = null;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800512 mTypeIdIndexMap.clear();
Winson Chung3ec9a452010-09-23 16:40:28 -0700513 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700514
Winson Chung3ec9a452010-09-23 16:40:28 -0700515 public int getMappedViewType(int typeId) {
Sunny Goyal4ea54842016-02-03 16:29:47 -0800516 int mappedTypeId = mTypeIdIndexMap.get(typeId, -1);
517 if (mappedTypeId == -1) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700518 // We +1 because the loading view always has view type id of 0
Sunny Goyal4ea54842016-02-03 16:29:47 -0800519 mappedTypeId = mTypeIdIndexMap.size() + 1;
520 mTypeIdIndexMap.put(typeId, mappedTypeId);
Winson Chung3ec9a452010-09-23 16:40:28 -0700521 }
Sunny Goyal4ea54842016-02-03 16:29:47 -0800522 return mappedTypeId;
Winson Chung3ec9a452010-09-23 16:40:28 -0700523 }
524
Adam Cohena5a06872012-07-11 15:23:10 -0700525 public boolean isViewTypeInRange(int typeId) {
526 int mappedType = getMappedViewType(typeId);
Sunny Goyal4ea54842016-02-03 16:29:47 -0800527 return (mappedType < viewTypeCount);
Adam Cohena5a06872012-07-11 15:23:10 -0700528 }
529
Sunny Goyal5c022632016-02-17 16:30:41 -0800530 public synchronized LoadingViewTemplate getLoadingTemplate(Context context) {
531 if (loadingTemplate == null) {
532 loadingTemplate = new LoadingViewTemplate(null, context);
Winson Chung499cb9f2010-07-16 11:18:17 -0700533 }
Sunny Goyal5c022632016-02-17 16:30:41 -0800534 return loadingTemplate;
Winson Chung3ec9a452010-09-23 16:40:28 -0700535 }
536 }
537
538 /**
539 * The meta-data associated with a single item in the cache.
540 */
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700541 private static class RemoteViewsIndexMetaData {
Winson Chung3ec9a452010-09-23 16:40:28 -0700542 int typeId;
543 long itemId;
544
Adam Cohen591ff972012-07-24 22:46:11 -0700545 public RemoteViewsIndexMetaData(RemoteViews v, long itemId) {
546 set(v, itemId);
Winson Chung3ec9a452010-09-23 16:40:28 -0700547 }
548
Adam Cohen591ff972012-07-24 22:46:11 -0700549 public void set(RemoteViews v, long id) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700550 itemId = id;
Adam Cohena5a06872012-07-11 15:23:10 -0700551 if (v != null) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700552 typeId = v.getLayoutId();
Adam Cohena5a06872012-07-11 15:23:10 -0700553 } else {
Winson Chung3ec9a452010-09-23 16:40:28 -0700554 typeId = 0;
Adam Cohena5a06872012-07-11 15:23:10 -0700555 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700556 }
557 }
558
559 /**
Sunny Goyal758e88b2019-06-06 16:26:38 -0700560 * Config diff flags for which the cache should be reset
561 */
562 private static final int CACHE_RESET_CONFIG_FLAGS = ActivityInfo.CONFIG_FONT_SCALE
563 | ActivityInfo.CONFIG_UI_MODE | ActivityInfo.CONFIG_DENSITY
564 | ActivityInfo.CONFIG_ASSETS_PATHS;
565 /**
Winson Chung3ec9a452010-09-23 16:40:28 -0700566 *
567 */
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700568 private static class FixedSizeRemoteViewsCache {
Winson Chung3ec9a452010-09-23 16:40:28 -0700569
570 // The meta data related to all the RemoteViews, ie. count, is stable, etc.
Adam Cohen4c994982012-04-02 13:30:32 -0700571 // The meta data objects are made final so that they can be locked on independently
572 // of the FixedSizeRemoteViewsCache. If we ever lock on both meta data objects, it is in
573 // the order mTemporaryMetaData followed by mMetaData.
Sunny Goyal4ea54842016-02-03 16:29:47 -0800574 private final RemoteViewsMetaData mMetaData = new RemoteViewsMetaData();
575 private final RemoteViewsMetaData mTemporaryMetaData = new RemoteViewsMetaData();
Winson Chung3ec9a452010-09-23 16:40:28 -0700576
577 // The cache/mapping of position to RemoteViewsMetaData. This set is guaranteed to be
578 // greater than or equal to the set of RemoteViews.
579 // Note: The reason that we keep this separate from the RemoteViews cache below is that this
580 // we still need to be able to access the mapping of position to meta data, without keeping
581 // the heavy RemoteViews around. The RemoteViews cache is trimmed to fixed constraints wrt.
582 // memory and size, but this metadata cache will retain information until the data at the
583 // position is guaranteed as not being necessary any more (usually on notifyDataSetChanged).
Sunny Goyal4ea54842016-02-03 16:29:47 -0800584 private final SparseArray<RemoteViewsIndexMetaData> mIndexMetaData = new SparseArray<>();
Winson Chung3ec9a452010-09-23 16:40:28 -0700585
586 // The cache of actual RemoteViews, which may be pruned if the cache gets too large, or uses
587 // too much memory.
Sunny Goyal4ea54842016-02-03 16:29:47 -0800588 private final SparseArray<RemoteViews> mIndexRemoteViews = new SparseArray<>();
Winson Chung3ec9a452010-09-23 16:40:28 -0700589
Sunny Goyaldd60f4d2017-10-18 15:22:42 -0700590 // An array of indices to load, Indices which are explicitly requested are set to true,
Sunny Goyal4ea54842016-02-03 16:29:47 -0800591 // and those determined by the preloading algorithm to prefetch are set to false.
592 private final SparseBooleanArray mIndicesToLoad = new SparseBooleanArray();
Winson Chung3ec9a452010-09-23 16:40:28 -0700593
Winson Chungb90a91c2011-01-26 13:36:34 -0800594 // We keep a reference of the last requested index to determine which item to prune the
595 // farthest items from when we hit the memory limit
596 private int mLastRequestedIndex;
597
Winson Chung3ec9a452010-09-23 16:40:28 -0700598 // The lower and upper bounds of the preloaded range
599 private int mPreloadLowerBound;
600 private int mPreloadUpperBound;
601
602 // The bounds of this fixed cache, we will try and fill as many items into the cache up to
603 // the maxCount number of items, or the maxSize memory usage.
604 // The maxCountSlack is used to determine if a new position in the cache to be loaded is
605 // sufficiently ouside the old set, prompting a shifting of the "window" of items to be
606 // preloaded.
Sunny Goyal4ea54842016-02-03 16:29:47 -0800607 private final int mMaxCount;
608 private final int mMaxCountSlack;
Winson Chung3ec9a452010-09-23 16:40:28 -0700609 private static final float sMaxCountSlackPercent = 0.75f;
Winson Chungb90a91c2011-01-26 13:36:34 -0800610 private static final int sMaxMemoryLimitInBytes = 2 * 1024 * 1024;
Winson Chung3ec9a452010-09-23 16:40:28 -0700611
Sunny Goyal758e88b2019-06-06 16:26:38 -0700612 // Configuration for which the cache was created
613 private final Configuration mConfiguration;
614
615 FixedSizeRemoteViewsCache(int maxCacheSize, Configuration configuration) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700616 mMaxCount = maxCacheSize;
617 mMaxCountSlack = Math.round(sMaxCountSlackPercent * (mMaxCount / 2));
618 mPreloadLowerBound = 0;
619 mPreloadUpperBound = -1;
Winson Chungb90a91c2011-01-26 13:36:34 -0800620 mLastRequestedIndex = -1;
Sunny Goyal758e88b2019-06-06 16:26:38 -0700621
622 mConfiguration = new Configuration(configuration);
Winson Chung3ec9a452010-09-23 16:40:28 -0700623 }
624
Sunny Goyal4ea54842016-02-03 16:29:47 -0800625 public void insert(int position, RemoteViews v, long itemId, int[] visibleWindow) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700626 // Trim the cache if we go beyond the count
627 if (mIndexRemoteViews.size() >= mMaxCount) {
Adam Cohen591ff972012-07-24 22:46:11 -0700628 mIndexRemoteViews.remove(getFarthestPositionFrom(position, visibleWindow));
Winson Chung499cb9f2010-07-16 11:18:17 -0700629 }
630
Winson Chung3ec9a452010-09-23 16:40:28 -0700631 // Trim the cache if we go beyond the available memory size constraints
Winson Chungb90a91c2011-01-26 13:36:34 -0800632 int pruneFromPosition = (mLastRequestedIndex > -1) ? mLastRequestedIndex : position;
633 while (getRemoteViewsBitmapMemoryUsage() >= sMaxMemoryLimitInBytes) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700634 // Note: This is currently the most naive mechanism for deciding what to prune when
635 // we hit the memory limit. In the future, we may want to calculate which index to
636 // remove based on both its position as well as it's current memory usage, as well
637 // as whether it was directly requested vs. whether it was preloaded by our caching
638 // mechanism.
Henrik Engström6d068252012-04-24 15:30:19 +0200639 int trimIndex = getFarthestPositionFrom(pruneFromPosition, visibleWindow);
640
641 // Need to check that this is a valid index, to cover the case where you have only
642 // a single view in the cache, but it's larger than the max memory limit
643 if (trimIndex < 0) {
644 break;
645 }
646
647 mIndexRemoteViews.remove(trimIndex);
Winson Chung3ec9a452010-09-23 16:40:28 -0700648 }
649
650 // Update the metadata cache
Sunny Goyal4ea54842016-02-03 16:29:47 -0800651 final RemoteViewsIndexMetaData metaData = mIndexMetaData.get(position);
652 if (metaData != null) {
Adam Cohen591ff972012-07-24 22:46:11 -0700653 metaData.set(v, itemId);
Winson Chung3ec9a452010-09-23 16:40:28 -0700654 } else {
Adam Cohen591ff972012-07-24 22:46:11 -0700655 mIndexMetaData.put(position, new RemoteViewsIndexMetaData(v, itemId));
Winson Chung3ec9a452010-09-23 16:40:28 -0700656 }
657 mIndexRemoteViews.put(position, v);
658 }
659
660 public RemoteViewsMetaData getMetaData() {
661 return mMetaData;
662 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800663 public RemoteViewsMetaData getTemporaryMetaData() {
664 return mTemporaryMetaData;
665 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700666 public RemoteViews getRemoteViewsAt(int position) {
Sunny Goyal4ea54842016-02-03 16:29:47 -0800667 return mIndexRemoteViews.get(position);
Winson Chung3ec9a452010-09-23 16:40:28 -0700668 }
669 public RemoteViewsIndexMetaData getMetaDataAt(int position) {
Sunny Goyal4ea54842016-02-03 16:29:47 -0800670 return mIndexMetaData.get(position);
Winson Chung499cb9f2010-07-16 11:18:17 -0700671 }
672
Winson Chung16c8d8a2011-01-20 16:19:33 -0800673 public void commitTemporaryMetaData() {
674 synchronized (mTemporaryMetaData) {
675 synchronized (mMetaData) {
676 mMetaData.set(mTemporaryMetaData);
677 }
678 }
679 }
680
Winson Chung3ec9a452010-09-23 16:40:28 -0700681 private int getRemoteViewsBitmapMemoryUsage() {
682 // Calculate the memory usage of all the RemoteViews bitmaps being cached
683 int mem = 0;
Sunny Goyal4ea54842016-02-03 16:29:47 -0800684 for (int i = mIndexRemoteViews.size() - 1; i >= 0; i--) {
685 final RemoteViews v = mIndexRemoteViews.valueAt(i);
Winson Chungaaffa8b2010-10-30 14:04:05 -0700686 if (v != null) {
Adam Cohen5d200642012-04-24 10:43:31 -0700687 mem += v.estimateMemoryUsage();
Winson Chungaaffa8b2010-10-30 14:04:05 -0700688 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700689 }
690 return mem;
691 }
Adam Cohen591ff972012-07-24 22:46:11 -0700692
Sunny Goyal4ea54842016-02-03 16:29:47 -0800693 private int getFarthestPositionFrom(int pos, int[] visibleWindow) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700694 // Find the index farthest away and remove that
695 int maxDist = 0;
696 int maxDistIndex = -1;
Adam Cohen591ff972012-07-24 22:46:11 -0700697 int maxDistNotVisible = 0;
698 int maxDistIndexNotVisible = -1;
Sunny Goyal4ea54842016-02-03 16:29:47 -0800699 for (int i = mIndexRemoteViews.size() - 1; i >= 0; i--) {
700 int index = mIndexRemoteViews.keyAt(i);
701 int dist = Math.abs(index-pos);
702 if (dist > maxDistNotVisible && Arrays.binarySearch(visibleWindow, index) < 0) {
Adam Cohen591ff972012-07-24 22:46:11 -0700703 // maxDistNotVisible/maxDistIndexNotVisible will store the index of the
704 // farthest non-visible position
Sunny Goyal4ea54842016-02-03 16:29:47 -0800705 maxDistIndexNotVisible = index;
Adam Cohen591ff972012-07-24 22:46:11 -0700706 maxDistNotVisible = dist;
Winson Chungb90a91c2011-01-26 13:36:34 -0800707 }
Adam Cohen35fbe2a2012-05-22 14:10:14 -0700708 if (dist >= maxDist) {
Winson Chungb90a91c2011-01-26 13:36:34 -0800709 // maxDist/maxDistIndex will store the index of the farthest position
Adam Cohen591ff972012-07-24 22:46:11 -0700710 // regardless of whether it is visible or not
Sunny Goyal4ea54842016-02-03 16:29:47 -0800711 maxDistIndex = index;
Winson Chung3ec9a452010-09-23 16:40:28 -0700712 maxDist = dist;
Winson Chung499cb9f2010-07-16 11:18:17 -0700713 }
714 }
Adam Cohen591ff972012-07-24 22:46:11 -0700715 if (maxDistIndexNotVisible > -1) {
716 return maxDistIndexNotVisible;
Winson Chungb90a91c2011-01-26 13:36:34 -0800717 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700718 return maxDistIndex;
Winson Chung499cb9f2010-07-16 11:18:17 -0700719 }
720
Winson Chung3ec9a452010-09-23 16:40:28 -0700721 public void queueRequestedPositionToLoad(int position) {
Winson Chungb90a91c2011-01-26 13:36:34 -0800722 mLastRequestedIndex = position;
Sunny Goyal4ea54842016-02-03 16:29:47 -0800723 synchronized (mIndicesToLoad) {
724 mIndicesToLoad.put(position, true);
Winson Chung3ec9a452010-09-23 16:40:28 -0700725 }
726 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800727 public boolean queuePositionsToBePreloadedFromRequestedPosition(int position) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700728 // Check if we need to preload any items
729 if (mPreloadLowerBound <= position && position <= mPreloadUpperBound) {
730 int center = (mPreloadUpperBound + mPreloadLowerBound) / 2;
731 if (Math.abs(position - center) < mMaxCountSlack) {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800732 return false;
Winson Chung499cb9f2010-07-16 11:18:17 -0700733 }
734 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700735
Sunny Goyale1273eb2017-10-18 00:17:20 -0700736 int count;
Winson Chung3ec9a452010-09-23 16:40:28 -0700737 synchronized (mMetaData) {
738 count = mMetaData.count;
Winson Chungc6d6d4a2010-07-22 13:54:27 -0700739 }
Sunny Goyal4ea54842016-02-03 16:29:47 -0800740 synchronized (mIndicesToLoad) {
741 // Remove all indices which have not been previously requested.
742 for (int i = mIndicesToLoad.size() - 1; i >= 0; i--) {
743 if (!mIndicesToLoad.valueAt(i)) {
744 mIndicesToLoad.removeAt(i);
745 }
746 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700747
Winson Chung3ec9a452010-09-23 16:40:28 -0700748 // Add all the preload indices
749 int halfMaxCount = mMaxCount / 2;
750 mPreloadLowerBound = position - halfMaxCount;
751 mPreloadUpperBound = position + halfMaxCount;
752 int effectiveLowerBound = Math.max(0, mPreloadLowerBound);
753 int effectiveUpperBound = Math.min(mPreloadUpperBound, count - 1);
754 for (int i = effectiveLowerBound; i <= effectiveUpperBound; ++i) {
Sunny Goyal4ea54842016-02-03 16:29:47 -0800755 if (mIndexRemoteViews.indexOfKey(i) < 0 && !mIndicesToLoad.get(i)) {
756 // If the index has not been requested, and has not been loaded.
757 mIndicesToLoad.put(i, false);
758 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700759 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700760 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800761 return true;
Winson Chung3ec9a452010-09-23 16:40:28 -0700762 }
Sunny Goyal4ea54842016-02-03 16:29:47 -0800763 /** Returns the next index to load */
764 public int getNextIndexToLoad() {
Winson Chung3ec9a452010-09-23 16:40:28 -0700765 // We try and prioritize items that have been requested directly, instead
766 // of items that are loaded as a result of the caching mechanism
Sunny Goyal4ea54842016-02-03 16:29:47 -0800767 synchronized (mIndicesToLoad) {
Winson Chung3ec9a452010-09-23 16:40:28 -0700768 // Prioritize requested indices to be loaded first
Sunny Goyal4ea54842016-02-03 16:29:47 -0800769 int index = mIndicesToLoad.indexOfValue(true);
770 if (index < 0) {
771 // Otherwise, preload other indices as necessary
772 index = mIndicesToLoad.indexOfValue(false);
Winson Chung3ec9a452010-09-23 16:40:28 -0700773 }
Sunny Goyal4ea54842016-02-03 16:29:47 -0800774 if (index < 0) {
775 return -1;
776 } else {
777 int key = mIndicesToLoad.keyAt(index);
778 mIndicesToLoad.removeAt(index);
779 return key;
Winson Chung3ec9a452010-09-23 16:40:28 -0700780 }
Winson Chung3ec9a452010-09-23 16:40:28 -0700781 }
782 }
783
784 public boolean containsRemoteViewAt(int position) {
Sunny Goyal4ea54842016-02-03 16:29:47 -0800785 return mIndexRemoteViews.indexOfKey(position) >= 0;
Winson Chung3ec9a452010-09-23 16:40:28 -0700786 }
787 public boolean containsMetaDataAt(int position) {
Sunny Goyal4ea54842016-02-03 16:29:47 -0800788 return mIndexMetaData.indexOfKey(position) >= 0;
Winson Chung3ec9a452010-09-23 16:40:28 -0700789 }
790
791 public void reset() {
Winson Chung61ac7e32010-09-28 10:08:31 -0700792 // Note: We do not try and reset the meta data, since that information is still used by
793 // collection views to validate it's own contents (and will be re-requested if the data
794 // is invalidated through the notifyDataSetChanged() flow).
795
Winson Chung3ec9a452010-09-23 16:40:28 -0700796 mPreloadLowerBound = 0;
797 mPreloadUpperBound = -1;
Winson Chungb90a91c2011-01-26 13:36:34 -0800798 mLastRequestedIndex = -1;
Winson Chung3ec9a452010-09-23 16:40:28 -0700799 mIndexRemoteViews.clear();
800 mIndexMetaData.clear();
Sunny Goyal4ea54842016-02-03 16:29:47 -0800801 synchronized (mIndicesToLoad) {
802 mIndicesToLoad.clear();
Winson Chung499cb9f2010-07-16 11:18:17 -0700803 }
804 }
805 }
806
Amith Yamasaniac2e6dd2012-12-03 13:33:10 -0800807 static class RemoteViewsCacheKey {
808 final Intent.FilterComparison filter;
809 final int widgetId;
Amith Yamasaniac2e6dd2012-12-03 13:33:10 -0800810
Svetoslav976e8bd2014-07-16 15:12:03 -0700811 RemoteViewsCacheKey(Intent.FilterComparison filter, int widgetId) {
Amith Yamasaniac2e6dd2012-12-03 13:33:10 -0800812 this.filter = filter;
813 this.widgetId = widgetId;
Amith Yamasaniac2e6dd2012-12-03 13:33:10 -0800814 }
815
816 @Override
817 public boolean equals(Object o) {
818 if (!(o instanceof RemoteViewsCacheKey)) {
819 return false;
820 }
821 RemoteViewsCacheKey other = (RemoteViewsCacheKey) o;
Svetoslav976e8bd2014-07-16 15:12:03 -0700822 return other.filter.equals(filter) && other.widgetId == widgetId;
Amith Yamasaniac2e6dd2012-12-03 13:33:10 -0800823 }
824
825 @Override
826 public int hashCode() {
Svetoslav976e8bd2014-07-16 15:12:03 -0700827 return (filter == null ? 0 : filter.hashCode()) ^ (widgetId << 2);
Amith Yamasaniac2e6dd2012-12-03 13:33:10 -0800828 }
829 }
830
Svetoslav976e8bd2014-07-16 15:12:03 -0700831 public RemoteViewsAdapter(Context context, Intent intent,
Sunny Goyal5c022632016-02-17 16:30:41 -0800832 RemoteAdapterConnectionCallback callback, boolean useAsyncLoader) {
Winson Chung499cb9f2010-07-16 11:18:17 -0700833 mContext = context;
834 mIntent = intent;
Svetoslav976e8bd2014-07-16 15:12:03 -0700835
Winson Chung9b3a2cf2010-09-16 14:45:32 -0700836 if (mIntent == null) {
837 throw new IllegalArgumentException("Non-null Intent must be specified.");
838 }
Henrik Baard68d26be2013-07-11 10:42:26 +0200839
Sunny Goyalc12d31c2018-11-12 16:29:18 -0800840 mAppWidgetId = intent.getIntExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, -1);
Winson Chung3ec9a452010-09-23 16:40:28 -0700841 mRequestedViews = new RemoteViewsFrameLayoutRefSet();
Sunny Goyalc12d31c2018-11-12 16:29:18 -0800842 mOnLightBackground = intent.getBooleanExtra(EXTRA_REMOTEADAPTER_ON_LIGHT_BACKGROUND, false);
Winson Chung499cb9f2010-07-16 11:18:17 -0700843
Winson Chung81f39eb2011-01-11 18:05:01 -0800844 // Strip the previously injected app widget id from service intent
Sunny Goyalc12d31c2018-11-12 16:29:18 -0800845 intent.removeExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID);
846 intent.removeExtra(EXTRA_REMOTEADAPTER_ON_LIGHT_BACKGROUND);
Winson Chung81f39eb2011-01-11 18:05:01 -0800847
848 // Initialize the worker thread
Winson Chung499cb9f2010-07-16 11:18:17 -0700849 mWorkerThread = new HandlerThread("RemoteViewsCache-loader");
850 mWorkerThread.start();
Sunny Goyale1273eb2017-10-18 00:17:20 -0700851 mMainHandler = new Handler(Looper.myLooper(), this);
852 mServiceHandler = new RemoteServiceHandler(mWorkerThread.getLooper(), this,
853 context.getApplicationContext());
Sunny Goyal5c022632016-02-17 16:30:41 -0800854 mAsyncViewLoadExecutor = useAsyncLoader ? new HandlerThreadExecutor(mWorkerThread) : null;
Sunny Goyale1273eb2017-10-18 00:17:20 -0700855 mCallback = callback;
Winson Chung499cb9f2010-07-16 11:18:17 -0700856
Adam Cohen335c3b62012-07-24 17:18:16 -0700857 if (sCacheRemovalThread == null) {
858 sCacheRemovalThread = new HandlerThread("RemoteViewsAdapter-cachePruner");
859 sCacheRemovalThread.start();
860 sCacheRemovalQueue = new Handler(sCacheRemovalThread.getLooper());
861 }
862
Amith Yamasaniac2e6dd2012-12-03 13:33:10 -0800863 RemoteViewsCacheKey key = new RemoteViewsCacheKey(new Intent.FilterComparison(mIntent),
Svetoslav976e8bd2014-07-16 15:12:03 -0700864 mAppWidgetId);
Adam Cohen335c3b62012-07-24 17:18:16 -0700865
866 synchronized(sCachedRemoteViewsCaches) {
Sunny Goyal758e88b2019-06-06 16:26:38 -0700867 FixedSizeRemoteViewsCache cache = sCachedRemoteViewsCaches.get(key);
868 Configuration config = context.getResources().getConfiguration();
869 if (cache == null
870 || (cache.mConfiguration.diff(config) & CACHE_RESET_CONFIG_FLAGS) != 0) {
871 mCache = new FixedSizeRemoteViewsCache(DEFAULT_CACHE_SIZE, config);
872 } else {
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700873 mCache = sCachedRemoteViewsCaches.get(key);
874 synchronized (mCache.mMetaData) {
875 if (mCache.mMetaData.count > 0) {
876 // As a precautionary measure, we verify that the meta data indicates a
877 // non-zero count before declaring that data is ready.
878 mDataReady = true;
879 }
880 }
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700881 }
882 if (!mDataReady) {
Adam Cohen335c3b62012-07-24 17:18:16 -0700883 requestBindService();
884 }
885 }
Winson Chung499cb9f2010-07-16 11:18:17 -0700886 }
887
Jeff Brownfc442bd2011-06-10 21:34:48 -0700888 @Override
889 protected void finalize() throws Throwable {
890 try {
Sunny Goyale1273eb2017-10-18 00:17:20 -0700891 mServiceHandler.unbindNow();
892 mWorkerThread.quit();
Jeff Brownfc442bd2011-06-10 21:34:48 -0700893 } finally {
894 super.finalize();
895 }
896 }
897
Mathew Inwood978c6e22018-08-21 15:58:55 +0100898 @UnsupportedAppUsage
Adam Cohen335c3b62012-07-24 17:18:16 -0700899 public boolean isDataReady() {
900 return mDataReady;
901 }
902
Mathew Inwood978c6e22018-08-21 15:58:55 +0100903 @UnsupportedAppUsage
Adam Cohena6a4cbc2012-09-26 17:36:40 -0700904 public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
905 mRemoteViewsOnClickHandler = handler;
906 }
907
Mathew Inwood978c6e22018-08-21 15:58:55 +0100908 @UnsupportedAppUsage
Adam Cohen335c3b62012-07-24 17:18:16 -0700909 public void saveRemoteViewsCache() {
Amith Yamasaniac2e6dd2012-12-03 13:33:10 -0800910 final RemoteViewsCacheKey key = new RemoteViewsCacheKey(
Svetoslav976e8bd2014-07-16 15:12:03 -0700911 new Intent.FilterComparison(mIntent), mAppWidgetId);
Adam Cohen335c3b62012-07-24 17:18:16 -0700912
913 synchronized(sCachedRemoteViewsCaches) {
914 // If we already have a remove runnable posted for this key, remove it.
915 if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
916 sCacheRemovalQueue.removeCallbacks(sRemoteViewsCacheRemoveRunnables.get(key));
917 sRemoteViewsCacheRemoveRunnables.remove(key);
918 }
919
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700920 int metaDataCount = 0;
921 int numRemoteViewsCached = 0;
922 synchronized (mCache.mMetaData) {
923 metaDataCount = mCache.mMetaData.count;
Adam Cohen335c3b62012-07-24 17:18:16 -0700924 }
Adam Cohen4a9df8d2012-07-27 15:35:13 -0700925 synchronized (mCache) {
926 numRemoteViewsCached = mCache.mIndexRemoteViews.size();
927 }
928 if (metaDataCount > 0 && numRemoteViewsCached > 0) {
929 sCachedRemoteViewsCaches.put(key, mCache);
930 }
931
Sunny Goyale1273eb2017-10-18 00:17:20 -0700932 Runnable r = () -> {
933 synchronized (sCachedRemoteViewsCaches) {
934 if (sCachedRemoteViewsCaches.containsKey(key)) {
935 sCachedRemoteViewsCaches.remove(key);
936 }
937 if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
938 sRemoteViewsCacheRemoveRunnables.remove(key);
Adam Cohen335c3b62012-07-24 17:18:16 -0700939 }
940 }
941 };
942 sRemoteViewsCacheRemoveRunnables.put(key, r);
943 sCacheRemovalQueue.postDelayed(r, REMOTE_VIEWS_CACHE_DURATION);
944 }
945 }
946
Sunny Goyale1273eb2017-10-18 00:17:20 -0700947 @WorkerThread
948 private void updateTemporaryMetaData(IRemoteViewsFactory factory) {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800949 try {
950 // get the properties/first view (so that we can use it to
951 // measure our dummy views)
952 boolean hasStableIds = factory.hasStableIds();
953 int viewTypeCount = factory.getViewTypeCount();
954 int count = factory.getCount();
Sunny Goyal5c022632016-02-17 16:30:41 -0800955 LoadingViewTemplate loadingTemplate =
956 new LoadingViewTemplate(factory.getLoadingView(), mContext);
957 if ((count > 0) && (loadingTemplate.remoteViews == null)) {
958 RemoteViews firstView = factory.getViewAt(0);
959 if (firstView != null) {
960 loadingTemplate.loadFirstViewHeight(firstView, mContext,
961 new HandlerThreadExecutor(mWorkerThread));
962 }
Winson Chung16c8d8a2011-01-20 16:19:33 -0800963 }
964 final RemoteViewsMetaData tmpMetaData = mCache.getTemporaryMetaData();
965 synchronized (tmpMetaData) {
966 tmpMetaData.hasStableIds = hasStableIds;
967 // We +1 because the base view type is the loading view
968 tmpMetaData.viewTypeCount = viewTypeCount + 1;
969 tmpMetaData.count = count;
Sunny Goyal5c022632016-02-17 16:30:41 -0800970 tmpMetaData.loadingTemplate = loadingTemplate;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800971 }
Sunny Goyale1273eb2017-10-18 00:17:20 -0700972 } catch (RemoteException | RuntimeException e) {
973 Log.e("RemoteViewsAdapter", "Error in updateMetaData: " + e.getMessage());
974
975 // If we encounter a crash when updating, we should reset the metadata & cache
976 // and trigger a notifyDataSetChanged to update the widget accordingly
977 synchronized (mCache.getMetaData()) {
978 mCache.getMetaData().reset();
979 }
980 synchronized (mCache) {
981 mCache.reset();
982 }
983 mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED);
Winson Chung3ec9a452010-09-23 16:40:28 -0700984 }
985 }
986
Sunny Goyale1273eb2017-10-18 00:17:20 -0700987 @WorkerThread
988 private void updateRemoteViews(IRemoteViewsFactory factory, int position,
989 boolean notifyWhenLoaded) {
Winson Chung16c8d8a2011-01-20 16:19:33 -0800990 // Load the item information from the remote service
Sunny Goyale1273eb2017-10-18 00:17:20 -0700991 final RemoteViews remoteViews;
992 final long itemId;
Winson Chung16c8d8a2011-01-20 16:19:33 -0800993 try {
994 remoteViews = factory.getViewAt(position);
995 itemId = factory.getItemId(position);
Sunny Goyale1273eb2017-10-18 00:17:20 -0700996
997 if (remoteViews == null) {
998 throw new RuntimeException("Null remoteViews");
999 }
1000 } catch (RemoteException | RuntimeException e) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001001 Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
Winson Chung3ec9a452010-09-23 16:40:28 -07001002
Winson Chung16c8d8a2011-01-20 16:19:33 -08001003 // Return early to prevent additional work in re-centering the view cache, and
1004 // swapping from the loading view
1005 return;
Winson Chung16c8d8a2011-01-20 16:19:33 -08001006 }
Winson Chung3ec9a452010-09-23 16:40:28 -07001007
Sunny Goyaldd60f4d2017-10-18 15:22:42 -07001008 if (remoteViews.mApplication != null) {
1009 // We keep track of last application info. This helps when all the remoteViews have
1010 // same applicationInfo, which should be the case for a typical adapter. But if every
1011 // view has different application info, there will not be any optimization.
1012 if (mLastRemoteViewAppInfo != null
1013 && remoteViews.hasSameAppInfo(mLastRemoteViewAppInfo)) {
1014 // We should probably also update the remoteViews for nested ViewActions.
1015 // Hopefully, RemoteViews in an adapter would be less complicated.
1016 remoteViews.mApplication = mLastRemoteViewAppInfo;
1017 } else {
1018 mLastRemoteViewAppInfo = remoteViews.mApplication;
1019 }
1020 }
1021
Adam Cohena5a06872012-07-11 15:23:10 -07001022 int layoutId = remoteViews.getLayoutId();
1023 RemoteViewsMetaData metaData = mCache.getMetaData();
1024 boolean viewTypeInRange;
Adam Cohen591ff972012-07-24 22:46:11 -07001025 int cacheCount;
Adam Cohena5a06872012-07-11 15:23:10 -07001026 synchronized (metaData) {
1027 viewTypeInRange = metaData.isViewTypeInRange(layoutId);
Adam Cohen591ff972012-07-24 22:46:11 -07001028 cacheCount = mCache.mMetaData.count;
Adam Cohena5a06872012-07-11 15:23:10 -07001029 }
1030 synchronized (mCache) {
1031 if (viewTypeInRange) {
Sunny Goyale1273eb2017-10-18 00:17:20 -07001032 int[] visibleWindow = getVisibleWindow(cacheCount);
Adam Cohena5a06872012-07-11 15:23:10 -07001033 // Cache the RemoteViews we loaded
Adam Cohen591ff972012-07-24 22:46:11 -07001034 mCache.insert(position, remoteViews, itemId, visibleWindow);
Adam Cohena5a06872012-07-11 15:23:10 -07001035
Adam Cohena5a06872012-07-11 15:23:10 -07001036 if (notifyWhenLoaded) {
Sunny Goyale1273eb2017-10-18 00:17:20 -07001037 // Notify all the views that we have previously returned for this index that
1038 // there is new data for it.
1039 Message.obtain(mMainHandler, MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED, position, 0,
1040 remoteViews).sendToTarget();
Adam Cohena5a06872012-07-11 15:23:10 -07001041 }
1042 } else {
1043 // We need to log an error here, as the the view type count specified by the
1044 // factory is less than the number of view types returned. We don't return this
1045 // view to the AdapterView, as this will cause an exception in the hosting process,
1046 // which contains the associated AdapterView.
1047 Log.e(TAG, "Error: widget's RemoteViewsFactory returns more view types than " +
1048 " indicated by getViewTypeCount() ");
Adam Cohenb9673922012-01-05 13:58:47 -08001049 }
Winson Chung3ec9a452010-09-23 16:40:28 -07001050 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001051 }
1052
Mathew Inwood978c6e22018-08-21 15:58:55 +01001053 @UnsupportedAppUsage
Winson Chung9b3a2cf2010-09-16 14:45:32 -07001054 public Intent getRemoteViewsServiceIntent() {
1055 return mIntent;
1056 }
1057
Winson Chung499cb9f2010-07-16 11:18:17 -07001058 public int getCount() {
Winson Chung3ec9a452010-09-23 16:40:28 -07001059 final RemoteViewsMetaData metaData = mCache.getMetaData();
1060 synchronized (metaData) {
1061 return metaData.count;
1062 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001063 }
1064
1065 public Object getItem(int position) {
Winson Chung3ec9a452010-09-23 16:40:28 -07001066 // Disallow arbitrary object to be associated with an item for the time being
Winson Chung499cb9f2010-07-16 11:18:17 -07001067 return null;
1068 }
1069
1070 public long getItemId(int position) {
Winson Chung3ec9a452010-09-23 16:40:28 -07001071 synchronized (mCache) {
1072 if (mCache.containsMetaDataAt(position)) {
1073 return mCache.getMetaDataAt(position).itemId;
1074 }
1075 return 0;
1076 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001077 }
1078
1079 public int getItemViewType(int position) {
Sunny Goyale1273eb2017-10-18 00:17:20 -07001080 final int typeId;
Winson Chung3ec9a452010-09-23 16:40:28 -07001081 synchronized (mCache) {
1082 if (mCache.containsMetaDataAt(position)) {
1083 typeId = mCache.getMetaDataAt(position).typeId;
1084 } else {
1085 return 0;
1086 }
1087 }
1088
1089 final RemoteViewsMetaData metaData = mCache.getMetaData();
1090 synchronized (metaData) {
1091 return metaData.getMappedViewType(typeId);
1092 }
1093 }
1094
1095 /**
Adam Cohenb9673922012-01-05 13:58:47 -08001096 * This method allows an AdapterView using this Adapter to provide information about which
1097 * views are currently being displayed. This allows for certain optimizations and preloading
1098 * which wouldn't otherwise be possible.
1099 */
Mathew Inwood978c6e22018-08-21 15:58:55 +01001100 @UnsupportedAppUsage
Adam Cohenb9673922012-01-05 13:58:47 -08001101 public void setVisibleRangeHint(int lowerBound, int upperBound) {
1102 mVisibleWindowLowerBound = lowerBound;
1103 mVisibleWindowUpperBound = upperBound;
1104 }
1105
Winson Chung499cb9f2010-07-16 11:18:17 -07001106 public View getView(int position, View convertView, ViewGroup parent) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001107 // "Request" an index so that we can queue it for loading, initiate subsequent
1108 // preloading, etc.
1109 synchronized (mCache) {
Sunny Goyala086f042016-02-04 15:30:48 -08001110 RemoteViews rv = mCache.getRemoteViewsAt(position);
1111 boolean isInCache = (rv != null);
Winson Chung16c8d8a2011-01-20 16:19:33 -08001112 boolean hasNewItems = false;
1113
Adam Cohenff067192012-12-05 18:14:39 -08001114 if (convertView != null && convertView instanceof RemoteViewsFrameLayout) {
1115 mRequestedViews.removeView((RemoteViewsFrameLayout) convertView);
1116 }
1117
Sunny Goyale1273eb2017-10-18 00:17:20 -07001118 if (!isInCache) {
Winson Chung16c8d8a2011-01-20 16:19:33 -08001119 // Requesting bind service will trigger a super.notifyDataSetChanged(), which will
1120 // in turn trigger another request to getView()
1121 requestBindService();
1122 } else {
Winson Chung3ec9a452010-09-23 16:40:28 -07001123 // Queue up other indices to be preloaded based on this position
Winson Chung16c8d8a2011-01-20 16:19:33 -08001124 hasNewItems = mCache.queuePositionsToBePreloadedFromRequestedPosition(position);
1125 }
1126
Sunny Goyal5c022632016-02-17 16:30:41 -08001127 final RemoteViewsFrameLayout layout;
1128 if (convertView instanceof RemoteViewsFrameLayout) {
1129 layout = (RemoteViewsFrameLayout) convertView;
1130 } else {
1131 layout = new RemoteViewsFrameLayout(parent.getContext(), mCache);
Sunny Goyal0ddf6aa2017-04-20 15:25:47 -07001132 layout.setExecutor(mAsyncViewLoadExecutor);
Sunny Goyalc12d31c2018-11-12 16:29:18 -08001133 layout.setOnLightBackground(mOnLightBackground);
Sunny Goyal5c022632016-02-17 16:30:41 -08001134 }
1135
Winson Chung16c8d8a2011-01-20 16:19:33 -08001136 if (isInCache) {
Sunny Goyal5c022632016-02-17 16:30:41 -08001137 // Apply the view synchronously if possible, to avoid flickering
1138 layout.onRemoteViewsLoaded(rv, mRemoteViewsOnClickHandler, false);
Sunny Goyale1273eb2017-10-18 00:17:20 -07001139 if (hasNewItems) {
1140 mServiceHandler.sendEmptyMessage(MSG_LOAD_NEXT_ITEM);
1141 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08001142 } else {
Sunny Goyala086f042016-02-04 15:30:48 -08001143 // If the views is not loaded, apply the loading view. If the loading view doesn't
1144 // exist, the layout will create a default view based on the firstView height.
Sunny Goyal5c022632016-02-17 16:30:41 -08001145 layout.onRemoteViewsLoaded(
1146 mCache.getMetaData().getLoadingTemplate(mContext).remoteViews,
1147 mRemoteViewsOnClickHandler,
1148 false);
Sunny Goyala086f042016-02-04 15:30:48 -08001149 mRequestedViews.add(position, layout);
Winson Chung16c8d8a2011-01-20 16:19:33 -08001150 mCache.queueRequestedPositionToLoad(position);
Sunny Goyale1273eb2017-10-18 00:17:20 -07001151 mServiceHandler.sendEmptyMessage(MSG_LOAD_NEXT_ITEM);
Winson Chung3ec9a452010-09-23 16:40:28 -07001152 }
Sunny Goyala086f042016-02-04 15:30:48 -08001153 return layout;
Winson Chung3ec9a452010-09-23 16:40:28 -07001154 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001155 }
1156
1157 public int getViewTypeCount() {
Winson Chung3ec9a452010-09-23 16:40:28 -07001158 final RemoteViewsMetaData metaData = mCache.getMetaData();
1159 synchronized (metaData) {
1160 return metaData.viewTypeCount;
1161 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001162 }
1163
1164 public boolean hasStableIds() {
Winson Chung3ec9a452010-09-23 16:40:28 -07001165 final RemoteViewsMetaData metaData = mCache.getMetaData();
1166 synchronized (metaData) {
1167 return metaData.hasStableIds;
1168 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001169 }
1170
1171 public boolean isEmpty() {
1172 return getCount() <= 0;
1173 }
1174
Sunny Goyal4ea54842016-02-03 16:29:47 -08001175 /**
1176 * Returns a sorted array of all integers between lower and upper.
1177 */
Sunny Goyale1273eb2017-10-18 00:17:20 -07001178 private int[] getVisibleWindow(int count) {
1179 int lower = mVisibleWindowLowerBound;
1180 int upper = mVisibleWindowUpperBound;
Adam Cohen4a9df8d2012-07-27 15:35:13 -07001181 // In the case that the window is invalid or uninitialized, return an empty window.
1182 if ((lower == 0 && upper == 0) || lower < 0 || upper < 0) {
Sunny Goyal4ea54842016-02-03 16:29:47 -08001183 return new int[0];
Adam Cohen4a9df8d2012-07-27 15:35:13 -07001184 }
1185
Sunny Goyal4ea54842016-02-03 16:29:47 -08001186 int[] window;
Adam Cohen591ff972012-07-24 22:46:11 -07001187 if (lower <= upper) {
Sunny Goyal4ea54842016-02-03 16:29:47 -08001188 window = new int[upper + 1 - lower];
1189 for (int i = lower, j = 0; i <= upper; i++, j++){
1190 window[j] = i;
Adam Cohen591ff972012-07-24 22:46:11 -07001191 }
1192 } else {
1193 // If the upper bound is less than the lower bound it means that the visible window
1194 // wraps around.
Sunny Goyal4ea54842016-02-03 16:29:47 -08001195 count = Math.max(count, lower);
1196 window = new int[count - lower + upper + 1];
1197 int j = 0;
1198 // Add the entries in sorted order
1199 for (int i = 0; i <= upper; i++, j++) {
1200 window[j] = i;
Adam Cohen591ff972012-07-24 22:46:11 -07001201 }
Sunny Goyal4ea54842016-02-03 16:29:47 -08001202 for (int i = lower; i < count; i++, j++) {
1203 window[j] = i;
Adam Cohen591ff972012-07-24 22:46:11 -07001204 }
1205 }
1206 return window;
1207 }
1208
Winson Chung16c8d8a2011-01-20 16:19:33 -08001209 public void notifyDataSetChanged() {
Sunny Goyale1273eb2017-10-18 00:17:20 -07001210 mServiceHandler.removeMessages(MSG_UNBIND_SERVICE);
1211 mServiceHandler.sendEmptyMessage(MSG_NOTIFY_DATA_SET_CHANGED);
Winson Chung3ec9a452010-09-23 16:40:28 -07001212 }
1213
Adam Cohenfb603862010-12-17 12:03:17 -08001214 void superNotifyDataSetChanged() {
Winson Chung499cb9f2010-07-16 11:18:17 -07001215 super.notifyDataSetChanged();
1216 }
1217
Winson Chung81f39eb2011-01-11 18:05:01 -08001218 @Override
1219 public boolean handleMessage(Message msg) {
Winson Chung81f39eb2011-01-11 18:05:01 -08001220 switch (msg.what) {
Sunny Goyale1273eb2017-10-18 00:17:20 -07001221 case MSG_MAIN_HANDLER_COMMIT_METADATA: {
1222 mCache.commitTemporaryMetaData();
1223 return true;
Winson Chung81f39eb2011-01-11 18:05:01 -08001224 }
Sunny Goyale1273eb2017-10-18 00:17:20 -07001225 case MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED: {
1226 superNotifyDataSetChanged();
1227 return true;
1228 }
1229 case MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED: {
1230 if (mCallback != null) {
1231 mCallback.onRemoteAdapterConnected();
1232 }
1233 return true;
1234 }
1235 case MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED: {
1236 if (mCallback != null) {
1237 mCallback.onRemoteAdapterDisconnected();
1238 }
1239 return true;
1240 }
1241 case MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED: {
1242 mRequestedViews.notifyOnRemoteViewsLoaded(msg.arg1, (RemoteViews) msg.obj);
1243 return true;
1244 }
Winson Chung81f39eb2011-01-11 18:05:01 -08001245 }
Sunny Goyale1273eb2017-10-18 00:17:20 -07001246 return false;
Winson Chung81f39eb2011-01-11 18:05:01 -08001247 }
1248
Sunny Goyale1273eb2017-10-18 00:17:20 -07001249 private void requestBindService() {
1250 mServiceHandler.removeMessages(MSG_UNBIND_SERVICE);
1251 Message.obtain(mServiceHandler, MSG_REQUEST_BIND, mAppWidgetId, 0, mIntent).sendToTarget();
Winson Chung499cb9f2010-07-16 11:18:17 -07001252 }
Sunny Goyal5c022632016-02-17 16:30:41 -08001253
1254 private static class HandlerThreadExecutor implements Executor {
1255 private final HandlerThread mThread;
1256
1257 HandlerThreadExecutor(HandlerThread thread) {
1258 mThread = thread;
1259 }
1260
1261 @Override
1262 public void execute(Runnable runnable) {
1263 if (Thread.currentThread().getId() == mThread.getId()) {
1264 runnable.run();
1265 } else {
1266 new Handler(mThread.getLooper()).post(runnable);
1267 }
1268 }
1269 }
1270
1271 private static class LoadingViewTemplate {
1272 public final RemoteViews remoteViews;
1273 public int defaultHeight;
1274
1275 LoadingViewTemplate(RemoteViews views, Context context) {
1276 remoteViews = views;
1277
1278 float density = context.getResources().getDisplayMetrics().density;
Sunny Goyale1273eb2017-10-18 00:17:20 -07001279 defaultHeight = Math.round(DEFAULT_LOADING_VIEW_HEIGHT * density);
Sunny Goyal5c022632016-02-17 16:30:41 -08001280 }
1281
1282 public void loadFirstViewHeight(
1283 RemoteViews firstView, Context context, Executor executor) {
1284 // Inflate the first view on the worker thread
1285 firstView.applyAsync(context, new RemoteViewsFrameLayout(context, null), executor,
1286 new RemoteViews.OnViewAppliedListener() {
1287 @Override
1288 public void onViewApplied(View v) {
1289 try {
1290 v.measure(
1291 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
1292 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
1293 defaultHeight = v.getMeasuredHeight();
1294 } catch (Exception e) {
1295 onError(e);
1296 }
1297 }
1298
1299 @Override
1300 public void onError(Exception e) {
1301 // Do nothing. The default height will stay the same.
1302 Log.w(TAG, "Error inflating first RemoteViews", e);
1303 }
1304 });
1305 }
1306 }
Winson Chung499cb9f2010-07-16 11:18:17 -07001307}