Add shortcut to install an app to Private space
Introduce long-press shortcut to install a copy
of the main user app to private space.
Test: manual, installed apps in different stores
and tried shortcut
https://screenshot.googleplex.com/6oAVamTytiYmvPC.png
Bug: 316118005
Flag: ACONFIG com.android.launcher3.Flags.enable_private_space_install_shortcut DEVELOPMENT
Change-Id: I702cd2a27388e3cc6e9e126308d5479836ba6655
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 22ed567..0e11f4b 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -104,3 +104,10 @@
description: "Enables support for archived apps in Launcher3, such as empty progress bar etc."
bug: "210590852"
}
+
+flag {
+ name: "enable_private_space_install_shortcut"
+ namespace: "launcher"
+ description: "Enables long-press shortcut to install a copy of an app to Private space"
+ bug: "316118005"
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
index 784c560..7113e49 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -129,8 +129,10 @@
public static Intent getAppMarketActivityIntent(Context context, String packageName,
UserHandle user) {
LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
- if (android.os.Flags.allowPrivateProfile() && Flags.enablePrivateSpace()
- && Flags.privateSpaceAppInstallerButton()) {
+ if (android.os.Flags.allowPrivateProfile()
+ && Flags.enablePrivateSpace()
+ && (Flags.privateSpaceAppInstallerButton()
+ || Flags.enablePrivateSpaceInstallShortcut())) {
StartActivityParams params = new StartActivityParams((PendingIntent) null, 0);
params.intentSender = launcherApps.getAppMarketActivityIntent(packageName, user);
return ProxyActivityStarter.getLaunchIntent(context, params);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index f45ddb6..806c26b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -40,6 +40,7 @@
import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition;
import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
import static com.android.launcher3.popup.SystemShortcut.INSTALL;
+import static com.android.launcher3.popup.SystemShortcut.PRIVATE_PROFILE_INSTALL;
import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
import static com.android.launcher3.taskbar.LauncherTaskbarUIController.ALL_APPS_PAGE_PROGRESS_INDEX;
import static com.android.launcher3.taskbar.LauncherTaskbarUIController.MINUS_ONE_PAGE_PROGRESS_INDEX;
@@ -94,6 +95,7 @@
import com.android.app.viewcapture.SettingsAwareViewCapture;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
import com.android.launcher3.HomeTransitionController;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings.Favorites;
@@ -414,6 +416,10 @@
shortcuts.addAll(getSplitShortcuts());
shortcuts.add(WIDGETS);
shortcuts.add(INSTALL);
+
+ if (Flags.enablePrivateSpaceInstallShortcut()) {
+ shortcuts.add(PRIVATE_PROFILE_INSTALL);
+ }
return shortcuts.stream();
}
diff --git a/res/drawable/ic_install_to_private.xml b/res/drawable/ic_install_to_private.xml
new file mode 100644
index 0000000..7f00f8d
--- /dev/null
+++ b/res/drawable/ic_install_to_private.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M420,600L540,600L517,471Q537,461 548.5,442Q560,423 560,400Q560,367 536.5,343.5Q513,320 480,320Q447,320 423.5,343.5Q400,367 400,400Q400,423 411.5,442Q423,461 443,471L420,600ZM480,880Q341,845 250.5,720.5Q160,596 160,444L160,200L480,80L800,200L800,444Q800,596 709.5,720.5Q619,845 480,880ZM480,796Q584,763 652,664Q720,565 720,444L720,255L480,165L240,255L240,444Q240,565 308,664Q376,763 480,796ZM480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Z"/>
+</vector>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6a4a9a4..b043662 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -171,6 +171,8 @@
<string name="uninstall_drop_target_label">Uninstall</string>
<!-- Label for app info drop target. [CHAR_LIMIT=20] -->
<string name="app_info_drop_target_label">App info</string>
+ <!-- Label for install to private profile shortcut label. [CHAR_LIMIT=20] -->
+ <string name="install_private_system_shortcut_label">Install in private</string>
<!-- Label for install drop target. [CHAR_LIMIT=20] -->
<string name="install_drop_target_label">Install</string>
<!-- Label for install dismiss prediction. -->
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 4ad4c71..ad764e3 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -1315,6 +1315,10 @@
: mViewPager == null ? AdapterHolder.MAIN : mViewPager.getNextPage();
}
+ public PrivateProfileManager getPrivateProfileManager() {
+ return mPrivateProfileManager;
+ }
+
/**
* Adds an update listener to animator that adds springs to the animation.
*/
diff --git a/src/com/android/launcher3/allapps/UserProfileManager.java b/src/com/android/launcher3/allapps/UserProfileManager.java
index 0261010..6bef725 100644
--- a/src/com/android/launcher3/allapps/UserProfileManager.java
+++ b/src/com/android/launcher3/allapps/UserProfileManager.java
@@ -94,6 +94,19 @@
return mCurrentState;
}
+ /** Returns if user profile is enabled. */
+ public boolean isEnabled() {
+ return mCurrentState == STATE_ENABLED;
+ }
+
+ /** Returns the UserHandle corresponding to the profile type, null in case no matches found. */
+ public UserHandle getProfileUser() {
+ return mUserCache.getUserProfiles().stream()
+ .filter(getUserMatcher())
+ .findAny()
+ .orElse(null);
+ }
+
/** Logs Event to StatsLogManager. */
protected void logEvents(StatsLogManager.EventEnum event) {
mStatsLogManager.logger().log(event);
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 6651fa0..0dc0d02 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -701,6 +701,9 @@
@UiEvent(doc = "User tapped private space settings button")
LAUNCHER_PRIVATE_SPACE_SETTINGS_TAP(1550),
+ @UiEvent(doc = "User tapped on install to private space system shortcut.")
+ LAUNCHER_PRIVATE_SPACE_INSTALL_SYSTEM_SHORTCUT_TAP(1565),
+
// ADD MORE
;
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index f39f806..8463361 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -1,5 +1,6 @@
package com.android.launcher3.popup;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_INSTALL_SYSTEM_SHORTCUT_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_WIDGETS_TAP;
@@ -8,6 +9,7 @@
import android.content.Intent;
import android.graphics.Rect;
import android.os.Process;
+import android.os.UserHandle;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ImageView;
@@ -20,10 +22,12 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.PrivateProfileManager;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.uioverrides.ApiWrapper;
+import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PackageUserKey;
@@ -212,6 +216,69 @@
}
}
+ public static final Factory<Launcher> PRIVATE_PROFILE_INSTALL =
+ (launcher, itemInfo, originalView) -> {
+ if (itemInfo.getTargetComponent() == null
+ || !(itemInfo instanceof com.android.launcher3.model.data.AppInfo)
+ || !itemInfo.getContainerInfo().hasAllAppsContainer()
+ || !Process.myUserHandle().equals(itemInfo.user)) {
+ return null;
+ }
+
+ PrivateProfileManager privateProfileManager =
+ launcher.getAppsView().getPrivateProfileManager();
+ if (privateProfileManager == null || !privateProfileManager.isEnabled()) {
+ return null;
+ }
+
+ UserHandle privateProfileUser = privateProfileManager.getProfileUser();
+ if (privateProfileUser == null) {
+ return null;
+ }
+ // Do not show shortcut if an app is already installed to the space
+ ComponentKey targetKey =
+ new ComponentKey(itemInfo.getTargetComponent(), privateProfileUser);
+ if (launcher.getAppsView().getAppsStore().getApp(targetKey) != null) {
+ return null;
+ }
+
+ // TODO(b/302666597): do not install app if it's in deny list (e.g. settings)
+
+ return new InstallToPrivateProfile(
+ launcher, itemInfo, originalView, privateProfileUser);
+ };
+
+ static class InstallToPrivateProfile extends SystemShortcut<Launcher> {
+ UserHandle mSpaceUser;
+
+ InstallToPrivateProfile(
+ Launcher target, ItemInfo itemInfo, View originalView, UserHandle spaceUser) {
+ // TODO(b/302666597): update icon once available
+ super(
+ R.drawable.ic_install_to_private,
+ R.string.install_private_system_shortcut_label,
+ target,
+ itemInfo,
+ originalView);
+ mSpaceUser = spaceUser;
+ }
+
+ @Override
+ public void onClick(View view) {
+ Intent intent =
+ ApiWrapper.getAppMarketActivityIntent(
+ view.getContext(),
+ mItemInfo.getTargetComponent().getPackageName(),
+ mSpaceUser);
+ mTarget.startActivitySafely(view, intent, mItemInfo);
+ AbstractFloatingView.closeAllOpenViews(mTarget);
+ mTarget.getStatsLogManager()
+ .logger()
+ .withItemInfo(mItemInfo)
+ .log(LAUNCHER_PRIVATE_SPACE_INSTALL_SYSTEM_SHORTCUT_TAP);
+ }
+ }
+
public static final Factory<BaseDraggingActivity> INSTALL =
(activity, itemInfo, originalView) -> {
boolean supportsWebUI = (itemInfo instanceof WorkspaceItemInfo)