diff options
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; |