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
This commit is contained in:
Beverly
2020-09-29 09:44:09 -04:00
parent 8862617d5f
commit b7ef5de9e0
11 changed files with 566 additions and 34 deletions

View File

@@ -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;

View File

@@ -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.

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -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 {
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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)
}
}

View File

@@ -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();
}

View File

@@ -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());

View File

@@ -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;