Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3cc22175 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Moving hotseat predictions to ModelDelegate" into ub-launcher3-master

parents 0734a362 0fc3d127
Loading
Loading
Loading
Loading
+0 −2
Original line number Diff line number Diff line
@@ -27,8 +27,6 @@

  <string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherExtension</string>

  <string name="prediction_model_class" translatable="false">com.android.launcher3.hybridhotseat.HotseatPredictionModel</string>

  <string name="model_delegate_class" translatable="false">com.android.launcher3.model.QuickstepModelDelegate</string>

</resources>
+0 −17
Original line number Diff line number Diff line
@@ -26,12 +26,10 @@ import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.content.Intent;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.CancellationSignal;

import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.model.WellbeingModel;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.proxy.ProxyActivityStarter;
@@ -40,14 +38,12 @@ import com.android.launcher3.statehandlers.BackButtonAlphaHandler;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.uioverrides.RecentsViewStateController;
import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.UiThreadHelper;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.QuickstepOnboardingPrefs;
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.util.RemoteFadeOutAnimationListener;
import com.android.quickstep.views.OverviewActionsView;
@@ -73,7 +69,6 @@ public abstract class BaseQuickstepLauncher extends Launcher
                    Float.intBitsToFloat(arg1), arg2 != 0);

    private OverviewActionsView mActionsView;
    protected HotseatPredictionController mHotseatPredictionController;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
@@ -221,11 +216,6 @@ public abstract class BaseQuickstepLauncher extends Launcher
        return mDepthController;
    }

    @Override
    protected OnboardingPrefs createOnboardingPrefs(SharedPreferences sharedPrefs) {
        return new QuickstepOnboardingPrefs(this, sharedPrefs);
    }

    @Override
    public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) {
        QuickstepAppTransitionManagerImpl appTransitionManager =
@@ -314,13 +304,6 @@ public abstract class BaseQuickstepLauncher extends Launcher
                Stream.of(WellbeingModel.SHORTCUT_FACTORY));
    }

    /**
     * Returns Prediction controller for hybrid hotseat
     */
    public HotseatPredictionController getHotseatPredictionController() {
        return mHotseatPredictionController;
    }

    public void setHintUserWillBeActive() {
        addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE);
    }
+0 −63
Original line number Diff line number Diff line
/**
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.launcher3.appprediction;

import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;

import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.util.ComponentKey;

public class ComponentKeyMapper {

    protected final ComponentKey componentKey;
    private final DynamicItemCache mCache;

    public ComponentKeyMapper(ComponentKey key, DynamicItemCache cache) {
        componentKey = key;
        mCache = cache;
    }

    public String getPackage() {
        return componentKey.componentName.getPackageName();
    }

    public String getComponentClass() {
        return componentKey.componentName.getClassName();
    }

    public ComponentKey getComponentKey() {
        return componentKey;
    }

    @Override
    public String toString() {
        return componentKey.toString();
    }

    public ItemInfoWithIcon getApp(AllAppsStore store) {
        AppInfo item = store.getApp(componentKey);
        if (item != null) {
            return item;
        } else if (getComponentClass().equals(COMPONENT_CLASS_MARKER)) {
            return mCache.getInstantApp(componentKey.componentName.getPackageName());
        } else {
            return mCache.getShortcutInfo(componentKey);
        }
    }
}
+0 −268
Original line number Diff line number Diff line
/**
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.launcher3.appprediction;

import static android.content.pm.PackageManager.MATCH_INSTANT;

import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;

import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.ArrayMap;
import android.util.Log;

import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.annotation.WorkerThread;

import com.android.launcher3.LauncherAppState;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.InstantAppResolver;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Utility class which loads and caches predicted items like instant apps and shortcuts, before
 * they can be displayed on the UI
 */
public class DynamicItemCache {

    private static final String TAG = "DynamicItemCache";
    private static final boolean DEBUG = false;
    private static final String DEFAULT_URL = "default-url";

    private static final int BG_MSG_LOAD_SHORTCUTS = 1;
    private static final int BG_MSG_LOAD_INSTANT_APPS = 2;

    private static final int UI_MSG_UPDATE_SHORTCUTS = 1;
    private static final int UI_MSG_UPDATE_INSTANT_APPS = 2;

    private final Context mContext;
    private final Handler mWorker;
    private final Handler mUiHandler;
    private final InstantAppResolver mInstantAppResolver;
    private final Runnable mOnUpdateCallback;
    private final IconCache mIconCache;

    private final Map<ComponentKey, WorkspaceItemInfo> mShortcuts;
    private final Map<String, InstantAppItemInfo> mInstantApps;

    public DynamicItemCache(Context context, Runnable onUpdateCallback) {
        mContext = context;
        mWorker = new Handler(MODEL_EXECUTOR.getLooper(), this::handleWorkerMessage);
        mUiHandler = new Handler(Looper.getMainLooper(), this::handleUiMessage);
        mInstantAppResolver = InstantAppResolver.newInstance(context);
        mOnUpdateCallback = onUpdateCallback;
        mIconCache = LauncherAppState.getInstance(mContext).getIconCache();

        mShortcuts = new HashMap<>();
        mInstantApps = new HashMap<>();
    }

    public void cacheItems(List<ShortcutKey> shortcutKeys, List<String> pkgNames) {
        if (!shortcutKeys.isEmpty()) {
            mWorker.removeMessages(BG_MSG_LOAD_SHORTCUTS);
            Message.obtain(mWorker, BG_MSG_LOAD_SHORTCUTS, shortcutKeys).sendToTarget();
        }
        if (!pkgNames.isEmpty()) {
            mWorker.removeMessages(BG_MSG_LOAD_INSTANT_APPS);
            Message.obtain(mWorker, BG_MSG_LOAD_INSTANT_APPS, pkgNames).sendToTarget();
        }
    }

    private boolean handleWorkerMessage(Message msg) {
        switch (msg.what) {
            case BG_MSG_LOAD_SHORTCUTS: {
                List<ShortcutKey> shortcutKeys = msg.obj != null ?
                        (List<ShortcutKey>) msg.obj : Collections.EMPTY_LIST;
                Map<ShortcutKey, WorkspaceItemInfo> shortcutKeyAndInfos = new ArrayMap<>();
                for (ShortcutKey shortcutKey : shortcutKeys) {
                    WorkspaceItemInfo workspaceItemInfo = loadShortcutWorker(shortcutKey);
                    if (workspaceItemInfo != null) {
                        shortcutKeyAndInfos.put(shortcutKey, workspaceItemInfo);
                    }
                }
                Message.obtain(mUiHandler, UI_MSG_UPDATE_SHORTCUTS, shortcutKeyAndInfos)
                        .sendToTarget();
                return true;
            }
            case BG_MSG_LOAD_INSTANT_APPS: {
                List<String> pkgNames = msg.obj != null ?
                        (List<String>) msg.obj : Collections.EMPTY_LIST;
                List<InstantAppItemInfo> instantAppItemInfos = new ArrayList<>();
                for (String pkgName : pkgNames) {
                    InstantAppItemInfo instantAppItemInfo = loadInstantApp(pkgName);
                    if (instantAppItemInfo != null) {
                        instantAppItemInfos.add(instantAppItemInfo);
                    }
                }
                Message.obtain(mUiHandler, UI_MSG_UPDATE_INSTANT_APPS, instantAppItemInfos)
                        .sendToTarget();
                return true;
            }
        }

        return false;
    }

    private boolean handleUiMessage(Message msg) {
        switch (msg.what) {
            case UI_MSG_UPDATE_SHORTCUTS: {
                mShortcuts.clear();
                mShortcuts.putAll((Map<ShortcutKey, WorkspaceItemInfo>) msg.obj);
                mOnUpdateCallback.run();
                return true;
            }
            case UI_MSG_UPDATE_INSTANT_APPS: {
                List<InstantAppItemInfo> instantAppItemInfos = (List<InstantAppItemInfo>) msg.obj;
                mInstantApps.clear();
                for (InstantAppItemInfo instantAppItemInfo : instantAppItemInfos) {
                    mInstantApps.put(instantAppItemInfo.getTargetComponent().getPackageName(),
                            instantAppItemInfo);
                }
                mOnUpdateCallback.run();
                if (DEBUG) {
                    Log.d(TAG, String.format("Cache size: %d, Cache: %s",
                            mInstantApps.size(), mInstantApps.toString()));
                }
                return true;
            }
        }

        return false;
    }

    @WorkerThread
    private WorkspaceItemInfo loadShortcutWorker(ShortcutKey shortcutKey) {
        List<ShortcutInfo> details = shortcutKey.buildRequest(mContext).query(ShortcutRequest.ALL);
        if (!details.isEmpty()) {
            WorkspaceItemInfo si = new WorkspaceItemInfo(details.get(0), mContext);
            mIconCache.getShortcutIcon(si, details.get(0));
            return si;
        }
        if (DEBUG) {
            Log.d(TAG, "No shortcut found: " + shortcutKey.toString());
        }
        return null;
    }

    private InstantAppItemInfo loadInstantApp(String pkgName) {
        PackageManager pm = mContext.getPackageManager();

        try {
            ApplicationInfo ai = pm.getApplicationInfo(pkgName, 0);
            if (!mInstantAppResolver.isInstantApp(ai)) {
                return null;
            }
        } catch (PackageManager.NameNotFoundException e) {
            return null;
        }

        String url = retrieveDefaultUrl(pkgName, pm);
        if (url == null) {
            Log.w(TAG, "no default-url available for pkg " + pkgName);
            return null;
        }

        Intent intent = new Intent(Intent.ACTION_VIEW)
                .addCategory(Intent.CATEGORY_BROWSABLE)
                .setData(Uri.parse(url));
        InstantAppItemInfo info = new InstantAppItemInfo(intent, pkgName);
        IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
        iconCache.getTitleAndIcon(info, false);
        if (info.bitmap.icon == null || iconCache.isDefaultIcon(info.bitmap, info.user)) {
            return null;
        }
        return info;
    }

    @Nullable
    public static String retrieveDefaultUrl(String pkgName, PackageManager pm) {
        Intent mainIntent = new Intent().setAction(Intent.ACTION_MAIN)
                .addCategory(Intent.CATEGORY_LAUNCHER).setPackage(pkgName);
        List<ResolveInfo> resolveInfos = pm.queryIntentActivities(
                mainIntent, MATCH_INSTANT | PackageManager.GET_META_DATA);
        String url = null;
        for (ResolveInfo resolveInfo : resolveInfos) {
            if (resolveInfo.activityInfo.metaData != null
                    && resolveInfo.activityInfo.metaData.containsKey(DEFAULT_URL)) {
                url = resolveInfo.activityInfo.metaData.getString(DEFAULT_URL);
            }
        }
        return url;
    }

    @UiThread
    public InstantAppItemInfo getInstantApp(String pkgName) {
        return mInstantApps.get(pkgName);
    }

    @MainThread
    public WorkspaceItemInfo getShortcutInfo(ComponentKey key) {
        return mShortcuts.get(key);
    }

    /**
     * requests and caches icons for app targets
     */
    public void updateDependencies(List<ComponentKeyMapper> componentKeyMappers,
            AllAppsStore appsStore, IconCache.ItemInfoUpdateReceiver callback, int itemCount) {
        List<String> instantAppsToLoad = new ArrayList<>();
        List<ShortcutKey> shortcutsToLoad = new ArrayList<>();
        int total = componentKeyMappers.size();
        for (int i = 0, count = 0; i < total && count < itemCount; i++) {
            ComponentKeyMapper mapper = componentKeyMappers.get(i);
            // Update instant apps
            if (COMPONENT_CLASS_MARKER.equals(mapper.getComponentClass())) {
                instantAppsToLoad.add(mapper.getPackage());
                count++;
            } else if (mapper.getComponentKey() instanceof ShortcutKey) {
                shortcutsToLoad.add((ShortcutKey) mapper.getComponentKey());
                count++;
            } else {
                // Reload high res icon
                AppInfo info = (AppInfo) mapper.getApp(appsStore);
                if (info != null) {
                    if (info.usingLowResIcon()) {
                        mIconCache.updateIconInBackground(callback, info);
                    }
                    count++;
                }
            }
        }
        cacheItems(shortcutsToLoad, instantAppsToLoad);
    }
}
+3 −11
Original line number Diff line number Diff line
@@ -47,35 +47,27 @@ import java.util.stream.IntStream;
 */
public class HotseatEduController {

    public static final String HOTSEAT_EDU_ACTION =
            "com.android.launcher3.action.SHOW_HYBRID_HOTSEAT_EDU";
    public static final String SETTINGS_ACTION =
            "android.settings.ACTION_CONTENT_SUGGESTIONS_SETTINGS";

    private final Launcher mLauncher;
    private final Hotseat mHotseat;
    private HotseatRestoreHelper mRestoreHelper;
    private List<WorkspaceItemInfo> mPredictedApps;
    private HotseatEduDialog mActiveDialog;

    private ArrayList<ItemInfo> mNewItems = new ArrayList<>();
    private IntArray mNewScreens = null;
    private Runnable mOnOnboardingComplete;

    HotseatEduController(Launcher launcher, HotseatRestoreHelper restoreHelper, Runnable runnable) {
    HotseatEduController(Launcher launcher) {
        mLauncher = launcher;
        mHotseat = launcher.getHotseat();
        mRestoreHelper = restoreHelper;
        mOnOnboardingComplete = runnable;
    }

    /**
     * Checks what type of migration should be used and migrates hotseat
     */
    void migrate() {
        if (mRestoreHelper != null) {
            mRestoreHelper.createBackup();
        }
        HotseatRestoreHelper.createBackup(mLauncher);
        if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) {
            migrateToFolder();
        } else {
@@ -227,7 +219,7 @@ public class HotseatEduController {
    }

    void finishOnboarding() {
        mOnOnboardingComplete.run();
        mLauncher.getModel().onWorkspaceUiChanged();
    }

    void showDimissTip() {
Loading