1 /* 2 * Copyright (C) 2017 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 com.android.launcher3.model; 18 19 import android.os.Looper; 20 import android.util.Log; 21 22 import com.android.launcher3.AllAppsList; 23 import com.android.launcher3.AppInfo; 24 import com.android.launcher3.InvariantDeviceProfile; 25 import com.android.launcher3.ItemInfo; 26 import com.android.launcher3.LauncherAppState; 27 import com.android.launcher3.LauncherAppWidgetInfo; 28 import com.android.launcher3.LauncherModel.Callbacks; 29 import com.android.launcher3.LauncherSettings; 30 import com.android.launcher3.MainThreadExecutor; 31 import com.android.launcher3.PagedView; 32 import com.android.launcher3.Utilities; 33 import com.android.launcher3.config.FeatureFlags; 34 import com.android.launcher3.util.ComponentKey; 35 import com.android.launcher3.util.LooperIdleLock; 36 import com.android.launcher3.util.MultiHashMap; 37 import com.android.launcher3.util.ViewOnDrawExecutor; 38 import com.android.launcher3.widget.WidgetListRowEntry; 39 40 import java.lang.ref.WeakReference; 41 import java.util.ArrayList; 42 import java.util.Collections; 43 import java.util.Comparator; 44 import java.util.HashSet; 45 import java.util.Iterator; 46 import java.util.Set; 47 import java.util.concurrent.Executor; 48 49 /** 50 * Helper class to handle results of {@link com.android.launcher3.model.LoaderTask}. 51 */ 52 public class LoaderResults { 53 54 private static final String TAG = "LoaderResults"; 55 private static final long INVALID_SCREEN_ID = -1L; 56 private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons 57 58 private final Executor mUiExecutor; 59 60 private final LauncherAppState mApp; 61 private final BgDataModel mBgDataModel; 62 private final AllAppsList mBgAllAppsList; 63 private final int mPageToBindFirst; 64 65 private final WeakReference<Callbacks> mCallbacks; 66 67 public LoaderResults(LauncherAppState app, BgDataModel dataModel, 68 AllAppsList allAppsList, int pageToBindFirst, WeakReference<Callbacks> callbacks) { 69 mUiExecutor = new MainThreadExecutor(); 70 mApp = app; 71 mBgDataModel = dataModel; 72 mBgAllAppsList = allAppsList; 73 mPageToBindFirst = pageToBindFirst; 74 mCallbacks = callbacks == null ? new WeakReference<Callbacks>(null) : callbacks; 75 } 76 77 /** 78 * Binds all loaded data to actual views on the main thread. 79 */ 80 public void bindWorkspace() { 81 Runnable r; 82 83 Callbacks callbacks = mCallbacks.get(); 84 // Don't use these two variables in any of the callback runnables. 85 // Otherwise we hold a reference to them. 86 if (callbacks == null) { 87 // This launcher has exited and nobody bothered to tell us. Just bail. 88 Log.w(TAG, "LoaderTask running with no launcher"); 89 return; 90 } 91 92 // Save a copy of all the bg-thread collections 93 ArrayList<ItemInfo> workspaceItems = new ArrayList<>(); 94 ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>(); 95 final ArrayList<Long> orderedScreenIds = new ArrayList<>(); 96 97 synchronized (mBgDataModel) { 98 workspaceItems.addAll(mBgDataModel.workspaceItems); 99 appWidgets.addAll(mBgDataModel.appWidgets); 100 orderedScreenIds.addAll(mBgDataModel.workspaceScreens); 101 mBgDataModel.lastBindId++; 102 } 103 104 final int currentScreen; 105 { 106 int currScreen = mPageToBindFirst != PagedView.INVALID_RESTORE_PAGE 107 ? mPageToBindFirst : callbacks.getCurrentWorkspaceScreen(); 108 if (currScreen >= orderedScreenIds.size()) { 109 // There may be no workspace screens (just hotseat items and an empty page). 110 currScreen = PagedView.INVALID_RESTORE_PAGE; 111 } 112 currentScreen = currScreen; 113 } 114 final boolean validFirstPage = currentScreen >= 0; 115 final long currentScreenId = 116 validFirstPage ? orderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID; 117 118 // Separate the items that are on the current screen, and all the other remaining items 119 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>(); 120 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>(); 121 ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>(); 122 ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>(); 123 124 filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems, 125 otherWorkspaceItems); 126 filterCurrentWorkspaceItems(currentScreenId, appWidgets, currentAppWidgets, 127 otherAppWidgets); 128 sortWorkspaceItemsSpatially(currentWorkspaceItems); 129 sortWorkspaceItemsSpatially(otherWorkspaceItems); 130 131 // Tell the workspace that we're about to start binding items 132 r = new Runnable() { 133 public void run() { 134 Callbacks callbacks = mCallbacks.get(); 135 if (callbacks != null) { 136 callbacks.clearPendingBinds(); 137 callbacks.startBinding(); 138 } 139 } 140 }; 141 mUiExecutor.execute(r); 142 143 // Bind workspace screens 144 mUiExecutor.execute(new Runnable() { 145 @Override 146 public void run() { 147 Callbacks callbacks = mCallbacks.get(); 148 if (callbacks != null) { 149 callbacks.bindScreens(orderedScreenIds); 150 } 151 } 152 }); 153 154 Executor mainExecutor = mUiExecutor; 155 // Load items on the current page. 156 bindWorkspaceItems(currentWorkspaceItems, currentAppWidgets, mainExecutor); 157 158 // In case of validFirstPage, only bind the first screen, and defer binding the 159 // remaining screens after first onDraw (and an optional the fade animation whichever 160 // happens later). 161 // This ensures that the first screen is immediately visible (eg. during rotation) 162 // In case of !validFirstPage, bind all pages one after other. 163 final Executor deferredExecutor = 164 validFirstPage ? new ViewOnDrawExecutor() : mainExecutor; 165 166 mainExecutor.execute(new Runnable() { 167 @Override 168 public void run() { 169 Callbacks callbacks = mCallbacks.get(); 170 if (callbacks != null) { 171 callbacks.finishFirstPageBind( 172 validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null); 173 } 174 } 175 }); 176 177 bindWorkspaceItems(otherWorkspaceItems, otherAppWidgets, deferredExecutor); 178 179 // Tell the workspace that we're done binding items 180 r = new Runnable() { 181 public void run() { 182 Callbacks callbacks = mCallbacks.get(); 183 if (callbacks != null) { 184 callbacks.finishBindingItems(); 185 } 186 } 187 }; 188 deferredExecutor.execute(r); 189 190 if (validFirstPage) { 191 r = new Runnable() { 192 public void run() { 193 Callbacks callbacks = mCallbacks.get(); 194 if (callbacks != null) { 195 // We are loading synchronously, which means, some of the pages will be 196 // bound after first draw. Inform the callbacks that page binding is 197 // not complete, and schedule the remaining pages. 198 if (currentScreen != PagedView.INVALID_RESTORE_PAGE) { 199 callbacks.onPageBoundSynchronously(currentScreen); 200 } 201 callbacks.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor); 202 } 203 } 204 }; 205 mUiExecutor.execute(r); 206 } 207 } 208 209 210 /** Filters the set of items who are directly or indirectly (via another container) on the 211 * specified screen. */ 212 public static <T extends ItemInfo> void filterCurrentWorkspaceItems(long currentScreenId, 213 ArrayList<T> allWorkspaceItems, 214 ArrayList<T> currentScreenItems, 215 ArrayList<T> otherScreenItems) { 216 // Purge any null ItemInfos 217 Iterator<T> iter = allWorkspaceItems.iterator(); 218 while (iter.hasNext()) { 219 ItemInfo i = iter.next(); 220 if (i == null) { 221 iter.remove(); 222 } 223 } 224 225 // Order the set of items by their containers first, this allows use to walk through the 226 // list sequentially, build up a list of containers that are in the specified screen, 227 // as well as all items in those containers. 228 Set<Long> itemsOnScreen = new HashSet<>(); 229 Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() { 230 @Override 231 public int compare(ItemInfo lhs, ItemInfo rhs) { 232 return Utilities.longCompare(lhs.container, rhs.container); 233 } 234 }); 235 for (T info : allWorkspaceItems) { 236 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 237 if (info.screenId == currentScreenId) { 238 currentScreenItems.add(info); 239 itemsOnScreen.add(info.id); 240 } else { 241 otherScreenItems.add(info); 242 } 243 } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 244 currentScreenItems.add(info); 245 itemsOnScreen.add(info.id); 246 } else { 247 if (itemsOnScreen.contains(info.container)) { 248 currentScreenItems.add(info); 249 itemsOnScreen.add(info.id); 250 } else { 251 otherScreenItems.add(info); 252 } 253 } 254 } 255 } 256 257 /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to 258 * right) */ 259 private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) { 260 final InvariantDeviceProfile profile = mApp.getInvariantDeviceProfile(); 261 final int screenCols = profile.numColumns; 262 final int screenCellCount = profile.numColumns * profile.numRows; 263 Collections.sort(workspaceItems, new Comparator<ItemInfo>() { 264 @Override 265 public int compare(ItemInfo lhs, ItemInfo rhs) { 266 if (lhs.container == rhs.container) { 267 // Within containers, order by their spatial position in that container 268 switch ((int) lhs.container) { 269 case LauncherSettings.Favorites.CONTAINER_DESKTOP: { 270 long lr = (lhs.screenId * screenCellCount + 271 lhs.cellY * screenCols + lhs.cellX); 272 long rr = (rhs.screenId * screenCellCount + 273 rhs.cellY * screenCols + rhs.cellX); 274 return Utilities.longCompare(lr, rr); 275 } 276 case LauncherSettings.Favorites.CONTAINER_HOTSEAT: { 277 // We currently use the screen id as the rank 278 return Utilities.longCompare(lhs.screenId, rhs.screenId); 279 } 280 default: 281 if (FeatureFlags.IS_DOGFOOD_BUILD) { 282 throw new RuntimeException("Unexpected container type when " + 283 "sorting workspace items."); 284 } 285 return 0; 286 } 287 } else { 288 // Between containers, order by hotseat, desktop 289 return Utilities.longCompare(lhs.container, rhs.container); 290 } 291 } 292 }); 293 } 294 295 private void bindWorkspaceItems(final ArrayList<ItemInfo> workspaceItems, 296 final ArrayList<LauncherAppWidgetInfo> appWidgets, 297 final Executor executor) { 298 299 // Bind the workspace items 300 int N = workspaceItems.size(); 301 for (int i = 0; i < N; i += ITEMS_CHUNK) { 302 final int start = i; 303 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i); 304 final Runnable r = new Runnable() { 305 @Override 306 public void run() { 307 Callbacks callbacks = mCallbacks.get(); 308 if (callbacks != null) { 309 callbacks.bindItems(workspaceItems.subList(start, start+chunkSize), false); 310 } 311 } 312 }; 313 executor.execute(r); 314 } 315 316 // Bind the widgets, one at a time 317 N = appWidgets.size(); 318 for (int i = 0; i < N; i++) { 319 final ItemInfo widget = appWidgets.get(i); 320 final Runnable r = new Runnable() { 321 public void run() { 322 Callbacks callbacks = mCallbacks.get(); 323 if (callbacks != null) { 324 callbacks.bindItems(Collections.singletonList(widget), false); 325 } 326 } 327 }; 328 executor.execute(r); 329 } 330 } 331 332 public void bindDeepShortcuts() { 333 final MultiHashMap<ComponentKey, String> shortcutMapCopy; 334 synchronized (mBgDataModel) { 335 shortcutMapCopy = mBgDataModel.deepShortcutMap.clone(); 336 } 337 Runnable r = new Runnable() { 338 @Override 339 public void run() { 340 Callbacks callbacks = mCallbacks.get(); 341 if (callbacks != null) { 342 callbacks.bindDeepShortcutMap(shortcutMapCopy); 343 } 344 } 345 }; 346 mUiExecutor.execute(r); 347 } 348 349 public void bindAllApps() { 350 // shallow copy 351 @SuppressWarnings("unchecked") 352 final ArrayList<AppInfo> list = (ArrayList<AppInfo>) mBgAllAppsList.data.clone(); 353 354 Runnable r = new Runnable() { 355 public void run() { 356 Callbacks callbacks = mCallbacks.get(); 357 if (callbacks != null) { 358 callbacks.bindAllApplications(list); 359 } 360 } 361 }; 362 mUiExecutor.execute(r); 363 } 364 365 public void bindWidgets() { 366 final ArrayList<WidgetListRowEntry> widgets = 367 mBgDataModel.widgetsModel.getWidgetsList(mApp.getContext()); 368 Runnable r = new Runnable() { 369 public void run() { 370 Callbacks callbacks = mCallbacks.get(); 371 if (callbacks != null) { 372 callbacks.bindAllWidgets(widgets); 373 } 374 } 375 }; 376 mUiExecutor.execute(r); 377 } 378 379 public LooperIdleLock newIdleLock(Object lock) { 380 LooperIdleLock idleLock = new LooperIdleLock(lock, Looper.getMainLooper()); 381 // If we are not binding, there is no reason to wait for idle. 382 if (mCallbacks.get() == null) { 383 idleLock.queueIdle(); 384 } 385 return idleLock; 386 } 387 } 388