Only show app icon + limit 2 lines toast on targetSdk S+

- Apps that don't yet target S will not show their app icon nor be limited
to 2 line toasts.
- Remove toast flag. Always show new toast ui and animation.
- Update ToastUITest to check new UI behavior

Test: manually install app targetting sdk < S, check 2+ lines + no app
icon
Test: manually install app targetting sdk S, check max 2 lines + see app
icon
Test: atest ToastUITest
Bug: 188914179

Change-Id: I2f4251ae2eabde5bd43f22e14d8edca200562e65
This commit is contained in:
Beverly
2021-05-24 12:21:27 -04:00
parent 3188bdda53
commit c797217b2b
6 changed files with 167 additions and 79 deletions

View File

@@ -38,8 +38,6 @@
<!-- The new animations to/from lockscreen and AOD! -->
<bool name="flag_lockscreen_animations">false</bool>
<bool name="flag_toast_style">false</bool>
<bool name="flag_pm_lite">false</bool>
<bool name="flag_alarm_tile">false</bool>

View File

@@ -61,10 +61,6 @@ public class FeatureFlags {
return mFlagReader.isEnabled(R.bool.flag_conversations);
}
public boolean isToastStyleEnabled() {
return mFlagReader.isEnabled(R.bool.flag_toast_style);
}
public boolean isMonetEnabled() {
return mFlagReader.isEnabled(R.bool.flag_monet);
}

View File

@@ -22,16 +22,19 @@ import android.annotation.Nullable;
import android.app.Application;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.UserHandle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.ToastPresenter;
import com.android.internal.R;
import com.android.launcher3.icons.IconFactory;
@@ -52,7 +55,6 @@ public class SystemUIToast implements ToastPlugin.Toast {
private final String mPackageName;
private final int mUserId;
private final LayoutInflater mLayoutInflater;
private final boolean mToastStyleEnabled;
final int mDefaultX = 0;
final int mDefaultHorizontalMargin = 0;
@@ -66,15 +68,14 @@ public class SystemUIToast implements ToastPlugin.Toast {
@Nullable private final Animator mOutAnimator;
SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text,
String packageName, int userId, boolean toastStyleEnabled, int orientation) {
String packageName, int userId, int orientation) {
this(layoutInflater, context, text, null, packageName, userId,
toastStyleEnabled, orientation);
orientation);
}
SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text,
ToastPlugin.Toast pluginToast, String packageName, int userId,
boolean toastStyleEnabled, int orientation) {
mToastStyleEnabled = toastStyleEnabled;
int orientation) {
mLayoutInflater = layoutInflater;
mContext = context;
mText = text;
@@ -167,23 +168,45 @@ public class SystemUIToast implements ToastPlugin.Toast {
return mPluginToast.getView();
}
View toastView;
if (mToastStyleEnabled) {
toastView = mLayoutInflater.inflate(
final View toastView = mLayoutInflater.inflate(
com.android.systemui.R.layout.text_toast, null);
((TextView) toastView.findViewById(com.android.systemui.R.id.text)).setText(mText);
final TextView textView = toastView.findViewById(com.android.systemui.R.id.text);
final ImageView iconView = toastView.findViewById(com.android.systemui.R.id.icon);
textView.setText(mText);
Drawable icon = getBadgedIcon(mContext, mPackageName, mUserId);
if (icon == null) {
toastView.findViewById(com.android.systemui.R.id.icon).setVisibility(View.GONE);
} else {
((ImageView) toastView.findViewById(com.android.systemui.R.id.icon))
.setImageDrawable(icon);
}
} else {
toastView = ToastPresenter.getTextToastView(mContext, mText);
ApplicationInfo appInfo = null;
try {
appInfo = mContext.getPackageManager()
.getApplicationInfoAsUser(mPackageName, 0, mUserId);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Package name not found package=" + mPackageName
+ " user=" + mUserId);
}
if (appInfo != null && appInfo.targetSdkVersion < Build.VERSION_CODES.S) {
// no two-line limit
textView.setMaxLines(Integer.MAX_VALUE);
// no app icon
toastView.findViewById(com.android.systemui.R.id.icon).setVisibility(View.GONE);
} else {
Drawable icon = getBadgedIcon(mContext, mPackageName, mUserId);
if (icon == null) {
iconView.setVisibility(View.GONE);
} else {
iconView.setImageDrawable(icon);
if (appInfo.labelRes != 0) {
try {
Resources res = mContext.getPackageManager().getResourcesForApplication(
appInfo,
new Configuration(mContext.getResources().getConfiguration()));
iconView.setContentDescription(res.getString(appInfo.labelRes));
} catch (PackageManager.NameNotFoundException e) {
Log.d(TAG, "Cannot find application resources for icon label.");
}
}
}
}
return toastView;
}
@@ -205,18 +228,14 @@ public class SystemUIToast implements ToastPlugin.Toast {
return mPluginToast.getInAnimation();
}
return mToastStyleEnabled
? ToastDefaultAnimation.Companion.toastIn(getView())
: null;
return ToastDefaultAnimation.Companion.toastIn(getView());
}
private Animator createOutAnimator() {
if (isPluginToast() && mPluginToast.getOutAnimation() != null) {
return mPluginToast.getOutAnimation();
}
return mToastStyleEnabled
? ToastDefaultAnimation.Companion.toastOut(getView())
: null;
return ToastDefaultAnimation.Companion.toastOut(getView());
}
/**
@@ -225,6 +244,10 @@ public class SystemUIToast implements ToastPlugin.Toast {
*/
public static Drawable getBadgedIcon(@NonNull Context context, String packageName,
int userId) {
if (!(context.getApplicationContext() instanceof Application)) {
return null;
}
final ApplicationsState appState =
ApplicationsState.getInstance((Application) context.getApplicationContext());
if (!appState.isUserAdded(userId)) {

View File

@@ -27,7 +27,6 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.ToastPlugin;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.FeatureFlags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -43,17 +42,14 @@ public class ToastFactory implements Dumpable {
// only one ToastPlugin can be connected at a time.
private ToastPlugin mPlugin;
private final LayoutInflater mLayoutInflater;
private final boolean mToastStyleEnabled;
@Inject
public ToastFactory(
LayoutInflater layoutInflater,
PluginManager pluginManager,
DumpManager dumpManager,
FeatureFlags featureFlags) {
DumpManager dumpManager) {
mLayoutInflater = layoutInflater;
dumpManager.registerDumpable("ToastFactory", this);
mToastStyleEnabled = featureFlags.isToastStyleEnabled();
pluginManager.addPluginListener(
new PluginListener<ToastPlugin>() {
@Override
@@ -77,10 +73,10 @@ public class ToastFactory implements Dumpable {
int userId, int orientation) {
if (isPluginAvailable()) {
return new SystemUIToast(mLayoutInflater, context, text, mPlugin.createToast(text,
packageName, userId), packageName, userId, mToastStyleEnabled, orientation);
packageName, userId), packageName, userId, orientation);
}
return new SystemUIToast(mLayoutInflater, context, text, packageName, userId,
mToastStyleEnabled, orientation);
orientation);
}
private boolean isPluginAvailable() {
@@ -91,6 +87,5 @@ public class ToastFactory implements Dumpable {
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("ToastFactory:");
pw.println(" mAttachedPlugin=" + mPlugin);
pw.println(" mToastStyleEnabled=" + mToastStyleEnabled);
}
}

View File

@@ -19,6 +19,7 @@ package com.android.systemui.toast;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -34,7 +35,8 @@ import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;
import android.widget.ToastPresenter;
import com.android.internal.annotations.VisibleForTesting;
import androidx.annotation.VisibleForTesting;
import com.android.systemui.SystemUI;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.CommandQueue;
@@ -60,11 +62,11 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks {
private final AccessibilityManager mAccessibilityManager;
private final ToastFactory mToastFactory;
private final ToastLogger mToastLogger;
private SystemUIToast mToast;
@Nullable private ToastPresenter mPresenter;
@Nullable private ITransientNotificationCallback mCallback;
private ToastOutAnimatorListener mToastOutAnimatorListener;
@VisibleForTesting SystemUIToast mToast;
private int mOrientation = ORIENTATION_PORTRAIT;
@Inject
@@ -191,7 +193,7 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks {
/**
* Once the out animation for a toast is finished, start showing the next toast.
*/
class ToastOutAnimatorListener implements Animator.AnimatorListener {
class ToastOutAnimatorListener extends AnimatorListenerAdapter {
final ToastPresenter mPrevPresenter;
final ITransientNotificationCallback mPrevCallback;
@Nullable Runnable mShowNextToastRunnable;
@@ -209,10 +211,6 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks {
mShowNextToastRunnable = runnable;
}
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
mPrevPresenter.hide(mPrevCallback);
@@ -221,15 +219,5 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks {
}
mToastOutAnimatorListener = null;
}
@Override
public void onAnimationCancel(Animator animation) {
onAnimationEnd(animation);
}
@Override
public void onAnimationRepeat(Animator animation) {
}
}
}

View File

@@ -17,7 +17,6 @@
package com.android.systemui.toast;
import static android.view.accessibility.AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
import static android.widget.ToastPresenter.TEXT_TOAST_LAYOUT;
import static com.google.common.truth.Truth.assertThat;
@@ -31,13 +30,20 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Application;
import android.app.INotificationManager;
import android.app.ITransientNotificationCallback;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -47,12 +53,11 @@ import android.view.accessibility.IAccessibilityManager;
import android.widget.FrameLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToastPresenter;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
import com.android.internal.util.IntPair;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.shared.plugins.PluginManager;
@@ -70,6 +75,7 @@ import org.mockito.stubbing.Answer;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class ToastUITest extends SysuiTestCase {
private static final int ANDROID_UID = 1000;
private static final int SYSTEMUI_UID = 10140;
@@ -85,12 +91,14 @@ public class ToastUITest extends SysuiTestCase {
private static final Binder WINDOW_TOKEN_2 = new Binder();
private static final String TEXT = "Hello World";
private static final int MESSAGE_RES_ID = R.id.message;
private static final int MESSAGE_RES_ID = R.id.text;
private Context mContextSpy;
private ToastUI mToastUI;
@Mock private LayoutInflater mLayoutInflater;
private View mToastView;
@Mock private Application mApplication;
@Mock private CommandQueue mCommandQueue;
@Mock private LayoutInflater mLayoutInflater;
@Mock private WindowManager mWindowManager;
@Mock private INotificationManager mNotificationManager;
@Mock private IAccessibilityManager mAccessibilityManager;
@@ -98,6 +106,7 @@ public class ToastUITest extends SysuiTestCase {
@Mock private DumpManager mDumpManager;
@Mock private ToastLogger mToastLogger;
@Mock private FeatureFlags mFeatureFlags;
@Mock private PackageManager mPackageManager;
@Mock private ITransientNotificationCallback mCallback;
@Captor private ArgumentCaptor<View> mViewCaptor;
@@ -106,29 +115,33 @@ public class ToastUITest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
when(mLayoutInflater.inflate(eq(TEXT_TOAST_LAYOUT), any())).thenReturn(
ToastPresenter.getTextToastView(mContext, TEXT));
when(mFeatureFlags.isToastStyleEnabled()).thenReturn(false);
mToastView = LayoutInflater.from(mContext).inflate(R.layout.text_toast, null);
when(mLayoutInflater.inflate(anyInt(), eq(null))).thenReturn(mToastView);
mContext.addMockSystemService(WindowManager.class, mWindowManager);
mContextSpy = spy(mContext);
when(mContextSpy.getPackageManager()).thenReturn(mPackageManager);
doReturn(mContextSpy).when(mContextSpy).createContextAsUser(any(), anyInt());
doReturn(mContextSpy).when(mContextSpy).createContextAsUser(any(), anyInt());
mToastUI = new ToastUI(mContextSpy, mCommandQueue, mNotificationManager,
mAccessibilityManager, new ToastFactory(mLayoutInflater, mPluginManager,
mDumpManager, mFeatureFlags), mToastLogger);
mToastUI = new ToastUI(
mContextSpy,
mCommandQueue,
mNotificationManager,
mAccessibilityManager,
new ToastFactory(
mLayoutInflater,
mPluginManager,
mDumpManager),
mToastLogger);
}
@Test
public void testStart_addToastUIAsCallbackToCommandQueue() throws Exception {
public void testStart_addToastUIAsCallbackToCommandQueue() {
mToastUI.start();
verify(mCommandQueue).addCallback(mToastUI);
}
@Test
public void testShowToast_addsCorrectViewToWindowManager() throws Exception {
public void testShowToast_addsCorrectViewToWindowManager() {
mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
null);
@@ -138,7 +151,7 @@ public class ToastUITest extends SysuiTestCase {
}
@Test
public void testShowToast_addsViewWithCorrectLayoutParamsToWindowManager() throws Exception {
public void testShowToast_addsViewWithCorrectLayoutParamsToWindowManager() {
mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
null);
@@ -217,9 +230,14 @@ public class ToastUITest extends SysuiTestCase {
public void testHideToast_removesView() throws Exception {
mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
mCallback);
View view = verifyWmAddViewAndAttachToParent();
final SystemUIToast toast = mToastUI.mToast;
View view = verifyWmAddViewAndAttachToParent();
mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1);
if (toast.getOutAnimation() != null) {
assertThat(toast.getOutAnimation().isRunning()).isTrue();
toast.getOutAnimation().cancel(); // if applicable, try to finish anim early
}
verify(mWindowManager).removeViewImmediate(view);
}
@@ -228,51 +246,81 @@ public class ToastUITest extends SysuiTestCase {
public void testHideToast_finishesToken() throws Exception {
mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
mCallback);
final SystemUIToast toast = mToastUI.mToast;
verifyWmAddViewAndAttachToParent();
mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1);
if (toast.getOutAnimation() != null) {
assertThat(toast.getOutAnimation().isRunning()).isTrue();
toast.getOutAnimation().cancel(); // if applicable, try to finish anim early
}
verify(mNotificationManager).finishToken(PACKAGE_NAME_1, TOKEN_1);
}
@Test
public void testHideToast_callsCallback() throws Exception {
public void testHideToast_callsCallback() throws RemoteException {
mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
mCallback);
final SystemUIToast toast = mToastUI.mToast;
verifyWmAddViewAndAttachToParent();
mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1);
if (toast.getOutAnimation() != null) {
assertThat(toast.getOutAnimation().isRunning()).isTrue();
toast.getOutAnimation().cancel();
}
verify(mCallback).onToastHidden();
}
@Test
public void testHideToast_whenNotCurrentToastToken_doesNotHideToast() throws Exception {
public void testHideToast_whenNotCurrentToastToken_doesNotHideToast() throws RemoteException {
mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
mCallback);
final SystemUIToast toast = mToastUI.mToast;
verifyWmAddViewAndAttachToParent();
mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_2);
if (toast.getOutAnimation() != null) {
assertThat(toast.getOutAnimation().isRunning()).isFalse();
}
verify(mCallback, never()).onToastHidden();
}
@Test
public void testHideToast_whenNotCurrentToastPackage_doesNotHideToast() throws Exception {
public void testHideToast_whenNotCurrentToastPackage_doesNotHideToast() throws RemoteException {
mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
mCallback);
final SystemUIToast toast = mToastUI.mToast;
verifyWmAddViewAndAttachToParent();
mToastUI.hideToast(PACKAGE_NAME_2, TOKEN_1);
if (toast.getOutAnimation() != null) {
assertThat(toast.getOutAnimation().isRunning()).isFalse();
}
verify(mCallback, never()).onToastHidden();
}
@Test
public void testShowToast_afterShowToast_hidesCurrentToast() throws Exception {
public void testShowToast_afterShowToast_hidesCurrentToast() throws RemoteException {
mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
mCallback);
View view = verifyWmAddViewAndAttachToParent();
final SystemUIToast toast = mToastUI.mToast;
View view = verifyWmAddViewAndAttachToParent();
mToastUI.showToast(UID_2, PACKAGE_NAME_2, TOKEN_2, TEXT, WINDOW_TOKEN_2, Toast.LENGTH_LONG,
null);
if (toast.getOutAnimation() != null) {
assertThat(toast.getOutAnimation().isRunning()).isTrue();
toast.getOutAnimation().cancel(); // end early if applicable
}
verify(mWindowManager).removeViewImmediate(view);
verify(mNotificationManager).finishToken(PACKAGE_NAME_1, TOKEN_1);
verify(mCallback).onToastHidden();
@@ -286,10 +334,49 @@ public class ToastUITest extends SysuiTestCase {
verify(mToastLogger).logOnShowToast(UID_1, PACKAGE_NAME_1, TEXT, TOKEN_1.toString());
}
@Test
public void testShowToast_targetsPreS_unlimitedLines_noAppIcon()
throws PackageManager.NameNotFoundException {
// GIVEN the application targets R
ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.targetSdkVersion = Build.VERSION_CODES.R;
when(mPackageManager.getApplicationInfoAsUser(PACKAGE_NAME_1, 0,
UserHandle.getUserHandleForUid(UID_1).getIdentifier())).thenReturn(applicationInfo);
// WHEN the package posts a toast
mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
mCallback);
// THEN the view can have unlimited lines
assertThat(((TextView) mToastUI.mToast.getView()
.findViewById(com.android.systemui.R.id.text))
.getMaxLines()).isEqualTo(Integer.MAX_VALUE);
}
@Test
public void testShowToast_targetsS_twoLineLimit_noAppIcon()
throws PackageManager.NameNotFoundException {
// GIVEN the application targets S
ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.targetSdkVersion = Build.VERSION_CODES.S;
when(mPackageManager.getApplicationInfoAsUser(PACKAGE_NAME_1, 0,
UserHandle.getUserHandleForUid(UID_1).getIdentifier())).thenReturn(applicationInfo);
// WHEN the package posts a toast
mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
mCallback);
// THEN the view is limited to 2 lines
assertThat(((TextView) mToastUI.mToast.getView()
.findViewById(com.android.systemui.R.id.text))
.getMaxLines()).isEqualTo(2);
}
@Test
public void testHideToast_logs() {
mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
mCallback);
verifyWmAddViewAndAttachToParent();
mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1);
verify(mToastLogger).logOnHideToast(PACKAGE_NAME_1, TOKEN_1.toString());
}
@@ -298,6 +385,7 @@ public class ToastUITest extends SysuiTestCase {
public void testHideToast_error_noLog() {
// no toast was shown, so this hide is invalid
mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1);
assertThat(mToastUI.mToast).isNull();
verify(mToastLogger, never()).logOnHideToast(PACKAGE_NAME_1, TOKEN_1.toString());
}