Merge "Change the load logic of FolderNameProvider Work profile apps are suggested as "Work" folder name" into ub-launcher3-master
diff --git a/go/src/com/android/launcher3/model/WidgetsModel.java b/go/src/com/android/launcher3/model/WidgetsModel.java
index 7b8f4e6..7269b41 100644
--- a/go/src/com/android/launcher3/model/WidgetsModel.java
+++ b/go/src/com/android/launcher3/model/WidgetsModel.java
@@ -19,8 +19,10 @@
import android.content.Context;
import android.os.UserHandle;
-import com.android.launcher3.icons.ComponentWithLabel;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.icons.ComponentWithLabel;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.WidgetListRowEntry;
@@ -29,8 +31,6 @@
import java.util.List;
import java.util.Set;
-import androidx.annotation.Nullable;
-
/**
* Widgets data model that is used by the adapters of the widget views and controllers.
*
@@ -39,7 +39,7 @@
public class WidgetsModel {
// True is the widget support is disabled.
- public static final boolean GO_DISABLE_WIDGETS = false;
+ public static final boolean GO_DISABLE_WIDGETS = true;
private static final ArrayList<WidgetListRowEntry> EMPTY_WIDGET_LIST = new ArrayList<>();
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 826a275..5d871c3 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -91,6 +91,17 @@
android:taskAffinity="${packageName}.locktask"
android:directBootAware="true" />
+ <activity
+ android:name="com.android.quickstep.interaction.BackGestureTutorialActivity"
+ android:autoRemoveFromRecents="true"
+ android:excludeFromRecents="true"
+ android:screenOrientation="portrait">
+ <intent-filter>
+ <action android:name="com.android.quickstep.action.BACK_GESTURE_TUTORIAL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
</application>
</manifest>
diff --git a/quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml b/quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml
new file mode 100644
index 0000000..f0e70a8
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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 android:height="24dp" android:viewportHeight="24"
+ android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@color/hotseat_edu_background" android:pathData="M19 9l1.25-2.75L23 5l-2.75-1.25L19 1l-1.25 2.75L15 5l2.75 1.25L19 9zm-7.5.5L9 4 6.5 9.5 1 12l5.5 2.5L9 20l2.5-5.5L17 12l-5.5-2.5zM19 15l-1.25 2.75L15 19l2.75 1.25L19 23l1.25-2.75L23 19l-2.75-1.25L19 15z"/>
+</vector>
diff --git a/quickstep/recents_ui_overrides/res/drawable/hotseat_prediction_edu_top.xml b/quickstep/recents_ui_overrides/res/drawable/hotseat_prediction_edu_top.xml
new file mode 100644
index 0000000..e3cc549
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/drawable/hotseat_prediction_edu_top.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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 android:height="15.53398dp" android:viewportHeight="32"
+ android:viewportWidth="412" android:width="200dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@color/hotseat_edu_background" android:pathData="M412,32v-2.64C349.26,10.51 279.5,0 206,0S62.74,10.51 0,29.36V32H412z"/>
+</vector>
diff --git a/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml b/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
new file mode 100644
index 0000000..ee38e3b
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+<com.android.launcher3.hybridhotseat.HotseatEduDialog xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_gravity="bottom"
+ android:layout_height="wrap_content"
+ android:gravity="bottom"
+ android:orientation="vertical">
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="32dp"
+ android:background="@drawable/hotseat_prediction_edu_top" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@color/hotseat_edu_background"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="18dp"
+ android:fontFamily="google-sans"
+ android:paddingLeft="@dimen/hotseat_edu_padding"
+ android:paddingRight="@dimen/hotseat_edu_padding"
+ android:text="@string/hotseat_migrate_title"
+ android:textAlignment="center"
+ android:textColor="@android:color/white"
+ android:textSize="20sp" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="18dp"
+ android:layout_marginBottom="18dp"
+ android:fontFamily="roboto-medium"
+ android:paddingLeft="@dimen/hotseat_edu_padding"
+ android:paddingRight="@dimen/hotseat_edu_padding"
+ android:text="@string/hotseat_migrate_message"
+ android:textAlignment="center"
+ android:textColor="@android:color/white"
+ android:textSize="16sp" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/hotseat_wrapper"
+ android:orientation="vertical">
+
+ <com.android.launcher3.CellLayout
+ android:id="@+id/sample_prediction"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ launcher:containerType="hotseat" />
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="@dimen/hotseat_edu_padding"
+ android:paddingTop="8dp"
+ android:paddingRight="@dimen/hotseat_edu_padding">
+
+ <Button
+ android:id="@+id/turn_predictions_on"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end"
+ android:background="?android:attr/selectableItemBackground"
+ android:text="@string/hotseat_migrate_accept"
+ android:textAlignment="textEnd"
+ android:textColor="@android:color/white" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/no_thanks"
+ android:text="@string/hotseat_migrate_dismiss"
+ android:layout_gravity="start"
+ android:background="?android:attr/selectableItemBackground"
+ android:textColor="@android:color/white" />
+
+ </FrameLayout>
+ </LinearLayout>
+ </LinearLayout>
+
+</com.android.launcher3.hybridhotseat.HotseatEduDialog>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/values/colors.xml b/quickstep/recents_ui_overrides/res/values/colors.xml
index 7426e30..4fa5684 100644
--- a/quickstep/recents_ui_overrides/res/values/colors.xml
+++ b/quickstep/recents_ui_overrides/res/values/colors.xml
@@ -6,4 +6,6 @@
<color name="all_apps_label_text_dark">#61FFFFFF</color>
<color name="all_apps_prediction_row_separator">#3c000000</color>
<color name="all_apps_prediction_row_separator_dark">#3cffffff</color>
+
+ <color name="hotseat_edu_background">#f01A73E8</color>
</resources>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/values/dimens.xml b/quickstep/recents_ui_overrides/res/values/dimens.xml
index ee672d4..c458ec7 100644
--- a/quickstep/recents_ui_overrides/res/values/dimens.xml
+++ b/quickstep/recents_ui_overrides/res/values/dimens.xml
@@ -29,8 +29,7 @@
<dimen name="swipe_up_y_overshoot">10dp</dimen>
<dimen name="swipe_up_max_workspace_trans_y">-60dp</dimen>
- <!-- Predicted icon related -->
- <dimen name="predicted_icon_background_corner_radius">15dp</dimen>
- <dimen name="predicted_icon_background_inset">8dp</dimen>
+ <!-- Hybrid hotseat related -->
+ <dimen name="hotseat_edu_padding">24dp</dimen>
</resources>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
index e45eded..06b9f1f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
@@ -26,7 +26,6 @@
import androidx.annotation.NonNull;
-import com.android.launcher3.HotseatPredictionController;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
import com.android.launcher3.ItemInfo;
@@ -39,6 +38,7 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsStore.OnUpdateListener;
+import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.userevent.nano.LauncherLogProto;
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
new file mode 100644
index 0000000..0fd4aac
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2020 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.hybridhotseat;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.core.app.NotificationCompat;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.R;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.util.ActivityTracker;
+
+import java.util.List;
+
+/**
+ * Controller class for managing user onboaridng flow for hybrid hotseat
+ */
+public class HotseatEduController {
+ public static final String KEY_HOTSEAT_EDU_SEEN = "hotseat_edu_seen";
+
+ private static final String NOTIFICATION_CHANNEL_ID = "launcher_onboarding";
+ private static final int ONBOARDING_NOTIFICATION_ID = 7641;
+
+ private final Launcher mLauncher;
+ private List<WorkspaceItemInfo> mPredictedApps;
+ private HotseatEduDialog mActiveDialog;
+
+ private final NotificationManager mNotificationManager;
+ private final Notification mNotification;
+
+ HotseatEduController(Launcher launcher) {
+ mLauncher = launcher;
+ mNotificationManager = mLauncher.getSystemService(NotificationManager.class);
+ createNotificationChannel();
+ mNotification = createNotification();
+ }
+
+ void migrate() {
+ ViewGroup hotseatVG = mLauncher.getHotseat().getShortcutsAndWidgets();
+ int workspacePageCount = mLauncher.getWorkspace().getPageCount();
+ for (int i = 0; i < hotseatVG.getChildCount(); i++) {
+ View child = hotseatVG.getChildAt(i);
+ ItemInfo tag = (ItemInfo) child.getTag();
+ mLauncher.getModelWriter().moveItemInDatabase(tag,
+ LauncherSettings.Favorites.CONTAINER_DESKTOP, workspacePageCount, tag.screenId,
+ 0);
+ }
+ }
+
+ void removeNotification() {
+ mNotificationManager.cancel(ONBOARDING_NOTIFICATION_ID);
+ }
+
+ void finishOnboarding() {
+ mLauncher.rebindModel();
+ mLauncher.getSharedPrefs().edit().putBoolean(KEY_HOTSEAT_EDU_SEEN, true).apply();
+ removeNotification();
+ }
+
+ void setPredictedApps(List<WorkspaceItemInfo> predictedApps) {
+ mPredictedApps = predictedApps;
+ if (!mPredictedApps.isEmpty()
+ && mLauncher.getOrientation() == Configuration.ORIENTATION_PORTRAIT) {
+ mNotificationManager.notify(ONBOARDING_NOTIFICATION_ID, mNotification);
+ }
+ }
+
+ private void createNotificationChannel() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
+ CharSequence name = mLauncher.getString(R.string.hotseat_migrate_title);
+ int importance = NotificationManager.IMPORTANCE_LOW;
+ NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, name,
+ importance);
+ mNotificationManager.createNotificationChannel(channel);
+ }
+
+ private Notification createNotification() {
+ Intent intent = new Intent(mLauncher.getApplicationContext(), mLauncher.getClass());
+ intent = new NotificationHandler().addToIntent(intent);
+
+ CharSequence name = mLauncher.getString(R.string.hotseat_migrate_prompt_title);
+ String description = mLauncher.getString(R.string.hotseat_migrate_prompt_content);
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(mLauncher,
+ NOTIFICATION_CHANNEL_ID)
+ .setContentTitle(name)
+ .setOngoing(true)
+ .setColor(mLauncher.getColor(R.color.hotseat_edu_background))
+ .setContentIntent(PendingIntent.getActivity(mLauncher, 0, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT))
+ .setSmallIcon(R.drawable.hotseat_edu_notification_icon)
+ .setContentText(description);
+ return builder.build();
+
+ }
+
+ void destroy() {
+ removeNotification();
+ if (mActiveDialog != null) {
+ mActiveDialog.setHotseatEduController(null);
+ }
+ }
+
+ void showDialog() {
+ if (mPredictedApps == null || mPredictedApps.isEmpty()) {
+ return;
+ }
+ if (mActiveDialog != null) {
+ mActiveDialog.handleClose(false);
+ }
+ mActiveDialog = HotseatEduDialog.getDialog(mLauncher);
+ mActiveDialog.setHotseatEduController(this);
+ mActiveDialog.show(mPredictedApps);
+ }
+
+ static class NotificationHandler implements
+ ActivityTracker.SchedulerCallback<QuickstepLauncher> {
+ @Override
+ public boolean init(QuickstepLauncher activity, boolean alreadyOnHome) {
+ activity.getHotseatPredictionController().showEduDialog();
+ return true;
+ }
+ }
+}
+
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
new file mode 100644
index 0000000..4c87945
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2020 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.hybridhotseat;
+
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.Toast;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.uioverrides.PredictedAppIcon;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.views.AbstractSlideInView;
+
+import java.util.List;
+
+/**
+ * User education dialog for hybrid hotseat. Allows user to migrate hotseat items to a new page in
+ * the workspace and shows predictions on the whole hotseat
+ */
+public class HotseatEduDialog extends AbstractSlideInView implements Insettable {
+
+ private static final int DEFAULT_CLOSE_DURATION = 200;
+
+ public static boolean shown = false;
+
+ private final Rect mInsets = new Rect();
+ private View mHotseatWrapper;
+ private CellLayout mSampleHotseat;
+
+ public void setHotseatEduController(HotseatEduController hotseatEduController) {
+ mHotseatEduController = hotseatEduController;
+ }
+
+ private HotseatEduController mHotseatEduController;
+
+ public HotseatEduDialog(Context context, AttributeSet attr) {
+ this(context, attr, 0);
+ }
+
+ public HotseatEduDialog(Context context, AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mContent = this;
+ }
+
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mHotseatWrapper = findViewById(R.id.hotseat_wrapper);
+ mSampleHotseat = findViewById(R.id.sample_prediction);
+
+ DeviceProfile grid = mLauncher.getDeviceProfile();
+ Rect padding = grid.getHotseatLayoutPadding();
+
+ mSampleHotseat.getLayoutParams().height = grid.cellHeightPx;
+ mSampleHotseat.setGridSize(grid.inv.numHotseatIcons, 1);
+ mSampleHotseat.setPadding(padding.left, 0, padding.right, 0);
+
+ Button turnOnBtn = findViewById(R.id.turn_predictions_on);
+ turnOnBtn.setOnClickListener(this::onMigrate);
+
+ Button learnMoreBtn = findViewById(R.id.no_thanks);
+ learnMoreBtn.setOnClickListener(this::onKeepDefault);
+
+ }
+
+ private void onMigrate(View v) {
+ if (mHotseatEduController == null) return;
+ handleClose(true);
+ mHotseatEduController.migrate();
+ mHotseatEduController.finishOnboarding();
+ Toast.makeText(mLauncher, R.string.hotseat_items_migrated, Toast.LENGTH_LONG).show();
+ }
+
+ private void onKeepDefault(View v) {
+ if (mHotseatEduController == null) return;
+ Toast.makeText(getContext(), R.string.hotseat_no_migration, Toast.LENGTH_LONG).show();
+ mHotseatEduController.finishOnboarding();
+ handleClose(true);
+ }
+
+ @Override
+ public void logActionCommand(int command) {
+ // Since this is on-boarding popup, it is not a user controlled action.
+ }
+
+ @Override
+ public int getLogContainerType() {
+ return LauncherLogProto.ContainerType.TIP;
+ }
+
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_ON_BOARD_POPUP) != 0;
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ int leftInset = insets.left - mInsets.left;
+ int rightInset = insets.right - mInsets.right;
+ int bottomInset = insets.bottom - mInsets.bottom;
+ mInsets.set(insets);
+ setPadding(leftInset, getPaddingTop(), rightInset, 0);
+ mHotseatWrapper.setPadding(mHotseatWrapper.getPaddingLeft(), getPaddingTop(),
+ mHotseatWrapper.getPaddingRight(), bottomInset);
+ mHotseatWrapper.getLayoutParams().height =
+ mLauncher.getDeviceProfile().hotseatBarSizePx + insets.bottom;
+ }
+
+
+ private void animateOpen() {
+ if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+ return;
+ }
+ mIsOpen = true;
+ mOpenCloseAnimator.setValues(
+ PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
+ mOpenCloseAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ mOpenCloseAnimator.start();
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ handleClose(true, DEFAULT_CLOSE_DURATION);
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ handleClose(false);
+ }
+
+ /**
+ * Opens User education dialog with a list of suggested apps
+ */
+ public void show(List<WorkspaceItemInfo> predictions) {
+ if (getParent() != null
+ || predictions.size() < mLauncher.getDeviceProfile().inv.numHotseatIcons) {
+ return;
+ }
+ mLauncher.getDragLayer().addView(this);
+ animateOpen();
+ for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) {
+ WorkspaceItemInfo info = predictions.get(i);
+ PredictedAppIcon icon = PredictedAppIcon.createIcon(mSampleHotseat, info);
+ icon.setEnabled(false);
+ icon.verifyHighRes();
+ CellLayout.LayoutParams lp = new CellLayout.LayoutParams(i, 0, 1, 1);
+ mSampleHotseat.addViewToCellLayout(icon, i, info.getViewId(), lp, true);
+ }
+ }
+
+ /**
+ * Factory method for HotseatPredictionUserEdu dialog
+ */
+ public static HotseatEduDialog getDialog(Launcher launcher) {
+ LayoutInflater layoutInflater = LayoutInflater.from(launcher);
+ return (HotseatEduDialog) layoutInflater.inflate(
+ R.layout.predicted_hotseat_edu, launcher.getDragLayer(),
+ false);
+
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
similarity index 92%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java
rename to quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index b94142a..8f6081b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.launcher3;
+package com.android.launcher3.hybridhotseat;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
@@ -35,6 +35,20 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.Hotseat;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.ItemInfoWithIcon;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.appprediction.ComponentKeyMapper;
@@ -93,6 +107,7 @@
private AllAppsStore mAllAppsStore;
private AnimatorSet mIconRemoveAnimators;
+ private HotseatEduController mHotseatEduController;
private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
@@ -118,6 +133,23 @@
}
}
+ /**
+ * Returns whether or not the prediction controller is ready to show predictions
+ */
+ public boolean isReady() {
+ return mLauncher.getSharedPrefs().getBoolean(HotseatEduController.KEY_HOTSEAT_EDU_SEEN,
+ false);
+ }
+
+ /**
+ * Transitions to NORMAL workspace mode and shows edu dialog
+ */
+ public void showEduDialog() {
+ if (mHotseatEduController == null) return;
+ mLauncher.getStateManager().goToState(LauncherState.NORMAL, true,
+ () -> mHotseatEduController.showDialog());
+ }
+
@Override
public void onViewAttachedToWindow(View view) {
mLauncher.getDragController().addDragListener(this);
@@ -133,7 +165,7 @@
}
private void fillGapsWithPrediction(boolean animate, Runnable callback) {
- if (mDragObject != null) {
+ if (!isReady() || mDragObject != null) {
return;
}
List<WorkspaceItemInfo> predictedApps = mapToWorkspaceItemInfo(mComponentKeyMappers);
@@ -234,6 +266,12 @@
mAppPredictor.registerPredictionUpdates(mLauncher.getMainExecutor(),
this::setPredictedApps);
+ if (!isReady()) {
+ if (mHotseatEduController != null) {
+ mHotseatEduController.destroy();
+ }
+ mHotseatEduController = new HotseatEduController(mLauncher);
+ }
mAppPredictor.requestPredictionUpdate();
}
@@ -244,6 +282,7 @@
bundle.putParcelableArrayList(BUNDLE_KEY_WORKSPACE, getPinnedAppTargetsInViewGroup(
mLauncher.getWorkspace().getScreenWithId(
Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets()));
+
return bundle;
}
@@ -272,7 +311,11 @@
mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache));
}
updateDependencies();
- fillGapsWithPrediction();
+ if (isReady()) {
+ fillGapsWithPrediction();
+ } else if (mHotseatEduController != null) {
+ mHotseatEduController.setPredictedApps(mapToWorkspaceItemInfo(mComponentKeyMappers));
+ }
}
private void updateDependencies() {
@@ -466,9 +509,7 @@
}
@Override
- public void reapplyItemInfo(ItemInfoWithIcon info) {
-
- }
+ public void reapplyItemInfo(ItemInfoWithIcon info) {}
@Override
public void onDropCompleted(View target, DropTarget.DragObject d, boolean success) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 27ac284..bd89626 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -47,7 +47,7 @@
private static final float RING_EFFECT_RATIO = 0.11f;
- private DeviceProfile mDeviceProfile;
+ private final DeviceProfile mDeviceProfile;
private final Paint mIconRingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private boolean mIsPinned = false;
private int mNormalizedIconRadius;
@@ -65,7 +65,7 @@
mDeviceProfile = Launcher.getLauncher(context).getDeviceProfile();
mNormalizedIconRadius = IconNormalizer.getNormalizedCircleSize(getIconSize()) / 2;
setOnClickListener(ItemClickHandler.INSTANCE);
- setOnFocusChangeListener(Launcher.getLauncher(context).mFocusHandler);
+ setOnFocusChangeListener(Launcher.getLauncher(context).getFocusHandler());
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index cae01ae..4b5ba95 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -27,12 +27,12 @@
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.HotseatPredictionController;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.RotationMode;
+import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index ad02de1..32855d7 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -18,7 +18,7 @@
import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_BOTH;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_POSITIVE;
@@ -286,7 +286,7 @@
}
});
}
- if (QUICKSTEP_SPRINGS.get()) {
+ if (UNSTABLE_SPRINGS.get()) {
mCurrentAnimation.dispatchOnStartWithVelocity(goingToEnd ? 1f : 0f, velocity);
}
anim.start();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
index 1b60404..8b5283e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
@@ -21,7 +21,7 @@
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
@@ -973,7 +973,7 @@
}
mLauncherTransitionController.getAnimationPlayer().setDuration(Math.max(0, duration));
- if (QUICKSTEP_SPRINGS.get()) {
+ if (UNSTABLE_SPRINGS.get()) {
mLauncherTransitionController.dispatchOnStartWithVelocity(end, velocityPxPerMs.y);
}
mLauncherTransitionController.getAnimationPlayer().start();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 29df5cc..71568b3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -23,6 +23,7 @@
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.config.FeatureFlags.FAKE_LANDSCAPE_UI;
import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
@@ -715,6 +716,7 @@
pw.println("FeatureFlags:");
pw.println(" APPLY_CONFIG_AT_RUNTIME=" + APPLY_CONFIG_AT_RUNTIME.get());
pw.println(" QUICKSTEP_SPRINGS=" + QUICKSTEP_SPRINGS.get());
+ pw.println(" UNSTABLE_SPRINGS=" + UNSTABLE_SPRINGS.get());
pw.println(" ADAPTIVE_ICON_WINDOW_ANIM=" + ADAPTIVE_ICON_WINDOW_ANIM.get());
pw.println(" ENABLE_QUICKSTEP_LIVE_TILE=" + ENABLE_QUICKSTEP_LIVE_TILE.get());
pw.println(" ENABLE_HINTS_IN_OVERVIEW=" + ENABLE_HINTS_IN_OVERVIEW.get());
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index 47bc31a..c836791 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -31,7 +31,7 @@
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
import static com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS;
import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CLEAR_ALL_BUTTON;
@@ -1105,13 +1105,13 @@
private void addDismissedTaskAnimations(View taskView, AnimatorSet anim, long duration) {
addAnim(ObjectAnimator.ofFloat(taskView, ALPHA, 0), duration, ACCEL_2, anim);
- if (QUICKSTEP_SPRINGS.get() && taskView instanceof TaskView)
+ if (UNSTABLE_SPRINGS.get() && taskView instanceof TaskView) {
addAnim(new SpringObjectAnimator<>(taskView, VIEW_TRANSLATE_Y,
MIN_VISIBLE_CHANGE_PIXELS, SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY,
SpringForce.STIFFNESS_MEDIUM,
0, -taskView.getHeight()),
duration, LINEAR, anim);
- else {
+ } else {
addAnim(ObjectAnimator.ofFloat(taskView, TRANSLATION_Y, -taskView.getHeight()),
duration, LINEAR, anim);
}
@@ -1185,7 +1185,7 @@
}
int scrollDiff = newScroll[i] - oldScroll[i] + offset;
if (scrollDiff != 0) {
- if (QUICKSTEP_SPRINGS.get() && child instanceof TaskView) {
+ if (UNSTABLE_SPRINGS.get() && child instanceof TaskView) {
addAnim(new SpringObjectAnimator<>(child, VIEW_TRANSLATE_X,
MIN_VISIBLE_CHANGE_PIXELS, SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY,
SpringForce.STIFFNESS_MEDIUM,
diff --git a/quickstep/res/drawable/ic_bulb_outline.xml b/quickstep/res/drawable/ic_bulb_outline.xml
deleted file mode 100644
index ef7bf9a..0000000
--- a/quickstep/res/drawable/ic_bulb_outline.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<!--
- Copyright (C) 2020 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="192dp"
- android:height="192dp"
- android:viewportWidth="192"
- android:viewportHeight="192">
- <path
- android:pathData="M96,90.01"
- android:strokeWidth="2.4297"
- android:fillColor="#00000000"
- android:strokeColor="#FFC800"/>
- <path
- android:pathData="M153.24,48.09c-0.5,-1.51 -0.99,-2.86 -1.51,-4.12c-3.03,-7.1 -7.26,-13.45 -12.59,-18.88C127.68,13.42 112.4,7 96.1,7c-14,0 -27.66,4.93 -38.45,13.87C47,29.69 39.61,41.99 36.82,55.51c-0.76,3.9 -1.14,7.86 -1.14,11.78c0,16.39 6.36,31.77 17.9,43.3c2.65,2.65 5.59,5.08 8.74,7.23l0.09,24.14v22.03c0,5.33 4.82,10.01 10.32,10.01h0.41h3.85v0c0,6.23 4.53,10.93 10.53,10.93h16.94c6.01,0 10.54,-4.7 10.54,-10.93v0h4.31c5.56,0 10.26,-4.5 10.26,-9.83v-22.01c0.01,-0.06 0.01,-0.13 0.01,-0.2v-23.74c16.75,-11.15 26.73,-30.15 26.73,-50.94C156.31,60.76 155.28,54.3 153.24,48.09zM118.51,111.08l-0.46,0.29l-0.46,0.29v0.55v0.55v4.38v22.77l-14.12,0V95.9h14h2v-2v-8.5v-2h-2H74.53h-2v2v8.5v2h2h14v44.02l-14.13,0l-0.09,-23.21l-0.02,-4.3l0,-0.54l0,-0.54l-0.45,-0.29l-0.45,-0.29l-3.6,-2.36c-2.81,-1.84 -5.41,-3.95 -7.73,-6.28c-9.27,-9.26 -14.38,-21.63 -14.38,-34.82c0,-3.14 0.3,-6.31 0.9,-9.43C53.25,35.35 73.23,19 96.1,19c13.05,0 25.3,5.15 34.48,14.5c4.27,4.34 7.66,9.43 10.08,15.1c0.39,0.96 0.78,2.03 1.18,3.24c1.64,5 2.47,10.2 2.47,15.45c0,17.1 -8.27,32.58 -22.11,41.43L118.51,111.08z"
- android:fillColor="#4285F4"/>
-</vector>
\ No newline at end of file
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 378858f..ce87527 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -67,14 +67,30 @@
<string name="all_apps_prediction_tip">Your predicted apps</string>
<!-- Content description for a close button. [CHAR LIMIT=NONE] -->
- <string name="back_gesture_tutorial_close_button_content_description" translatable="false">Close</string>
+ <string name="back_gesture_tutorial_close_button_content_description" translatable="false">Close</string>
- <!-- Title shown on the notification of Back gesture tutorial. [CHAR LIMIT=30] -->
- <string name="back_gesture_tutorial_notification_title" translatable="false">Try the new back gesture</string>
- <!-- Subtitle shown on the notification of Back gesture tutorial. [CHAR LIMIT=60] -->
- <string name="back_gesture_tutorial_notification_subtitle" translatable="false">Learn how to go back while using your apps</string>
- <!-- Action text shown on the notification of Back gesture tutorial. [CHAR LIMIT=14] -->
- <string name="back_gesture_tutorial_notification_action_label" translatable="false">Try it</string>
+ <!-- Hotseat migration notification title -->
+ <string translatable="false" name="hotseat_migrate_prompt_title">Your Hotseat just got smarter</string>
+ <!-- Hotseat migration notification content -->
+ <string translatable="false" name="hotseat_migrate_prompt_content">Tap here to setup and learn more</string>
+ <!-- Hotseat migration wizard title -->
+ <string translatable="false" name="hotseat_migrate_title">Pixel Suggests apps you\'ll need next</string>
+ <!-- Hotseat migration wizard message -->
+ <string translatable="false" name="hotseat_migrate_message">Suggested apps will replace the bottom row of apps. To pin an app, drag it over a suggested app. Touch & hold an app to hide it.</string>
+ <!-- Toast message user sees after opting into fully predicted hybrid hotseat -->
+ <string translatable="false" name="hotseat_items_migrated">Your hotseat items have been moved to the last page.</string>
+ <!-- Toast message user sees after opting into fully predicted hybrid hotseat -->
+ <string translatable="false" name="hotseat_no_migration">You can remove items from the hotseat manually to see suggested apps in their spot.</string>
+ <!-- Button text to opt in for fully predicted hotseat -->
+ <string translatable="false" name="hotseat_migrate_accept">Migrate</string>
+ <!-- Button text to dismiss opt in for fully predicted hotseat -->
+ <string translatable="false" name="hotseat_migrate_dismiss">No thanks</string>
+ <!-- Hotseat onboard notification title -->
+ <string translatable="false" name="hotseat_onboard_notification_title">Your hotseat just got smarter</string>
+ <!-- Hotseat onboard notification detail -->
+ <string translatable="false" name="hotseat_onboard_notification_detail">Tap here to set it up</string>
+
+
<!-- Title shown during interactive part of Back gesture tutorial for right edge. [CHAR LIMIT=30] -->
<string name="back_gesture_tutorial_playground_title_swipe_inward_right_edge" translatable="false">Try the back gesture</string>
diff --git a/quickstep/res/xml/notification_action.xml b/quickstep/res/xml/notification_action.xml
deleted file mode 100644
index cc3612e..0000000
--- a/quickstep/res/xml/notification_action.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<!--
- Copyright (C) 2020 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.
--->
-<alias xmlns:android="http://schemas.android.com/apk/res/android">
- <intent
- android:action="com.android.quickstep.action.BACK_GESTURE_TUTORIAL" />
-</alias>
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 99b2a81..d5ce734 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -25,7 +25,7 @@
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.animation.TimeInterpolator;
@@ -277,7 +277,7 @@
private void handleFirstSwipeToOverview(final ValueAnimator animator,
final long expectedDuration, final LauncherState targetState, final float velocity,
final boolean isFling) {
- if (QUICKSTEP_SPRINGS.get() && mFromState == OVERVIEW && mToState == ALL_APPS
+ if (UNSTABLE_SPRINGS.get() && mFromState == OVERVIEW && mToState == ALL_APPS
&& targetState == OVERVIEW) {
mFinishFastOnSecondTouch = true;
} else if (mFromState == NORMAL && mToState == OVERVIEW && targetState == OVERVIEW) {
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialActivity.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialActivity.java
new file mode 100644
index 0000000..295ab48
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialActivity.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2020 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.quickstep.interaction;
+
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.View;
+import android.view.Window;
+
+import androidx.fragment.app.FragmentActivity;
+
+import com.android.launcher3.R;
+import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialStep;
+import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType;
+
+import java.util.Optional;
+
+/** Shows the Back gesture interactive tutorial in full screen mode. */
+public class BackGestureTutorialActivity extends FragmentActivity {
+
+ Optional<BackGestureTutorialFragment> mFragment = Optional.empty();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(R.layout.back_gesture_tutorial_activity);
+
+ mFragment = Optional.of(BackGestureTutorialFragment.newInstance(TutorialStep.ENGAGED,
+ TutorialType.RIGHT_EDGE_BACK_NAVIGATION));
+ getSupportFragmentManager().beginTransaction()
+ .add(R.id.back_gesture_tutorial_fragment_container, mFragment.get())
+ .commit();
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ if (hasFocus) {
+ hideSystemUI();
+ }
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (mFragment.isPresent()) {
+ mFragment.get().onBackPressed();
+ }
+ }
+
+ private void hideSystemUI() {
+ getWindow().getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_FULLSCREEN);
+ getWindow().setNavigationBarColor(Color.TRANSPARENT);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialConfirmController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialConfirmController.java
new file mode 100644
index 0000000..486d676
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialConfirmController.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2020 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.quickstep.interaction;
+
+import android.view.View;
+
+import com.android.launcher3.R;
+import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialStep;
+
+import java.util.Optional;
+
+/**
+ * An implementation of {@link BackGestureTutorialController} that defines the behavior of the
+ * {@link TutorialStep#CONFIRM}.
+ */
+final class BackGestureTutorialConfirmController extends BackGestureTutorialController {
+
+ BackGestureTutorialConfirmController(BackGestureTutorialFragment fragment,
+ BackGestureTutorialTypeInfo tutorialTypeInfo) {
+ super(fragment, TutorialStep.CONFIRM, Optional.of(tutorialTypeInfo));
+ }
+
+ @Override
+ Optional<Integer> getTitleStringId() {
+ return Optional.of(mTutorialTypeInfo.get().getTutorialConfirmTitleId());
+ }
+
+ @Override
+ Optional<Integer> getSubtitleStringId() {
+ return Optional.of(mTutorialTypeInfo.get().getTutorialConfirmSubtitleId());
+ }
+
+ @Override
+ Optional<Integer> getActionButtonStringId() {
+ return Optional.of(R.string.back_gesture_tutorial_action_button_label);
+ }
+
+ @Override
+ Optional<Integer> getActionTextButtonStringId() {
+ return Optional.of(R.string.back_gesture_tutorial_action_text_button_label);
+ }
+
+ @Override
+ void onActionButtonClicked(View button) {
+ hideHandCoachingAnimation();
+ if (button == mActionTextButton) {
+ mFragment.startSystemNavigationSetting();
+ }
+ mFragment.closeTutorial();
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
new file mode 100644
index 0000000..3fe91a3
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2020 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.quickstep.interaction;
+
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.launcher3.R;
+import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialStep;
+import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType;
+
+import java.util.Optional;
+
+/**
+ * Defines the behavior of the particular {@link TutorialStep} and implements the transition to it.
+ */
+abstract class BackGestureTutorialController {
+
+ final BackGestureTutorialFragment mFragment;
+ final TutorialStep mTutorialStep;
+ final Optional<BackGestureTutorialTypeInfo> mTutorialTypeInfo;
+ final Button mActionTextButton;
+ final Button mActionButton;
+ final TextView mSubtitleTextView;
+ final ImageButton mCloseButton;
+ final BackGestureTutorialHandAnimation mHandCoachingAnimation;
+ final LinearLayout mTitlesContainer;
+
+ private final TextView mTitleTextView;
+ private final ImageView mHandCoachingView;
+
+ BackGestureTutorialController(
+ BackGestureTutorialFragment fragment,
+ TutorialStep tutorialStep,
+ Optional<BackGestureTutorialTypeInfo> tutorialTypeInfo) {
+ mFragment = fragment;
+ mTutorialStep = tutorialStep;
+ mTutorialTypeInfo = tutorialTypeInfo;
+
+ View rootView = fragment.getRootView();
+ mActionTextButton = rootView.findViewById(
+ R.id.back_gesture_tutorial_fragment_action_text_button);
+ mActionButton = rootView.findViewById(R.id.back_gesture_tutorial_fragment_action_button);
+ mSubtitleTextView = rootView.findViewById(
+ R.id.back_gesture_tutorial_fragment_subtitle_view);
+ mTitleTextView = rootView.findViewById(R.id.back_gesture_tutorial_fragment_title_view);
+ mHandCoachingView = rootView.findViewById(
+ R.id.back_gesture_tutorial_fragment_hand_coaching);
+ mHandCoachingAnimation = mFragment.getHandAnimation();
+ mHandCoachingView.bringToFront();
+ mCloseButton = rootView.findViewById(R.id.back_gesture_tutorial_fragment_close_button);
+ mTitlesContainer = rootView.findViewById(
+ R.id.back_gesture_tutorial_fragment_titles_container);
+ }
+
+ void transitToController() {
+ updateTitles();
+ updateActionButtons();
+ }
+
+ void hideHandCoachingAnimation() {
+ mHandCoachingAnimation.stop();
+ }
+
+ void onGestureDetected() {
+ hideHandCoachingAnimation();
+
+ if (mTutorialStep == TutorialStep.CONFIRM) {
+ mFragment.closeTutorial();
+ return;
+ }
+
+ if (mTutorialTypeInfo.get().getTutorialType() == TutorialType.RIGHT_EDGE_BACK_NAVIGATION) {
+ mFragment.changeController(TutorialStep.ENGAGED,
+ TutorialType.LEFT_EDGE_BACK_NAVIGATION);
+ return;
+ }
+
+ mFragment.changeController(TutorialStep.CONFIRM);
+ }
+
+ abstract Optional<Integer> getTitleStringId();
+
+ abstract Optional<Integer> getSubtitleStringId();
+
+ abstract Optional<Integer> getActionButtonStringId();
+
+ abstract Optional<Integer> getActionTextButtonStringId();
+
+ abstract void onActionButtonClicked(View button);
+
+ private void updateActionButtons() {
+ updateButton(mActionButton, getActionButtonStringId(), this::onActionButtonClicked);
+ updateButton(mActionTextButton, getActionTextButtonStringId(), this::onActionButtonClicked);
+ }
+
+ private static void updateButton(Button button, Optional<Integer> stringId,
+ View.OnClickListener listener) {
+ if (!stringId.isPresent()) {
+ button.setVisibility(View.INVISIBLE);
+ return;
+ }
+
+ button.setVisibility(View.VISIBLE);
+ button.setText(stringId.get());
+ button.setOnClickListener(listener);
+ }
+
+ private void updateTitles() {
+ updateTitleView(mTitleTextView, getTitleStringId(),
+ R.style.TextAppearance_BackGestureTutorial_Title);
+ updateTitleView(mSubtitleTextView, getSubtitleStringId(),
+ R.style.TextAppearance_BackGestureTutorial_Subtitle);
+ }
+
+ private static void updateTitleView(TextView textView, Optional<Integer> stringId,
+ int styleId) {
+ if (!stringId.isPresent()) {
+ textView.setVisibility(View.GONE);
+ return;
+ }
+
+ textView.setVisibility(View.VISIBLE);
+ textView.setText(stringId.get());
+ textView.setTextAppearance(styleId);
+ }
+
+ /**
+ * Constructs {@link BackGestureTutorialController} for providing {@link TutorialType} and
+ * {@link TutorialStep}.
+ */
+ static Optional<BackGestureTutorialController> getTutorialController(
+ BackGestureTutorialFragment fragment, TutorialStep tutorialStep,
+ TutorialType tutorialType) {
+ BackGestureTutorialTypeInfo tutorialTypeInfo =
+ BackGestureTutorialTypeInfoProvider.getTutorialTypeInfo(tutorialType);
+ switch (tutorialStep) {
+ case ENGAGED:
+ return Optional.of(
+ new BackGestureTutorialEngagedController(fragment, tutorialTypeInfo));
+ case CONFIRM:
+ return Optional.of(
+ new BackGestureTutorialConfirmController(fragment, tutorialTypeInfo));
+ default:
+ throw new AssertionError("Unexpected tutorial step: " + tutorialStep);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialEngagedController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialEngagedController.java
new file mode 100644
index 0000000..c9ee1e2
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialEngagedController.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2020 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.quickstep.interaction;
+
+import android.view.View;
+
+import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialStep;
+
+import java.util.Optional;
+
+/**
+ * An implementation of {@link BackGestureTutorialController} that defines the behavior of the
+ * {@link TutorialStep#ENGAGED}.
+ */
+final class BackGestureTutorialEngagedController extends BackGestureTutorialController {
+
+ BackGestureTutorialEngagedController(
+ BackGestureTutorialFragment fragment, BackGestureTutorialTypeInfo tutorialTypeInfo) {
+ super(fragment, TutorialStep.ENGAGED, Optional.of(tutorialTypeInfo));
+ }
+
+ @Override
+ void transitToController() {
+ super.transitToController();
+ mHandCoachingAnimation.maybeStartLoopedAnimation(mTutorialTypeInfo.get().getTutorialType());
+ }
+
+ @Override
+ Optional<Integer> getTitleStringId() {
+ return Optional.of(mTutorialTypeInfo.get().getTutorialPlaygroundTitleId());
+ }
+
+ @Override
+ Optional<Integer> getSubtitleStringId() {
+ return Optional.of(mTutorialTypeInfo.get().getTutorialEngagedSubtitleId());
+ }
+
+ @Override
+ Optional<Integer> getActionButtonStringId() {
+ return Optional.empty();
+ }
+
+ @Override
+ Optional<Integer> getActionTextButtonStringId() {
+ return Optional.empty();
+ }
+
+ @Override
+ void onActionButtonClicked(View button) {
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
new file mode 100644
index 0000000..54408ce
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2020 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.quickstep.interaction;
+
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.launcher3.R;
+
+import java.net.URISyntaxException;
+import java.util.Optional;
+
+/** Shows the Back gesture interactive tutorial. */
+public class BackGestureTutorialFragment extends Fragment {
+
+ private static final String LOG_TAG = "TutorialFragment";
+ private static final String KEY_TUTORIAL_STEP = "tutorialStep";
+ private static final String KEY_TUTORIAL_TYPE = "tutorialType";
+ private static final String SYSTEM_NAVIGATION_SETTING_INTENT =
+ "#Intent;action=com.android.settings.SEARCH_RESULT_TRAMPOLINE;S"
+ + ".:settings:fragment_args_key=gesture_system_navigation_input_summary;S"
+ + ".:settings:show_fragment=com.android.settings.gestures"
+ + ".SystemNavigationGestureSettings;end";
+
+ private TutorialStep mTutorialStep;
+ private TutorialType mTutorialType;
+ private Optional<BackGestureTutorialController> mTutorialController = Optional.empty();
+ private View mRootView;
+ private BackGestureTutorialHandAnimation mHandCoachingAnimation;
+
+ public static BackGestureTutorialFragment newInstance(
+ TutorialStep tutorialStep, TutorialType tutorialType) {
+ BackGestureTutorialFragment fragment = new BackGestureTutorialFragment();
+ Bundle args = new Bundle();
+ args.putSerializable(KEY_TUTORIAL_STEP, tutorialStep);
+ args.putSerializable(KEY_TUTORIAL_TYPE, tutorialType);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Bundle args = savedInstanceState != null ? savedInstanceState : getArguments();
+ mTutorialStep = (TutorialStep) args.getSerializable(KEY_TUTORIAL_STEP);
+ mTutorialType = (TutorialType) args.getSerializable(KEY_TUTORIAL_TYPE);
+ }
+
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ super.onCreateView(inflater, container, savedInstanceState);
+
+ mRootView = inflater.inflate(R.layout.back_gesture_tutorial_fragment,
+ container, /* attachToRoot= */ false);
+ mRootView.findViewById(R.id.back_gesture_tutorial_fragment_close_button)
+ .setOnClickListener(this::onCloseButtonClicked);
+ mHandCoachingAnimation = new BackGestureTutorialHandAnimation(getContext(), mRootView);
+
+ return mRootView;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ changeController(mTutorialStep, mTutorialType);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mHandCoachingAnimation.stop();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle savedInstanceState) {
+ savedInstanceState.putSerializable(KEY_TUTORIAL_STEP, mTutorialStep);
+ savedInstanceState.putSerializable(KEY_TUTORIAL_TYPE, mTutorialType);
+ super.onSaveInstanceState(savedInstanceState);
+ }
+
+ View getRootView() {
+ return mRootView;
+ }
+
+ BackGestureTutorialHandAnimation getHandAnimation() {
+ return mHandCoachingAnimation;
+ }
+
+ void changeController(TutorialStep tutorialStep) {
+ changeController(tutorialStep, mTutorialType);
+ }
+
+ void changeController(TutorialStep tutorialStep, TutorialType tutorialType) {
+ Optional<BackGestureTutorialController> tutorialController =
+ BackGestureTutorialController.getTutorialController(/* fragment= */ this,
+ tutorialStep, tutorialType);
+ if (!tutorialController.isPresent()) {
+ return;
+ }
+
+ mTutorialController = tutorialController;
+ mTutorialController.get().transitToController();
+ this.mTutorialStep = mTutorialController.get().mTutorialStep;
+ this.mTutorialType = tutorialType;
+ }
+
+ void onBackPressed() {
+ if (mTutorialController.isPresent()) {
+ mTutorialController.get().onGestureDetected();
+ }
+ }
+
+ void closeTutorial() {
+ getActivity().finish();
+ }
+
+ void startSystemNavigationSetting() {
+ try {
+ startActivityForResult(
+ Intent.parseUri(SYSTEM_NAVIGATION_SETTING_INTENT, /* flags= */ 0),
+ /* requestCode= */ 0);
+ } catch (URISyntaxException e) {
+ Log.e(LOG_TAG, "The launch Intent Uri is wrong syntax: " + e);
+ } catch (ActivityNotFoundException e) {
+ Log.e(LOG_TAG, "The launch Activity not found: " + e);
+ }
+ }
+
+ private void onCloseButtonClicked(View button) {
+ closeTutorial();
+ }
+
+ /** Denotes the step of the tutorial. */
+ enum TutorialStep {
+ ENGAGED,
+ CONFIRM,
+ }
+
+ /** Denotes the type of the tutorial. */
+ enum TutorialType {
+ RIGHT_EDGE_BACK_NAVIGATION,
+ LEFT_EDGE_BACK_NAVIGATION,
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialHandAnimation.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialHandAnimation.java
new file mode 100644
index 0000000..d03811d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialHandAnimation.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2020 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.quickstep.interaction;
+
+import android.content.Context;
+import android.graphics.drawable.Animatable2;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.widget.ImageView;
+
+import androidx.core.content.ContextCompat;
+
+import com.android.launcher3.R;
+import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType;
+
+import java.time.Duration;
+
+/** Hand coaching animation. */
+final class BackGestureTutorialHandAnimation {
+
+ // A delay for waiting the Activity fully launches.
+ private static final Duration ANIMATION_START_DELAY = Duration.ofMillis(300L);
+
+ private final ImageView mHandCoachingView;
+ private final AnimatedVectorDrawable mGestureAnimation;
+
+ private boolean mIsAnimationPlayed = false;
+
+ BackGestureTutorialHandAnimation(Context context, View rootView) {
+ mHandCoachingView = rootView.findViewById(
+ R.id.back_gesture_tutorial_fragment_hand_coaching);
+ mGestureAnimation = (AnimatedVectorDrawable) ContextCompat.getDrawable(context,
+ R.drawable.back_gesture);
+ }
+
+ boolean isRunning() {
+ return mGestureAnimation.isRunning();
+ }
+
+ /**
+ * Starts animation if the playground is launched for the first time.
+ */
+ void maybeStartLoopedAnimation(TutorialType tutorialType) {
+ if (isRunning() || mIsAnimationPlayed) {
+ return;
+ }
+
+ mIsAnimationPlayed = true;
+ clearAnimationCallbacks();
+ mGestureAnimation.registerAnimationCallback(
+ new Animatable2.AnimationCallback() {
+ @Override
+ public void onAnimationEnd(Drawable drawable) {
+ super.onAnimationEnd(drawable);
+ mGestureAnimation.start();
+ }
+ });
+ start(tutorialType);
+ }
+
+ private void start(TutorialType tutorialType) {
+ // Because the gesture animation has only the right side form.
+ // The left side form of the gesture animation is made from flipping the View.
+ float rotationY = tutorialType == TutorialType.LEFT_EDGE_BACK_NAVIGATION ? 180f : 0f;
+ mHandCoachingView.setRotationY(rotationY);
+ mHandCoachingView.setImageDrawable(mGestureAnimation);
+ mHandCoachingView.postDelayed(() -> mGestureAnimation.start(),
+ ANIMATION_START_DELAY.toMillis());
+ }
+
+ private void clearAnimationCallbacks() {
+ mGestureAnimation.clearAnimationCallbacks();
+ }
+
+ void stop() {
+ mIsAnimationPlayed = false;
+ clearAnimationCallbacks();
+ mGestureAnimation.stop();
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialTypeInfo.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialTypeInfo.java
new file mode 100644
index 0000000..ac8443d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialTypeInfo.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2020 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.quickstep.interaction;
+
+import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType;
+
+/** Defines the UI element identifiers for the particular {@link TutorialType}. */
+final class BackGestureTutorialTypeInfo {
+
+ private final TutorialType mTutorialType;
+ private final int mTutorialPlaygroundTitleId;
+ private final int mTutorialEngagedSubtitleId;
+ private final int mTutorialConfirmTitleId;
+ private final int mTutorialConfirmSubtitleId;
+
+ TutorialType getTutorialType() {
+ return mTutorialType;
+ }
+
+ int getTutorialPlaygroundTitleId() {
+ return mTutorialPlaygroundTitleId;
+ }
+
+ int getTutorialEngagedSubtitleId() {
+ return mTutorialEngagedSubtitleId;
+ }
+
+ int getTutorialConfirmTitleId() {
+ return mTutorialConfirmTitleId;
+ }
+
+ int getTutorialConfirmSubtitleId() {
+ return mTutorialConfirmSubtitleId;
+ }
+
+ static Builder builder() {
+ return new Builder();
+ }
+
+ private BackGestureTutorialTypeInfo(
+ TutorialType tutorialType,
+ int tutorialPlaygroundTitleId,
+ int tutorialEngagedSubtitleId,
+ int tutorialConfirmTitleId,
+ int tutorialConfirmSubtitleId) {
+ mTutorialType = tutorialType;
+ mTutorialPlaygroundTitleId = tutorialPlaygroundTitleId;
+ mTutorialEngagedSubtitleId = tutorialEngagedSubtitleId;
+ mTutorialConfirmTitleId = tutorialConfirmTitleId;
+ mTutorialConfirmSubtitleId = tutorialConfirmSubtitleId;
+ }
+
+ /** Builder for producing {@link BackGestureTutorialTypeInfo} objects. */
+ static class Builder {
+
+ private TutorialType mTutorialType;
+ private Integer mTutorialPlaygroundTitleId;
+ private Integer mTutorialEngagedSubtitleId;
+ private Integer mTutorialConfirmTitleId;
+ private Integer mTutorialConfirmSubtitleId;
+
+ Builder setTutorialType(TutorialType tutorialType) {
+ mTutorialType = tutorialType;
+ return this;
+ }
+
+ Builder setTutorialPlaygroundTitleId(int stringId) {
+ mTutorialPlaygroundTitleId = stringId;
+ return this;
+ }
+
+ Builder setTutorialEngagedSubtitleId(int stringId) {
+ mTutorialEngagedSubtitleId = stringId;
+ return this;
+ }
+
+ Builder setTutorialConfirmTitleId(int stringId) {
+ mTutorialConfirmTitleId = stringId;
+ return this;
+ }
+
+ Builder setTutorialConfirmSubtitleId(int stringId) {
+ mTutorialConfirmSubtitleId = stringId;
+ return this;
+ }
+
+ BackGestureTutorialTypeInfo build() {
+ return new BackGestureTutorialTypeInfo(
+ mTutorialType,
+ mTutorialPlaygroundTitleId,
+ mTutorialEngagedSubtitleId,
+ mTutorialConfirmTitleId,
+ mTutorialConfirmSubtitleId);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialTypeInfoProvider.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialTypeInfoProvider.java
new file mode 100644
index 0000000..9575d83
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialTypeInfoProvider.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2020 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.quickstep.interaction;
+
+import com.android.launcher3.R;
+import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType;
+
+/** Provides instances of {@link BackGestureTutorialTypeInfo} for each {@link TutorialType}. */
+final class BackGestureTutorialTypeInfoProvider {
+
+ private static final BackGestureTutorialTypeInfo RIGHT_EDGE_BACK_NAV_TUTORIAL_INFO =
+ BackGestureTutorialTypeInfo.builder()
+ .setTutorialType(TutorialType.RIGHT_EDGE_BACK_NAVIGATION)
+ .setTutorialPlaygroundTitleId(
+ R.string.back_gesture_tutorial_playground_title_swipe_inward_right_edge)
+ .setTutorialEngagedSubtitleId(
+ R.string.back_gesture_tutorial_engaged_subtitle_swipe_inward_right_edge)
+ .setTutorialConfirmTitleId(R.string.back_gesture_tutorial_confirm_title)
+ .setTutorialConfirmSubtitleId(R.string.back_gesture_tutorial_confirm_subtitle)
+ .build();
+
+ private static final BackGestureTutorialTypeInfo LEFT_EDGE_BACK_NAV_TUTORIAL_INFO =
+ BackGestureTutorialTypeInfo.builder()
+ .setTutorialType(TutorialType.LEFT_EDGE_BACK_NAVIGATION)
+ .setTutorialPlaygroundTitleId(
+ R.string.back_gesture_tutorial_playground_title_swipe_inward_left_edge)
+ .setTutorialEngagedSubtitleId(
+ R.string.back_gesture_tutorial_engaged_subtitle_swipe_inward_left_edge)
+ .setTutorialConfirmTitleId(R.string.back_gesture_tutorial_confirm_title)
+ .setTutorialConfirmSubtitleId(R.string.back_gesture_tutorial_confirm_subtitle)
+ .build();
+
+ static BackGestureTutorialTypeInfo getTutorialTypeInfo(TutorialType tutorialType) {
+ switch (tutorialType) {
+ case RIGHT_EDGE_BACK_NAVIGATION:
+ return RIGHT_EDGE_BACK_NAV_TUTORIAL_INFO;
+ case LEFT_EDGE_BACK_NAVIGATION:
+ return LEFT_EDGE_BACK_NAV_TUTORIAL_INFO;
+ default:
+ throw new AssertionError("Unexpected tutorial type: " + tutorialType);
+ }
+ }
+
+ private BackGestureTutorialTypeInfoProvider() {
+ }
+}
diff --git a/res/values/strings.xml b/res/values/strings.xml
index dec8939..218f6db 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -103,7 +103,7 @@
<!-- Label for install drop target. [CHAR_LIMIT=20] -->
<string name="install_drop_target_label">Install</string>
<!-- Label for install dismiss prediction. -->
- <string translatable="false" name="dismiss_prediction_label">Dismiss prediction</string>
+ <string translatable="false" name="dismiss_prediction_label">Don\'t suggest app</string>
<!-- Label for pinning predicted app. -->
<string name="pin_prediction" translatable="false">Pin Prediction</string>
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 03ee707..b89e727 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -49,12 +49,17 @@
super(context, attrs, defStyle);
}
- /* Get the orientation specific coordinates given an invariant order in the hotseat. */
- int getCellXFromOrder(int rank) {
+ /**
+ * Returns orientation specific cell X given invariant order in the hotseat
+ */
+ public int getCellXFromOrder(int rank) {
return mHasVerticalHotseat ? 0 : rank;
}
- int getCellYFromOrder(int rank) {
+ /**
+ * Returns orientation specific cell Y given invariant order in the hotseat
+ */
+ public int getCellYFromOrder(int rank) {
return mHasVerticalHotseat ? (getCountY() - (rank + 1)) : 0;
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index d8c4c5c..a7bb9ee 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -307,7 +307,7 @@
// Request id for any pending activity result
protected int mPendingActivityRequestCode = -1;
- public ViewGroupFocusHelper mFocusHandler;
+ private ViewGroupFocusHelper mFocusHandler;
private RotationHelper mRotationHelper;
@@ -617,6 +617,10 @@
return mRotationHelper;
}
+ public ViewGroupFocusHelper getFocusHandler() {
+ return mFocusHandler;
+ }
+
public LauncherStateManager getStateManager() {
return mStateManager;
}
@@ -1740,7 +1744,7 @@
getModelWriter().addItemToDatabase(folderInfo, container, screenId, cellX, cellY);
// Create the view
- FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo);
+ FolderIcon newFolder = FolderIcon.inflateFolderAndIcon(R.layout.folder_icon, this, layout, folderInfo);
mWorkspace.addInScreen(newFolder, folderInfo);
// Force measure the new folder icon
CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder);
@@ -2101,7 +2105,7 @@
break;
}
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: {
- view = FolderIcon.fromXml(R.layout.folder_icon, this,
+ view = FolderIcon.inflateFolderAndIcon(R.layout.folder_icon, this,
(ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
(FolderInfo) item);
break;
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 7af979c..9a3a379 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -83,7 +83,6 @@
import com.android.launcher3.pageindicators.WorkspacePageIndicator;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.WorkspaceTouchListener;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -418,9 +417,6 @@
}
// Always enter the spring loaded mode
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "Switching to SPRING_LOADED");
- }
mLauncher.getStateManager().goToState(SPRING_LOADED);
}
@@ -1760,9 +1756,6 @@
public void prepareAccessibilityDrop() { }
public void onDrop(final DragObject d, DragOptions options) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "Workspace.onDrop");
- }
mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
CellLayout dropTargetLayout = mDropToLayout;
@@ -2440,9 +2433,6 @@
* to add an item to one of the workspace screens.
*/
private void onDropExternal(final int[] touchXY, final CellLayout cellLayout, DragObject d) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "Workspace.onDropExternal");
- }
if (d.dragInfo instanceof PendingAddShortcutInfo) {
WorkspaceItemInfo si = ((PendingAddShortcutInfo) d.dragInfo)
.activityInfo.createWorkspaceItemInfo();
@@ -2556,7 +2546,7 @@
view = mLauncher.createShortcut(cellLayout, (WorkspaceItemInfo) info);
break;
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
- view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
+ view = FolderIcon.inflateFolderAndIcon(R.layout.folder_icon, mLauncher, cellLayout,
(FolderInfo) info);
break;
default:
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 08ce9c2..0681919 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -11,10 +11,12 @@
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
+import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
import static com.android.launcher3.util.SystemUiController.UI_STATE_ALL_APPS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
import android.util.FloatProperty;
import android.view.animation.Interpolator;
@@ -183,8 +185,11 @@
}
public Animator createSpringAnimation(float... progressValues) {
- return new SpringObjectAnimator<>(this, ALL_APPS_PROGRESS, 1f / mShiftRange,
- SPRING_DAMPING_RATIO, SPRING_STIFFNESS, progressValues);
+ if (UNSTABLE_SPRINGS.get()) {
+ return new SpringObjectAnimator<>(this, ALL_APPS_PROGRESS, 1f / mShiftRange,
+ SPRING_DAMPING_RATIO, SPRING_STIFFNESS, progressValues);
+ }
+ return ObjectAnimator.ofFloat(this, ALL_APPS_PROGRESS, progressValues);
}
private void setAlphas(LauncherState toState, AnimationConfig config,
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 003ca82..81dcba3 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -85,7 +85,10 @@
"APPLY_CONFIG_AT_RUNTIME", true, "Apply display changes dynamically");
public static final TogglableFlag QUICKSTEP_SPRINGS = new TogglableFlag("QUICKSTEP_SPRINGS",
- false, "Enable springs for quickstep animations");
+ true, "Enable springs for quickstep animations");
+
+ public static final TogglableFlag UNSTABLE_SPRINGS = new TogglableFlag("UNSTABLE_SPRINGS",
+ false, "Enable unstable springs for quickstep animations");
public static final TogglableFlag ADAPTIVE_ICON_WINDOW_ANIM = new TogglableFlag(
"ADAPTIVE_ICON_WINDOW_ANIM", true,
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index dcdf5d6..8adec27 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -594,9 +594,6 @@
}
private void drop(DropTarget dropTarget, Runnable flingAnimation) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "DragController.drop");
- }
final int[] coordinates = mCoordinatesTemp;
mDragObject.x = coordinates[0];
mDragObject.y = coordinates[1];
diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java
index 01e0f92..87461d5 100644
--- a/src/com/android/launcher3/dragndrop/DragDriver.java
+++ b/src/com/android/launcher3/dragndrop/DragDriver.java
@@ -54,16 +54,10 @@
mEventListener.onDriverDragMove(ev.getX(), ev.getY());
break;
case MotionEvent.ACTION_UP:
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "DragDriver.ACTION_UP");
- }
mEventListener.onDriverDragMove(ev.getX(), ev.getY());
mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
break;
case MotionEvent.ACTION_CANCEL:
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "DragDriver.ACTION_CANCEL");
- }
mEventListener.onDriverDragCancel();
break;
}
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index f59a192..52d8f7f 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -423,7 +423,7 @@
mFolderName.setHint(suggestName[0]);
mFolderName.setText(suggestName[0]);
mInfo.title = suggestName[0];
- animateOpen();
+ animateOpen(mInfo.contents, 0, true);
mFolderName.showKeyboard();
mFolderName.displayCompletions(
Arrays.asList(suggestName).subList(1, suggestName.length));
@@ -519,6 +519,15 @@
* is played.
*/
private void animateOpen(List<WorkspaceItemInfo> items, int pageNo) {
+ animateOpen(items, pageNo, false);
+ }
+
+ /**
+ * Opens the user folder described by the specified tag. The opening of the folder
+ * is animated relative to the specified View. If the View is null, no animation
+ * is played.
+ */
+ private void animateOpen(List<WorkspaceItemInfo> items, int pageNo, boolean skipUserEventLog) {
Folder openFolder = getOpen(mLauncher);
if (openFolder != null && openFolder != this) {
// Close any open folder before opening a folder.
@@ -565,10 +574,13 @@
mState = STATE_OPEN;
announceAccessibilityChanges();
- mLauncher.getUserEventDispatcher().logActionOnItem(
+ if (!skipUserEventLog) {
+ mLauncher.getUserEventDispatcher().logActionOnItem(
Touch.TAP,
Direction.NONE,
ItemType.FOLDER_ICON, mInfo.cellX, mInfo.cellY);
+ }
+
mContent.setFocusOnFirstChild();
}
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 7bbd45d..8c56823 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -67,6 +67,7 @@
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.Thunk;
+import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.IconLabelDotView;
import com.android.launcher3.widget.PendingAddShortcutInfo;
@@ -79,7 +80,7 @@
*/
public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView {
- @Thunk Launcher mLauncher;
+ @Thunk ActivityContext mActivity;
@Thunk Folder mFolder;
private FolderInfo mInfo;
@@ -153,7 +154,21 @@
mDotParams = new DotRenderer.DrawParams();
}
- public static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group,
+ public static FolderIcon inflateFolderAndIcon(int resId, Launcher launcher, ViewGroup group,
+ FolderInfo folderInfo) {
+ Folder folder = Folder.fromXml(launcher);
+ folder.setDragController(launcher.getDragController());
+
+ FolderIcon icon = inflateIcon(resId, launcher, group, folderInfo);
+ folder.setFolderIcon(icon);
+ folder.bind(folderInfo);
+ icon.setFolder(folder);
+
+ icon.setOnFocusChangeListener(launcher.getFocusHandler());
+ return icon;
+ }
+
+ public static FolderIcon inflateIcon(int resId, ActivityContext activity, ViewGroup group,
FolderInfo folderInfo) {
@SuppressWarnings("all") // suppress dead code warning
final boolean error = INITIAL_ITEM_ANIMATION_DURATION >= DROP_IN_ANIMATION_DURATION;
@@ -163,7 +178,7 @@
"is dependent on this");
}
- DeviceProfile grid = launcher.getWallpaperDeviceProfile();
+ DeviceProfile grid = activity.getWallpaperDeviceProfile();
FolderIcon icon = (FolderIcon) LayoutInflater.from(group.getContext())
.inflate(resId, group, false);
@@ -177,19 +192,27 @@
icon.setTag(folderInfo);
icon.setOnClickListener(ItemClickHandler.INSTANCE);
icon.mInfo = folderInfo;
- icon.mLauncher = launcher;
+ icon.mActivity = activity;
icon.mDotRenderer = grid.mDotRendererWorkSpace;
- icon.setContentDescription(launcher.getString(R.string.folder_name_format, folderInfo.title));
- Folder folder = Folder.fromXml(launcher);
- folder.setDragController(launcher.getDragController());
- folder.setFolderIcon(icon);
- folder.bind(folderInfo);
- icon.setFolder(folder);
- icon.setAccessibilityDelegate(launcher.getAccessibilityDelegate());
+
+ icon.setContentDescription(
+ group.getContext().getString(R.string.folder_name_format, folderInfo.title));
+
+ // Keep the notification dot up to date with the sum of all the content's dots.
+ FolderDotInfo folderDotInfo = new FolderDotInfo();
+ for (WorkspaceItemInfo si : folderInfo.contents) {
+ folderDotInfo.addDotInfo(activity.getDotInfoForItem(si));
+ }
+ icon.setDotInfo(folderDotInfo);
+
+ icon.setAccessibilityDelegate(activity.getAccessibilityDelegate());
+
+ icon.mPreviewVerifier = new FolderGridOrganizer(activity.getDeviceProfile().inv);
+ icon.mPreviewVerifier.setFolderInfo(folderInfo);
+ icon.updatePreviewItems(false);
folderInfo.addListener(icon);
- icon.setOnFocusChangeListener(launcher.mFocusHandler);
return icon;
}
@@ -217,9 +240,6 @@
private void setFolder(Folder folder) {
mFolder = folder;
- mPreviewVerifier = new FolderGridOrganizer(mLauncher.getDeviceProfile().inv);
- mPreviewVerifier.setFolderInfo(mFolder.getInfo());
- updatePreviewItems(false);
}
private boolean willAcceptItem(ItemInfo item) {
@@ -301,14 +321,15 @@
// Typically, the animateView corresponds to the DragView; however, if this is being done
// after a configuration activity (ie. for a Shortcut being dragged from AllApps) we
// will not have a view to animate
- if (animateView != null) {
- DragLayer dragLayer = mLauncher.getDragLayer();
+ if (animateView != null && mActivity instanceof Launcher) {
+ final Launcher launcher = (Launcher) mActivity;
+ DragLayer dragLayer = launcher.getDragLayer();
Rect from = new Rect();
dragLayer.getViewRectRelativeToSelf(animateView, from);
Rect to = finalRect;
if (to == null) {
to = new Rect();
- Workspace workspace = mLauncher.getWorkspace();
+ Workspace workspace = launcher.getWorkspace();
// Set cellLayout and this to it's final state to compute final animation locations
workspace.setFinalTransitionTransform();
float scaleX = getScaleX();
@@ -374,7 +395,7 @@
String[] suggestedNameOut = new String[FolderNameProvider.SUGGEST_MAX];
if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
Executors.UI_HELPER_EXECUTOR.post(() -> {
- mLauncher.getFolderNameProvider().getSuggestedFolderName(
+ launcher.getFolderNameProvider().getSuggestedFolderName(
getContext(), mInfo.contents, suggestedNameOut);
showFinalView(finalIndex, item, suggestedNameOut);
});
@@ -539,7 +560,7 @@
if (!mForceHideDot && ((mDotInfo != null && mDotInfo.hasDot()) || mDotScale > 0)) {
Rect iconBounds = mDotParams.iconBounds;
BubbleTextView.getIconBounds(this, iconBounds,
- mLauncher.getWallpaperDeviceProfile().iconSizePx);
+ mActivity.getWallpaperDeviceProfile().iconSizePx);
float iconScale = (float) mBackground.previewSize / iconBounds.width();
Utilities.scaleRectAboutCenter(iconBounds, iconScale);
@@ -597,7 +618,7 @@
@Override
public void onAdd(WorkspaceItemInfo item, int rank) {
boolean wasDotted = mDotInfo.hasDot();
- mDotInfo.addDotInfo(mLauncher.getDotInfoForItem(item));
+ mDotInfo.addDotInfo(mActivity.getDotInfoForItem(item));
boolean isDotted = mDotInfo.hasDot();
updateDotScale(wasDotted, isDotted);
invalidate();
@@ -607,7 +628,7 @@
@Override
public void onRemove(WorkspaceItemInfo item) {
boolean wasDotted = mDotInfo.hasDot();
- mDotInfo.subtractDotInfo(mLauncher.getDotInfoForItem(item));
+ mDotInfo.subtractDotInfo(mActivity.getDotInfoForItem(item));
boolean isDotted = mDotInfo.hasDot();
updateDotScale(wasDotted, isDotted);
invalidate();
diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java
index 5b3a05e..27aa43e 100644
--- a/src/com/android/launcher3/folder/PreviewItemManager.java
+++ b/src/com/android/launcher3/folder/PreviewItemManager.java
@@ -37,10 +37,10 @@
import androidx.annotation.NonNull;
-import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.graphics.PreloadIconDrawable;
+import com.android.launcher3.views.ActivityContext;
import java.util.ArrayList;
import java.util.List;
@@ -94,7 +94,8 @@
public PreviewItemManager(FolderIcon icon) {
mContext = icon.getContext();
mIcon = icon;
- mIconSize = Launcher.getLauncher(mContext).getDeviceProfile().folderChildIconSizePx;
+ mIconSize = ActivityContext.lookupContext(
+ mContext).getDeviceProfile().folderChildIconSizePx;
}
/**
@@ -132,7 +133,7 @@
mTotalWidth = totalSize;
mPrevTopPadding = mIcon.getPaddingTop();
- mIcon.mBackground.setup(mIcon.mLauncher, mIcon.mLauncher, mIcon, mTotalWidth,
+ mIcon.mBackground.setup(mIcon.getContext(), mIcon.mActivity, mIcon, mTotalWidth,
mIcon.getPaddingTop());
mIcon.mPreviewLayoutRule.init(mIcon.mBackground.previewSize, mIntrinsicIconSize,
Utilities.isRtl(mIcon.getResources()));
@@ -152,7 +153,7 @@
}
private PreviewItemDrawingParams getFinalIconParams(PreviewItemDrawingParams params) {
- float iconSize = mIcon.mLauncher.getDeviceProfile().iconSizePx;
+ float iconSize = mIcon.mActivity.getDeviceProfile().iconSizePx;
final float scale = iconSize / mReferenceDrawable.getIntrinsicWidth();
final float trans = (mIcon.mBackground.previewSize - iconSize) / 2;
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 0c5535f..bfe7351 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -49,6 +49,7 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.FolderInfo;
import com.android.launcher3.Hotseat;
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.InvariantDeviceProfile;
@@ -63,6 +64,7 @@
import com.android.launcher3.WorkspaceLayoutManager;
import com.android.launcher3.allapps.SearchUiManager;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.BitmapRenderer;
@@ -239,6 +241,12 @@
addInScreenFromBind(icon, info);
}
+ private void inflateAndAddFolder(FolderInfo info) {
+ FolderIcon folderIcon = FolderIcon.inflateIcon(R.layout.folder_icon, this, mWorkspace,
+ info);
+ addInScreenFromBind(folderIcon, info);
+ }
+
private void dispatchVisibilityAggregated(View view, boolean isVisible) {
// Similar to View.dispatchVisibilityAggregated implementation.
final boolean thisVisible = view.getVisibility() == VISIBLE;
@@ -288,7 +296,7 @@
inflateAndAddIcon((WorkspaceItemInfo) itemInfo);
break;
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
- // TODO: for folder implementation here.
+ inflateAndAddFolder((FolderInfo) itemInfo);
break;
default:
break;
diff --git a/src/com/android/launcher3/notification/NotificationFooterLayout.java b/src/com/android/launcher3/notification/NotificationFooterLayout.java
index c7de5b0..fd3d41a 100644
--- a/src/com/android/launcher3/notification/NotificationFooterLayout.java
+++ b/src/com/android/launcher3/notification/NotificationFooterLayout.java
@@ -80,17 +80,28 @@
int iconSize = res.getDimensionPixelSize(R.dimen.notification_footer_icon_size);
mIconLayoutParams = new LayoutParams(iconSize, iconSize);
mIconLayoutParams.gravity = Gravity.CENTER_VERTICAL;
- // Compute margin start for each icon such that the icons between the first one
- // and the ellipsis are evenly spaced out.
+ setWidth((int) res.getDimension(R.dimen.bg_popup_item_width));
+ mBackgroundColor = Themes.getAttrColor(context, R.attr.popupColorPrimary);
+ }
+
+
+ /**
+ * Compute margin start for each icon such that the icons between the first one and the ellipsis
+ * are evenly spaced out.
+ */
+ public void setWidth(int width) {
+ if (getLayoutParams() != null) {
+ getLayoutParams().width = width;
+ }
+ Resources res = getResources();
+ int iconSize = res.getDimensionPixelSize(R.dimen.notification_footer_icon_size);
+
int paddingEnd = res.getDimensionPixelSize(R.dimen.notification_footer_icon_row_padding);
int ellipsisSpace = res.getDimensionPixelSize(R.dimen.horizontal_ellipsis_offset)
+ res.getDimensionPixelSize(R.dimen.horizontal_ellipsis_size);
- int footerWidth = res.getDimensionPixelSize(R.dimen.bg_popup_item_width);
- int availableIconRowSpace = footerWidth - paddingEnd - ellipsisSpace
+ int availableIconRowSpace = width - paddingEnd - ellipsisSpace
- iconSize * MAX_FOOTER_NOTIFICATIONS;
mIconLayoutParams.setMarginStart(availableIconRowSpace / MAX_FOOTER_NOTIFICATIONS);
-
- mBackgroundColor = Themes.getAttrColor(context, R.attr.popupColorPrimary);
}
@Override
diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java
index 021fb30..0320aa3 100644
--- a/src/com/android/launcher3/notification/NotificationItemView.java
+++ b/src/com/android/launcher3/notification/NotificationItemView.java
@@ -86,6 +86,13 @@
}
}
+ /**
+ * Sets width for notification footer and spaces out items evenly
+ */
+ public void setFooterWidth(int footerWidth) {
+ mFooter.setWidth(footerWidth);
+ }
+
public void removeFooter() {
if (mContainer.indexOfChild(mFooter) >= 0) {
mContainer.removeView(mFooter);
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index e70673a..72c95c4 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -37,7 +37,6 @@
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
-import android.util.Log;
import android.util.Pair;
import android.view.MotionEvent;
import android.view.View;
@@ -66,7 +65,6 @@
import com.android.launcher3.popup.PopupDataProvider.PopupDataChangeListener;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.PackageUserKey;
@@ -257,6 +255,16 @@
mNumNotifications = notificationKeys.size();
mOriginalIcon = originalIcon;
+ boolean hasDeepShortcuts = shortcutCount > 0;
+ int containerWidth = (int) getResources().getDimension(R.dimen.bg_popup_item_width);
+
+ // if there are deep shortcuts, we might want to increase the width of shortcuts to fit
+ // horizontally laid out system shortcuts.
+ if (hasDeepShortcuts) {
+ containerWidth = (int) Math.max(containerWidth,
+ systemShortcuts.size() * getResources().getDimension(
+ R.dimen.system_shortcut_header_icon_touch_size));
+ }
// Add views
if (mNumNotifications > 0) {
// Add notification entries
@@ -265,18 +273,22 @@
if (mNumNotifications == 1) {
mNotificationItemView.removeFooter();
}
+ else {
+ mNotificationItemView.setFooterWidth(containerWidth);
+ }
updateNotificationHeader();
}
int viewsToFlip = getChildCount();
mSystemShortcutContainer = this;
-
- if (shortcutCount > 0) {
+ if (hasDeepShortcuts) {
if (mNotificationItemView != null) {
mNotificationItemView.addGutter();
}
for (int i = shortcutCount; i > 0; i--) {
- mShortcuts.add(inflateAndAdd(R.layout.deep_shortcut, this));
+ DeepShortcutView v = inflateAndAdd(R.layout.deep_shortcut, this);
+ v.getLayoutParams().width = containerWidth;
+ mShortcuts.add(v);
}
updateHiddenShortcuts();
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index b580bd6..48f1c49 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -178,7 +178,10 @@
public static final Factory<Launcher> DISMISS_PREDICTION = (launcher, itemInfo) -> {
if (!FeatureFlags.ENABLE_PREDICTION_DISMISS.get()) return null;
- if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_PREDICTION) return null;
+ if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_PREDICTION
+ && itemInfo.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
+ return null;
+ }
return new DismissPrediction(launcher, itemInfo);
};
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 40e267b..ecfc77c 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -24,6 +24,7 @@
import android.graphics.Color;
import android.os.Bundle;
import android.os.Debug;
+import android.system.Os;
import android.view.View;
import androidx.annotation.Keep;
@@ -136,6 +137,11 @@
break;
}
+ case TestProtocol.REQUEST_PID: {
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, Os.getpid());
+ break;
+ }
+
case TestProtocol.REQUEST_TOTAL_PSS_KB: {
runGcAndFinalizersSync();
Debug.MemoryInfo mem = new Debug.MemoryInfo();
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 5aae841..929315a 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -73,6 +73,7 @@
public static final String REQUEST_APPS_LIST_SCROLL_Y = "apps-list-scroll-y";
public static final String REQUEST_OVERVIEW_LEFT_GESTURE_MARGIN = "overview-left-margin";
public static final String REQUEST_OVERVIEW_RIGHT_GESTURE_MARGIN = "overview-right-margin";
+ public static final String REQUEST_PID = "pid";
public static final String REQUEST_TOTAL_PSS_KB = "total_pss";
public static final String REQUEST_JAVA_LEAK = "java-leak";
public static final String REQUEST_NATIVE_LEAK = "native-leak";
@@ -86,6 +87,5 @@
public static final String PERMANENT_DIAG_TAG = "TaplTarget";
public static final String NO_BACKGROUND_TO_OVERVIEW_TAG = "b/138251824";
- public static final String NO_DRAG_TO_WORKSPACE = "b/138729456";
public static final String APP_NOT_DISABLED = "b/139891609";
}
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index f40f976..f470edb 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -23,7 +23,7 @@
import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_SCALE_COMPONENT;
import static com.android.launcher3.LauncherStateManager.NON_ATOMIC_COMPONENT;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
import android.animation.Animator;
@@ -434,7 +434,7 @@
updateSwipeCompleteAnimation(anim, Math.max(duration, getRemainingAtomicDuration()),
targetState, velocity, fling);
mCurrentAnimation.dispatchOnStartWithVelocity(endProgress, velocity);
- if (fling && targetState == LauncherState.ALL_APPS && !QUICKSTEP_SPRINGS.get()) {
+ if (fling && targetState == LauncherState.ALL_APPS && !UNSTABLE_SPRINGS.get()) {
mLauncher.getAppsView().addSpringFromFlingUpdateListener(anim, velocity);
}
anim.start();
diff --git a/src/com/android/launcher3/util/LogConfig.java b/src/com/android/launcher3/util/LogConfig.java
index 4acdb5c..b54074e 100644
--- a/src/com/android/launcher3/util/LogConfig.java
+++ b/src/com/android/launcher3/util/LogConfig.java
@@ -28,4 +28,9 @@
* When turned on, icon cache is only fetched from memory and not disk.
*/
public static final String MEMORY_ONLY_ICON_CACHE = "MemoryOnlyIconCache";
+
+ /**
+ * When turned on, we enable doodle related logging.
+ */
+ public static final String DOODLE_LOGGING = "DoodleLogging";
}
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index e43fc8a..cae2c3a 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -261,10 +261,6 @@
}
case ACTION_CANCEL:
case ACTION_UP:
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE,
- "BaseDragLayer.ACTION_UP/CANCEL " + ev);
- }
mTouchDispatchState &= ~TOUCH_DISPATCHING_GESTURE;
mTouchDispatchState &= ~TOUCH_DISPATCHING_VIEW;
break;
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index 56eca6d..1c8f095 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -22,6 +22,7 @@
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
<uses-permission android:name="android.permission.READ_LOGS"/>
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
<application android:debuggable="true">
<uses-library android:name="android.test.runner"/>
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 7ea70a1..61f5150 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -44,6 +44,7 @@
import com.android.launcher3.widget.WidgetsFullSheet;
import com.android.launcher3.widget.WidgetsRecyclerView;
+import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@@ -54,10 +55,18 @@
public class TaplTestsLauncher3 extends AbstractLauncherUiTest {
private static final String APP_NAME = "LauncherTestApp";
+ private int mLauncherPid;
+
@Before
public void setUp() throws Exception {
super.setUp();
initialize(this);
+ mLauncherPid = mLauncher.getPid();
+ }
+
+ @After
+ public void teardown() {
+ assertEquals("Launcher crashed, pid mismatch:", mLauncherPid, mLauncher.getPid());
}
public static void initialize(AbstractLauncherUiTest test) throws Exception {
@@ -104,14 +113,14 @@
mLauncher.pressHome();
}
- // b/146432215: remove @Stability after 1/1/2020 if this test doesn't flake
+ // b/146432215: remove @Stability after 2/1/2020 if this test doesn't flake
@Test
@Stability(flavors = LOCAL | UNBUNDLED_POSTSUBMIT)
public void testOpenHomeSettingsFromWorkspace() {
mDevice.pressMenu();
mDevice.waitForIdle();
- mLauncher.getWorkspace().getMenu().getMenuItem("Home settings").launch(
- "com.google.android.apps.nexuslauncher");
+ mLauncher.getOptionsPopupMenu().getMenuItem("Home settings")
+ .launch(mDevice.getLauncherPackageName());
}
@Test
diff --git a/tests/tapl/com/android/launcher3/tapl/Folder.java b/tests/tapl/com/android/launcher3/tapl/Folder.java
deleted file mode 100644
index 6e6734d..0000000
--- a/tests/tapl/com/android/launcher3/tapl/Folder.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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.tapl;
-
-import android.widget.FrameLayout;
-
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.BySelector;
-import androidx.test.uiautomator.UiObject2;
-
-/**
- * App folder in workspace/
- */
-public final class Folder {
- Folder(LauncherInstrumentation launcher, UiObject2 icon) {
- }
-
- static BySelector getSelector(String folderName, LauncherInstrumentation launcher) {
- return By.clazz(FrameLayout.class).desc(folderName).pkg(launcher.getLauncherPackageName());
- }
-}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index ce60ef2..de6fdb1 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -55,6 +55,7 @@
import androidx.test.uiautomator.BySelector;
import androidx.test.uiautomator.Configurator;
import androidx.test.uiautomator.Direction;
+import androidx.test.uiautomator.StaleObjectException;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
@@ -183,13 +184,8 @@
.authority(testProviderAuthority)
.build();
- try {
- mDevice.executeShellCommand("pm grant " + testPackage +
- " android.permission.WRITE_SECURE_SETTINGS");
- } catch (IOException e) {
- fail(e.toString());
- }
-
+ mInstrumentation.getUiAutomation().grantRuntimePermission(
+ testPackage, "android.permission.WRITE_SECURE_SETTINGS");
PackageManager pm = getContext().getPackageManager();
ProviderInfo pi = pm.resolveContentProvider(
@@ -311,12 +307,21 @@
private String getVisiblePackages() {
return mDevice.findObjects(By.textStartsWith(""))
.stream()
- .map(object -> object.getApplicationPackage())
+ .map(LauncherInstrumentation::getApplicationPackageSafe)
.distinct()
- .filter(pkg -> !"com.android.systemui".equals(pkg))
+ .filter(pkg -> pkg != null && !"com.android.systemui".equals(pkg))
.collect(Collectors.joining(", "));
}
+ private static String getApplicationPackageSafe(UiObject2 object) {
+ try {
+ return object.getApplicationPackage();
+ } catch (StaleObjectException e) {
+ // We are looking at all object in the system; external ones can suddenly go away.
+ return null;
+ }
+ }
+
private String getVisibleStateMessage() {
if (hasLauncherObject(CONTEXT_MENU_RES_ID)) return "Context Menu";
if (hasLauncherObject(WIDGETS_RES_ID)) return "Widgets";
@@ -336,14 +341,11 @@
private String getSystemHealthMessage() {
final String testPackage = getContext().getPackageName();
- try {
- mDevice.executeShellCommand("pm grant " + testPackage +
- " android.permission.READ_LOGS");
- mDevice.executeShellCommand("pm grant " + testPackage +
- " android.permission.PACKAGE_USAGE_STATS");
- } catch (IOException e) {
- e.printStackTrace();
- }
+
+ mInstrumentation.getUiAutomation().grantRuntimePermission(
+ testPackage, "android.permission.READ_LOGS");
+ mInstrumentation.getUiAutomation().grantRuntimePermission(
+ testPackage, "android.permission.PACKAGE_USAGE_STATS");
return mSystemHealthSupplier != null
? mSystemHealthSupplier.apply(START_TIME)
@@ -681,6 +683,20 @@
}
}
+ /**
+ * Gets the Options Popup Menu object if the current state is showing the popup menu. Fails if
+ * the launcher is not in that state.
+ *
+ * @return Options Popup Menu object.
+ */
+ @NonNull
+ public OptionsPopupMenu getOptionsPopupMenu() {
+ try (LauncherInstrumentation.Closable c = addContextLayer(
+ "want to get context menu object")) {
+ return new OptionsPopupMenu(this);
+ }
+ }
+
void waitUntilGone(String resId) {
assertTrue("Unexpected launcher object visible: " + resId,
mDevice.wait(Until.gone(getLauncherObjectSelector(resId)),
@@ -990,7 +1006,7 @@
}
@NonNull
- public UiObject2 clickAndGet(@NonNull final UiObject2 target, @NonNull String resName) {
+ UiObject2 clickAndGet(@NonNull final UiObject2 target, @NonNull String resName) {
final Point targetCenter = target.getVisibleCenter();
final long downTime = SystemClock.uptimeMillis();
sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter);
@@ -1058,6 +1074,10 @@
getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
+ public int getPid() {
+ return getTestInfo(TestProtocol.REQUEST_PID).getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ }
+
public void produceJavaLeak() {
getTestInfo(TestProtocol.REQUEST_JAVA_LEAK);
}
diff --git a/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenu.java b/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenu.java
index 14cc2a1..282fca9 100644
--- a/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenu.java
+++ b/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenu.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -16,6 +16,7 @@
package com.android.launcher3.tapl;
import androidx.annotation.NonNull;
+import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiObject2;
public class OptionsPopupMenu {
@@ -23,10 +24,9 @@
private final LauncherInstrumentation mLauncher;
private final UiObject2 mDeepShortcutsContainer;
- OptionsPopupMenu(LauncherInstrumentation launcher,
- UiObject2 deepShortcutsContainer) {
+ OptionsPopupMenu(LauncherInstrumentation launcher) {
mLauncher = launcher;
- mDeepShortcutsContainer = deepShortcutsContainer;
+ mDeepShortcutsContainer = launcher.waitForLauncherObject("deep_shortcuts_container");
}
/**
@@ -34,10 +34,8 @@
*/
@NonNull
public OptionsPopupMenuItem getMenuItem(@NonNull final String label) {
- final UiObject2 obj = mLauncher
- .getObjectsInContainer(mDeepShortcutsContainer, "bubble_text").stream()
- .filter(menuItem -> label.equals(menuItem.getText())).findFirst().orElseThrow(() ->
- new IllegalStateException("Cannot find option with label: " + label));
- return new OptionsPopupMenuItem(mLauncher, obj);
+ final UiObject2 menuItem = mLauncher.waitForObjectInContainer(mDeepShortcutsContainer,
+ By.text(label));
+ return new OptionsPopupMenuItem(mLauncher, menuItem);
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java b/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java
index 600d79d..8527d05 100644
--- a/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java
+++ b/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 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.
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 3b3bbda..af7e552 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -183,12 +183,6 @@
mHotseat, AppIcon.getAppIconSelector(appName, mLauncher)));
}
- @NonNull
- public Folder getHotseatFolder(String appName) {
- return new Folder(mLauncher, mLauncher.waitForObjectInContainer(
- mHotseat, Folder.getSelector(appName, mLauncher)));
- }
-
static void dragIconToWorkspace(
LauncherInstrumentation launcher, Launchable launchable, Point dest,
String longPressIndicator) {
@@ -276,15 +270,4 @@
By.clazz("com.android.launcher3.widget.PendingAppWidgetHostView"), timeout);
return widget != null ? new Widget(mLauncher, widget) : null;
}
-
- /**
- * Long-clicks the workspace to open menu.
- */
- public OptionsPopupMenu getMenu() {
- try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
- "want to open menu from workspace")) {
- return new OptionsPopupMenu(mLauncher, mLauncher.waitForLauncherObject(
- "deep_shortcuts_container"));
- }
- }
}
\ No newline at end of file