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