1 /* 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 17 package android.widget; 18 19 import static android.widget.RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID; 20 import static android.widget.RemoteViews.EXTRA_REMOTEADAPTER_ON_LIGHT_BACKGROUND; 21 22 import android.annotation.Nullable; 23 import android.annotation.WorkerThread; 24 import android.app.IServiceConnection; 25 import android.appwidget.AppWidgetHostView; 26 import android.appwidget.AppWidgetManager; 27 import android.compat.annotation.UnsupportedAppUsage; 28 import android.content.ComponentName; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.ServiceConnection; 32 import android.content.pm.ActivityInfo; 33 import android.content.pm.ApplicationInfo; 34 import android.content.res.Configuration; 35 import android.os.Build; 36 import android.os.Handler; 37 import android.os.HandlerThread; 38 import android.os.IBinder; 39 import android.os.Looper; 40 import android.os.Message; 41 import android.os.RemoteException; 42 import android.util.Log; 43 import android.util.SparseArray; 44 import android.util.SparseBooleanArray; 45 import android.util.SparseIntArray; 46 import android.view.LayoutInflater; 47 import android.view.View; 48 import android.view.View.MeasureSpec; 49 import android.view.ViewGroup; 50 import android.widget.RemoteViews.InteractionHandler; 51 52 import com.android.internal.widget.IRemoteViewsFactory; 53 54 import java.lang.ref.WeakReference; 55 import java.util.ArrayList; 56 import java.util.Arrays; 57 import java.util.HashMap; 58 import java.util.concurrent.Executor; 59 60 /** 61 * An adapter to a RemoteViewsService which fetches and caches RemoteViews to be later inflated as 62 * child views. 63 * 64 * The adapter runs in the host process, typically a Launcher app. 65 * 66 * It makes a service connection to the {@link RemoteViewsService} running in the 67 * AppWidgetsProvider's process. This connection is made on a background thread (and proxied via 68 * the platform to get the bind permissions) and all interaction with the service is done on the 69 * background thread. 70 * 71 * On first bind, the adapter will load can cache the RemoteViews locally. Afterwards the 72 * connection is only made when new RemoteViews are required. 73 * @hide 74 */ 75 public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback { 76 77 private static final String TAG = "RemoteViewsAdapter"; 78 79 // The max number of items in the cache 80 private static final int DEFAULT_CACHE_SIZE = 40; 81 // The delay (in millis) to wait until attempting to unbind from a service after a request. 82 // This ensures that we don't stay continually bound to the service and that it can be destroyed 83 // if we need the memory elsewhere in the system. 84 private static final int UNBIND_SERVICE_DELAY = 5000; 85 86 // Default height for the default loading view, in case we cannot get inflate the first view 87 private static final int DEFAULT_LOADING_VIEW_HEIGHT = 50; 88 89 // We cache the FixedSizeRemoteViewsCaches across orientation and re-inflation due to color 90 // palette changes. These are the related data structures: 91 private static final HashMap<RemoteViewsCacheKey, FixedSizeRemoteViewsCache> 92 sCachedRemoteViewsCaches = new HashMap<>(); 93 private static final HashMap<RemoteViewsCacheKey, Runnable> 94 sRemoteViewsCacheRemoveRunnables = new HashMap<>(); 95 96 private static HandlerThread sCacheRemovalThread; 97 private static Handler sCacheRemovalQueue; 98 99 // We keep the cache around for a duration after onSaveInstanceState for use on re-inflation. 100 // If a new RemoteViewsAdapter with the same intent / widget id isn't constructed within this 101 // duration, the cache is dropped. 102 private static final int REMOTE_VIEWS_CACHE_DURATION = 5000; 103 104 private final Context mContext; 105 private final Intent mIntent; 106 private final int mAppWidgetId; 107 private final boolean mOnLightBackground; 108 private final Executor mAsyncViewLoadExecutor; 109 110 private InteractionHandler mRemoteViewsInteractionHandler; 111 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 112 private final FixedSizeRemoteViewsCache mCache; 113 private int mVisibleWindowLowerBound; 114 private int mVisibleWindowUpperBound; 115 116 // The set of requested views that are to be notified when the associated RemoteViews are 117 // loaded. 118 private RemoteViewsFrameLayoutRefSet mRequestedViews; 119 120 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 121 private final HandlerThread mWorkerThread; 122 // items may be interrupted within the normally processed queues 123 private final Handler mMainHandler; 124 private final RemoteServiceHandler mServiceHandler; 125 private final RemoteAdapterConnectionCallback mCallback; 126 127 // Used to indicate to the AdapterView that it can use this Adapter immediately after 128 // construction (happens when we have a cached FixedSizeRemoteViewsCache). 129 private boolean mDataReady = false; 130 131 /** 132 * USed to dedupe {@link RemoteViews#mApplication} so that we do not hold on to 133 * multiple copies of the same ApplicationInfo object. 134 */ 135 private ApplicationInfo mLastRemoteViewAppInfo; 136 137 /** 138 * An interface for the RemoteAdapter to notify other classes when adapters 139 * are actually connected to/disconnected from their actual services. 140 */ 141 public interface RemoteAdapterConnectionCallback { 142 /** 143 * @return whether the adapter was set or not. 144 */ onRemoteAdapterConnected()145 boolean onRemoteAdapterConnected(); 146 onRemoteAdapterDisconnected()147 void onRemoteAdapterDisconnected(); 148 149 /** 150 * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not 151 * connected yet. 152 */ deferNotifyDataSetChanged()153 void deferNotifyDataSetChanged(); 154 setRemoteViewsAdapter(Intent intent, boolean isAsync)155 void setRemoteViewsAdapter(Intent intent, boolean isAsync); 156 } 157 158 public static class AsyncRemoteAdapterAction implements Runnable { 159 160 private final RemoteAdapterConnectionCallback mCallback; 161 private final Intent mIntent; 162 AsyncRemoteAdapterAction(RemoteAdapterConnectionCallback callback, Intent intent)163 public AsyncRemoteAdapterAction(RemoteAdapterConnectionCallback callback, Intent intent) { 164 mCallback = callback; 165 mIntent = intent; 166 } 167 168 @Override run()169 public void run() { 170 mCallback.setRemoteViewsAdapter(mIntent, true); 171 } 172 } 173 174 static final int MSG_REQUEST_BIND = 1; 175 static final int MSG_NOTIFY_DATA_SET_CHANGED = 2; 176 static final int MSG_LOAD_NEXT_ITEM = 3; 177 static final int MSG_UNBIND_SERVICE = 4; 178 179 private static final int MSG_MAIN_HANDLER_COMMIT_METADATA = 1; 180 private static final int MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED = 2; 181 private static final int MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED = 3; 182 private static final int MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED = 4; 183 private static final int MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED = 5; 184 185 /** 186 * Handler for various interactions with the {@link RemoteViewsService}. 187 */ 188 private static class RemoteServiceHandler extends Handler implements ServiceConnection { 189 190 private final WeakReference<RemoteViewsAdapter> mAdapter; 191 private final Context mContext; 192 193 private IRemoteViewsFactory mRemoteViewsFactory; 194 195 // The last call to notifyDataSetChanged didn't succeed, try again on next service bind. 196 private boolean mNotifyDataSetChangedPending = false; 197 private boolean mBindRequested = false; 198 RemoteServiceHandler(Looper workerLooper, RemoteViewsAdapter adapter, Context context)199 RemoteServiceHandler(Looper workerLooper, RemoteViewsAdapter adapter, Context context) { 200 super(workerLooper); 201 mAdapter = new WeakReference<>(adapter); 202 mContext = context; 203 } 204 205 @Override onServiceConnected(ComponentName name, IBinder service)206 public void onServiceConnected(ComponentName name, IBinder service) { 207 // This is called on the same thread. 208 mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service); 209 enqueueDeferredUnbindServiceMessage(); 210 211 RemoteViewsAdapter adapter = mAdapter.get(); 212 if (adapter == null) { 213 return; 214 } 215 216 if (mNotifyDataSetChangedPending) { 217 mNotifyDataSetChangedPending = false; 218 Message msg = Message.obtain(this, MSG_NOTIFY_DATA_SET_CHANGED); 219 handleMessage(msg); 220 msg.recycle(); 221 } else { 222 if (!sendNotifyDataSetChange(false)) { 223 return; 224 } 225 226 // Request meta data so that we have up to date data when calling back to 227 // the remote adapter callback 228 adapter.updateTemporaryMetaData(mRemoteViewsFactory); 229 adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_COMMIT_METADATA); 230 adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED); 231 } 232 } 233 234 @Override onServiceDisconnected(ComponentName name)235 public void onServiceDisconnected(ComponentName name) { 236 mRemoteViewsFactory = null; 237 RemoteViewsAdapter adapter = mAdapter.get(); 238 if (adapter != null) { 239 adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED); 240 } 241 } 242 243 @Override handleMessage(Message msg)244 public void handleMessage(Message msg) { 245 RemoteViewsAdapter adapter = mAdapter.get(); 246 247 switch (msg.what) { 248 case MSG_REQUEST_BIND: { 249 if (adapter == null || mRemoteViewsFactory != null) { 250 enqueueDeferredUnbindServiceMessage(); 251 } 252 if (mBindRequested) { 253 return; 254 } 255 int flags = Context.BIND_AUTO_CREATE 256 | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE; 257 final IServiceConnection sd = mContext.getServiceDispatcher(this, this, flags); 258 Intent intent = (Intent) msg.obj; 259 int appWidgetId = msg.arg1; 260 try { 261 mBindRequested = AppWidgetManager.getInstance(mContext) 262 .bindRemoteViewsService(mContext, appWidgetId, intent, sd, flags); 263 } catch (Exception e) { 264 Log.e(TAG, "Failed to bind remoteViewsService: " + e.getMessage()); 265 } 266 return; 267 } 268 case MSG_NOTIFY_DATA_SET_CHANGED: { 269 enqueueDeferredUnbindServiceMessage(); 270 if (adapter == null) { 271 return; 272 } 273 if (mRemoteViewsFactory == null) { 274 mNotifyDataSetChangedPending = true; 275 adapter.requestBindService(); 276 return; 277 } 278 if (!sendNotifyDataSetChange(true)) { 279 return; 280 } 281 282 // Flush the cache so that we can reload new items from the service 283 synchronized (adapter.mCache) { 284 adapter.mCache.reset(); 285 } 286 287 // Re-request the new metadata (only after the notification to the factory) 288 adapter.updateTemporaryMetaData(mRemoteViewsFactory); 289 int newCount; 290 int[] visibleWindow; 291 synchronized (adapter.mCache.getTemporaryMetaData()) { 292 newCount = adapter.mCache.getTemporaryMetaData().count; 293 visibleWindow = adapter.getVisibleWindow(newCount); 294 } 295 296 // Pre-load (our best guess of) the views which are currently visible in the 297 // AdapterView. This mitigates flashing and flickering of loading views when a 298 // widget notifies that its data has changed. 299 for (int position : visibleWindow) { 300 // Because temporary meta data is only ever modified from this thread 301 // (ie. mWorkerThread), it is safe to assume that count is a valid 302 // representation. 303 if (position < newCount) { 304 adapter.updateRemoteViews(mRemoteViewsFactory, position, false); 305 } 306 } 307 308 // Propagate the notification back to the base adapter 309 adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_COMMIT_METADATA); 310 adapter.mMainHandler.sendEmptyMessage( 311 MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED); 312 return; 313 } 314 315 case MSG_LOAD_NEXT_ITEM: { 316 if (adapter == null || mRemoteViewsFactory == null) { 317 return; 318 } 319 removeMessages(MSG_UNBIND_SERVICE); 320 // Get the next index to load 321 final int position = adapter.mCache.getNextIndexToLoad(); 322 if (position > -1) { 323 // Load the item, and notify any existing RemoteViewsFrameLayouts 324 adapter.updateRemoteViews(mRemoteViewsFactory, position, true); 325 326 // Queue up for the next one to load 327 sendEmptyMessage(MSG_LOAD_NEXT_ITEM); 328 } else { 329 // No more items to load, so queue unbind 330 enqueueDeferredUnbindServiceMessage(); 331 } 332 return; 333 } 334 case MSG_UNBIND_SERVICE: { 335 unbindNow(); 336 return; 337 } 338 } 339 } 340 unbindNow()341 protected void unbindNow() { 342 if (mBindRequested) { 343 mBindRequested = false; 344 mContext.unbindService(this); 345 } 346 mRemoteViewsFactory = null; 347 } 348 sendNotifyDataSetChange(boolean always)349 private boolean sendNotifyDataSetChange(boolean always) { 350 try { 351 if (always || !mRemoteViewsFactory.isCreated()) { 352 mRemoteViewsFactory.onDataSetChanged(); 353 } 354 return true; 355 } catch (RemoteException | RuntimeException e) { 356 Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage()); 357 return false; 358 } 359 } 360 enqueueDeferredUnbindServiceMessage()361 private void enqueueDeferredUnbindServiceMessage() { 362 removeMessages(MSG_UNBIND_SERVICE); 363 sendEmptyMessageDelayed(MSG_UNBIND_SERVICE, UNBIND_SERVICE_DELAY); 364 } 365 } 366 367 /** 368 * A FrameLayout which contains a loading view, and manages the re/applying of RemoteViews when 369 * they are loaded. 370 */ 371 static class RemoteViewsFrameLayout extends AppWidgetHostView { 372 private final FixedSizeRemoteViewsCache mCache; 373 374 public int cacheIndex = -1; 375 RemoteViewsFrameLayout(Context context, FixedSizeRemoteViewsCache cache)376 public RemoteViewsFrameLayout(Context context, FixedSizeRemoteViewsCache cache) { 377 super(context); 378 mCache = cache; 379 } 380 381 /** 382 * Updates this RemoteViewsFrameLayout depending on the view that was loaded. 383 * @param view the RemoteViews that was loaded. If null, the RemoteViews was not loaded 384 * successfully. 385 * @param forceApplyAsync when true, the host will always try to inflate the view 386 * asynchronously (for eg, when we are already showing the loading 387 * view) 388 */ onRemoteViewsLoaded(RemoteViews view, InteractionHandler handler, boolean forceApplyAsync)389 public void onRemoteViewsLoaded(RemoteViews view, InteractionHandler handler, 390 boolean forceApplyAsync) { 391 setInteractionHandler(handler); 392 applyRemoteViews(view, forceApplyAsync || ((view != null) && view.prefersAsyncApply())); 393 } 394 395 /** 396 * Creates a default loading view. Uses the size of the first row as a guide for the 397 * size of the loading view. 398 */ 399 @Override getDefaultView()400 protected View getDefaultView() { 401 int viewHeight = mCache.getMetaData().getLoadingTemplate(getContext()).defaultHeight; 402 // Compose the loading view text 403 TextView loadingTextView = (TextView) LayoutInflater.from(getContext()).inflate( 404 com.android.internal.R.layout.remote_views_adapter_default_loading_view, 405 this, false); 406 loadingTextView.setHeight(viewHeight); 407 return loadingTextView; 408 } 409 410 @Override getRemoteContextEnsuringCorrectCachedApkPath()411 protected Context getRemoteContextEnsuringCorrectCachedApkPath() { 412 return null; 413 } 414 415 @Override getErrorView()416 protected View getErrorView() { 417 // Use the default loading view as the error view. 418 return getDefaultView(); 419 } 420 } 421 422 /** 423 * Stores the references of all the RemoteViewsFrameLayouts that have been returned by the 424 * adapter that have not yet had their RemoteViews loaded. 425 */ 426 private class RemoteViewsFrameLayoutRefSet 427 extends SparseArray<ArrayList<RemoteViewsFrameLayout>> { 428 429 /** 430 * Adds a new reference to a RemoteViewsFrameLayout returned by the adapter. 431 */ add(int position, RemoteViewsFrameLayout layout)432 public void add(int position, RemoteViewsFrameLayout layout) { 433 ArrayList<RemoteViewsFrameLayout> refs = get(position); 434 435 // Create the list if necessary 436 if (refs == null) { 437 refs = new ArrayList<>(); 438 put(position, refs); 439 } 440 441 // Add the references to the list 442 layout.cacheIndex = position; 443 refs.add(layout); 444 } 445 446 /** 447 * Notifies each of the RemoteViewsFrameLayouts associated with a particular position that 448 * the associated RemoteViews has loaded. 449 */ notifyOnRemoteViewsLoaded(int position, RemoteViews view)450 public void notifyOnRemoteViewsLoaded(int position, RemoteViews view) { 451 if (view == null) return; 452 453 // Remove this set from the original mapping 454 final ArrayList<RemoteViewsFrameLayout> refs = removeReturnOld(position); 455 if (refs != null) { 456 // Notify all the references for that position of the newly loaded RemoteViews 457 for (final RemoteViewsFrameLayout ref : refs) { 458 ref.onRemoteViewsLoaded(view, mRemoteViewsInteractionHandler, true); 459 } 460 } 461 } 462 463 /** 464 * We need to remove views from this set if they have been recycled by the AdapterView. 465 */ removeView(RemoteViewsFrameLayout rvfl)466 public void removeView(RemoteViewsFrameLayout rvfl) { 467 if (rvfl.cacheIndex < 0) { 468 return; 469 } 470 final ArrayList<RemoteViewsFrameLayout> refs = get(rvfl.cacheIndex); 471 if (refs != null) { 472 refs.remove(rvfl); 473 } 474 rvfl.cacheIndex = -1; 475 } 476 } 477 478 /** 479 * The meta-data associated with the cache in it's current state. 480 */ 481 private static class RemoteViewsMetaData { 482 int count; 483 int viewTypeCount; 484 boolean hasStableIds; 485 486 // Used to determine how to construct loading views. If a loading view is not specified 487 // by the user, then we try and load the first view, and use its height as the height for 488 // the default loading view. 489 LoadingViewTemplate loadingTemplate; 490 491 // A mapping from type id to a set of unique type ids 492 private final SparseIntArray mTypeIdIndexMap = new SparseIntArray(); 493 RemoteViewsMetaData()494 public RemoteViewsMetaData() { 495 reset(); 496 } 497 set(RemoteViewsMetaData d)498 public void set(RemoteViewsMetaData d) { 499 synchronized (d) { 500 count = d.count; 501 viewTypeCount = d.viewTypeCount; 502 hasStableIds = d.hasStableIds; 503 loadingTemplate = d.loadingTemplate; 504 } 505 } 506 reset()507 public void reset() { 508 count = 0; 509 510 // by default there is at least one placeholder view type 511 viewTypeCount = 1; 512 hasStableIds = true; 513 loadingTemplate = null; 514 mTypeIdIndexMap.clear(); 515 } 516 getMappedViewType(int typeId)517 public int getMappedViewType(int typeId) { 518 int mappedTypeId = mTypeIdIndexMap.get(typeId, -1); 519 if (mappedTypeId == -1) { 520 // We +1 because the loading view always has view type id of 0 521 mappedTypeId = mTypeIdIndexMap.size() + 1; 522 mTypeIdIndexMap.put(typeId, mappedTypeId); 523 } 524 return mappedTypeId; 525 } 526 isViewTypeInRange(int typeId)527 public boolean isViewTypeInRange(int typeId) { 528 int mappedType = getMappedViewType(typeId); 529 return (mappedType < viewTypeCount); 530 } 531 getLoadingTemplate(Context context)532 public synchronized LoadingViewTemplate getLoadingTemplate(Context context) { 533 if (loadingTemplate == null) { 534 loadingTemplate = new LoadingViewTemplate(null, context); 535 } 536 return loadingTemplate; 537 } 538 } 539 540 /** 541 * The meta-data associated with a single item in the cache. 542 */ 543 private static class RemoteViewsIndexMetaData { 544 int typeId; 545 long itemId; 546 RemoteViewsIndexMetaData(RemoteViews v, long itemId)547 public RemoteViewsIndexMetaData(RemoteViews v, long itemId) { 548 set(v, itemId); 549 } 550 set(RemoteViews v, long id)551 public void set(RemoteViews v, long id) { 552 itemId = id; 553 if (v != null) { 554 typeId = v.getLayoutId(); 555 } else { 556 typeId = 0; 557 } 558 } 559 } 560 561 /** 562 * Config diff flags for which the cache should be reset 563 */ 564 private static final int CACHE_RESET_CONFIG_FLAGS = ActivityInfo.CONFIG_FONT_SCALE 565 | ActivityInfo.CONFIG_UI_MODE | ActivityInfo.CONFIG_DENSITY 566 | ActivityInfo.CONFIG_ASSETS_PATHS; 567 /** 568 * 569 */ 570 private static class FixedSizeRemoteViewsCache { 571 572 // The meta data related to all the RemoteViews, ie. count, is stable, etc. 573 // The meta data objects are made final so that they can be locked on independently 574 // of the FixedSizeRemoteViewsCache. If we ever lock on both meta data objects, it is in 575 // the order mTemporaryMetaData followed by mMetaData. 576 private final RemoteViewsMetaData mMetaData = new RemoteViewsMetaData(); 577 private final RemoteViewsMetaData mTemporaryMetaData = new RemoteViewsMetaData(); 578 579 // The cache/mapping of position to RemoteViewsMetaData. This set is guaranteed to be 580 // greater than or equal to the set of RemoteViews. 581 // Note: The reason that we keep this separate from the RemoteViews cache below is that this 582 // we still need to be able to access the mapping of position to meta data, without keeping 583 // the heavy RemoteViews around. The RemoteViews cache is trimmed to fixed constraints wrt. 584 // memory and size, but this metadata cache will retain information until the data at the 585 // position is guaranteed as not being necessary any more (usually on notifyDataSetChanged). 586 private final SparseArray<RemoteViewsIndexMetaData> mIndexMetaData = new SparseArray<>(); 587 588 // The cache of actual RemoteViews, which may be pruned if the cache gets too large, or uses 589 // too much memory. 590 private final SparseArray<RemoteViews> mIndexRemoteViews = new SparseArray<>(); 591 592 // An array of indices to load, Indices which are explicitly requested are set to true, 593 // and those determined by the preloading algorithm to prefetch are set to false. 594 private final SparseBooleanArray mIndicesToLoad = new SparseBooleanArray(); 595 596 // We keep a reference of the last requested index to determine which item to prune the 597 // farthest items from when we hit the memory limit 598 private int mLastRequestedIndex; 599 600 // The lower and upper bounds of the preloaded range 601 private int mPreloadLowerBound; 602 private int mPreloadUpperBound; 603 604 // The bounds of this fixed cache, we will try and fill as many items into the cache up to 605 // the maxCount number of items, or the maxSize memory usage. 606 // The maxCountSlack is used to determine if a new position in the cache to be loaded is 607 // sufficiently ouside the old set, prompting a shifting of the "window" of items to be 608 // preloaded. 609 private final int mMaxCount; 610 private final int mMaxCountSlack; 611 private static final float sMaxCountSlackPercent = 0.75f; 612 private static final int sMaxMemoryLimitInBytes = 2 * 1024 * 1024; 613 614 // Configuration for which the cache was created 615 private final Configuration mConfiguration; 616 FixedSizeRemoteViewsCache(int maxCacheSize, Configuration configuration)617 FixedSizeRemoteViewsCache(int maxCacheSize, Configuration configuration) { 618 mMaxCount = maxCacheSize; 619 mMaxCountSlack = Math.round(sMaxCountSlackPercent * (mMaxCount / 2)); 620 mPreloadLowerBound = 0; 621 mPreloadUpperBound = -1; 622 mLastRequestedIndex = -1; 623 624 mConfiguration = new Configuration(configuration); 625 } 626 insert(int position, RemoteViews v, long itemId, int[] visibleWindow)627 public void insert(int position, RemoteViews v, long itemId, int[] visibleWindow) { 628 // Trim the cache if we go beyond the count 629 if (mIndexRemoteViews.size() >= mMaxCount) { 630 mIndexRemoteViews.remove(getFarthestPositionFrom(position, visibleWindow)); 631 } 632 633 // Trim the cache if we go beyond the available memory size constraints 634 int pruneFromPosition = (mLastRequestedIndex > -1) ? mLastRequestedIndex : position; 635 while (getRemoteViewsBitmapMemoryUsage() >= sMaxMemoryLimitInBytes) { 636 // Note: This is currently the most naive mechanism for deciding what to prune when 637 // we hit the memory limit. In the future, we may want to calculate which index to 638 // remove based on both its position as well as it's current memory usage, as well 639 // as whether it was directly requested vs. whether it was preloaded by our caching 640 // mechanism. 641 int trimIndex = getFarthestPositionFrom(pruneFromPosition, visibleWindow); 642 643 // Need to check that this is a valid index, to cover the case where you have only 644 // a single view in the cache, but it's larger than the max memory limit 645 if (trimIndex < 0) { 646 break; 647 } 648 649 mIndexRemoteViews.remove(trimIndex); 650 } 651 652 // Update the metadata cache 653 final RemoteViewsIndexMetaData metaData = mIndexMetaData.get(position); 654 if (metaData != null) { 655 metaData.set(v, itemId); 656 } else { 657 mIndexMetaData.put(position, new RemoteViewsIndexMetaData(v, itemId)); 658 } 659 mIndexRemoteViews.put(position, v); 660 } 661 getMetaData()662 public RemoteViewsMetaData getMetaData() { 663 return mMetaData; 664 } getTemporaryMetaData()665 public RemoteViewsMetaData getTemporaryMetaData() { 666 return mTemporaryMetaData; 667 } getRemoteViewsAt(int position)668 public RemoteViews getRemoteViewsAt(int position) { 669 return mIndexRemoteViews.get(position); 670 } getMetaDataAt(int position)671 public RemoteViewsIndexMetaData getMetaDataAt(int position) { 672 return mIndexMetaData.get(position); 673 } 674 commitTemporaryMetaData()675 public void commitTemporaryMetaData() { 676 synchronized (mTemporaryMetaData) { 677 synchronized (mMetaData) { 678 mMetaData.set(mTemporaryMetaData); 679 } 680 } 681 } 682 getRemoteViewsBitmapMemoryUsage()683 private int getRemoteViewsBitmapMemoryUsage() { 684 // Calculate the memory usage of all the RemoteViews bitmaps being cached 685 int mem = 0; 686 for (int i = mIndexRemoteViews.size() - 1; i >= 0; i--) { 687 final RemoteViews v = mIndexRemoteViews.valueAt(i); 688 if (v != null) { 689 mem += v.estimateMemoryUsage(); 690 } 691 } 692 return mem; 693 } 694 getFarthestPositionFrom(int pos, int[] visibleWindow)695 private int getFarthestPositionFrom(int pos, int[] visibleWindow) { 696 // Find the index farthest away and remove that 697 int maxDist = 0; 698 int maxDistIndex = -1; 699 int maxDistNotVisible = 0; 700 int maxDistIndexNotVisible = -1; 701 for (int i = mIndexRemoteViews.size() - 1; i >= 0; i--) { 702 int index = mIndexRemoteViews.keyAt(i); 703 int dist = Math.abs(index-pos); 704 if (dist > maxDistNotVisible && Arrays.binarySearch(visibleWindow, index) < 0) { 705 // maxDistNotVisible/maxDistIndexNotVisible will store the index of the 706 // farthest non-visible position 707 maxDistIndexNotVisible = index; 708 maxDistNotVisible = dist; 709 } 710 if (dist >= maxDist) { 711 // maxDist/maxDistIndex will store the index of the farthest position 712 // regardless of whether it is visible or not 713 maxDistIndex = index; 714 maxDist = dist; 715 } 716 } 717 if (maxDistIndexNotVisible > -1) { 718 return maxDistIndexNotVisible; 719 } 720 return maxDistIndex; 721 } 722 queueRequestedPositionToLoad(int position)723 public void queueRequestedPositionToLoad(int position) { 724 mLastRequestedIndex = position; 725 synchronized (mIndicesToLoad) { 726 mIndicesToLoad.put(position, true); 727 } 728 } queuePositionsToBePreloadedFromRequestedPosition(int position)729 public boolean queuePositionsToBePreloadedFromRequestedPosition(int position) { 730 // Check if we need to preload any items 731 if (mPreloadLowerBound <= position && position <= mPreloadUpperBound) { 732 int center = (mPreloadUpperBound + mPreloadLowerBound) / 2; 733 if (Math.abs(position - center) < mMaxCountSlack) { 734 return false; 735 } 736 } 737 738 int count; 739 synchronized (mMetaData) { 740 count = mMetaData.count; 741 } 742 synchronized (mIndicesToLoad) { 743 // Remove all indices which have not been previously requested. 744 for (int i = mIndicesToLoad.size() - 1; i >= 0; i--) { 745 if (!mIndicesToLoad.valueAt(i)) { 746 mIndicesToLoad.removeAt(i); 747 } 748 } 749 750 // Add all the preload indices 751 int halfMaxCount = mMaxCount / 2; 752 mPreloadLowerBound = position - halfMaxCount; 753 mPreloadUpperBound = position + halfMaxCount; 754 int effectiveLowerBound = Math.max(0, mPreloadLowerBound); 755 int effectiveUpperBound = Math.min(mPreloadUpperBound, count - 1); 756 for (int i = effectiveLowerBound; i <= effectiveUpperBound; ++i) { 757 if (mIndexRemoteViews.indexOfKey(i) < 0 && !mIndicesToLoad.get(i)) { 758 // If the index has not been requested, and has not been loaded. 759 mIndicesToLoad.put(i, false); 760 } 761 } 762 } 763 return true; 764 } 765 /** Returns the next index to load */ getNextIndexToLoad()766 public int getNextIndexToLoad() { 767 // We try and prioritize items that have been requested directly, instead 768 // of items that are loaded as a result of the caching mechanism 769 synchronized (mIndicesToLoad) { 770 // Prioritize requested indices to be loaded first 771 int index = mIndicesToLoad.indexOfValue(true); 772 if (index < 0) { 773 // Otherwise, preload other indices as necessary 774 index = mIndicesToLoad.indexOfValue(false); 775 } 776 if (index < 0) { 777 return -1; 778 } else { 779 int key = mIndicesToLoad.keyAt(index); 780 mIndicesToLoad.removeAt(index); 781 return key; 782 } 783 } 784 } 785 containsRemoteViewAt(int position)786 public boolean containsRemoteViewAt(int position) { 787 return mIndexRemoteViews.indexOfKey(position) >= 0; 788 } containsMetaDataAt(int position)789 public boolean containsMetaDataAt(int position) { 790 return mIndexMetaData.indexOfKey(position) >= 0; 791 } 792 reset()793 public void reset() { 794 // Note: We do not try and reset the meta data, since that information is still used by 795 // collection views to validate it's own contents (and will be re-requested if the data 796 // is invalidated through the notifyDataSetChanged() flow). 797 798 mPreloadLowerBound = 0; 799 mPreloadUpperBound = -1; 800 mLastRequestedIndex = -1; 801 mIndexRemoteViews.clear(); 802 mIndexMetaData.clear(); 803 synchronized (mIndicesToLoad) { 804 mIndicesToLoad.clear(); 805 } 806 } 807 } 808 809 static class RemoteViewsCacheKey { 810 final Intent.FilterComparison filter; 811 final int widgetId; 812 RemoteViewsCacheKey(Intent.FilterComparison filter, int widgetId)813 RemoteViewsCacheKey(Intent.FilterComparison filter, int widgetId) { 814 this.filter = filter; 815 this.widgetId = widgetId; 816 } 817 818 @Override equals(@ullable Object o)819 public boolean equals(@Nullable Object o) { 820 if (!(o instanceof RemoteViewsCacheKey)) { 821 return false; 822 } 823 RemoteViewsCacheKey other = (RemoteViewsCacheKey) o; 824 return other.filter.equals(filter) && other.widgetId == widgetId; 825 } 826 827 @Override hashCode()828 public int hashCode() { 829 return (filter == null ? 0 : filter.hashCode()) ^ (widgetId << 2); 830 } 831 } 832 RemoteViewsAdapter(Context context, Intent intent, RemoteAdapterConnectionCallback callback, boolean useAsyncLoader)833 public RemoteViewsAdapter(Context context, Intent intent, 834 RemoteAdapterConnectionCallback callback, boolean useAsyncLoader) { 835 mContext = context; 836 mIntent = intent; 837 838 if (mIntent == null) { 839 throw new IllegalArgumentException("Non-null Intent must be specified."); 840 } 841 842 mAppWidgetId = intent.getIntExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, -1); 843 mRequestedViews = new RemoteViewsFrameLayoutRefSet(); 844 mOnLightBackground = intent.getBooleanExtra(EXTRA_REMOTEADAPTER_ON_LIGHT_BACKGROUND, false); 845 846 // Strip the previously injected app widget id from service intent 847 intent.removeExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID); 848 intent.removeExtra(EXTRA_REMOTEADAPTER_ON_LIGHT_BACKGROUND); 849 850 // Initialize the worker thread 851 mWorkerThread = new HandlerThread("RemoteViewsCache-loader"); 852 mWorkerThread.start(); 853 mMainHandler = new Handler(Looper.myLooper(), this); 854 mServiceHandler = new RemoteServiceHandler(mWorkerThread.getLooper(), this, 855 context.getApplicationContext()); 856 mAsyncViewLoadExecutor = useAsyncLoader ? new HandlerThreadExecutor(mWorkerThread) : null; 857 mCallback = callback; 858 859 if (sCacheRemovalThread == null) { 860 sCacheRemovalThread = new HandlerThread("RemoteViewsAdapter-cachePruner"); 861 sCacheRemovalThread.start(); 862 sCacheRemovalQueue = new Handler(sCacheRemovalThread.getLooper()); 863 } 864 865 RemoteViewsCacheKey key = new RemoteViewsCacheKey(new Intent.FilterComparison(mIntent), 866 mAppWidgetId); 867 868 synchronized(sCachedRemoteViewsCaches) { 869 FixedSizeRemoteViewsCache cache = sCachedRemoteViewsCaches.get(key); 870 Configuration config = context.getResources().getConfiguration(); 871 if (cache == null 872 || (cache.mConfiguration.diff(config) & CACHE_RESET_CONFIG_FLAGS) != 0) { 873 mCache = new FixedSizeRemoteViewsCache(DEFAULT_CACHE_SIZE, config); 874 } else { 875 mCache = sCachedRemoteViewsCaches.get(key); 876 synchronized (mCache.mMetaData) { 877 if (mCache.mMetaData.count > 0) { 878 // As a precautionary measure, we verify that the meta data indicates a 879 // non-zero count before declaring that data is ready. 880 mDataReady = true; 881 } 882 } 883 } 884 if (!mDataReady) { 885 requestBindService(); 886 } 887 } 888 } 889 890 @Override finalize()891 protected void finalize() throws Throwable { 892 try { 893 mServiceHandler.unbindNow(); 894 mWorkerThread.quit(); 895 } finally { 896 super.finalize(); 897 } 898 } 899 900 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) isDataReady()901 public boolean isDataReady() { 902 return mDataReady; 903 } 904 905 /** @hide */ setRemoteViewsInteractionHandler(InteractionHandler handler)906 public void setRemoteViewsInteractionHandler(InteractionHandler handler) { 907 mRemoteViewsInteractionHandler = handler; 908 } 909 910 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) saveRemoteViewsCache()911 public void saveRemoteViewsCache() { 912 final RemoteViewsCacheKey key = new RemoteViewsCacheKey( 913 new Intent.FilterComparison(mIntent), mAppWidgetId); 914 915 synchronized(sCachedRemoteViewsCaches) { 916 // If we already have a remove runnable posted for this key, remove it. 917 if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) { 918 sCacheRemovalQueue.removeCallbacks(sRemoteViewsCacheRemoveRunnables.get(key)); 919 sRemoteViewsCacheRemoveRunnables.remove(key); 920 } 921 922 int metaDataCount = 0; 923 int numRemoteViewsCached = 0; 924 synchronized (mCache.mMetaData) { 925 metaDataCount = mCache.mMetaData.count; 926 } 927 synchronized (mCache) { 928 numRemoteViewsCached = mCache.mIndexRemoteViews.size(); 929 } 930 if (metaDataCount > 0 && numRemoteViewsCached > 0) { 931 sCachedRemoteViewsCaches.put(key, mCache); 932 } 933 934 Runnable r = () -> { 935 synchronized (sCachedRemoteViewsCaches) { 936 sCachedRemoteViewsCaches.remove(key); 937 sRemoteViewsCacheRemoveRunnables.remove(key); 938 } 939 }; 940 sRemoteViewsCacheRemoveRunnables.put(key, r); 941 sCacheRemovalQueue.postDelayed(r, REMOTE_VIEWS_CACHE_DURATION); 942 } 943 } 944 945 @WorkerThread updateTemporaryMetaData(IRemoteViewsFactory factory)946 private void updateTemporaryMetaData(IRemoteViewsFactory factory) { 947 try { 948 // get the properties/first view (so that we can use it to 949 // measure our placeholder views) 950 boolean hasStableIds = factory.hasStableIds(); 951 int viewTypeCount = factory.getViewTypeCount(); 952 int count = factory.getCount(); 953 LoadingViewTemplate loadingTemplate = 954 new LoadingViewTemplate(factory.getLoadingView(), mContext); 955 if ((count > 0) && (loadingTemplate.remoteViews == null)) { 956 RemoteViews firstView = factory.getViewAt(0); 957 if (firstView != null) { 958 loadingTemplate.loadFirstViewHeight(firstView, mContext, 959 new HandlerThreadExecutor(mWorkerThread)); 960 } 961 } 962 final RemoteViewsMetaData tmpMetaData = mCache.getTemporaryMetaData(); 963 synchronized (tmpMetaData) { 964 tmpMetaData.hasStableIds = hasStableIds; 965 // We +1 because the base view type is the loading view 966 tmpMetaData.viewTypeCount = viewTypeCount + 1; 967 tmpMetaData.count = count; 968 tmpMetaData.loadingTemplate = loadingTemplate; 969 } 970 } catch (RemoteException | RuntimeException e) { 971 Log.e("RemoteViewsAdapter", "Error in updateMetaData: " + e.getMessage()); 972 973 // If we encounter a crash when updating, we should reset the metadata & cache 974 // and trigger a notifyDataSetChanged to update the widget accordingly 975 synchronized (mCache.getMetaData()) { 976 mCache.getMetaData().reset(); 977 } 978 synchronized (mCache) { 979 mCache.reset(); 980 } 981 mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED); 982 } 983 } 984 985 @WorkerThread updateRemoteViews(IRemoteViewsFactory factory, int position, boolean notifyWhenLoaded)986 private void updateRemoteViews(IRemoteViewsFactory factory, int position, 987 boolean notifyWhenLoaded) { 988 // Load the item information from the remote service 989 final RemoteViews remoteViews; 990 final long itemId; 991 try { 992 remoteViews = factory.getViewAt(position); 993 itemId = factory.getItemId(position); 994 995 if (remoteViews == null) { 996 throw new RuntimeException("Null remoteViews"); 997 } 998 } catch (RemoteException | RuntimeException e) { 999 Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage()); 1000 1001 // Return early to prevent additional work in re-centering the view cache, and 1002 // swapping from the loading view 1003 return; 1004 } 1005 1006 if (remoteViews.mApplication != null) { 1007 // We keep track of last application info. This helps when all the remoteViews have 1008 // same applicationInfo, which should be the case for a typical adapter. But if every 1009 // view has different application info, there will not be any optimization. 1010 if (mLastRemoteViewAppInfo != null 1011 && remoteViews.hasSameAppInfo(mLastRemoteViewAppInfo)) { 1012 // We should probably also update the remoteViews for nested ViewActions. 1013 // Hopefully, RemoteViews in an adapter would be less complicated. 1014 remoteViews.mApplication = mLastRemoteViewAppInfo; 1015 } else { 1016 mLastRemoteViewAppInfo = remoteViews.mApplication; 1017 } 1018 } 1019 1020 int layoutId = remoteViews.getLayoutId(); 1021 RemoteViewsMetaData metaData = mCache.getMetaData(); 1022 boolean viewTypeInRange; 1023 int cacheCount; 1024 synchronized (metaData) { 1025 viewTypeInRange = metaData.isViewTypeInRange(layoutId); 1026 cacheCount = mCache.mMetaData.count; 1027 } 1028 synchronized (mCache) { 1029 if (viewTypeInRange) { 1030 int[] visibleWindow = getVisibleWindow(cacheCount); 1031 // Cache the RemoteViews we loaded 1032 mCache.insert(position, remoteViews, itemId, visibleWindow); 1033 1034 if (notifyWhenLoaded) { 1035 // Notify all the views that we have previously returned for this index that 1036 // there is new data for it. 1037 Message.obtain(mMainHandler, MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED, position, 0, 1038 remoteViews).sendToTarget(); 1039 } 1040 } else { 1041 // We need to log an error here, as the the view type count specified by the 1042 // factory is less than the number of view types returned. We don't return this 1043 // view to the AdapterView, as this will cause an exception in the hosting process, 1044 // which contains the associated AdapterView. 1045 Log.e(TAG, "Error: widget's RemoteViewsFactory returns more view types than " + 1046 " indicated by getViewTypeCount() "); 1047 } 1048 } 1049 } 1050 1051 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getRemoteViewsServiceIntent()1052 public Intent getRemoteViewsServiceIntent() { 1053 return mIntent; 1054 } 1055 getCount()1056 public int getCount() { 1057 final RemoteViewsMetaData metaData = mCache.getMetaData(); 1058 synchronized (metaData) { 1059 return metaData.count; 1060 } 1061 } 1062 getItem(int position)1063 public Object getItem(int position) { 1064 // Disallow arbitrary object to be associated with an item for the time being 1065 return null; 1066 } 1067 getItemId(int position)1068 public long getItemId(int position) { 1069 synchronized (mCache) { 1070 if (mCache.containsMetaDataAt(position)) { 1071 return mCache.getMetaDataAt(position).itemId; 1072 } 1073 return 0; 1074 } 1075 } 1076 getItemViewType(int position)1077 public int getItemViewType(int position) { 1078 final int typeId; 1079 synchronized (mCache) { 1080 if (mCache.containsMetaDataAt(position)) { 1081 typeId = mCache.getMetaDataAt(position).typeId; 1082 } else { 1083 return 0; 1084 } 1085 } 1086 1087 final RemoteViewsMetaData metaData = mCache.getMetaData(); 1088 synchronized (metaData) { 1089 return metaData.getMappedViewType(typeId); 1090 } 1091 } 1092 1093 /** 1094 * This method allows an AdapterView using this Adapter to provide information about which 1095 * views are currently being displayed. This allows for certain optimizations and preloading 1096 * which wouldn't otherwise be possible. 1097 */ 1098 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) setVisibleRangeHint(int lowerBound, int upperBound)1099 public void setVisibleRangeHint(int lowerBound, int upperBound) { 1100 mVisibleWindowLowerBound = lowerBound; 1101 mVisibleWindowUpperBound = upperBound; 1102 } 1103 getView(int position, View convertView, ViewGroup parent)1104 public View getView(int position, View convertView, ViewGroup parent) { 1105 // "Request" an index so that we can queue it for loading, initiate subsequent 1106 // preloading, etc. 1107 synchronized (mCache) { 1108 RemoteViews rv = mCache.getRemoteViewsAt(position); 1109 boolean isInCache = (rv != null); 1110 boolean hasNewItems = false; 1111 1112 if (convertView != null && convertView instanceof RemoteViewsFrameLayout) { 1113 mRequestedViews.removeView((RemoteViewsFrameLayout) convertView); 1114 } 1115 1116 if (!isInCache) { 1117 // Requesting bind service will trigger a super.notifyDataSetChanged(), which will 1118 // in turn trigger another request to getView() 1119 requestBindService(); 1120 } else { 1121 // Queue up other indices to be preloaded based on this position 1122 hasNewItems = mCache.queuePositionsToBePreloadedFromRequestedPosition(position); 1123 } 1124 1125 final RemoteViewsFrameLayout layout; 1126 if (convertView instanceof RemoteViewsFrameLayout) { 1127 layout = (RemoteViewsFrameLayout) convertView; 1128 } else { 1129 layout = new RemoteViewsFrameLayout(parent.getContext(), mCache); 1130 layout.setExecutor(mAsyncViewLoadExecutor); 1131 layout.setOnLightBackground(mOnLightBackground); 1132 } 1133 1134 if (isInCache) { 1135 // Apply the view synchronously if possible, to avoid flickering 1136 layout.onRemoteViewsLoaded(rv, mRemoteViewsInteractionHandler, false); 1137 if (hasNewItems) { 1138 mServiceHandler.sendEmptyMessage(MSG_LOAD_NEXT_ITEM); 1139 } 1140 } else { 1141 // If the views is not loaded, apply the loading view. If the loading view doesn't 1142 // exist, the layout will create a default view based on the firstView height. 1143 layout.onRemoteViewsLoaded( 1144 mCache.getMetaData().getLoadingTemplate(mContext).remoteViews, 1145 mRemoteViewsInteractionHandler, 1146 false); 1147 mRequestedViews.add(position, layout); 1148 mCache.queueRequestedPositionToLoad(position); 1149 mServiceHandler.sendEmptyMessage(MSG_LOAD_NEXT_ITEM); 1150 } 1151 return layout; 1152 } 1153 } 1154 getViewTypeCount()1155 public int getViewTypeCount() { 1156 final RemoteViewsMetaData metaData = mCache.getMetaData(); 1157 synchronized (metaData) { 1158 return metaData.viewTypeCount; 1159 } 1160 } 1161 hasStableIds()1162 public boolean hasStableIds() { 1163 final RemoteViewsMetaData metaData = mCache.getMetaData(); 1164 synchronized (metaData) { 1165 return metaData.hasStableIds; 1166 } 1167 } 1168 isEmpty()1169 public boolean isEmpty() { 1170 return getCount() <= 0; 1171 } 1172 1173 /** 1174 * Returns a sorted array of all integers between lower and upper. 1175 */ getVisibleWindow(int count)1176 private int[] getVisibleWindow(int count) { 1177 int lower = mVisibleWindowLowerBound; 1178 int upper = mVisibleWindowUpperBound; 1179 // In the case that the window is invalid or uninitialized, return an empty window. 1180 if ((lower == 0 && upper == 0) || lower < 0 || upper < 0) { 1181 return new int[0]; 1182 } 1183 1184 int[] window; 1185 if (lower <= upper) { 1186 window = new int[upper + 1 - lower]; 1187 for (int i = lower, j = 0; i <= upper; i++, j++){ 1188 window[j] = i; 1189 } 1190 } else { 1191 // If the upper bound is less than the lower bound it means that the visible window 1192 // wraps around. 1193 count = Math.max(count, lower); 1194 window = new int[count - lower + upper + 1]; 1195 int j = 0; 1196 // Add the entries in sorted order 1197 for (int i = 0; i <= upper; i++, j++) { 1198 window[j] = i; 1199 } 1200 for (int i = lower; i < count; i++, j++) { 1201 window[j] = i; 1202 } 1203 } 1204 return window; 1205 } 1206 notifyDataSetChanged()1207 public void notifyDataSetChanged() { 1208 mServiceHandler.removeMessages(MSG_UNBIND_SERVICE); 1209 mServiceHandler.sendEmptyMessage(MSG_NOTIFY_DATA_SET_CHANGED); 1210 } 1211 superNotifyDataSetChanged()1212 void superNotifyDataSetChanged() { 1213 super.notifyDataSetChanged(); 1214 } 1215 1216 @Override handleMessage(Message msg)1217 public boolean handleMessage(Message msg) { 1218 switch (msg.what) { 1219 case MSG_MAIN_HANDLER_COMMIT_METADATA: { 1220 mCache.commitTemporaryMetaData(); 1221 return true; 1222 } 1223 case MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED: { 1224 superNotifyDataSetChanged(); 1225 return true; 1226 } 1227 case MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED: { 1228 if (mCallback != null) { 1229 mCallback.onRemoteAdapterConnected(); 1230 } 1231 return true; 1232 } 1233 case MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED: { 1234 if (mCallback != null) { 1235 mCallback.onRemoteAdapterDisconnected(); 1236 } 1237 return true; 1238 } 1239 case MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED: { 1240 mRequestedViews.notifyOnRemoteViewsLoaded(msg.arg1, (RemoteViews) msg.obj); 1241 return true; 1242 } 1243 } 1244 return false; 1245 } 1246 requestBindService()1247 private void requestBindService() { 1248 mServiceHandler.removeMessages(MSG_UNBIND_SERVICE); 1249 Message.obtain(mServiceHandler, MSG_REQUEST_BIND, mAppWidgetId, 0, mIntent).sendToTarget(); 1250 } 1251 1252 private static class HandlerThreadExecutor implements Executor { 1253 private final HandlerThread mThread; 1254 HandlerThreadExecutor(HandlerThread thread)1255 HandlerThreadExecutor(HandlerThread thread) { 1256 mThread = thread; 1257 } 1258 1259 @Override execute(Runnable runnable)1260 public void execute(Runnable runnable) { 1261 if (Thread.currentThread().getId() == mThread.getId()) { 1262 runnable.run(); 1263 } else { 1264 new Handler(mThread.getLooper()).post(runnable); 1265 } 1266 } 1267 } 1268 1269 private static class LoadingViewTemplate { 1270 public final RemoteViews remoteViews; 1271 public int defaultHeight; 1272 LoadingViewTemplate(RemoteViews views, Context context)1273 LoadingViewTemplate(RemoteViews views, Context context) { 1274 remoteViews = views; 1275 1276 float density = context.getResources().getDisplayMetrics().density; 1277 defaultHeight = Math.round(DEFAULT_LOADING_VIEW_HEIGHT * density); 1278 } 1279 loadFirstViewHeight( RemoteViews firstView, Context context, Executor executor)1280 public void loadFirstViewHeight( 1281 RemoteViews firstView, Context context, Executor executor) { 1282 // Inflate the first view on the worker thread 1283 firstView.applyAsync(context, new RemoteViewsFrameLayout(context, null), executor, 1284 new RemoteViews.OnViewAppliedListener() { 1285 @Override 1286 public void onViewApplied(View v) { 1287 try { 1288 v.measure( 1289 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 1290 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); 1291 defaultHeight = v.getMeasuredHeight(); 1292 } catch (Exception e) { 1293 onError(e); 1294 } 1295 } 1296 1297 @Override 1298 public void onError(Exception e) { 1299 // Do nothing. The default height will stay the same. 1300 Log.w(TAG, "Error inflating first RemoteViews", e); 1301 } 1302 }); 1303 } 1304 } 1305 } 1306