diff options
27 files changed, 278 insertions, 114 deletions
diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java index a4800726bbe8..825077ffd57a 100644 --- a/core/java/android/view/ImeFocusController.java +++ b/core/java/android/view/ImeFocusController.java @@ -125,6 +125,13 @@ public final class ImeFocusController { final View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView; onViewFocusChanged(viewForWindowFocus, true); + // Skip starting input when the next focused view is same as served view and the served + // input connection still exists. + final boolean nextFocusIsServedView = mServedView != null && mServedView == focusedView; + if (nextFocusIsServedView && immDelegate.isAcceptingText()) { + forceFocus = false; + } + immDelegate.startInputAsyncOnWindowFocusGain(viewForWindowFocus, windowAttribute.softInputMode, windowAttribute.flags, forceFocus); } @@ -247,6 +254,7 @@ public final class ImeFocusController { void setCurrentRootView(ViewRootImpl rootView); boolean isCurrentRootView(ViewRootImpl rootView); boolean isRestartOnNextWindowFocus(boolean reset); + boolean isAcceptingText(); } public View getServedView() { diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 71dd6653f6a6..477dd1d13295 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -616,12 +616,19 @@ public final class InputMethodManager { // For some reason we didn't do a startInput + windowFocusGain, so // we'll just do a window focus gain and call it a day. try { - if (DEBUG) Log.v(TAG, "Reporting focus gain, without startInput"); + View servedView = controller.getServedView(); + boolean nextFocusIsServedView = servedView != null && servedView == focusedView; + if (DEBUG) { + Log.v(TAG, "Reporting focus gain, without startInput" + + ", nextFocusIsServedView=" + nextFocusIsServedView); + } mService.startInputOrWindowGainedFocus( StartInputReason.WINDOW_FOCUS_GAIN_REPORT_ONLY, mClient, focusedView.getWindowToken(), startInputFlags, softInputMode, windowFlags, - null, null, 0 /* missingMethodFlags */, + nextFocusIsServedView ? mCurrentTextBoxAttribute : null, + nextFocusIsServedView ? mServedInputConnectionWrapper : null, + 0 /* missingMethodFlags */, mCurRootView.mContext.getApplicationInfo().targetSdkVersion); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -646,8 +653,7 @@ public final class InputMethodManager { public void setCurrentRootView(ViewRootImpl rootView) { synchronized (mH) { if (mCurRootView != null) { - // Reset the last served view and restart window focus state of the root view. - mCurRootView.getImeFocusController().setServedView(null); + // Restart the input when the next window focus state of the root view changed. mRestartOnNextWindowFocus = true; } mCurRootView = rootView; @@ -677,6 +683,18 @@ public final class InputMethodManager { } return result; } + + /** + * For {@link ImeFocusController} to check if the currently served view is accepting full + * text edits. + */ + @Override + public boolean isAcceptingText() { + synchronized (mH) { + return mServedInputConnectionWrapper != null + && mServedInputConnectionWrapper.getInputConnection() != null; + } + } } /** @hide */ diff --git a/core/java/com/android/internal/inputmethod/StartInputReason.java b/core/java/com/android/internal/inputmethod/StartInputReason.java index a01c45919b8f..a4eaa21538f7 100644 --- a/core/java/com/android/internal/inputmethod/StartInputReason.java +++ b/core/java/com/android/internal/inputmethod/StartInputReason.java @@ -50,8 +50,9 @@ public @interface StartInputReason { int WINDOW_FOCUS_GAIN = 1; /** * {@link android.view.Window} gained focus but there is no {@link android.view.View} that is - * eligible to have IME focus. {@link android.view.inputmethod.InputMethodManager} just reports - * this window focus change event. + * eligible to have IME focus, or the focused view is same as current served view and its + * input connection remains. {@link android.view.inputmethod.InputMethodManager} just reports + * this window focus change event to sync IME input target for system. */ int WINDOW_FOCUS_GAIN_REPORT_ONLY = 2; /** diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index 1f2ae5f96449..b7cdeadd482b 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -21,6 +21,10 @@ import static com.android.internal.widget.MessagingGroup.IMAGE_DISPLAY_LOCATION_ import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_IN; import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_OUT; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; import android.annotation.AttrRes; import android.annotation.NonNull; import android.annotation.Nullable; @@ -36,6 +40,7 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Typeface; +import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.Icon; import android.os.Bundle; import android.os.Parcelable; @@ -93,8 +98,12 @@ public class ConversationLayout extends FrameLayout public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f); public static final Interpolator FAST_OUT_LINEAR_IN = new PathInterpolator(0.4f, 0f, 1f, 1f); public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f); + public static final Interpolator OVERSHOOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f); public static final OnLayoutChangeListener MESSAGING_PROPERTY_ANIMATOR = new MessagingPropertyAnimator(); + public static final int IMPORTANCE_ANIM_GROW_DURATION = 250; + public static final int IMPORTANCE_ANIM_SHRINK_DURATION = 200; + public static final int IMPORTANCE_ANIM_SHRINK_DELAY = 25; private List<MessagingMessage> mMessages = new ArrayList<>(); private List<MessagingMessage> mHistoricMessages = new ArrayList<>(); private MessagingLinearLayout mMessagingLinearLayout; @@ -331,14 +340,74 @@ public class ConversationLayout extends FrameLayout mNameReplacement = nameReplacement; } - /** - * Sets this conversation as "important", adding some additional UI treatment. - */ + /** Sets this conversation as "important", adding some additional UI treatment. */ @RemotableViewMethod public void setIsImportantConversation(boolean isImportantConversation) { + setIsImportantConversation(isImportantConversation, false); + } + + /** @hide **/ + public void setIsImportantConversation(boolean isImportantConversation, boolean animate) { mImportantConversation = isImportantConversation; - mImportanceRingView.setVisibility(isImportantConversation - && mIcon.getVisibility() != GONE ? VISIBLE : GONE); + mImportanceRingView.setVisibility(isImportantConversation && mIcon.getVisibility() != GONE + ? VISIBLE : GONE); + + if (animate && isImportantConversation) { + GradientDrawable ring = (GradientDrawable) mImportanceRingView.getDrawable(); + ring.mutate(); + GradientDrawable bg = (GradientDrawable) mConversationIconBadgeBg.getDrawable(); + bg.mutate(); + int ringColor = getResources() + .getColor(R.color.conversation_important_highlight); + int standardThickness = getResources() + .getDimensionPixelSize(R.dimen.importance_ring_stroke_width); + int largeThickness = getResources() + .getDimensionPixelSize(R.dimen.importance_ring_anim_max_stroke_width); + int standardSize = getResources().getDimensionPixelSize( + R.dimen.importance_ring_size); + int baseSize = standardSize - standardThickness * 2; + int bgSize = getResources() + .getDimensionPixelSize(R.dimen.conversation_icon_size_badged); + + ValueAnimator.AnimatorUpdateListener animatorUpdateListener = animation -> { + int strokeWidth = Math.round((float) animation.getAnimatedValue()); + ring.setStroke(strokeWidth, ringColor); + int newSize = baseSize + strokeWidth * 2; + ring.setSize(newSize, newSize); + mImportanceRingView.invalidate(); + }; + + ValueAnimator growAnimation = ValueAnimator.ofFloat(0, largeThickness); + growAnimation.setInterpolator(LINEAR_OUT_SLOW_IN); + growAnimation.setDuration(IMPORTANCE_ANIM_GROW_DURATION); + growAnimation.addUpdateListener(animatorUpdateListener); + + ValueAnimator shrinkAnimation = + ValueAnimator.ofFloat(largeThickness, standardThickness); + shrinkAnimation.setDuration(IMPORTANCE_ANIM_SHRINK_DURATION); + shrinkAnimation.setStartDelay(IMPORTANCE_ANIM_SHRINK_DELAY); + shrinkAnimation.setInterpolator(OVERSHOOT); + shrinkAnimation.addUpdateListener(animatorUpdateListener); + shrinkAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + // Shrink the badge bg so that it doesn't peek behind the animation + bg.setSize(baseSize, baseSize); + mConversationIconBadgeBg.invalidate(); + } + + @Override + public void onAnimationEnd(Animator animation) { + // Reset bg back to normal size + bg.setSize(bgSize, bgSize); + mConversationIconBadgeBg.invalidate(); + } + }); + + AnimatorSet anims = new AnimatorSet(); + anims.playSequentially(growAnimation, shrinkAnimation); + anims.start(); + } } public boolean isImportantConversation() { diff --git a/core/res/res/drawable/conversation_badge_background.xml b/core/res/res/drawable/conversation_badge_background.xml index 0dd0dcda40fb..9e6405dc1040 100644 --- a/core/res/res/drawable/conversation_badge_background.xml +++ b/core/res/res/drawable/conversation_badge_background.xml @@ -22,7 +22,7 @@ android:color="#ffffff"/> <size - android:width="26dp" - android:height="26dp"/> + android:width="20dp" + android:height="20dp"/> </shape> diff --git a/core/res/res/drawable/conversation_badge_ring.xml b/core/res/res/drawable/conversation_badge_ring.xml index 11ba8ad69505..eee53d1c21b5 100644 --- a/core/res/res/drawable/conversation_badge_ring.xml +++ b/core/res/res/drawable/conversation_badge_ring.xml @@ -16,17 +16,18 @@ --> <shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="oval"> - - <solid - android:color="@color/transparent"/> + android:shape="oval" +> + <solid android:color="@color/transparent" /> <stroke android:color="@color/conversation_important_highlight" - android:width="2dp"/> + android:width="@dimen/importance_ring_stroke_width" + /> <size - android:width="26dp" - android:height="26dp"/> + android:width="@dimen/importance_ring_size" + android:height="@dimen/importance_ring_size" + /> </shape> diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml index 9a9d8b96c677..0411f55e6006 100644 --- a/core/res/res/layout/notification_template_material_conversation.xml +++ b/core/res/res/layout/notification_template_material_conversation.xml @@ -38,6 +38,8 @@ <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" + android:clipChildren="false" + android:clipToPadding="false" android:layout_gravity="top|center_horizontal" > @@ -63,13 +65,17 @@ android:layout_height="@dimen/conversation_icon_size_badged" android:layout_marginLeft="@dimen/conversation_badge_side_margin" android:layout_marginTop="@dimen/conversation_badge_side_margin" + android:clipChildren="false" + android:clipToPadding="false" > <com.android.internal.widget.CachingIconView android:id="@+id/conversation_icon_badge_bg" android:layout_width="match_parent" android:layout_height="match_parent" + android:layout_gravity="center" android:src="@drawable/conversation_badge_background" android:forceHasOverlappingRendering="false" + android:scaleType="center" /> <com.android.internal.widget.CachingIconView android:id="@+id/icon" @@ -81,11 +87,14 @@ /> <com.android.internal.widget.CachingIconView android:id="@+id/conversation_icon_badge_ring" - android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" android:src="@drawable/conversation_badge_ring" android:visibility="gone" android:forceHasOverlappingRendering="false" + android:clipToPadding="false" + android:scaleType="center" /> </FrameLayout> </FrameLayout> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 59bb052cbdf5..a771904f115b 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -720,6 +720,12 @@ <dimen name="conversation_face_pile_protection_width_expanded">1dp</dimen> <!-- The padding of the expanded message container--> <dimen name="expanded_group_conversation_message_padding">14dp</dimen> + <!-- The stroke width of the ring used to visually mark a conversation as important --> + <dimen name="importance_ring_stroke_width">2dp</dimen> + <!-- The maximum stroke width used for the animation shown when a conversation is marked as important --> + <dimen name="importance_ring_anim_max_stroke_width">10dp</dimen> + <!-- The size of the importance ring --> + <dimen name="importance_ring_size">20dp</dimen> <!-- The top padding of the conversation icon container in the regular state--> <dimen name="conversation_icon_container_top_padding">9dp</dimen> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index ab4005b4893c..23ae1e7d271e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3903,6 +3903,12 @@ <java-symbol type="array" name="config_defaultImperceptibleKillingExemptionPkgs" /> <java-symbol type="array" name="config_defaultImperceptibleKillingExemptionProcStates" /> + <java-symbol type="color" name="conversation_important_highlight" /> + <java-symbol type="dimen" name="importance_ring_stroke_width" /> + <java-symbol type="dimen" name="importance_ring_anim_max_stroke_width" /> + <java-symbol type="dimen" name="importance_ring_size" /> + <java-symbol type="dimen" name="conversation_icon_size_badged" /> + <java-symbol type="id" name="header_icon_container" /> <java-symbol type="attr" name="notificationHeaderTextAppearance" /> <java-symbol type="string" name="conversation_single_line_name_display" /> diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java index 1d4cfdc88ad5..a89cf37e2d06 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -60,6 +60,7 @@ import androidx.lifecycle.OnLifecycleEvent; import com.android.internal.R; import com.android.internal.util.ArrayUtils; import com.android.settingslib.Utils; +import com.android.settingslib.utils.ThreadUtils; import java.io.File; import java.io.IOException; @@ -1588,6 +1589,15 @@ public class ApplicationsState { this.size = SIZE_UNKNOWN; this.sizeStale = true; ensureLabel(context); + // Speed up the cache of the icon and label description if they haven't been created. + ThreadUtils.postOnBackgroundThread(() -> { + if (this.icon == null) { + this.ensureIconLocked(context); + } + if (this.labelDescription == null) { + this.ensureLabelDescriptionLocked(context); + } + }); } public void ensureLabel(Context context) { diff --git a/packages/SystemUI/res/layout/controls_base_item.xml b/packages/SystemUI/res/layout/controls_base_item.xml index 477a70f4c7ad..5f83f45958e9 100644 --- a/packages/SystemUI/res/layout/controls_base_item.xml +++ b/packages/SystemUI/res/layout/controls_base_item.xml @@ -52,6 +52,7 @@ android:singleLine="true" android:ellipsize="marquee" android:marqueeRepeatLimit = "marquee_forever" + android:textDirection="locale" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="@+id/icon" app:layout_constraintStart_toEndOf="@+id/icon" /> @@ -67,6 +68,7 @@ android:focusable="false" android:maxLines="1" android:ellipsize="end" + android:textDirection="locale" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toTopOf="@id/barrier"/> @@ -90,6 +92,7 @@ android:focusable="false" android:maxLines="1" android:ellipsize="end" + android:textDirection="locale" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@id/favorite" app:layout_constraintTop_toTopOf="@id/favorite" diff --git a/packages/SystemUI/res/layout/controls_more_item.xml b/packages/SystemUI/res/layout/controls_more_item.xml index df03787d567c..da9c43ccc1e9 100644 --- a/packages/SystemUI/res/layout/controls_more_item.xml +++ b/packages/SystemUI/res/layout/controls_more_item.xml @@ -20,5 +20,6 @@ android:layout_height="wrap_content" android:layout_gravity="start" android:paddingStart="@dimen/control_menu_horizontal_padding" - android:paddingEnd="@dimen/control_menu_horizontal_padding"/> + android:paddingEnd="@dimen/control_menu_horizontal_padding" + android:textDirection="locale"/> diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt index 07319207d9ab..45ba1e6012fe 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt @@ -190,11 +190,6 @@ interface ControlsController : UserAwareController { fun countFavoritesForComponent(componentName: ComponentName): Int /** - * TEMPORARY for testing - */ - fun resetFavorites() - - /** * Interface for structure to pass data to [ControlsFavoritingActivity]. */ interface LoadData { diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt index 93f0c7f41ce3..ebdcdccead90 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -524,13 +524,6 @@ class ControlsControllerImpl @Inject constructor ( } } - override fun resetFavorites() { - executor.execute { - Favorites.clear() - persistenceWrapper.storeFavorites(Favorites.getAllStructures()) - } - } - override fun refreshStatus(componentName: ComponentName, control: Control) { if (!confirmAvailability()) { Log.d(TAG, "Controls not available") diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index d31b6eb9d857..0f5aef7eeec1 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -19,16 +19,13 @@ package com.android.systemui.controls.ui import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ObjectAnimator -import android.app.AlertDialog import android.content.ComponentName import android.content.Context -import android.content.DialogInterface import android.content.Intent import android.content.SharedPreferences import android.content.res.Configuration import android.graphics.drawable.Drawable import android.graphics.drawable.LayerDrawable -import android.os.Process import android.service.controls.Control import android.util.Log import android.util.TypedValue @@ -36,7 +33,6 @@ import android.view.ContextThemeWrapper import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.view.WindowManager import android.view.animation.AccelerateInterpolator import android.view.animation.DecelerateInterpolator import android.widget.AdapterView @@ -272,8 +268,7 @@ class ControlsUiControllerImpl @Inject constructor ( private fun createMenu() { val items = arrayOf( context.resources.getString(R.string.controls_menu_add), - context.resources.getString(R.string.controls_menu_edit), - "Reset" + context.resources.getString(R.string.controls_menu_edit) ) var adapter = ArrayAdapter<String>(context, R.layout.controls_more_item, items) @@ -298,10 +293,6 @@ class ControlsUiControllerImpl @Inject constructor ( 0 -> startFavoritingActivity(view.context, selectedStructure) // 1: Edit controls 1 -> startEditingActivity(view.context, selectedStructure) - // 2: TEMPORARY for reset controls - 2 -> showResetConfirmation() - else -> Log.w(ControlsUiController.TAG, - "Unsupported index ($pos) on 'more' menu selection") } dismiss() } @@ -312,39 +303,6 @@ class ControlsUiControllerImpl @Inject constructor ( }) } - private fun showResetConfirmation() { - val builder = AlertDialog.Builder( - context, - android.R.style.Theme_DeviceDefault_Dialog_Alert - ).apply { - setMessage("For testing purposes: Would you like to " + - "reset your favorited device controls?") - setPositiveButton( - android.R.string.ok, - DialogInterface.OnClickListener { dialog, _ -> - val userHandle = Process.myUserHandle() - val userContext = context.createContextAsUser(userHandle, 0) - val prefs = userContext.getSharedPreferences( - "controls_prefs", Context.MODE_PRIVATE) - prefs.edit().remove("SeedingCompleted").apply() - controlsController.get().resetFavorites() - dialog.dismiss() - context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) - }) - setNegativeButton( - android.R.string.cancel, - DialogInterface.OnClickListener { - dialog, _ -> dialog.cancel() - } - ) - } - builder.create().apply { - getWindow().apply { - setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY) - } - }.show() - } - private fun createDropDown(items: List<SelectionItem>) { items.forEach { RenderInfo.registerComponentIcon(it.componentName, it.icon) diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java index 6b71f1e9d86f..1dbbb4d69493 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java @@ -19,6 +19,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; +import android.util.LayoutDirection; import android.view.View; import android.view.View.MeasureSpec; import android.view.WindowManager; @@ -106,7 +107,11 @@ public class GlobalActionsPopupMenu extends ListPopupWindow { listView.setPadding(0, mMenuVerticalPadding, 0, mMenuVerticalPadding); setWidth(width); - setHorizontalOffset(getAnchorView().getWidth() - mGlobalActionsSidePadding - width); + if (getAnchorView().getLayoutDirection() == LayoutDirection.LTR) { + setHorizontalOffset(getAnchorView().getWidth() - mGlobalActionsSidePadding - width); + } else { + setHorizontalOffset(mGlobalActionsSidePadding); + } } super.show(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt index fc6c2be1ce9a..1972b869ba25 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt @@ -19,13 +19,16 @@ package com.android.systemui.statusbar.notification import android.app.Notification import android.content.Context import android.content.pm.LauncherApps +import android.os.Handler import android.service.notification.NotificationListenerService.Ranking import android.service.notification.NotificationListenerService.RankingMap import com.android.internal.statusbar.NotificationVisibility import com.android.internal.widget.ConversationLayout +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationContentView +import com.android.systemui.statusbar.notification.stack.StackStateAnimator import com.android.systemui.statusbar.phone.NotificationGroupManager import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject @@ -62,7 +65,8 @@ class ConversationNotificationProcessor @Inject constructor( class ConversationNotificationManager @Inject constructor( private val notificationEntryManager: NotificationEntryManager, private val notificationGroupManager: NotificationGroupManager, - private val context: Context + private val context: Context, + @Main private val mainHandler: Handler ) { // Need this state to be thread safe, since it's accessed from the ui thread // (NotificationEntryListener) and a bg thread (NotificationContentInflater) @@ -72,32 +76,41 @@ class ConversationNotificationManager @Inject constructor( init { notificationEntryManager.addNotificationEntryListener(object : NotificationEntryListener { - override fun onNotificationRankingUpdated(rankingMap: RankingMap) { fun getLayouts(view: NotificationContentView) = sequenceOf(view.contractedChild, view.expandedChild, view.headsUpChild) val ranking = Ranking() - states.keys.asSequence() + val activeConversationEntries = states.keys.asSequence() .mapNotNull { notificationEntryManager.getActiveNotificationUnfiltered(it) } - .forEach { entry -> - if (rankingMap.getRanking(entry.sbn.key, ranking) && - ranking.isConversation) { - val important = ranking.channel.isImportantConversation - var changed = false - entry.row?.layouts?.asSequence() - ?.flatMap(::getLayouts) - ?.mapNotNull { it as? ConversationLayout } - ?.forEach { - if (important != it.isImportantConversation) { - it.setIsImportantConversation(important) - changed = true - } - } - if (changed) { - notificationGroupManager.updateIsolation(entry) - } + for (entry in activeConversationEntries) { + if (rankingMap.getRanking(entry.sbn.key, ranking) && ranking.isConversation) { + val important = ranking.channel.isImportantConversation + val layouts = entry.row?.layouts?.asSequence() + ?.flatMap(::getLayouts) + ?.mapNotNull { it as? ConversationLayout } + ?: emptySequence() + var changed = false + for (layout in layouts) { + if (important == layout.isImportantConversation) { + continue + } + changed = true + if (important && entry.isMarkedForUserTriggeredMovement) { + // delay this so that it doesn't animate in until after + // the notif has been moved in the shade + mainHandler.postDelayed({ + layout.setIsImportantConversation( + important, true /* animate */) + }, IMPORTANCE_ANIMATION_DELAY.toLong()) + } else { + layout.setIsImportantConversation(important) } } + if (changed) { + notificationGroupManager.updateIsolation(entry) + } + } + } } override fun onEntryInflated(entry: NotificationEntry) { @@ -177,9 +190,16 @@ class ConversationNotificationManager @Inject constructor( private fun resetBadgeUi(row: ExpandableNotificationRow): Unit = (row.layouts?.asSequence() ?: emptySequence()) - .flatMap { layout -> layout.allViews.asSequence()} + .flatMap { layout -> layout.allViews.asSequence() } .mapNotNull { view -> view as? ConversationLayout } .forEach { convoLayout -> convoLayout.setUnreadCount(0) } private data class ConversationState(val unreadCount: Int, val notification: Notification) + + companion object { + private const val IMPORTANCE_ANIMATION_DELAY = + StackStateAnimator.ANIMATION_DURATION_STANDARD + + StackStateAnimator.ANIMATION_DURATION_PRIORITY_CHANGE + + 100 + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java index 87612f15ed3d..e445c9d73bbb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java @@ -188,7 +188,9 @@ public class AppOpsInfo extends LinearLayout implements NotificationGuts.GutsCon @Override public boolean handleCloseControls(boolean save, boolean force) { - mMetricsLogger.visibility(MetricsEvent.APP_OPS_GUTS, false); + if (mMetricsLogger != null) { + mMetricsLogger.visibility(MetricsEvent.APP_OPS_GUTS, false); + } return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java index e9d89589172e..f4afb91396b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java @@ -53,7 +53,6 @@ import android.transition.TransitionManager; import android.transition.TransitionSet; import android.util.AttributeSet; import android.util.Log; -import android.util.Slog; import android.view.LayoutInflater; import android.view.View; import android.view.accessibility.AccessibilityEvent; @@ -508,10 +507,10 @@ public class NotificationConversationInfo extends LinearLayout implements mBgHandler.post( new UpdateChannelRunnable(mINotificationManager, mPackageName, mAppUid, mSelectedAction, mNotificationChannel)); - mMainHandler.postDelayed(() -> { - mEntry.markForUserTriggeredMovement(true); - mVisualStabilityManager.temporarilyAllowReordering(); - }, StackStateAnimator.ANIMATION_DURATION_STANDARD); + mEntry.markForUserTriggeredMovement(true); + mMainHandler.postDelayed( + mVisualStabilityManager::temporarilyAllowReordering, + StackStateAnimator.ANIMATION_DURATION_STANDARD); } private boolean shouldShowPriorityOnboarding() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java index 4c9cb209424a..c747a7c300b7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java @@ -262,7 +262,10 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { stack.push(mView); while (!stack.isEmpty()) { View child = stack.pop(); - if (child instanceof ImageView) { + if (child instanceof ImageView + // Skip the importance ring for conversations, disabled cropping is needed for + // its animation + && child.getId() != com.android.internal.R.id.conversation_icon_badge_ring) { ((ImageView) child).setCropToPadding(true); } else if (child instanceof ViewGroup){ ViewGroup group = (ViewGroup) child; diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index 34998a025663..b9240c78d711 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -898,11 +898,16 @@ final class AccessibilityController { /* ignore */ } mSurfaceControl = surfaceControl; - mService.mTransactionFactory.get().setLayer(mSurfaceControl, - mService.mPolicy.getWindowLayerFromTypeLw(TYPE_MAGNIFICATION_OVERLAY) - * WindowManagerService.TYPE_LAYER_MULTIPLIER) - .setPosition(mSurfaceControl, 0, 0) - .apply(); + + final SurfaceControl.Transaction t = mService.mTransactionFactory.get(); + final int layer = + mService.mPolicy.getWindowLayerFromTypeLw(TYPE_MAGNIFICATION_OVERLAY) * + WindowManagerService.TYPE_LAYER_MULTIPLIER; + t.setLayer(mSurfaceControl, layer).setPosition(mSurfaceControl, 0, 0); + InputMonitor.setTrustedOverlayInputInfo(mSurfaceControl, t, + mDisplayContent.getDisplayId(), "Magnification Overlay"); + t.apply(); + mSurface.copyFrom(mSurfaceControl); mAnimationController = new AnimationController(context, diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 5f591b54b067..7c935d0ea546 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -4873,6 +4873,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return; } + if (isInPictureInPictureMode(activity)) { + throw new IllegalStateException("Activity is already in PIP mode"); + } + final boolean canEnterPictureInPicture = activity.checkEnterPictureInPictureState( "requestPictureInPictureMode", /* beforeStopping */ false); if (!canEnterPictureInPicture) { diff --git a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java index 2165b0e8b593..c9cc94423fe2 100644 --- a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java +++ b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java @@ -66,6 +66,9 @@ class EmulatorDisplayOverlay { t.setLayer(ctrl, zOrder); t.setPosition(ctrl, 0, 0); t.show(ctrl); + // Ensure we aren't considered as obscuring for Input purposes. + InputMonitor.setTrustedOverlayInputInfo(ctrl, t, + dc.getDisplayId(), "EmulatorDisplayOverlay"); mSurface.copyFrom(ctrl); } catch (OutOfResourcesException e) { } diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index efcd61df5c75..8734b5efa45d 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -16,13 +16,17 @@ package com.android.server.wm; +import static android.os.Process.myPid; +import static android.os.Process.myUid; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION; import static android.view.WindowManager.INPUT_CONSUMER_PIP; import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; import static android.view.WindowManager.INPUT_CONSUMER_WALLPAPER; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; +import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS; +import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT; @@ -550,4 +554,26 @@ final class InputMonitor { } } } + + /** + * Helper function to generate an InputInfo with type SECURE_SYSTEM_OVERLAY. This input + * info will not have an input channel or be touchable, but is used to omit Surfaces + * from occlusion detection, so that System global overlays like the Watermark aren't + * counted by the InputDispatcher as occluding applications below. + */ + static void setTrustedOverlayInputInfo(SurfaceControl sc, SurfaceControl.Transaction t, + int displayId, String name) { + InputWindowHandle inputWindowHandle = new InputWindowHandle(null, displayId); + inputWindowHandle.name = name; + inputWindowHandle.layoutParamsType = TYPE_SECURE_SYSTEM_OVERLAY; + inputWindowHandle.dispatchingTimeoutNanos = -1; + inputWindowHandle.visible = true; + inputWindowHandle.canReceiveKeys = false; + inputWindowHandle.hasFocus = false; + inputWindowHandle.ownerPid = myPid(); + inputWindowHandle.ownerUid = myUid(); + inputWindowHandle.inputFeatures = INPUT_FEATURE_NO_INPUT_CHANNEL; + inputWindowHandle.scaleFactor = 1; + t.setInputWindowInfo(sc, inputWindowHandle); + } } diff --git a/services/core/java/com/android/server/wm/StrictModeFlash.java b/services/core/java/com/android/server/wm/StrictModeFlash.java index f537005c955c..fa62daaff3fc 100644 --- a/services/core/java/com/android/server/wm/StrictModeFlash.java +++ b/services/core/java/com/android/server/wm/StrictModeFlash.java @@ -54,6 +54,10 @@ class StrictModeFlash { t.setLayer(ctrl, WindowManagerService.TYPE_LAYER_MULTIPLIER * 101); t.setPosition(ctrl, 0, 0); t.show(ctrl); + // Ensure we aren't considered as obscuring for Input purposes. + InputMonitor.setTrustedOverlayInputInfo(ctrl, t, dc.getDisplayId(), + "StrictModeFlash"); + mSurface.copyFrom(ctrl); } catch (OutOfResourcesException e) { } diff --git a/services/core/java/com/android/server/wm/Watermark.java b/services/core/java/com/android/server/wm/Watermark.java index 4e1b2177c87c..3d49ebe306e6 100644 --- a/services/core/java/com/android/server/wm/Watermark.java +++ b/services/core/java/com/android/server/wm/Watermark.java @@ -29,6 +29,7 @@ import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; import android.view.Display; +import android.view.InputWindowHandle; import android.view.Surface; import android.view.Surface.OutOfResourcesException; import android.view.SurfaceControl; @@ -124,6 +125,8 @@ class Watermark { t.setLayer(ctrl, WindowManagerService.TYPE_LAYER_MULTIPLIER * 100) .setPosition(ctrl, 0, 0) .show(ctrl); + // Ensure we aren't considered as obscuring for Input purposes. + InputMonitor.setTrustedOverlayInputInfo(ctrl, t, dc.getDisplayId(), "Watermark"); mSurface.copyFrom(ctrl); } catch (OutOfResourcesException e) { } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java index bd616a3a96f3..64b5eca1beb8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java @@ -17,7 +17,6 @@ package com.android.server.wm; import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; @@ -111,7 +110,7 @@ public class ActivityTaskManagerServiceTests extends ActivityTestsBase { final ActivityStack stack = new StackBuilder(mRootWindowContainer).build(); final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity(); ClientLifecycleManager lifecycleManager = mService.getLifecycleManager(); - doNothing().when(lifecycleManager).scheduleTransaction(any()); + doReturn(false).when(activity).inPinnedWindowingMode(); doReturn(false).when(activity).checkEnterPictureInPictureState(anyString(), anyBoolean()); mService.requestPictureInPictureMode(activity.token); @@ -120,6 +119,19 @@ public class ActivityTaskManagerServiceTests extends ActivityTestsBase { verify(lifecycleManager, times(0)).scheduleTransaction(any()); } + @Test(expected = IllegalStateException.class) + public void testOnPictureInPictureRequested_alreadyInPIPMode() throws RemoteException { + final ActivityStack stack = new StackBuilder(mRootWindowContainer).build(); + final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity(); + ClientLifecycleManager lifecycleManager = mService.getLifecycleManager(); + doReturn(true).when(activity).inPinnedWindowingMode(); + + mService.requestPictureInPictureMode(activity.token); + + // Check that no transactions with enter pip requests are made. + verify(lifecycleManager, times(0)).scheduleTransaction(any()); + } + @Test public void testDisplayWindowListener() { final ArrayList<Integer> added = new ArrayList<>(); |