Merge "Add placeholder divider view when starting split tasks" into 24D1-dev
diff --git a/quickstep/res/values-land/dimens.xml b/quickstep/res/values-land/dimens.xml
index 2b17b93..e862b9e 100644
--- a/quickstep/res/values-land/dimens.xml
+++ b/quickstep/res/values-land/dimens.xml
@@ -78,7 +78,8 @@
<!-- Taskbar 3 button spacing -->
<dimen name="taskbar_button_margin_split">88dp</dimen>
<dimen name="taskbar_button_margin_6_5">219.6dp</dimen>
- <dimen name="taskbar_contextual_button_margin">48dp</dimen>
+ <dimen name="taskbar_contextual_button_suw_margin">48dp</dimen>
+ <dimen name="taskbar_contextual_button_suw_height">48dp</dimen>
<dimen name="taskbar_suw_frame">96dp</dimen>
<dimen name="taskbar_suw_insets">24dp</dimen>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 3331321..89a193b 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -325,9 +325,8 @@
<dimen name="taskbar_contextual_padding_top">8dp</dimen>
<dimen name="taskbar_nav_buttons_size">44dp</dimen>
<dimen name="taskbar_split_instructions_margin">48dp</dimen>
- <dimen name="taskbar_contextual_button_margin">120dp</dimen>
<dimen name="taskbar_suw_insets">48dp</dimen>
- <dimen name="taskbar_suw_frame">48dp</dimen>
+ <dimen name="taskbar_suw_frame">96dp</dimen>
<dimen name="taskbar_hotseat_nav_spacing">24dp</dimen>
<dimen name="taskbar_contextual_buttons_size">35dp</dimen>
<dimen name="taskbar_stashed_size">24dp</dimen>
@@ -346,6 +345,11 @@
<dimen name="taskbar_icon_size_kids">32dp</dimen>
<dimen name="taskbar_all_apps_button_translation_x_offset">6dp</dimen>
<dimen name="taskbar_all_apps_search_button_translation_x_offset">6dp</dimen>
+ <dimen name="taskbar_contextual_button_suw_margin">64dp</dimen>
+ <dimen name="taskbar_contextual_button_suw_height">64dp</dimen>
+ <dimen name="taskbar_back_button_suw_start_margin">48dp</dimen>
+ <dimen name="taskbar_back_button_suw_bottom_margin">1dp</dimen>
+ <dimen name="taskbar_back_button_suw_height">72dp</dimen>
<!-- Transient taskbar -->
<dimen name="transient_taskbar_padding">12dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 2f11fd7..2c5aeb3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -142,6 +142,9 @@
private static final String NAV_BUTTONS_SEPARATE_WINDOW_TITLE = "Taskbar Nav Buttons";
+ private static final double SQUARE_ASPECT_RATIO_BOTTOM_BOUND = 0.95;
+ private static final double SQUARE_ASPECT_RATIO_UPPER_BOUND = 1.05;
+
public static final int ALPHA_INDEX_IMMERSIVE_MODE = 0;
public static final int ALPHA_INDEX_KEYGUARD_OR_DISABLE = 1;
public static final int ALPHA_INDEX_SUW = 2;
@@ -735,18 +738,39 @@
// end-aligned, so start-align instead.
FrameLayout.LayoutParams navButtonsLayoutParams = (FrameLayout.LayoutParams)
mNavButtonContainer.getLayoutParams();
+ FrameLayout.LayoutParams navButtonsViewLayoutParams = (FrameLayout.LayoutParams)
+ mNavButtonsView.getLayoutParams();
Resources resources = mContext.getResources();
DeviceProfile deviceProfile = mContext.getDeviceProfile();
- int setupMargin = resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_margin);
- navButtonsLayoutParams.setMarginStart(setupMargin);
- navButtonsLayoutParams.bottomMargin = !deviceProfile.isLandscape
- ? 0
- : setupMargin -
- (resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size) / 2);
+
navButtonsLayoutParams.setMarginEnd(0);
navButtonsLayoutParams.gravity = Gravity.START;
- mNavButtonsView.getLayoutParams().height =
- mControllers.taskbarActivityContext.getSetupWindowSize();
+ mControllers.taskbarActivityContext.setTaskbarWindowSize(
+ mControllers.taskbarActivityContext.getSetupWindowSize());
+
+ // If SUW is on a large screen device that is landscape (or has a square aspect
+ // ratio) the back button has to be placed accordingly
+ if ((deviceProfile.isTablet && deviceProfile.isLandscape)
+ || (deviceProfile.aspectRatio > SQUARE_ASPECT_RATIO_BOTTOM_BOUND
+ && deviceProfile.aspectRatio < SQUARE_ASPECT_RATIO_UPPER_BOUND)) {
+ navButtonsLayoutParams.setMarginStart(
+ resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_start_margin));
+ navButtonsViewLayoutParams.bottomMargin = resources.getDimensionPixelSize(
+ R.dimen.taskbar_back_button_suw_bottom_margin);
+ navButtonsLayoutParams.height = resources.getDimensionPixelSize(
+ R.dimen.taskbar_back_button_suw_height);
+ } else {
+ int phoneOrPortraitSetupMargin = resources.getDimensionPixelSize(
+ R.dimen.taskbar_contextual_button_suw_margin);
+ navButtonsLayoutParams.setMarginStart(phoneOrPortraitSetupMargin);
+ navButtonsLayoutParams.bottomMargin = !deviceProfile.isLandscape
+ ? 0
+ : phoneOrPortraitSetupMargin - (resources.getDimensionPixelSize(
+ R.dimen.taskbar_nav_buttons_size) / 2);
+ navButtonsViewLayoutParams.height = resources.getDimensionPixelSize(
+ R.dimen.taskbar_contextual_button_suw_height);
+ }
+ mNavButtonsView.setLayoutParams(navButtonsViewLayoutParams);
mNavButtonContainer.setLayoutParams(navButtonsLayoutParams);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
index 2b60dc0..5bfdce9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
@@ -20,7 +20,6 @@
import android.view.Surface.ROTATION_90
import android.view.Surface.Rotation
import android.view.ViewGroup
-import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.Space
@@ -56,18 +55,18 @@
* @param isThreeButtonNav are no-ops when taskbar is present/showing
*/
fun getUiLayoutter(
- deviceProfile: DeviceProfile,
- navButtonsView: NearestTouchFrame,
- imeSwitcher: ImageView?,
- rotationButton: RotationButton?,
- a11yButton: ImageView?,
- space: Space?,
- resources: Resources,
- isKidsMode: Boolean,
- isInSetup: Boolean,
- isThreeButtonNav: Boolean,
- phoneMode: Boolean,
- @Rotation surfaceRotation: Int
+ deviceProfile: DeviceProfile,
+ navButtonsView: NearestTouchFrame,
+ imeSwitcher: ImageView?,
+ rotationButton: RotationButton?,
+ a11yButton: ImageView?,
+ space: Space?,
+ resources: Resources,
+ isKidsMode: Boolean,
+ isInSetup: Boolean,
+ isThreeButtonNav: Boolean,
+ phoneMode: Boolean,
+ @Rotation surfaceRotation: Int
): NavButtonLayoutter {
val navButtonContainer =
navButtonsView.requireViewById<LinearLayout>(ID_END_NAV_BUTTONS)
@@ -82,43 +81,6 @@
if (!deviceProfile.isLandscape) {
navButtonsView.setIsVertical(false)
PhonePortraitNavLayoutter(
- resources,
- navButtonContainer,
- endContextualContainer,
- startContextualContainer,
- imeSwitcher,
- rotationButton,
- a11yButton,
- space
- )
- } else if (surfaceRotation == ROTATION_90) {
- navButtonsView.setIsVertical(true)
- PhoneLandscapeNavLayoutter(
- resources,
- navButtonContainer,
- endContextualContainer,
- startContextualContainer,
- imeSwitcher,
- rotationButton,
- a11yButton,
- space
- )
- } else {
- navButtonsView.setIsVertical(true)
- PhoneSeascapeNavLayoutter(
- resources,
- navButtonContainer,
- endContextualContainer,
- startContextualContainer,
- imeSwitcher,
- rotationButton,
- a11yButton,
- space
- )
- }
- }
- isPhoneGestureMode ->{
- PhoneGestureLayoutter(
resources,
navButtonContainer,
endContextualContainer,
@@ -127,44 +89,82 @@
rotationButton,
a11yButton,
space
+ )
+ } else if (surfaceRotation == ROTATION_90) {
+ navButtonsView.setIsVertical(true)
+ PhoneLandscapeNavLayoutter(
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ rotationButton,
+ a11yButton,
+ space
+ )
+ } else {
+ navButtonsView.setIsVertical(true)
+ PhoneSeascapeNavLayoutter(
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ rotationButton,
+ a11yButton,
+ space
+ )
+ }
+ }
+ isPhoneGestureMode -> {
+ PhoneGestureLayoutter(
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ rotationButton,
+ a11yButton,
+ space
)
}
deviceProfile.isTaskbarPresent -> {
return when {
isInSetup -> {
SetupNavLayoutter(
- resources,
- navButtonContainer,
- endContextualContainer,
- startContextualContainer,
- imeSwitcher,
- rotationButton,
- a11yButton,
- space
+ resources,
+ navButtonsView,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ rotationButton,
+ a11yButton,
+ space
)
}
isKidsMode -> {
KidsNavLayoutter(
- resources,
- navButtonContainer,
- endContextualContainer,
- startContextualContainer,
- imeSwitcher,
- rotationButton,
- a11yButton,
- space
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ rotationButton,
+ a11yButton,
+ space
)
}
else ->
TaskbarNavLayoutter(
- resources,
- navButtonContainer,
- endContextualContainer,
- startContextualContainer,
- imeSwitcher,
- rotationButton,
- a11yButton,
- space
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ rotationButton,
+ a11yButton,
+ space
)
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
index 181e0ed..8eff95c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
@@ -24,50 +24,89 @@
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.Space
+import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.systemui.shared.rotation.RotationButton
+const val SQUARE_ASPECT_RATIO_BOTTOM_BOUND = 0.95
+const val SQUARE_ASPECT_RATIO_UPPER_BOUND = 1.05
+
class SetupNavLayoutter(
- resources: Resources,
- navButtonContainer: LinearLayout,
- endContextualContainer: ViewGroup,
- startContextualContainer: ViewGroup,
- imeSwitcher: ImageView?,
- rotationButton: RotationButton?,
- a11yButton: ImageView?,
- space: Space?
+ resources: Resources,
+ navButtonsView: NearestTouchFrame,
+ navButtonContainer: LinearLayout,
+ endContextualContainer: ViewGroup,
+ startContextualContainer: ViewGroup,
+ imeSwitcher: ImageView?,
+ rotationButton: RotationButton?,
+ a11yButton: ImageView?,
+ space: Space?
) :
AbstractNavButtonLayoutter(
- resources,
- navButtonContainer,
- endContextualContainer,
- startContextualContainer,
- imeSwitcher,
- rotationButton,
- a11yButton,
- space
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ rotationButton,
+ a11yButton,
+ space
) {
+ private val mNavButtonsView = navButtonsView
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
// Since setup wizard only has back button enabled, it looks strange to be
// end-aligned, so start-align instead.
val navButtonsLayoutParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
- navButtonsLayoutParams.apply {
- marginStart = navButtonsLayoutParams.marginEnd
- marginEnd = 0
- gravity = Gravity.START
+ val navButtonsViewLayoutParams = mNavButtonsView.layoutParams as FrameLayout.LayoutParams
+ val deviceProfile: DeviceProfile = context.deviceProfile
+
+ navButtonsLayoutParams.marginEnd = 0
+ navButtonsLayoutParams.gravity = Gravity.START
+ context.setTaskbarWindowSize(context.setupWindowSize)
+
+ // If SUW is on a large screen device that is landscape (or has a square aspect
+ // ratio) the back button has to be placed accordingly
+ if (
+ deviceProfile.isTablet && deviceProfile.isLandscape ||
+ (deviceProfile.aspectRatio > SQUARE_ASPECT_RATIO_BOTTOM_BOUND &&
+ deviceProfile.aspectRatio < SQUARE_ASPECT_RATIO_UPPER_BOUND)
+ ) {
+ navButtonsLayoutParams.marginStart =
+ resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_start_margin)
+ navButtonsViewLayoutParams.bottomMargin =
+ resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_bottom_margin)
+ navButtonsLayoutParams.height =
+ resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_height)
+ } else {
+ val phoneOrPortraitSetupMargin =
+ resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_suw_margin)
+ navButtonsLayoutParams.marginStart = phoneOrPortraitSetupMargin
+ navButtonsLayoutParams.bottomMargin =
+ if (!deviceProfile.isLandscape) 0
+ else
+ phoneOrPortraitSetupMargin -
+ resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size) / 2
+ navButtonsViewLayoutParams.height =
+ resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_suw_height)
}
- navButtonContainer.requestLayout()
+ mNavButtonsView.layoutParams = navButtonsViewLayoutParams
+ navButtonContainer.layoutParams = navButtonsLayoutParams
endContextualContainer.removeAllViews()
startContextualContainer.removeAllViews()
- val contextualMargin = resources.getDimensionPixelSize(
- R.dimen.taskbar_contextual_button_padding)
+ val contextualMargin =
+ resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_padding)
repositionContextualContainer(endContextualContainer, WRAP_CONTENT, 0, 0, Gravity.END)
- repositionContextualContainer(startContextualContainer, WRAP_CONTENT, contextualMargin,
- contextualMargin, Gravity.START)
+ repositionContextualContainer(
+ startContextualContainer,
+ WRAP_CONTENT,
+ contextualMargin,
+ contextualMargin,
+ Gravity.START
+ )
if (imeSwitcher != null) {
startContextualContainer.addView(imeSwitcher)
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 76109c0..69c7114 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -396,7 +396,7 @@
<dimen name="transient_taskbar_clamped_offset_bound">0dp</dimen>
<dimen name="taskbar_icon_spacing">0dp</dimen>
<dimen name="taskbar_nav_buttons_size">0dp</dimen>
- <dimen name="taskbar_contextual_button_margin">0dp</dimen>
+ <dimen name="taskbar_contextual_button_suw_margin">0dp</dimen>
<dimen name="taskbar_hotseat_nav_spacing">0dp</dimen>
<dimen name="taskbar_button_margin_default">0dp</dimen>
<dimen name="taskbar_button_space_inbetween">0dp</dimen>
diff --git a/src/com/android/launcher3/apppairs/AppPairIcon.java b/src/com/android/launcher3/apppairs/AppPairIcon.java
index 0e955ad..1f73241 100644
--- a/src/com/android/launcher3/apppairs/AppPairIcon.java
+++ b/src/com/android/launcher3/apppairs/AppPairIcon.java
@@ -143,7 +143,7 @@
CharSequence app2 = getInfo().getSecondApp().title;
String a11yTitle = getContext().getString(R.string.app_pair_name_format, app1, app2);
setContentDescription(
- getInfo().shouldDrawAsDisabled(getContext())
+ getInfo().shouldReportDisabled(getContext())
? getContext().getString(R.string.disabled_app_label, a11yTitle)
: a11yTitle);
}
diff --git a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
index 7809102..dce97eb 100644
--- a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
+++ b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
@@ -55,6 +55,12 @@
appIcon1.setBounds(0, 0, p.memberIconSize.toInt(), p.memberIconSize.toInt())
appIcon2.setBounds(0, 0, p.memberIconSize.toInt(), p.memberIconSize.toInt())
+ // If icons are unlaunchable due to screen size, manually override disabled appearance.
+ // (otherwise, leave disabled state alone; icons will naturally inherit the app's state)
+ val (isApp1Launchable, isApp2Launchable) = appPairInfo.isLaunchable(p.context)
+ if (!isApp1Launchable) appIcon1.setIsDisabled(true)
+ if (!isApp2Launchable) appIcon2.setIsDisabled(true)
+
// Create icon drawable.
val fullIconDrawable = AppPairIconDrawable(p, appIcon1, appIcon2)
fullIconDrawable.setBounds(0, 0, p.iconSize, p.iconSize)
diff --git a/src/com/android/launcher3/model/data/AppPairInfo.kt b/src/com/android/launcher3/model/data/AppPairInfo.kt
index fad365c..3dbd45b 100644
--- a/src/com/android/launcher3/model/data/AppPairInfo.kt
+++ b/src/com/android/launcher3/model/data/AppPairInfo.kt
@@ -68,12 +68,15 @@
/** Returns if either of the app pair members is currently disabled. */
override fun isDisabled() = anyMatch { it.isDisabled }
- /** Checks if the app pair is launchable at the current screen size. */
- fun isLaunchable(context: Context) =
- (ActivityContext.lookupContext(context) as ActivityContext).getDeviceProfile().isTablet ||
- getAppContents().stream().noneMatch {
- it.hasStatusFlag(WorkspaceItemInfo.FLAG_NON_RESIZEABLE)
- }
+ /** Checks if member apps are launchable at the current screen size. */
+ fun isLaunchable(context: Context): Pair<Boolean, Boolean> {
+ val isTablet =
+ (ActivityContext.lookupContext(context) as ActivityContext).getDeviceProfile().isTablet
+ return Pair(
+ isTablet || !getFirstApp().hasStatusFlag(WorkspaceItemInfo.FLAG_NON_RESIZEABLE),
+ isTablet || !getSecondApp().hasStatusFlag(WorkspaceItemInfo.FLAG_NON_RESIZEABLE)
+ )
+ }
/** Fetches high-res icons for member apps if needed. */
fun fetchHiResIconsIfNeeded(iconCache: IconCache) {
@@ -83,13 +86,14 @@
}
/**
- * App pairs will draw as "disabled" if either of the following is true:
+ * App pairs will report itself as "disabled" (for accessibility) if either of the following is
+ * true:
* 1) One of the member WorkspaceItemInfos is disabled (i.e. the app software itself is paused
* or can't be launched for some other reason).
* 2) One of the member apps can't be launched due to screen size requirements.
*/
- fun shouldDrawAsDisabled(context: Context): Boolean {
- return isDisabled || !isLaunchable(context)
+ fun shouldReportDisabled(context: Context): Boolean {
+ return isDisabled || !isLaunchable(context).first || !isLaunchable(context).second
}
/** Generates a default title for the app pair and sets it. */
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 816d5e9..f226b8d 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -149,9 +149,12 @@
*/
private static void onClickAppPairIcon(View v) {
Launcher launcher = Launcher.getLauncher(v.getContext());
- AppPairIcon appPairIcon = (AppPairIcon) v;
- if (!appPairIcon.getInfo().isLaunchable(launcher)) {
- // Display a message for app pairs that are disabled due to screen size
+ AppPairIcon icon = (AppPairIcon) v;
+ AppPairInfo info = icon.getInfo();
+ boolean isApp1Launchable = info.isLaunchable(launcher).getFirst(),
+ isApp2Launchable = info.isLaunchable(launcher).getSecond();
+ if (!isApp1Launchable || !isApp2Launchable) {
+ // App pair is unlaunchable due to screen size.
boolean isFoldable = InvariantDeviceProfile.INSTANCE.get(launcher)
.supportedProfiles.stream().anyMatch(dp -> dp.isTwoPanels);
Toast.makeText(launcher, isFoldable
@@ -159,26 +162,27 @@
: R.string.app_pair_unlaunchable_at_screen_size,
Toast.LENGTH_SHORT).show();
return;
- } else if (appPairIcon.getInfo().isDisabled()) {
- WorkspaceItemInfo app1 = appPairIcon.getInfo().getFirstApp();
- WorkspaceItemInfo app2 = appPairIcon.getInfo().getSecondApp();
+ } else if (info.isDisabled()) {
+ // App pair is disabled for another reason.
+ WorkspaceItemInfo app1 = info.getFirstApp();
+ WorkspaceItemInfo app2 = info.getSecondApp();
// Show the user why the app pair is disabled.
if (app1.isDisabled() && app2.isDisabled()) {
- // Both apps are disabled, show "app pair is not available" toast.
+ // Both apps are disabled, show generic "app pair is not available" toast.
Toast.makeText(launcher, R.string.app_pair_not_available, Toast.LENGTH_SHORT)
.show();
return;
} else if ((app1.isDisabled() && handleDisabledItemClicked(app1, launcher))
|| (app2.isDisabled() && handleDisabledItemClicked(app2, launcher))) {
- // Only one is disabled, and handleDisabledItemClicked() will show a toast, so we
- // are done.
+ // Only one is disabled, and handleDisabledItemClicked() showed a specific toast
+ // explaining why, so we are done.
return;
}
}
// Either the app pair is not disabled, or it is a disabled state that can be handled by
// framework directly (e.g. one app is paused), so go ahead and launch.
- launcher.launchAppPair(appPairIcon);
+ launcher.launchAppPair(icon);
}
/**