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 static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems; 20 import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially; 21 22 import android.util.Log; 23 24 import com.android.launcher3.InvariantDeviceProfile; 25 import com.android.launcher3.LauncherAppState; 26 import com.android.launcher3.LauncherModel.CallbackTask; 27 import com.android.launcher3.PagedView; 28 import com.android.launcher3.model.BgDataModel.Callbacks; 29 import com.android.launcher3.model.BgDataModel.FixedContainerItems; 30 import com.android.launcher3.model.data.AppInfo; 31 import com.android.launcher3.model.data.ItemInfo; 32 import com.android.launcher3.model.data.LauncherAppWidgetInfo; 33 import com.android.launcher3.util.IntArray; 34 import com.android.launcher3.util.LooperExecutor; 35 import com.android.launcher3.util.LooperIdleLock; 36 import com.android.launcher3.util.ViewOnDrawExecutor; 37 38 import java.util.ArrayList; 39 import java.util.Collections; 40 import java.util.List; 41 import java.util.concurrent.Executor; 42 43 /** 44 * Base Helper class to handle results of {@link com.android.launcher3.model.LoaderTask}. 45 */ 46 public abstract class BaseLoaderResults { 47 48 protected static final String TAG = "LoaderResults"; 49 protected static final int INVALID_SCREEN_ID = -1; 50 private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons 51 52 protected final LooperExecutor mUiExecutor; 53 54 protected final LauncherAppState mApp; 55 protected final BgDataModel mBgDataModel; 56 private final AllAppsList mBgAllAppsList; 57 58 private final Callbacks[] mCallbacksList; 59 60 private int mMyBindingId; 61 BaseLoaderResults(LauncherAppState app, BgDataModel dataModel, AllAppsList allAppsList, Callbacks[] callbacksList, LooperExecutor uiExecutor)62 public BaseLoaderResults(LauncherAppState app, BgDataModel dataModel, 63 AllAppsList allAppsList, Callbacks[] callbacksList, LooperExecutor uiExecutor) { 64 mUiExecutor = uiExecutor; 65 mApp = app; 66 mBgDataModel = dataModel; 67 mBgAllAppsList = allAppsList; 68 mCallbacksList = callbacksList; 69 } 70 71 /** 72 * Binds all loaded data to actual views on the main thread. 73 */ bindWorkspace()74 public void bindWorkspace() { 75 // Save a copy of all the bg-thread collections 76 ArrayList<ItemInfo> workspaceItems = new ArrayList<>(); 77 ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>(); 78 final IntArray orderedScreenIds = new IntArray(); 79 ArrayList<FixedContainerItems> extraItems = new ArrayList<>(); 80 81 synchronized (mBgDataModel) { 82 workspaceItems.addAll(mBgDataModel.workspaceItems); 83 appWidgets.addAll(mBgDataModel.appWidgets); 84 orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens()); 85 mBgDataModel.extraItems.forEach(extraItems::add); 86 mBgDataModel.lastBindId++; 87 mMyBindingId = mBgDataModel.lastBindId; 88 } 89 90 for (Callbacks cb : mCallbacksList) { 91 new WorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId, 92 workspaceItems, appWidgets, extraItems, orderedScreenIds).bind(); 93 } 94 } 95 bindDeepShortcuts()96 public abstract void bindDeepShortcuts(); 97 bindAllApps()98 public void bindAllApps() { 99 // shallow copy 100 AppInfo[] apps = mBgAllAppsList.copyData(); 101 int flags = mBgAllAppsList.getFlags(); 102 executeCallbacksTask(c -> c.bindAllApplications(apps, flags), mUiExecutor); 103 } 104 bindWidgets()105 public abstract void bindWidgets(); 106 executeCallbacksTask(CallbackTask task, Executor executor)107 protected void executeCallbacksTask(CallbackTask task, Executor executor) { 108 executor.execute(() -> { 109 if (mMyBindingId != mBgDataModel.lastBindId) { 110 Log.d(TAG, "Too many consecutive reloads, skipping obsolete data-bind"); 111 return; 112 } 113 for (Callbacks cb : mCallbacksList) { 114 task.execute(cb); 115 } 116 }); 117 } 118 newIdleLock(Object lock)119 public LooperIdleLock newIdleLock(Object lock) { 120 LooperIdleLock idleLock = new LooperIdleLock(lock, mUiExecutor.getLooper()); 121 // If we are not binding or if the main looper is already idle, there is no reason to wait 122 if (mUiExecutor.getLooper().getQueue().isIdle()) { 123 idleLock.queueIdle(); 124 } 125 return idleLock; 126 } 127 128 private static class WorkspaceBinder { 129 130 private final Executor mUiExecutor; 131 private final Callbacks mCallbacks; 132 133 private final LauncherAppState mApp; 134 private final BgDataModel mBgDataModel; 135 136 private final int mMyBindingId; 137 private final ArrayList<ItemInfo> mWorkspaceItems; 138 private final ArrayList<LauncherAppWidgetInfo> mAppWidgets; 139 private final IntArray mOrderedScreenIds; 140 private final ArrayList<FixedContainerItems> mExtraItems; 141 WorkspaceBinder(Callbacks callbacks, Executor uiExecutor, LauncherAppState app, BgDataModel bgDataModel, int myBindingId, ArrayList<ItemInfo> workspaceItems, ArrayList<LauncherAppWidgetInfo> appWidgets, ArrayList<FixedContainerItems> extraItems, IntArray orderedScreenIds)142 WorkspaceBinder(Callbacks callbacks, 143 Executor uiExecutor, 144 LauncherAppState app, 145 BgDataModel bgDataModel, 146 int myBindingId, 147 ArrayList<ItemInfo> workspaceItems, 148 ArrayList<LauncherAppWidgetInfo> appWidgets, 149 ArrayList<FixedContainerItems> extraItems, 150 IntArray orderedScreenIds) { 151 mCallbacks = callbacks; 152 mUiExecutor = uiExecutor; 153 mApp = app; 154 mBgDataModel = bgDataModel; 155 mMyBindingId = myBindingId; 156 mWorkspaceItems = workspaceItems; 157 mAppWidgets = appWidgets; 158 mExtraItems = extraItems; 159 mOrderedScreenIds = orderedScreenIds; 160 } 161 bind()162 private void bind() { 163 final int currentScreen; 164 { 165 // Create an anonymous scope to calculate currentScreen as it has to be a 166 // final variable. 167 int currScreen = mCallbacks.getPageToBindSynchronously(); 168 if (currScreen >= mOrderedScreenIds.size()) { 169 // There may be no workspace screens (just hotseat items and an empty page). 170 currScreen = PagedView.INVALID_PAGE; 171 } 172 currentScreen = currScreen; 173 } 174 final boolean validFirstPage = currentScreen >= 0; 175 final int currentScreenId = 176 validFirstPage ? mOrderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID; 177 178 // Separate the items that are on the current screen, and all the other remaining items 179 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>(); 180 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>(); 181 ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>(); 182 ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>(); 183 184 filterCurrentWorkspaceItems(currentScreenId, mWorkspaceItems, currentWorkspaceItems, 185 otherWorkspaceItems); 186 filterCurrentWorkspaceItems(currentScreenId, mAppWidgets, currentAppWidgets, 187 otherAppWidgets); 188 final InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile(); 189 sortWorkspaceItemsSpatially(idp, currentWorkspaceItems); 190 sortWorkspaceItemsSpatially(idp, otherWorkspaceItems); 191 192 // Tell the workspace that we're about to start binding items 193 executeCallbacksTask(c -> { 194 c.clearPendingBinds(); 195 c.startBinding(); 196 }, mUiExecutor); 197 198 // Bind workspace screens 199 executeCallbacksTask(c -> c.bindScreens(mOrderedScreenIds), mUiExecutor); 200 201 Executor mainExecutor = mUiExecutor; 202 // Load items on the current page. 203 bindWorkspaceItems(currentWorkspaceItems, mainExecutor); 204 bindAppWidgets(currentAppWidgets, mainExecutor); 205 mExtraItems.forEach(item -> 206 executeCallbacksTask(c -> c.bindExtraContainerItems(item), mainExecutor)); 207 208 // In case of validFirstPage, only bind the first screen, and defer binding the 209 // remaining screens after first onDraw (and an optional the fade animation whichever 210 // happens later). 211 // This ensures that the first screen is immediately visible (eg. during rotation) 212 // In case of !validFirstPage, bind all pages one after other. 213 214 final Executor deferredExecutor = 215 validFirstPage ? new ViewOnDrawExecutor() : mainExecutor; 216 217 executeCallbacksTask(c -> c.finishFirstPageBind( 218 validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null), mainExecutor); 219 220 bindWorkspaceItems(otherWorkspaceItems, deferredExecutor); 221 bindAppWidgets(otherAppWidgets, deferredExecutor); 222 // Tell the workspace that we're done binding items 223 executeCallbacksTask(c -> c.finishBindingItems(currentScreen), deferredExecutor); 224 225 if (validFirstPage) { 226 executeCallbacksTask(c -> { 227 // We are loading synchronously, which means, some of the pages will be 228 // bound after first draw. Inform the mCallbacks that page binding is 229 // not complete, and schedule the remaining pages. 230 c.onPageBoundSynchronously(currentScreen); 231 c.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor); 232 233 }, mUiExecutor); 234 } 235 } 236 bindWorkspaceItems( final ArrayList<ItemInfo> workspaceItems, final Executor executor)237 private void bindWorkspaceItems( 238 final ArrayList<ItemInfo> workspaceItems, final Executor executor) { 239 // Bind the workspace items 240 int count = workspaceItems.size(); 241 for (int i = 0; i < count; i += ITEMS_CHUNK) { 242 final int start = i; 243 final int chunkSize = (i + ITEMS_CHUNK <= count) ? ITEMS_CHUNK : (count - i); 244 executeCallbacksTask( 245 c -> c.bindItems(workspaceItems.subList(start, start + chunkSize), false), 246 executor); 247 } 248 } 249 bindAppWidgets(List<LauncherAppWidgetInfo> appWidgets, Executor executor)250 private void bindAppWidgets(List<LauncherAppWidgetInfo> appWidgets, Executor executor) { 251 // Bind the widgets, one at a time 252 int count = appWidgets.size(); 253 for (int i = 0; i < count; i++) { 254 final ItemInfo widget = appWidgets.get(i); 255 executeCallbacksTask( 256 c -> c.bindItems(Collections.singletonList(widget), false), executor); 257 } 258 } 259 executeCallbacksTask(CallbackTask task, Executor executor)260 protected void executeCallbacksTask(CallbackTask task, Executor executor) { 261 executor.execute(() -> { 262 if (mMyBindingId != mBgDataModel.lastBindId) { 263 Log.d(TAG, "Too many consecutive reloads, skipping obsolete data-bind"); 264 return; 265 } 266 task.execute(mCallbacks); 267 }); 268 } 269 } 270 } 271