diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml index 6393147f8b0be..f4086edf627d3 100644 --- a/packages/SystemUI/res/values/flags.xml +++ b/packages/SystemUI/res/values/flags.xml @@ -38,8 +38,6 @@ false - false - false false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java index ac6d9e8820ad2..c7b6e67cf96de 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java @@ -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); } diff --git a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java index 4284148c38a80..c5e35a4979564 100644 --- a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java +++ b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java @@ -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)) { diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java index 8b782d4b79231..148bffa0000ea 100644 --- a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java +++ b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java @@ -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() { @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); } } diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java index 92ea1d0e5fbdd..42f66875e7a19 100644 --- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java +++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java @@ -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) { - - } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java index 365c62cddbdf6..9b177e1cd0e50 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java @@ -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 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()); }