Home | History | Annotate | Download | only in model
      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