Merge "Popup container color changes for Dark Theme Bug: 111411630" into ub-launcher3-qt-dev
am: 9beda35dc9
Change-Id: I5db7c0edfae12d4ed3ac2a9d6a333b51f1922032
diff --git a/go/quickstep/res/drawable/default_thumbnail.xml b/go/quickstep/res/drawable/default_thumbnail.xml
new file mode 100644
index 0000000..0a2dbf0
--- /dev/null
+++ b/go/quickstep/res/drawable/default_thumbnail.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@android:color/darker_gray"/>
+ <corners android:radius="2dp"/>
+</shape>
diff --git a/go/quickstep/res/drawable/empty_content_box.xml b/go/quickstep/res/drawable/empty_content_box.xml
new file mode 100644
index 0000000..a488388
--- /dev/null
+++ b/go/quickstep/res/drawable/empty_content_box.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@android:color/transparent"/>
+ <stroke android:color="@android:color/white" android:width="4px"/>
+ <corners android:radius="2dp"/>
+</shape>
\ No newline at end of file
diff --git a/go/quickstep/src/com/android/quickstep/TaskAdapter.java b/go/quickstep/src/com/android/quickstep/TaskAdapter.java
index 674fcae..66c074b 100644
--- a/go/quickstep/src/com/android/quickstep/TaskAdapter.java
+++ b/go/quickstep/src/com/android/quickstep/TaskAdapter.java
@@ -94,7 +94,7 @@
return;
}
Task task = tasks.get(position);
- holder.bindTask(task);
+ holder.bindTask(task, false /* willAnimate */);
mLoader.loadTaskIconAndLabel(task, () -> {
// Ensure holder still has the same task.
if (Objects.equals(task, holder.getTask())) {
@@ -110,6 +110,13 @@
}
@Override
+ public void onBindViewHolder(@NonNull TaskHolder holder, int position,
+ @NonNull List<Object> payloads) {
+ // TODO: Bind task in preparation for animation. For now, we apply UI changes immediately.
+ super.onBindViewHolder(holder, position, payloads);
+ }
+
+ @Override
public void onViewAttachedToWindow(@NonNull TaskHolder holder) {
if (holder.getTask() == null) {
return;
diff --git a/go/quickstep/src/com/android/quickstep/TaskHolder.java b/go/quickstep/src/com/android/quickstep/TaskHolder.java
index 98dc989..a3fa5c1 100644
--- a/go/quickstep/src/com/android/quickstep/TaskHolder.java
+++ b/go/quickstep/src/com/android/quickstep/TaskHolder.java
@@ -15,6 +15,9 @@
*/
package com.android.quickstep;
+import android.graphics.Bitmap;
+
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
@@ -40,13 +43,28 @@
}
/**
- * Bind a task to the holder, resetting the view and preparing it for content to load in.
+ * Bind the task model to the holder. This will take the current task content in the task
+ * object (i.e. icon, thumbnail, label) and either apply the content immediately or simply bind
+ * the content to animate to at a later time. If the task does not have all its content loaded,
+ * the view will prepare appropriate default placeholders and it is the callers responsibility
+ * to change them at a later time.
+ *
+ * Regardless of whether it is animating, input handlers will be bound immediately (see
+ * {@link TaskActionController}).
*
* @param task the task to bind to the view
+ * @param willAnimate true if UI should animate in later, false if it should apply immediately
*/
- public void bindTask(Task task) {
+ public void bindTask(@NonNull Task task, boolean willAnimate) {
mTask = task;
- mTaskItemView.resetTaskItemView();
+ Bitmap thumbnail = (task.thumbnail != null) ? task.thumbnail.thumbnail : null;
+ if (willAnimate) {
+ mTaskItemView.startContentAnimation(task.icon, thumbnail, task.titleDescription);
+ } else {
+ mTaskItemView.setIcon(task.icon);
+ mTaskItemView.setThumbnail(thumbnail);
+ mTaskItemView.setLabel(task.titleDescription);
+ }
}
/**
@@ -55,10 +73,7 @@
*/
public void bindEmptyUi() {
mTask = null;
- // TODO: Set the task view to a loading, empty UI.
- // Temporarily using the one below for visual confirmation but should be swapped out to new
- // UI later.
- mTaskItemView.resetTaskItemView();
+ mTaskItemView.resetToEmptyUi();
}
/**
diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
index c06b6ec..7f77b6c 100644
--- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
+++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
@@ -24,14 +24,12 @@
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.content.Context;
+import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.view.View;
import android.view.ViewDebug;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.Animation;
-import android.view.animation.AnimationSet;
-import android.view.animation.LayoutAnimationController;
+import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
@@ -40,6 +38,7 @@
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver;
+import androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener;
import com.android.launcher3.R;
import com.android.quickstep.RecentsToActivityHelper;
@@ -90,7 +89,6 @@
private final TaskListLoader mTaskLoader;
private final TaskAdapter mTaskAdapter;
private final TaskActionController mTaskActionController;
- private final LayoutAnimationController mLayoutAnimation;
private RecentsToActivityHelper mActivityHelper;
private RecyclerView mTaskRecyclerView;
@@ -98,6 +96,9 @@
private View mContentView;
private View mClearAllView;
private boolean mTransitionedFromApp;
+ private AnimatorSet mLayoutAnimation;
+ private final ArraySet<View> mLayingOutViews = new ArraySet<>();
+
public IconRecentsView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -106,7 +107,6 @@
mTaskAdapter = new TaskAdapter(mTaskLoader);
mTaskActionController = new TaskActionController(mTaskLoader, mTaskAdapter);
mTaskAdapter.setActionController(mTaskActionController);
- mLayoutAnimation = createLayoutAnimation();
}
@Override
@@ -120,7 +120,20 @@
ItemTouchHelper helper = new ItemTouchHelper(
new TaskSwipeCallback(mTaskActionController));
helper.attachToRecyclerView(mTaskRecyclerView);
- mTaskRecyclerView.setLayoutAnimation(mLayoutAnimation);
+ mTaskRecyclerView.addOnChildAttachStateChangeListener(
+ new OnChildAttachStateChangeListener() {
+ @Override
+ public void onChildViewAttachedToWindow(@NonNull View view) {
+ if (mLayoutAnimation != null && !mLayingOutViews.contains(view)) {
+ // Child view was added that is not part of current layout animation
+ // so restart the animation.
+ animateFadeInLayoutAnimation();
+ }
+ }
+
+ @Override
+ public void onChildViewDetachedFromWindow(@NonNull View view) { }
+ });
mEmptyView = findViewById(R.id.recent_task_empty_view);
mContentView = findViewById(R.id.recent_task_content_view);
@@ -165,8 +178,7 @@
* becomes visible.
*/
public void onBeginTransitionToOverview() {
- mTaskRecyclerView.scheduleLayoutAnimation();
-
+ scheduleFadeInLayoutAnimation();
// Load any task changes
if (!mTaskLoader.needsToLoad()) {
return;
@@ -338,17 +350,56 @@
});
}
- private static LayoutAnimationController createLayoutAnimation() {
- AnimationSet anim = new AnimationSet(false /* shareInterpolator */);
+ /**
+ * Schedule a one-shot layout animation on the next layout. Separate from
+ * {@link #scheduleLayoutAnimation()} as the animation is {@link Animator} based and acts on the
+ * view properties themselves, allowing more controllable behavior and making it easier to
+ * manage when the animation conflicts with another animation.
+ */
+ private void scheduleFadeInLayoutAnimation() {
+ ViewTreeObserver viewTreeObserver = mTaskRecyclerView.getViewTreeObserver();
+ viewTreeObserver.addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ animateFadeInLayoutAnimation();
+ viewTreeObserver.removeOnGlobalLayoutListener(this);
+ }
+ });
+ }
- Animation alphaAnim = new AlphaAnimation(0, 1);
- alphaAnim.setDuration(LAYOUT_ITEM_ANIMATE_IN_DURATION);
- anim.addAnimation(alphaAnim);
-
- LayoutAnimationController layoutAnim = new LayoutAnimationController(anim);
- layoutAnim.setDelay(
- (float) LAYOUT_ITEM_ANIMATE_IN_DELAY_BETWEEN / LAYOUT_ITEM_ANIMATE_IN_DURATION);
-
- return layoutAnim;
+ /**
+ * Start animating the layout animation where items fade in.
+ */
+ private void animateFadeInLayoutAnimation() {
+ if (mLayoutAnimation != null) {
+ // If layout animation still in progress, cancel and restart.
+ mLayoutAnimation.cancel();
+ }
+ TaskItemView[] views = getTaskViews();
+ int delay = 0;
+ mLayoutAnimation = new AnimatorSet();
+ for (TaskItemView view : views) {
+ view.setAlpha(0.0f);
+ Animator alphaAnim = ObjectAnimator.ofFloat(view, ALPHA, 0.0f, 1.0f);
+ alphaAnim.setDuration(LAYOUT_ITEM_ANIMATE_IN_DURATION).setStartDelay(delay);
+ alphaAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setAlpha(1.0f);
+ mLayingOutViews.remove(view);
+ }
+ });
+ delay += LAYOUT_ITEM_ANIMATE_IN_DELAY_BETWEEN;
+ mLayoutAnimation.play(alphaAnim);
+ mLayingOutViews.add(view);
+ }
+ mLayoutAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mLayoutAnimation = null;
+ }
+ });
+ mLayoutAnimation.start();
}
}
diff --git a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java
index d831b20..572747b 100644
--- a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java
+++ b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java
@@ -16,15 +16,18 @@
package com.android.quickstep.views;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
+import android.util.FloatProperty;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.R;
@@ -34,16 +37,41 @@
*/
public final class TaskItemView extends LinearLayout {
+ private static final String EMPTY_LABEL = "";
private static final String DEFAULT_LABEL = "...";
private final Drawable mDefaultIcon;
+ private final Drawable mDefaultThumbnail;
+ private final TaskLayerDrawable mIconDrawable;
+ private final TaskLayerDrawable mThumbnailDrawable;
private TextView mLabelView;
private ImageView mIconView;
private ImageView mThumbnailView;
+ private float mContentTransitionProgress;
+
+ /**
+ * Property representing the content transition progress of the view. 1.0f represents that the
+ * currently bound icon, thumbnail, and label are fully animated in and visible.
+ */
+ public static FloatProperty CONTENT_TRANSITION_PROGRESS =
+ new FloatProperty<TaskItemView>("taskContentTransitionProgress") {
+ @Override
+ public void setValue(TaskItemView view, float progress) {
+ view.setContentTransitionProgress(progress);
+ }
+
+ @Override
+ public Float get(TaskItemView view) {
+ return view.mContentTransitionProgress;
+ }
+ };
public TaskItemView(Context context, AttributeSet attrs) {
super(context, attrs);
- mDefaultIcon = context.getResources().getDrawable(
- android.R.drawable.sym_def_app_icon, context.getTheme());
+ Resources res = context.getResources();
+ mDefaultIcon = res.getDrawable(android.R.drawable.sym_def_app_icon, context.getTheme());
+ mDefaultThumbnail = res.getDrawable(R.drawable.default_thumbnail, context.getTheme());
+ mIconDrawable = new TaskLayerDrawable(context);
+ mThumbnailDrawable = new TaskLayerDrawable(context);
}
@Override
@@ -52,15 +80,21 @@
mLabelView = findViewById(R.id.task_label);
mThumbnailView = findViewById(R.id.task_thumbnail);
mIconView = findViewById(R.id.task_icon);
+
+ mThumbnailView.setImageDrawable(mThumbnailDrawable);
+ mIconView.setImageDrawable(mIconDrawable);
+
+ resetToEmptyUi();
+ CONTENT_TRANSITION_PROGRESS.setValue(this, 1.0f);
}
/**
- * Resets task item view to default values.
+ * Resets task item view to empty, loading UI.
*/
- public void resetTaskItemView() {
- setLabel(DEFAULT_LABEL);
- setIcon(null);
- setThumbnail(null);
+ public void resetToEmptyUi() {
+ mIconDrawable.resetDrawable();
+ mThumbnailDrawable.resetDrawable();
+ setLabel(EMPTY_LABEL);
}
/**
@@ -69,11 +103,8 @@
* @param label task label
*/
public void setLabel(@Nullable String label) {
- if (label == null) {
- mLabelView.setText(DEFAULT_LABEL);
- return;
- }
- mLabelView.setText(label);
+ mLabelView.setText(getSafeLabel(label));
+ // TODO: Animation for label
}
/**
@@ -86,11 +117,7 @@
// The icon proper is actually smaller than the drawable and has "padding" on the side for
// the purpose of drawing the shadow, allowing the icon to pop up, so we need to scale the
// view if we want the icon to be flush with the bottom of the thumbnail.
- if (icon == null) {
- mIconView.setImageDrawable(mDefaultIcon);
- return;
- }
- mIconView.setImageDrawable(icon);
+ mIconDrawable.setCurrentDrawable(getSafeIcon(icon));
}
/**
@@ -99,16 +126,48 @@
* @param thumbnail task thumbnail for the task
*/
public void setThumbnail(@Nullable Bitmap thumbnail) {
- if (thumbnail == null) {
- mThumbnailView.setImageBitmap(null);
- mThumbnailView.setBackgroundColor(Color.GRAY);
- return;
- }
- mThumbnailView.setBackgroundColor(Color.TRANSPARENT);
- mThumbnailView.setImageBitmap(thumbnail);
+ mThumbnailDrawable.setCurrentDrawable(getSafeThumbnail(thumbnail));
}
public View getThumbnailView() {
return mThumbnailView;
}
+
+ /**
+ * Start a new animation from the current task content to the specified new content. The caller
+ * is responsible for the actual animation control via the property
+ * {@link #CONTENT_TRANSITION_PROGRESS}.
+ *
+ * @param endIcon the icon to animate to
+ * @param endThumbnail the thumbnail to animate to
+ * @param endLabel the label to animate to
+ */
+ public void startContentAnimation(@Nullable Drawable endIcon, @Nullable Bitmap endThumbnail,
+ @Nullable String endLabel) {
+ mIconDrawable.startNewTransition(getSafeIcon(endIcon));
+ mThumbnailDrawable.startNewTransition(getSafeThumbnail(endThumbnail));
+ // TODO: Animation for label
+
+ setContentTransitionProgress(0.0f);
+ }
+
+ private void setContentTransitionProgress(float progress) {
+ mContentTransitionProgress = progress;
+ mIconDrawable.setTransitionProgress(progress);
+ mThumbnailDrawable.setTransitionProgress(progress);
+ // TODO: Animation for label
+ }
+
+ private @NonNull Drawable getSafeIcon(@Nullable Drawable icon) {
+ return (icon != null) ? icon : mDefaultIcon;
+ }
+
+ private @NonNull Drawable getSafeThumbnail(@Nullable Bitmap thumbnail) {
+ return (thumbnail != null) ? new BitmapDrawable(getResources(), thumbnail)
+ : mDefaultThumbnail;
+ }
+
+ private @NonNull String getSafeLabel(@Nullable String label) {
+ return (label != null) ? label : DEFAULT_LABEL;
+ }
}
diff --git a/go/quickstep/src/com/android/quickstep/views/TaskLayerDrawable.java b/go/quickstep/src/com/android/quickstep/views/TaskLayerDrawable.java
new file mode 100644
index 0000000..98b66b9
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/views/TaskLayerDrawable.java
@@ -0,0 +1,101 @@
+/*
+ * 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.quickstep.views;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.R;
+
+/**
+ * A layer drawable for task content that transitions between two drawables by crossfading. Similar
+ * to {@link android.graphics.drawable.TransitionDrawable} but allows callers to control transition
+ * progress and provides a default, empty drawable.
+ */
+public final class TaskLayerDrawable extends LayerDrawable {
+ private final Drawable mEmptyDrawable;
+ private float mProgress;
+
+ public TaskLayerDrawable(Context context) {
+ super(new Drawable[0]);
+
+ // Use empty drawable for both layers initially.
+ mEmptyDrawable = context.getResources().getDrawable(
+ R.drawable.empty_content_box, context.getTheme());
+ addLayer(mEmptyDrawable);
+ addLayer(mEmptyDrawable);
+ setTransitionProgress(1.0f);
+ }
+
+ /**
+ * Immediately set the front-most drawable layer.
+ *
+ * @param drawable drawable to set
+ */
+ public void setCurrentDrawable(@NonNull Drawable drawable) {
+ setDrawable(0, drawable);
+ applyTransitionProgress(mProgress);
+ }
+
+ /**
+ * Immediately reset the drawable to showing the empty drawable.
+ */
+ public void resetDrawable() {
+ setCurrentDrawable(mEmptyDrawable);
+ }
+
+ /**
+ * Prepare to start animating the transition by pushing the current drawable to the back and
+ * setting a new drawable to the front layer and making it invisible.
+ *
+ * @param endDrawable drawable to animate to
+ */
+ public void startNewTransition(@NonNull Drawable endDrawable) {
+ Drawable oldDrawable = getDrawable(0);
+ setDrawable(1, oldDrawable);
+ setDrawable(0, endDrawable);
+ setTransitionProgress(0.0f);
+ }
+
+ /**
+ * Set the progress of the transition animation to crossfade the two drawables.
+ *
+ * @param progress current transition progress between 0 (front view invisible) and 1
+ * (front view visible)
+ */
+ public void setTransitionProgress(float progress) {
+ if (progress > 1 || progress < 0) {
+ throw new IllegalArgumentException("Transition progress should be between 0 and 1");
+ }
+ mProgress = progress;
+ applyTransitionProgress(progress);
+ }
+
+ private void applyTransitionProgress(float progress) {
+ int drawableAlpha = (int) (progress * 255);
+ getDrawable(0).setAlpha(drawableAlpha);
+ if (getDrawable(0) != getDrawable(1)) {
+ // Only do this if it's a different drawable so that it fades out.
+ // Otherwise, we'd just be overwriting the front drawable's alpha.
+ getDrawable(1).setAlpha(255 - drawableAlpha);
+ }
+ invalidateSelf();
+ }
+}
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index 089d672..3686493 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -18,39 +18,39 @@
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.launcher3.tests">
- <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
+ <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/>
<application android:debuggable="true">
- <uses-library android:name="android.test.runner" />
+ <uses-library android:name="android.test.runner"/>
<receiver
android:name="com.android.launcher3.testcomponent.AppWidgetNoConfig"
android:label="No Config">
<intent-filter>
- <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data android:name="android.appwidget.provider"
- android:resource="@xml/appwidget_no_config" />
+ android:resource="@xml/appwidget_no_config"/>
</receiver>
<receiver
android:name="com.android.launcher3.testcomponent.AppWdigetHidden"
android:label="Hidden widget">
<intent-filter>
- <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data android:name="android.appwidget.provider"
- android:resource="@xml/appwidget_hidden" />
+ android:resource="@xml/appwidget_hidden"/>
</receiver>
<receiver
android:name="com.android.launcher3.testcomponent.AppWidgetWithConfig"
android:label="With Config">
<intent-filter>
- <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data android:name="android.appwidget.provider"
- android:resource="@xml/appwidget_with_config" />
+ android:resource="@xml/appwidget_with_config"/>
</receiver>
<activity
@@ -61,8 +61,8 @@
</activity>
<activity
android:name="com.android.launcher3.testcomponent.RequestPinItemActivity"
- android:label="Test Pin Item"
- android:icon="@drawable/test_drawable_pin_item">
+ android:icon="@drawable/test_drawable_pin_item"
+ android:label="Test Pin Item">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
@@ -73,28 +73,28 @@
<provider
android:name="com.android.launcher3.testcomponent.TestCommandReceiver"
android:authorities="${packageName}.commands"
- android:exported="true" />
+ android:exported="true"/>
<activity
android:name="com.android.launcher3.testcomponent.TestLauncherActivity"
- android:launchMode="singleTask"
android:clearTaskOnLaunch="true"
- android:label="Test launcher"
- android:stateNotNeeded="true"
- android:theme="@android:style/Theme.DeviceDefault.Light"
- android:windowSoftInputMode="adjustPan"
- android:screenOrientation="unspecified"
android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
- android:resizeableActivity="true"
- android:taskAffinity=""
+ android:enabled="false"
+ android:label="Test launcher"
+ android:launchMode="singleTask"
android:process=":testLauncherProcess"
- android:enabled="false">
+ android:resizeableActivity="true"
+ android:screenOrientation="unspecified"
+ android:stateNotNeeded="true"
+ android:taskAffinity=""
+ android:theme="@android:style/Theme.DeviceDefault.Light"
+ android:windowSoftInputMode="adjustPan">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.HOME" />
- <category android:name="android.intent.category.DEFAULT" />
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.HOME"/>
+ <category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.MONKEY"/>
- <category android:name="android.intent.category.LAUNCHER_APP" />
+ <category android:name="android.intent.category.LAUNCHER_APP"/>
</intent-filter>
</activity>
<activity
@@ -104,6 +104,12 @@
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
+ <intent-filter>
+ <action android:name="com.android.launcher3.intent.action.test_shortcut"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ <meta-data android:name="android.app.shortcuts"
+ android:resource="@xml/shortcuts"/>
</activity>
</application>
</manifest>
diff --git a/tests/res/values/strings.xml b/tests/res/values/strings.xml
new file mode 100644
index 0000000..0ad87fb
--- /dev/null
+++ b/tests/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="shortcut1" translatable="false">Shortcut 1</string>
+ <string name="shortcut2" translatable="false">Shortcut 2</string>
+ <string name="shortcut3" translatable="false">Shortcut 3</string>
+</resources>
diff --git a/tests/res/xml/shortcuts.xml b/tests/res/xml/shortcuts.xml
new file mode 100644
index 0000000..bdc22f9
--- /dev/null
+++ b/tests/res/xml/shortcuts.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+ <shortcut
+ android:shortcutId="shortcut1"
+ android:shortcutShortLabel="@string/shortcut1">
+ <intent android:action="com.android.launcher3.intent.action.test_shortcut"/>
+ </shortcut>
+ <shortcut
+ android:shortcutId="shortcut2"
+ android:shortcutShortLabel="@string/shortcut2">
+ <intent android:action="com.android.launcher3.intent.action.test_shortcut"/>
+ </shortcut>
+ <shortcut
+ android:shortcutId="shortcut3"
+ android:shortcutShortLabel="@string/shortcut3">
+ <intent android:action="com.android.launcher3.intent.action.test_shortcut"/>
+ </shortcut>
+</shortcuts>
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index e11e62e..dfb0edf 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -25,7 +25,6 @@
import static org.junit.Assert.assertTrue;
import android.content.Intent;
-import android.content.pm.LauncherActivityInfo;
import android.util.Log;
import androidx.test.filters.LargeTest;
@@ -62,6 +61,7 @@
@RunWith(AndroidJUnit4.class)
public class TaplTestsLauncher3 extends AbstractLauncherUiTest {
private static final String TAG = "TaplTestsAosp";
+ private static final String APP_NAME = "LauncherTestApp";
private static int sScreenshotCount = 0;
@@ -307,12 +307,11 @@
@PortraitLandscape
public void testLaunchMenuItem() throws Exception {
if (!TestHelpers.isInLauncherProcess()) return;
- final LauncherActivityInfo testApp = getSettingsApp();
final AppIconMenu menu = mLauncher.
getWorkspace().
switchToAllApps().
- getAppIcon(testApp.getLabel().toString()).
+ getAppIcon(APP_NAME).
openMenu();
executeOnLauncher(
@@ -322,7 +321,7 @@
final AppIconMenuItem menuItem = menu.getMenuItem(1);
final String itemName = menuItem.getText();
- menuItem.launch(testApp.getComponentName().getPackageName(), itemName);
+ menuItem.launch(getAppPackageName());
}
@Test
@@ -330,16 +329,15 @@
public void testDragAppIcon() throws Throwable {
try {
TestProtocol.sDebugTracing = true;
- final String appName = "LauncherTestApp";
// 1. Open all apps and wait for load complete.
// 2. Drag icon to homescreen.
// 3. Verify that the icon works on homescreen.
mLauncher.getWorkspace().
switchToAllApps().
- getAppIcon(appName).
+ getAppIcon(APP_NAME).
dragToWorkspace().
- getWorkspaceAppIcon(appName).
- launch(getInstrumentation().getContext().getPackageName());
+ getWorkspaceAppIcon(APP_NAME).
+ launch(getAppPackageName());
} finally {
TestProtocol.sDebugTracing = false;
}
@@ -349,7 +347,6 @@
@PortraitLandscape
public void testDragShortcut() throws Throwable {
if (!TestHelpers.isInLauncherProcess()) return;
- LauncherActivityInfo testApp = getSettingsApp();
// 1. Open all apps and wait for load complete.
// 2. Find the app and long press it to show shortcuts.
@@ -357,7 +354,7 @@
final AppIconMenuItem menuItem = mLauncher.
getWorkspace().
switchToAllApps().
- getAppIcon(testApp.getLabel().toString()).
+ getAppIcon(APP_NAME).
openMenu().
getMenuItem(0);
final String shortcutName = menuItem.getText();
@@ -368,6 +365,10 @@
menuItem.
dragToWorkspace().
getWorkspaceAppIcon(shortcutName).
- launch(testApp.getComponentName().getPackageName(), shortcutName);
+ launch(getAppPackageName());
+ }
+
+ private static String getAppPackageName() {
+ return getInstrumentation().getContext().getPackageName();
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index fbeb3a2..44fc3f7 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -50,4 +50,9 @@
downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, iconCenter);
return new AppIconMenu(mLauncher, deepShortcutsContainer);
}
+
+ @Override
+ protected String getLongPressIndicator() {
+ return "deep_shortcuts_container";
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java b/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
index c39f8d1..ba9c10e 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
@@ -32,4 +32,9 @@
public String getText() {
return mObject.getText();
}
+
+ @Override
+ protected String getLongPressIndicator() {
+ return "drop_target_bar";
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index 16ddba8..1b372ec 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -27,7 +27,7 @@
/**
* Ancestor for AppIcon and AppMenuItem.
*/
-class Launchable {
+abstract class Launchable {
protected final LauncherInstrumentation mLauncher;
protected final UiObject2 mObject;
@@ -45,24 +45,17 @@
* Clicks the object to launch its app.
*/
public Background launch(String expectedPackageName) {
- return launch(expectedPackageName, By.pkg(expectedPackageName).depth(0));
+ return launch(By.pkg(expectedPackageName));
}
- /**
- * Clicks the object to launch its app.
- */
- public Background launch(String expectedPackageName, String expectedAppText) {
- return launch(expectedPackageName, By.pkg(expectedPackageName).text(expectedAppText));
- }
-
- private Background launch(String errorMessage, BySelector selector) {
+ private Background launch(BySelector selector) {
LauncherInstrumentation.log("Launchable.launch before click " +
mObject.getVisibleCenter());
mLauncher.assertTrue(
"Launching an app didn't open a new window: " + mObject.getText(),
mObject.clickAndWait(Until.newWindow(), LauncherInstrumentation.WAIT_TIME_MS));
mLauncher.assertTrue(
- "App didn't start: " + errorMessage,
+ "App didn't start: " + selector,
mLauncher.getDevice().wait(Until.hasObject(selector),
LauncherInstrumentation.WAIT_TIME_MS));
return new Background(mLauncher);
@@ -76,10 +69,13 @@
Workspace.dragIconToWorkspace(
mLauncher,
this,
- new Point(device.getDisplayWidth() / 2, device.getDisplayHeight() / 2));
+ new Point(device.getDisplayWidth() / 2, device.getDisplayHeight() / 2),
+ getLongPressIndicator());
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"dragged launchable to workspace")) {
return new Workspace(mLauncher);
}
}
+
+ protected abstract String getLongPressIndicator();
}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 3a45e93..08f2681 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -201,13 +201,19 @@
Closable addContextLayer(String piece) {
mDiagnosticContext.addLast(piece);
- return () -> mDiagnosticContext.removeLast();
+ log("Added context: " + getContextDescription());
+ return () -> {
+ log("Removing context: " + getContextDescription());
+ mDiagnosticContext.removeLast();
+ };
}
private void fail(String message) {
- final String ctxt = mDiagnosticContext.isEmpty() ? "" : String.join(", ",
- mDiagnosticContext) + "; ";
- Assert.fail("http://go/tapl : " + ctxt + message);
+ Assert.fail("http://go/tapl : " + getContextDescription() + message);
+ }
+
+ private String getContextDescription() {
+ return mDiagnosticContext.isEmpty() ? "" : String.join(", ", mDiagnosticContext) + "; ";
}
void assertTrue(String message, boolean condition) {
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 943332e..46562ce 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -118,7 +118,7 @@
mLauncher,
getHotseatAppIcon("Chrome"),
new Point(mLauncher.getDevice().getDisplayWidth(),
- workspace.getVisibleBounds().centerY()));
+ workspace.getVisibleBounds().centerY()), "deep_shortcuts_container");
verifyActiveContainer();
}
assertTrue("Home screen workspace didn't become scrollable",
@@ -136,12 +136,13 @@
}
static void dragIconToWorkspace(
- LauncherInstrumentation launcher, Launchable launchable, Point dest) {
+ LauncherInstrumentation launcher, Launchable launchable, Point dest,
+ String longPressIndicator) {
LauncherInstrumentation.log("dragIconToWorkspace: begin");
final Point launchableCenter = launchable.getObject().getVisibleCenter();
final long downTime = SystemClock.uptimeMillis();
launcher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, launchableCenter);
- launcher.waitForLauncherObject("deep_shortcuts_container");
+ launcher.waitForLauncherObject(longPressIndicator);
launcher.movePointer(downTime, DRAG_DURACTION, launchableCenter, dest);
launcher.sendPointer(
downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest);