diff options
| author | 2020-09-29 09:44:09 -0400 | |
|---|---|---|
| committer | 2020-09-30 20:48:29 -0400 | |
| commit | b7ef5de9e06f2efcb57612ab099028b427a5604e (patch) | |
| tree | 22954a7f972f87688fe664e71df908e8955d9d22 | |
| parent | 8862617d5f80b50185d1a8092a70309a8fc52ee4 (diff) | |
Add Plugin interface for Toasts
Allows Toasts that route through SystemUI to be implemented by a
ToastPlugin. This CL also adds the ability for plugins to create a
custom animation for when the toast shows and hides.
Also adds logging for Toasts that get routed through SystemUI. By
default, these logs aren't logged to logcat but can be enabled via adb (see
LogBuffer.kt).
To dump ToastLog:
adb shell dumpsys activity service com.android.systemui/.SystemUIService ToastLog
Bug: 169587378
Test: manually add CustomToastPlugin
Test: atest ToastUITest
Change-Id: I0a0b16fdc2a5ba1908054197f6dc6728f10a0d2e
11 files changed, 566 insertions, 34 deletions
diff --git a/core/java/android/widget/ToastPresenter.java b/core/java/android/widget/ToastPresenter.java index fb5d55dd2141..b484dfacbf6c 100644 --- a/core/java/android/widget/ToastPresenter.java +++ b/core/java/android/widget/ToastPresenter.java @@ -48,6 +48,8 @@ import com.android.internal.util.ArrayUtils; public class ToastPresenter { private static final String TAG = "ToastPresenter"; private static final String WINDOW_TITLE = "Toast"; + + // exclusively used to guarantee window timeouts private static final long SHORT_DURATION_TIMEOUT = 4000; private static final long LONG_DURATION_TIMEOUT = 7000; @@ -145,7 +147,7 @@ public class ToastPresenter { */ private void adjustLayoutParams(WindowManager.LayoutParams params, IBinder windowToken, int duration, int gravity, int xOffset, int yOffset, float horizontalMargin, - float verticalMargin) { + float verticalMargin, boolean removeWindowAnimations) { Configuration config = mResources.getConfiguration(); int absGravity = Gravity.getAbsoluteGravity(gravity, config.getLayoutDirection()); params.gravity = absGravity; @@ -163,6 +165,10 @@ public class ToastPresenter { params.hideTimeoutMilliseconds = (duration == Toast.LENGTH_LONG) ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT; params.token = windowToken; + + if (removeWindowAnimations && params.windowAnimations == R.style.Animation_Toast) { + params.windowAnimations = 0; + } } /** @@ -193,16 +199,28 @@ public class ToastPresenter { /** * Shows the toast in {@code view} with the parameters passed and callback {@code callback}. + * Uses window animations to animate the toast. */ public void show(View view, IBinder token, IBinder windowToken, int duration, int gravity, int xOffset, int yOffset, float horizontalMargin, float verticalMargin, @Nullable ITransientNotificationCallback callback) { + show(view, token, windowToken, duration, gravity, xOffset, yOffset, horizontalMargin, + verticalMargin, callback, false /* removeWindowAnimations */); + } + + /** + * Shows the toast in {@code view} with the parameters passed and callback {@code callback}. + * Can optionally remove window animations from the toast window. + */ + public void show(View view, IBinder token, IBinder windowToken, int duration, int gravity, + int xOffset, int yOffset, float horizontalMargin, float verticalMargin, + @Nullable ITransientNotificationCallback callback, boolean removeWindowAnimations) { checkState(mView == null, "Only one toast at a time is allowed, call hide() first."); mView = view; mToken = token; adjustLayoutParams(mParams, windowToken, duration, gravity, xOffset, yOffset, - horizontalMargin, verticalMargin); + horizontalMargin, verticalMargin, removeWindowAnimations); if (mView.getParent() != null) { mWindowManager.removeView(mView); } @@ -247,7 +265,8 @@ public class ToastPresenter { try { callback.onToastHidden(); } catch (RemoteException e) { - Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastHide()", e); + Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastHide()", + e); } } mView = null; diff --git a/packages/SystemUI/docs/plugin_hooks.md b/packages/SystemUI/docs/plugin_hooks.md index 9fe2e181971a..1b1bb37ca0b1 100644 --- a/packages/SystemUI/docs/plugin_hooks.md +++ b/packages/SystemUI/docs/plugin_hooks.md @@ -1,33 +1,34 @@ # Plugin hooks ### Action: com.android.systemui.action.PLUGIN_OVERLAY -Expected interface: [OverlayPlugin](/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java) +Expected interface: [OverlayPlugin](/frameworks/base/packages/SystemUI/plugin/src/com/android +/systemui/plugins/OverlayPlugin.java) Use: Allows plugin access to the status bar and nav bar window for whatever nefarious purposes you can imagine. ### Action: com.android.systemui.action.PLUGIN_QS -Expected interface: [QS](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java) +Expected interface: [QS](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java) Use: Allows the entire QS panel to be replaced with something else that is optionally expandable. Notes: To not mess up the notification panel interaction, much of the QSContainer interface needs to actually be implemented. ### Action: com.android.systemui.action.PLUGIN_QS_FACTORY -Expected interface: [QSFactory](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSFactory.java) +Expected interface: [QSFactory](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSFactory.java) Use: Controls the creation of QS Tiles and their views, can used to add or change system QS tiles, can also be used to change the layout/interaction of the tile views. ### Action: com.android.systemui.action.PLUGIN_NAV_BUTTON -Expected interface: [NavBarButtonProvider](/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java) +Expected interface: [NavBarButtonProvider](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java) Use: Allows a plugin to create a new nav bar button, or override an existing one with a view of its own. ### Action: com.android.systemui.action.PLUGIN_NAV_GESTURE -Expected interface: [NavGesture](/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java) +Expected interface: [NavGesture](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java) Use: Allows touch events from the nav bar to be intercepted and used for other gestures. ### Action: com.android.systemui.action.PLUGIN_LOCKSCREEN_RIGHT_BUTTON -Expected interface: [IntentButtonProvider](/packages/SystemUI/plugin/src/com/android/systemui/plugins/IntentButtonProvider.java) +Expected interface: [IntentButtonProvider](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/IntentButtonProvider.java) Use: Allows a plugin to specify the icon for the bottom right lock screen button, and the intent that gets launched when it is activated. @@ -37,28 +38,34 @@ Expected interface: [IntentButtonProvider](/packages/SystemUI/plugin/src/com/and Use: Allows a plugin to specify the icon for the bottom left lock screen button, and the intent that gets launched when it is activated. ### Action: com.android.systemui.action.PLUGIN_GLOBAL_ACTIONS -Expected interface: [GlobalActions](/packages/SystemUI/plugin/src/com/android/systemui/plugins/GlobalActions.java) +Expected interface: [GlobalActions](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/GlobalActions.java) Use: Allows the long-press power menu to be completely replaced. ### Action: com.android.systemui.action.PLUGIN_VOLUME -Expected interface: [VolumeDialog](/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialog.java) +Expected interface: [VolumeDialog](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialog.java) Use: Allows replacement of the volume dialog. ### Action: com.android.systemui.action.PLUGIN_NOTIFICATION_SWIPE_ACTION -Expected interface: [NotificationSwipeActionHelper](/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java) +Expected interface: [NotificationSwipeActionHelper](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java) Use: Control over swipes/input for notification views, can be used to control what happens when you swipe/long-press ### Action: com.android.systemui.action.PLUGIN_CLOCK -Expected interface: [ClockPlugin](/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java) +Expected interface: [ClockPlugin](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java) Use: Allows replacement of the keyguard main clock. +### Action: com.android.systemui.action.PLUGIN_TOAST +Expected interface: [ToastPlugin](/frameworks/base/packages/SystemUI/plugin/src/com/android +/systemui/plugins/ClockPlugin.java) + +Use: Allows replacement of uncustomized toasts created via Toast.makeText(). + # Global plugin dependencies These classes can be accessed by any plugin using PluginDependency as long as they @Requires them. -[VolumeDialogController](/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java) - Mostly just API for the volume plugin +[VolumeDialogController](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java) - Mostly just API for the volume plugin -[ActivityStarter](/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java) - Allows starting of intents while co-operating with keyguard unlocks. +[ActivityStarter](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java) - Allows starting of intents while co-operating with keyguard unlocks. diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java new file mode 100644 index 000000000000..0831e0ef7795 --- /dev/null +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java @@ -0,0 +1,107 @@ +/* + * 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.systemui.plugins; + +import android.animation.Animator; +import android.annotation.NonNull; +import android.view.View; + +import com.android.systemui.plugins.annotations.ProvidesInterface; + +/** + * Customize toasts displayed by SystemUI (via Toast#makeText) + */ +@ProvidesInterface(action = ToastPlugin.ACTION, version = ToastPlugin.VERSION) +public interface ToastPlugin extends Plugin { + + String ACTION = "com.android.systemui.action.PLUGIN_TOAST"; + int VERSION = 1; + + /** + * Creates a CustomPluginToast. + */ + @NonNull Toast createToast(CharSequence text, String packageName, int userId); + + /** + * Custom Toast with the ability to change toast positioning, styling and animations. + */ + interface Toast { + /** + * Retrieve the Toast view's gravity. + * If no changes, returns null. + */ + default Integer getGravity() { + return null; + } + + /** + * Retrieve the Toast view's X-offset. + * If no changes, returns null. + */ + default Integer getXOffset() { + return null; + } + + /** + * Retrieve the Toast view's Y-offset. + * If no changes, returns null. + */ + default Integer getYOffset() { + return null; + } + + /** + * Retrieve the Toast view's horizontal margin. + * If no changes, returns null. + */ + default Integer getHorizontalMargin() { + return null; + } + + /** + * Retrieve the Toast view's vertical margin. + * If no changes, returns null. + */ + default Integer getVerticalMargin() { + return null; + } + + /** + * Retrieve the Toast view to show. + * If no changes, returns null. + */ + default View getView() { + return null; + } + + /** + * Retrieve the Toast's animate in. + * If no changes, returns null. + */ + default Animator getInAnimation() { + return null; + } + + /** + * Retrieve the Toast's animate out. + * If no changes, returns null. + */ + default Animator getOutAnimation() { + return null; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 6db408659c96..e3ee2a10821b 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -108,6 +108,18 @@ public class LogModule { return buffer; } + /** Provides a logging buffer for all logs related to Toasts shown by SystemUI. */ + @Provides + @SysUISingleton + @ToastLog + public static LogBuffer provideToastLogBuffer( + LogcatEchoTracker bufferFilter, + DumpManager dumpManager) { + LogBuffer buffer = new LogBuffer("ToastLog", 50, 10, bufferFilter); + buffer.attach(dumpManager); + return buffer; + } + /** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */ @Provides @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java new file mode 100644 index 000000000000..8671dbfdf1fe --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java @@ -0,0 +1,33 @@ +/* + * 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.systemui.log.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.android.systemui.log.LogBuffer; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +/** A {@link LogBuffer} for ToastLog-related messages. */ +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface ToastLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java new file mode 100644 index 000000000000..e9fcf1aa9598 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java @@ -0,0 +1,139 @@ +/* + * 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.systemui.toast; + +import android.animation.Animator; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.view.View; +import android.widget.ToastPresenter; + +import com.android.internal.R; +import com.android.systemui.plugins.ToastPlugin; + +/** + * SystemUI TextToast that can be customized by ToastPlugins. Should never instantiate this class + * directly. Instead, use {@link ToastFactory#createToast}. + */ +public class SystemUIToast implements ToastPlugin.Toast { + final Context mContext; + final CharSequence mText; + final ToastPlugin.Toast mPluginToast; + + final int mDefaultGravity; + final int mDefaultY; + final int mDefaultX = 0; + final int mDefaultHorizontalMargin = 0; + final int mDefaultVerticalMargin = 0; + + SystemUIToast(Context context, CharSequence text) { + this(context, text, null); + } + + SystemUIToast(Context context, CharSequence text, ToastPlugin.Toast pluginToast) { + mContext = context; + mText = text; + mPluginToast = pluginToast; + + mDefaultGravity = context.getResources().getInteger(R.integer.config_toastDefaultGravity); + mDefaultY = context.getResources().getDimensionPixelSize(R.dimen.toast_y_offset); + } + + @Override + @NonNull + public Integer getGravity() { + if (isPluginToast() && mPluginToast.getGravity() != null) { + return mPluginToast.getGravity(); + } + return mDefaultGravity; + } + + @Override + @NonNull + public Integer getXOffset() { + if (isPluginToast() && mPluginToast.getXOffset() != null) { + return mPluginToast.getXOffset(); + } + return mDefaultX; + } + + @Override + @NonNull + public Integer getYOffset() { + if (isPluginToast() && mPluginToast.getYOffset() != null) { + return mPluginToast.getYOffset(); + } + return mDefaultY; + } + + @Override + @NonNull + public Integer getHorizontalMargin() { + if (isPluginToast() && mPluginToast.getHorizontalMargin() != null) { + return mPluginToast.getHorizontalMargin(); + } + return mDefaultHorizontalMargin; + } + + @Override + @NonNull + public Integer getVerticalMargin() { + if (isPluginToast() && mPluginToast.getVerticalMargin() != null) { + return mPluginToast.getVerticalMargin(); + } + return mDefaultVerticalMargin; + } + + @Override + @NonNull + public View getView() { + if (isPluginToast() && mPluginToast.getView() != null) { + return mPluginToast.getView(); + } + return ToastPresenter.getTextToastView(mContext, mText); + } + + @Override + @Nullable + public Animator getInAnimation() { + if (isPluginToast() && mPluginToast.getInAnimation() != null) { + return mPluginToast.getInAnimation(); + } + return null; + } + + @Override + @Nullable + public Animator getOutAnimation() { + if (isPluginToast() && mPluginToast.getOutAnimation() != null) { + return mPluginToast.getOutAnimation(); + } + return null; + } + + /** + * Whether this toast has a custom animation. + */ + public boolean hasCustomAnimation() { + return getInAnimation() != null || getOutAnimation() != null; + } + + private boolean isPluginToast() { + return mPluginToast != null; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java new file mode 100644 index 000000000000..d8cb61c6b349 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java @@ -0,0 +1,83 @@ +/* + * 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.systemui.toast; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import com.android.systemui.Dumpable; +import com.android.systemui.dagger.SysUISingleton; +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 java.io.FileDescriptor; +import java.io.PrintWriter; + +import javax.inject.Inject; + +/** + * Factory for creating toasts to be shown by ToastUI. + * These toasts can be customized by {@link ToastPlugin}. + */ +@SysUISingleton +public class ToastFactory implements Dumpable { + // only one ToastPlugin can be connected at a time. + private ToastPlugin mPlugin; + + @Inject + public ToastFactory(PluginManager pluginManager, DumpManager dumpManager) { + dumpManager.registerDumpable("ToastFactory", this); + pluginManager.addPluginListener( + new PluginListener<ToastPlugin>() { + @Override + public void onPluginConnected(ToastPlugin plugin, Context pluginContext) { + mPlugin = plugin; + } + + @Override + public void onPluginDisconnected(ToastPlugin plugin) { + if (plugin.equals(mPlugin)) { + mPlugin = null; + } + } + }, ToastPlugin.class, false /* Allow multiple plugins */); + } + + /** + * Create a toast to be shown by ToastUI. + */ + public SystemUIToast createToast(Context context, CharSequence text, String packageName, + int userId) { + if (isPluginAvailable()) { + return new SystemUIToast(context, text, mPlugin.createToast(text, packageName, userId)); + } + return new SystemUIToast(context, text); + } + + private boolean isPluginAvailable() { + return mPlugin != null; + } + + @Override + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + pw.println("ToastFactory:"); + pw.println(" mAttachedPlugin=" + mPlugin); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt new file mode 100644 index 000000000000..78173cf62a93 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt @@ -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.systemui.toast + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.LogMessage +import com.android.systemui.log.dagger.ToastLog +import javax.inject.Inject + +private const val TAG = "ToastLog" + +class ToastLogger @Inject constructor( + @ToastLog private val buffer: LogBuffer +) { + + fun logOnShowToast(uid: Int, packageName: String, text: String, token: String) { + log(DEBUG, { + int1 = uid + str1 = packageName + str2 = text + str3 = token + }, { + "[$str3] Show toast for ($str1, $int1). msg=\'$str2\'" + }) + } + + fun logOnHideToast(packageName: String, token: String) { + log(DEBUG, { + str1 = packageName + str2 = token + }, { + "[$str2] Hide toast for [$str1]" + }) + } + + private inline fun log( + logLevel: LogLevel, + initializer: LogMessage.() -> Unit, + noinline printer: LogMessage.() -> String + ) { + buffer.log(TAG, logLevel, initializer, printer) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java index a2203732c47c..1c682e3bb7dc 100644 --- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java +++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java @@ -16,25 +16,27 @@ package com.android.systemui.toast; +import android.animation.Animator; import android.annotation.MainThread; import android.annotation.Nullable; import android.app.INotificationManager; import android.app.ITransientNotificationCallback; import android.content.Context; -import android.content.res.Resources; import android.os.IBinder; import android.os.ServiceManager; import android.os.UserHandle; import android.util.Log; -import android.view.View; +import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IAccessibilityManager; +import android.widget.Toast; import android.widget.ToastPresenter; -import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.SystemUI; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.util.concurrency.DelayableExecutor; import java.util.Objects; @@ -45,35 +47,53 @@ import javax.inject.Inject; */ @SysUISingleton public class ToastUI extends SystemUI implements CommandQueue.Callbacks { + // values from NotificationManagerService#LONG_DELAY and NotificationManagerService#SHORT_DELAY + private static final int TOAST_LONG_TIME = 3500; // 3.5 seconds + private static final int TOAST_SHORT_TIME = 2000; // 2 seconds + private static final String TAG = "ToastUI"; private final CommandQueue mCommandQueue; private final INotificationManager mNotificationManager; - private final IAccessibilityManager mAccessibilityManager; - private final int mGravity; - private final int mY; + private final IAccessibilityManager mIAccessibilityManager; + private final AccessibilityManager mAccessibilityManager; + private final ToastFactory mToastFactory; + private final DelayableExecutor mMainExecutor; + private final ToastLogger mToastLogger; + private SystemUIToast mToast; @Nullable private ToastPresenter mPresenter; @Nullable private ITransientNotificationCallback mCallback; @Inject - public ToastUI(Context context, CommandQueue commandQueue) { + public ToastUI( + Context context, + CommandQueue commandQueue, + ToastFactory toastFactory, + @Main DelayableExecutor mainExecutor, + ToastLogger toastLogger) { this(context, commandQueue, INotificationManager.Stub.asInterface( ServiceManager.getService(Context.NOTIFICATION_SERVICE)), IAccessibilityManager.Stub.asInterface( - ServiceManager.getService(Context.ACCESSIBILITY_SERVICE))); + ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)), + toastFactory, + mainExecutor, + toastLogger); } @VisibleForTesting ToastUI(Context context, CommandQueue commandQueue, INotificationManager notificationManager, - @Nullable IAccessibilityManager accessibilityManager) { + @Nullable IAccessibilityManager accessibilityManager, + ToastFactory toastFactory, DelayableExecutor mainExecutor, ToastLogger toastLogger + ) { super(context); mCommandQueue = commandQueue; mNotificationManager = notificationManager; - mAccessibilityManager = accessibilityManager; - Resources resources = mContext.getResources(); - mGravity = resources.getInteger(R.integer.config_toastDefaultGravity); - mY = resources.getDimensionPixelSize(R.dimen.toast_y_offset); + mIAccessibilityManager = accessibilityManager; + mToastFactory = toastFactory; + mMainExecutor = mainExecutor; + mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); + mToastLogger = toastLogger; } @Override @@ -88,12 +108,31 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks { if (mPresenter != null) { hideCurrentToast(); } - Context context = mContext.createContextAsUser(UserHandle.getUserHandleForUid(uid), 0); - View view = ToastPresenter.getTextToastView(context, text); + UserHandle userHandle = UserHandle.getUserHandleForUid(uid); + Context context = mContext.createContextAsUser(userHandle, 0); + mToast = mToastFactory.createToast(context, text, packageName, userHandle.getIdentifier()); + + if (mToast.hasCustomAnimation()) { + if (mToast.getInAnimation() != null) { + mToast.getInAnimation().start(); + } + final Animator hideAnimator = mToast.getOutAnimation(); + if (hideAnimator != null) { + final long durationMillis = duration == Toast.LENGTH_LONG + ? TOAST_LONG_TIME : TOAST_SHORT_TIME; + final long updatedDuration = mAccessibilityManager.getRecommendedTimeoutMillis( + (int) durationMillis, AccessibilityManager.FLAG_CONTENT_TEXT); + mMainExecutor.executeDelayed(() -> hideAnimator.start(), + updatedDuration - hideAnimator.getTotalDuration()); + } + } mCallback = callback; - mPresenter = new ToastPresenter(context, mAccessibilityManager, mNotificationManager, + mPresenter = new ToastPresenter(context, mIAccessibilityManager, mNotificationManager, packageName); - mPresenter.show(view, token, windowToken, duration, mGravity, 0, mY, 0, 0, mCallback); + mToastLogger.logOnShowToast(uid, packageName, text.toString(), token.toString()); + mPresenter.show(mToast.getView(), token, windowToken, duration, mToast.getGravity(), + mToast.getXOffset(), mToast.getYOffset(), mToast.getHorizontalMargin(), + mToast.getVerticalMargin(), mCallback, mToast.hasCustomAnimation()); } @Override @@ -104,6 +143,7 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks { Log.w(TAG, "Attempt to hide non-current toast from package " + packageName); return; } + mToastLogger.logOnHideToast(packageName, token.toString()); hideCurrentToast(); } 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 0a10ab2fbf02..c743fd07c492 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java @@ -54,7 +54,11 @@ import androidx.test.filters.SmallTest; import com.android.internal.R; import com.android.internal.util.IntPair; import com.android.systemui.SysuiTestCase; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; @@ -84,6 +88,7 @@ public class ToastUITest extends SysuiTestCase { private static final String TEXT = "Hello World"; private static final int MESSAGE_RES_ID = R.id.message; + private FakeExecutor mFakeDelayableExecutor = new FakeExecutor(new FakeSystemClock()); private Context mContextSpy; private ToastUI mToastUI; @Mock private LayoutInflater mLayoutInflater; @@ -91,6 +96,10 @@ public class ToastUITest extends SysuiTestCase { @Mock private WindowManager mWindowManager; @Mock private INotificationManager mNotificationManager; @Mock private IAccessibilityManager mAccessibilityManager; + @Mock private PluginManager mPluginManager; + @Mock private DumpManager mDumpManager; + @Mock private ToastLogger mToastLogger; + @Mock private ITransientNotificationCallback mCallback; @Captor private ArgumentCaptor<View> mViewCaptor; @Captor private ArgumentCaptor<ViewGroup.LayoutParams> mParamsCaptor; @@ -109,8 +118,10 @@ public class ToastUITest extends SysuiTestCase { mContextSpy = spy(mContext); doReturn(mContextSpy).when(mContextSpy).createContextAsUser(any(), anyInt()); + doReturn(mContextSpy).when(mContextSpy).createContextAsUser(any(), anyInt()); mToastUI = new ToastUI(mContextSpy, mCommandQueue, mNotificationManager, - mAccessibilityManager); + mAccessibilityManager, new ToastFactory(mPluginManager, mDumpManager), + mFakeDelayableExecutor, mToastLogger); } @Test @@ -271,6 +282,29 @@ public class ToastUITest extends SysuiTestCase { verify(mCallback).onToastHidden(); } + @Test + public void testShowToast_logs() { + mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, + mCallback); + + verify(mToastLogger).logOnShowToast(UID_1, PACKAGE_NAME_1, TEXT, TOKEN_1.toString()); + } + + @Test + public void testHideToast_logs() { + mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, + mCallback); + mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1); + verify(mToastLogger).logOnHideToast(PACKAGE_NAME_1, TOKEN_1.toString()); + } + + @Test + public void testHideToast_error_noLog() { + // no toast was shown, so this hide is invalid + mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1); + verify(mToastLogger, never()).logOnHideToast(PACKAGE_NAME_1, TOKEN_1.toString()); + } + private View verifyWmAddViewAndAttachToParent() { ArgumentCaptor<View> viewCaptor = ArgumentCaptor.forClass(View.class); verify(mWindowManager).addView(viewCaptor.capture(), any()); diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index dce798e81a86..3ff369a34764 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -929,7 +929,6 @@ public class DisplayPolicy { attrs.hideTimeoutMilliseconds = mAccessibilityManager.getRecommendedTimeoutMillis( (int) attrs.hideTimeoutMilliseconds, AccessibilityManager.FLAG_CONTENT_TEXT); - attrs.windowAnimations = com.android.internal.R.style.Animation_Toast; // Toasts can't be clickable attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; break; |