diff options
22 files changed, 1134 insertions, 131 deletions
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java index 5c43f8f829b0..08d990581390 100644 --- a/core/java/android/service/notification/StatusBarNotification.java +++ b/core/java/android/service/notification/StatusBarNotification.java @@ -486,15 +486,8 @@ public class StatusBarNotification implements Parcelable { /** * @hide */ - public String getShortcutId(Context context) { - String conversationId = getNotification().getShortcutId(); - if (TextUtils.isEmpty(conversationId) - && (Settings.Global.getInt(context.getContentResolver(), - Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 0) == 0) - && getNotification().getNotificationStyle() == Notification.MessagingStyle.class) { - conversationId = getId() + getTag() + PLACEHOLDER_CONVERSATION_ID; - } - return conversationId; + public String getShortcutId() { + return getNotification().getShortcutId(); } /** diff --git a/packages/SystemUI/res/layout/partial_conversation_info.xml b/packages/SystemUI/res/layout/partial_conversation_info.xml new file mode 100644 index 000000000000..2401dfbc2435 --- /dev/null +++ b/packages/SystemUI/res/layout/partial_conversation_info.xml @@ -0,0 +1,200 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 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. +--> + +<com.android.systemui.statusbar.notification.row.PartialConversationInfo + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/notification_guts" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:focusable="true" + android:clipChildren="false" + android:clipToPadding="true" + android:orientation="vertical" + android:paddingStart="@*android:dimen/notification_content_margin_start"> + + <!-- Package Info --> + <LinearLayout + android:id="@+id/header" + android:layout_width="match_parent" + android:layout_height="@dimen/notification_guts_conversation_header_height" + android:gravity="center_vertical" + android:clipChildren="false" + android:clipToPadding="false"> + <ImageView + android:id="@+id/conversation_icon" + android:layout_width="@dimen/notification_guts_conversation_icon_size" + android:layout_height="@dimen/notification_guts_conversation_icon_size" + android:layout_centerVertical="true" + android:layout_alignParentStart="true" + android:layout_marginEnd="15dp" /> + <LinearLayout + android:id="@+id/names" + android:layout_weight="1" + android:layout_width="0dp" + android:orientation="vertical" + + android:layout_height="wrap_content" + android:minHeight="@dimen/notification_guts_conversation_icon_size" + android:layout_centerVertical="true" + android:gravity="center_vertical" + android:layout_alignEnd="@id/conversation_icon" + android:layout_toEndOf="@id/conversation_icon"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="start" + android:orientation="horizontal"> + <TextView + android:id="@+id/name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@style/TextAppearance.NotificationImportanceChannel"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + style="@style/TextAppearance.NotificationImportanceHeader" + android:layout_marginStart="2dp" + android:layout_marginEnd="2dp" + android:text="@*android:string/notification_header_divider_symbol" /> + <TextView + android:id="@+id/parent_channel_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@style/TextAppearance.NotificationImportanceChannel"/> + + </LinearLayout> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="start" + android:orientation="horizontal"> + <TextView + android:id="@+id/pkg_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@style/TextAppearance.NotificationImportanceChannelGroup" + android:ellipsize="end" + android:maxLines="1"/> + <TextView + android:id="@+id/group_divider" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + style="@style/TextAppearance.NotificationImportanceHeader" + android:layout_marginStart="2dp" + android:layout_marginEnd="2dp" + android:text="@*android:string/notification_header_divider_symbol" /> + <TextView + android:id="@+id/group_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + style="@style/TextAppearance.NotificationImportanceChannelGroup"/> + </LinearLayout> + <TextView + android:id="@+id/delegate_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + style="@style/TextAppearance.NotificationImportanceHeader" + android:layout_marginStart="2dp" + android:layout_marginEnd="2dp" + android:ellipsize="end" + android:text="@string/notification_delegate_header" + android:maxLines="1" /> + + </LinearLayout> + + <!-- end aligned fields --> + <ImageButton + android:id="@+id/info" + android:layout_width="@dimen/notification_importance_toggle_size" + android:layout_height="@dimen/notification_importance_toggle_size" + android:layout_centerVertical="true" + android:background="@drawable/ripple_drawable" + android:contentDescription="@string/notification_more_settings" + android:src="@drawable/ic_settings" + android:layout_alignParentEnd="true" + android:tint="@color/notification_guts_link_icon_tint"/> + + </LinearLayout> + + <LinearLayout + android:id="@+id/inline_controls" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingEnd="@*android:dimen/notification_content_margin_end" + android:layout_marginTop="@dimen/notification_guts_option_vertical_padding" + android:clipChildren="false" + android:clipToPadding="false" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipChildren="false" + android:clipToPadding="false" + android:orientation="horizontal"> + <ImageView + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:contentDescription="@null" + android:src="@drawable/ic_info" + android:tint="?android:attr/textColorPrimary" + android:layout_marginEnd="8dp"/> + <TextView + android:id="@+id/non_configurable_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@style/TextAppearance.NotificationImportanceChannelGroup" /> + </LinearLayout> + + <RelativeLayout + android:id="@+id/bottom_buttons" + android:layout_width="match_parent" + android:layout_height="60dp" + android:gravity="center_vertical" + android:paddingStart="4dp" + android:paddingEnd="4dp" + > + <TextView + android:id="@+id/turn_off_notifications" + android:text="@string/inline_turn_off_notifications" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:gravity="start|center_vertical" + android:minWidth="@dimen/notification_importance_toggle_size" + android:minHeight="@dimen/notification_importance_toggle_size" + android:maxWidth="200dp" + style="@style/TextAppearance.NotificationInfo.Button"/> + <TextView + android:id="@+id/done" + android:text="@string/inline_done_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:gravity="end|center_vertical" + android:minWidth="@dimen/notification_importance_toggle_size" + android:minHeight="@dimen/notification_importance_toggle_size" + android:maxWidth="125dp" + style="@style/TextAppearance.NotificationInfo.Button"/> + </RelativeLayout> + + </LinearLayout> +</com.android.systemui.statusbar.notification.row.PartialConversationInfo> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 9f0f48233458..43ebb4082650 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1847,6 +1847,9 @@ <!-- [CHAR LIMIT=150] Notification Importance title: important conversation level --> <string name="notification_priority_title">Priority</string> + <!-- Text shown in notification guts for conversation notifications that don't implement the full feature --> + <string name="no_shortcut"><xliff:g id="app_name" example="YouTube">%1$s</xliff:g> does not support conversation specific settings</string> + <!-- [CHAR LIMIT=NONE] Empty overflow title --> <string name="bubble_overflow_empty_title">No recent bubbles</string> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationChannelHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationChannelHelper.java index ff945d15a4ed..1c2a00ed601a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationChannelHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationChannelHelper.java @@ -26,7 +26,6 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.Slog; -import com.android.systemui.R; import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** @@ -44,32 +43,18 @@ public class NotificationChannelHelper { if (!TextUtils.isEmpty(channel.getConversationId())) { return channel; } - final String conversationId = entry.getSbn().getShortcutId(context); + final String conversationId = entry.getSbn().getShortcutId(); final String pkg = entry.getSbn().getPackageName(); final int appUid = entry.getSbn().getUid(); - if (TextUtils.isEmpty(conversationId) || TextUtils.isEmpty(pkg)) { + if (TextUtils.isEmpty(conversationId) || TextUtils.isEmpty(pkg) + || entry.getRanking().getShortcutInfo() == null) { return channel; } - String name; - if (entry.getRanking().getShortcutInfo() != null) { - name = entry.getRanking().getShortcutInfo().getShortLabel().toString(); - } else { - Bundle extras = entry.getSbn().getNotification().extras; - String nameString = extras.getString(Notification.EXTRA_CONVERSATION_TITLE); - if (TextUtils.isEmpty(nameString)) { - nameString = extras.getString(Notification.EXTRA_TITLE); - } - name = nameString; - } - // If this channel is not already a customized conversation channel, create // a custom channel try { - // TODO: When shortcuts are enforced remove this and use the shortcut label for naming - channel.setName(context.getString( - R.string.notification_summary_message_format, - name, channel.getName())); + channel.setName(getName(entry)); notificationManager.createConversationNotificationChannelForPackage( pkg, appUid, entry.getSbn().getKey(), channel, conversationId); @@ -81,4 +66,19 @@ public class NotificationChannelHelper { } return channel; } + + private static String getName(NotificationEntry entry) { + if (entry.getRanking().getShortcutInfo().getShortLabel() != null) { + return entry.getRanking().getShortcutInfo().getShortLabel().toString(); + } + Bundle extras = entry.getSbn().getNotification().extras; + String nameString = extras.getString(Notification.EXTRA_CONVERSATION_TITLE); + if (TextUtils.isEmpty(nameString)) { + nameString = extras.getString(Notification.EXTRA_TITLE); + } + if (TextUtils.isEmpty(nameString)) { + nameString = "fallback"; + } + return nameString; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index a9bb416cbebb..66b2ca633ca1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -1141,6 +1141,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (mMenuRow.shouldUseDefaultMenuItems()) { ArrayList<MenuItem> items = new ArrayList<>(); items.add(NotificationMenuRow.createConversationItem(mContext)); + items.add(NotificationMenuRow.createPartialConversationItem(mContext)); items.add(NotificationMenuRow.createInfoItem(mContext)); items.add(NotificationMenuRow.createSnoozeItem(mContext)); items.add(NotificationMenuRow.createAppOpsItem(mContext)); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index f23f3bf28312..e9849ec84987 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -1347,11 +1347,11 @@ public class NotificationContentView extends FrameLayout { if (bubbleButton == null || actionContainer == null) { return; } - boolean isPerson = + boolean isPersonWithShortcut = mPeopleIdentifier.getPeopleNotificationType(entry.getSbn(), entry.getRanking()) - != PeopleNotificationIdentifier.TYPE_NON_PERSON; + >= PeopleNotificationIdentifier.TYPE_FULL_PERSON; boolean showButton = isBubblesEnabled() - && isPerson + && isPersonWithShortcut && entry.getBubbleMetadata() != null; if (showButton) { Drawable d = mContext.getResources().getDrawable(entry.isBubble() 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 23b911b6f687..863951e655e9 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 @@ -97,7 +97,6 @@ public class NotificationConversationInfo extends LinearLayout implements private String mDelegatePkg; private NotificationChannel mNotificationChannel; private ShortcutInfo mShortcutInfo; - private String mConversationId; private StatusBarNotification mSbn; @Nullable private Notification.BubbleMetadata mBubbleMetadata; private Context mUserContext; @@ -233,14 +232,10 @@ public class NotificationConversationInfo extends LinearLayout implements mBuilderProvider = builderProvider; mShortcutManager = shortcutManager; - mConversationId = mNotificationChannel.getConversationId(); - if (TextUtils.isEmpty(mNotificationChannel.getConversationId())) { - mConversationId = mSbn.getShortcutId(mContext); - } - if (TextUtils.isEmpty(mConversationId)) { + mShortcutInfo = entry.getRanking().getShortcutInfo(); + if (mShortcutInfo == null) { throw new IllegalArgumentException("Does not have required information"); } - mShortcutInfo = entry.getRanking().getShortcutInfo(); mNotificationChannel = NotificationChannelHelper.createConversationChannelIfNeeded( getContext(), mINotificationManager, entry, mNotificationChannel); @@ -319,31 +314,9 @@ public class NotificationConversationInfo extends LinearLayout implements private void bindIcon(boolean important) { ImageView image = findViewById(R.id.conversation_icon); - if (mShortcutInfo != null) { - image.setImageDrawable(mIconFactory.getConversationDrawable( - mShortcutInfo, mPackageName, mAppUid, - important)); - } else { - if (mSbn.getNotification().extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION, false)) { - // TODO: maybe use a generic group icon, or a composite of recent senders - image.setImageDrawable(mPm.getDefaultActivityIcon()); - } else { - final List<Notification.MessagingStyle.Message> messages = - Notification.MessagingStyle.Message.getMessagesFromBundleArray( - (Parcelable[]) mSbn.getNotification().extras.get( - Notification.EXTRA_MESSAGES)); - - final Notification.MessagingStyle.Message latestMessage = - Notification.MessagingStyle.findLatestIncomingMessage(messages); - Icon personIcon = latestMessage.getSenderPerson().getIcon(); - if (personIcon != null) { - image.setImageIcon(latestMessage.getSenderPerson().getIcon()); - } else { - // TODO: choose something better - image.setImageDrawable(mPm.getDefaultActivityIcon()); - } - } - } + image.setImageDrawable(mIconFactory.getConversationDrawable( + mShortcutInfo, mPackageName, mAppUid, important)); + } private void bindPackage() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index 1c808cf90321..9c7de2bbf2ae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -252,6 +252,9 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx } else if (gutsView instanceof NotificationConversationInfo) { initializeConversationNotificationInfo( row, (NotificationConversationInfo) gutsView); + } else if (gutsView instanceof PartialConversationInfo) { + initializePartialConversationNotificationInfo(row, + (PartialConversationInfo) gutsView); } return true; } catch (Exception e) { @@ -357,7 +360,47 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx } /** - * Sets up the {@link NotificationConversationInfo} inside the notification row's guts. + * Sets up the {@link PartialConversationInfo} inside the notification row's guts. + * @param row view to set up the guts for + * @param notificationInfoView view to set up/bind within {@code row} + */ + @VisibleForTesting + void initializePartialConversationNotificationInfo( + final ExpandableNotificationRow row, + PartialConversationInfo notificationInfoView) throws Exception { + NotificationGuts guts = row.getGuts(); + StatusBarNotification sbn = row.getEntry().getSbn(); + String packageName = sbn.getPackageName(); + // Settings link is only valid for notifications that specify a non-system user + NotificationInfo.OnSettingsClickListener onSettingsClick = null; + UserHandle userHandle = sbn.getUser(); + PackageManager pmUser = StatusBar.getPackageManagerForUser( + mContext, userHandle.getIdentifier()); + + if (!userHandle.equals(UserHandle.ALL) + || mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) { + onSettingsClick = (View v, NotificationChannel channel, int appUid) -> { + mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO); + guts.resetFalsingCheck(); + mOnSettingsClickListener.onSettingsClick(sbn.getKey()); + startAppNotificationSettingsActivity(packageName, appUid, channel, row); + }; + } + + notificationInfoView.bindNotification( + pmUser, + mNotificationManager, + packageName, + row.getEntry().getChannel(), + row.getUniqueChannels(), + row.getEntry(), + onSettingsClick, + mDeviceProvisionedController.isDeviceProvisioned(), + row.getIsNonblockable()); + } + + /** + * Sets up the {@link ConversationInfo} inside the notification row's guts. * @param row view to set up the guts for * @param notificationInfoView view to set up/bind within {@code row} */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java index 83a6eb297ab3..5e1e3b255867 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java @@ -268,7 +268,9 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl NotificationEntry entry = mParent.getEntry(); int personNotifType = mPeopleNotificationIdentifier .getPeopleNotificationType(entry.getSbn(), entry.getRanking()); - if (personNotifType != PeopleNotificationIdentifier.TYPE_NON_PERSON) { + if (personNotifType == PeopleNotificationIdentifier.TYPE_PERSON) { + mInfoItem = createPartialConversationItem(mContext); + } else if (personNotifType >= PeopleNotificationIdentifier.TYPE_FULL_PERSON) { mInfoItem = createConversationItem(mContext); } else { mInfoItem = createInfoItem(mContext); @@ -667,6 +669,16 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl R.drawable.ic_settings); } + static NotificationMenuItem createPartialConversationItem(Context context) { + Resources res = context.getResources(); + String infoDescription = res.getString(R.string.notification_menu_gear_description); + PartialConversationInfo infoContent = + (PartialConversationInfo) LayoutInflater.from(context).inflate( + R.layout.partial_conversation_info, null, false); + return new NotificationMenuItem(context, infoDescription, infoContent, + R.drawable.ic_settings); + } + static NotificationMenuItem createInfoItem(Context context) { Resources res = context.getResources(); String infoDescription = res.getString(R.string.notification_menu_gear_description); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java new file mode 100644 index 000000000000..2189b872da43 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java @@ -0,0 +1,376 @@ +/* + * 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.statusbar.notification.row; + +import static android.app.Notification.EXTRA_IS_GROUP_CONVERSATION; +import static android.app.NotificationManager.IMPORTANCE_LOW; +import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; + +import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.IntDef; +import android.app.INotificationManager; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.os.Bundle; +import android.os.Parcelable; +import android.os.RemoteException; +import android.service.notification.StatusBarNotification; +import android.text.TextUtils; +import android.transition.ChangeBounds; +import android.transition.Fade; +import android.transition.TransitionManager; +import android.transition.TransitionSet; +import android.util.AttributeSet; +import android.view.View; +import android.view.accessibility.AccessibilityEvent; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Dependency; +import com.android.systemui.R; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; + +import java.lang.annotation.Retention; +import java.util.List; +import java.util.Set; + +/** + * The guts of a conversation notification that doesn't use valid shortcuts that is revealed when + * performing a long press. + */ +public class PartialConversationInfo extends LinearLayout implements + NotificationGuts.GutsContent { + private static final String TAG = "PartialConvoGuts"; + + private INotificationManager mINotificationManager; + private PackageManager mPm; + private String mPackageName; + private String mAppName; + private int mAppUid; + private String mDelegatePkg; + private NotificationChannel mNotificationChannel; + private StatusBarNotification mSbn; + private boolean mIsDeviceProvisioned; + private boolean mIsNonBlockable; + private Set<NotificationChannel> mUniqueChannelsInRow; + private Drawable mPkgIcon; + + private @Action int mSelectedAction = -1; + private boolean mPressedApply; + private boolean mPresentingChannelEditorDialog = false; + + private NotificationInfo.OnSettingsClickListener mOnSettingsClickListener; + private NotificationGuts mGutsContainer; + private ChannelEditorDialogController mChannelEditorDialogController; + + @VisibleForTesting + boolean mSkipPost = false; + + @Retention(SOURCE) + @IntDef({ACTION_SETTINGS}) + private @interface Action {} + static final int ACTION_SETTINGS = 5; + + private OnClickListener mOnDone = v -> { + mPressedApply = true; + closeControls(v, true); + }; + + public PartialConversationInfo(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public void bindNotification( + PackageManager pm, + INotificationManager iNotificationManager, + String pkg, + NotificationChannel notificationChannel, + Set<NotificationChannel> uniqueChannelsInRow, + NotificationEntry entry, + NotificationInfo.OnSettingsClickListener onSettingsClick, + boolean isDeviceProvisioned, + boolean isNonBlockable) { + mSelectedAction = -1; + mINotificationManager = iNotificationManager; + mPackageName = pkg; + mSbn = entry.getSbn(); + mPm = pm; + mAppName = mPackageName; + mOnSettingsClickListener = onSettingsClick; + mNotificationChannel = notificationChannel; + mAppUid = mSbn.getUid(); + mDelegatePkg = mSbn.getOpPkg(); + mIsDeviceProvisioned = isDeviceProvisioned; + mIsNonBlockable = isNonBlockable; + mChannelEditorDialogController = Dependency.get(ChannelEditorDialogController.class); + mUniqueChannelsInRow = uniqueChannelsInRow; + + bindHeader(); + bindActions(); + + View turnOffButton = findViewById(R.id.turn_off_notifications); + turnOffButton.setOnClickListener(getTurnOffNotificationsClickListener()); + turnOffButton.setVisibility(turnOffButton.hasOnClickListeners() && !mIsNonBlockable + ? VISIBLE : GONE); + + View done = findViewById(R.id.done); + done.setOnClickListener(mOnDone); + } + + private void bindActions() { + final View settingsButton = findViewById(R.id.info); + settingsButton.setOnClickListener(getSettingsOnClickListener()); + settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE); + + TextView msg = findViewById(R.id.non_configurable_text); + msg.setText(getResources().getString(R.string.no_shortcut, mAppName)); + } + + private void bindHeader() { + bindConversationDetails(); + + // Delegate + bindDelegate(); + } + + private OnClickListener getSettingsOnClickListener() { + if (mAppUid >= 0 && mOnSettingsClickListener != null && mIsDeviceProvisioned) { + final int appUidF = mAppUid; + return ((View view) -> { + mOnSettingsClickListener.onClick(view, mNotificationChannel, appUidF); + }); + } + return null; + } + + private OnClickListener getTurnOffNotificationsClickListener() { + return ((View view) -> { + if (!mPresentingChannelEditorDialog && mChannelEditorDialogController != null) { + mPresentingChannelEditorDialog = true; + + mChannelEditorDialogController.prepareDialogForApp(mAppName, mPackageName, mAppUid, + mUniqueChannelsInRow, mPkgIcon, mOnSettingsClickListener); + mChannelEditorDialogController.setOnFinishListener(() -> { + mPresentingChannelEditorDialog = false; + closeControls(this, false); + }); + mChannelEditorDialogController.show(); + } + }); + } + + private void bindConversationDetails() { + final TextView channelName = findViewById(R.id.parent_channel_name); + channelName.setText(mNotificationChannel.getName()); + + bindGroup(); + bindName(); + bindPackage(); + bindIcon(); + } + + private void bindName() { + TextView name = findViewById(R.id.name); + Bundle extras = mSbn.getNotification().extras; + String nameString = extras.getString(Notification.EXTRA_CONVERSATION_TITLE); + if (TextUtils.isEmpty(nameString)) { + nameString = extras.getString(Notification.EXTRA_TITLE); + } + name.setText(nameString); + } + + private void bindIcon() { + ImageView image = findViewById(R.id.conversation_icon); + if (mSbn.getNotification().extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION, false)) { + // TODO: maybe use a generic group icon, or a composite of recent senders + image.setImageDrawable(mPkgIcon); + } else { + final List<Notification.MessagingStyle.Message> messages = + Notification.MessagingStyle.Message.getMessagesFromBundleArray( + (Parcelable[]) mSbn.getNotification().extras.get( + Notification.EXTRA_MESSAGES)); + + final Notification.MessagingStyle.Message latestMessage = + Notification.MessagingStyle.findLatestIncomingMessage(messages); + Icon personIcon = null; + if (latestMessage != null && latestMessage.getSenderPerson() != null) { + personIcon = latestMessage.getSenderPerson().getIcon(); + } + if (personIcon != null) { + image.setImageIcon(latestMessage.getSenderPerson().getIcon()); + } else { + image.setImageDrawable(mPkgIcon); + } + } + } + + private void bindPackage() { + ApplicationInfo info; + try { + info = mPm.getApplicationInfo( + mPackageName, + PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DISABLED_COMPONENTS + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.MATCH_DIRECT_BOOT_AWARE); + if (info != null) { + mAppName = String.valueOf(mPm.getApplicationLabel(info)); + mPkgIcon = mPm.getApplicationIcon(info); + } + } catch (PackageManager.NameNotFoundException e) { + mPkgIcon = mPm.getDefaultActivityIcon(); + } + ((TextView) findViewById(R.id.pkg_name)).setText(mAppName); + } + + private void bindDelegate() { + TextView delegateView = findViewById(R.id.delegate_name); + + if (!TextUtils.equals(mPackageName, mDelegatePkg)) { + // this notification was posted by a delegate! + delegateView.setVisibility(View.VISIBLE); + } else { + delegateView.setVisibility(View.GONE); + } + } + + private void bindGroup() { + // Set group information if this channel has an associated group. + CharSequence groupName = null; + if (mNotificationChannel != null && mNotificationChannel.getGroup() != null) { + try { + final NotificationChannelGroup notificationChannelGroup = + mINotificationManager.getNotificationChannelGroupForPackage( + mNotificationChannel.getGroup(), mPackageName, mAppUid); + if (notificationChannelGroup != null) { + groupName = notificationChannelGroup.getName(); + } + } catch (RemoteException e) { + } + } + TextView groupNameView = findViewById(R.id.group_name); + View groupDivider = findViewById(R.id.group_divider); + if (groupName != null) { + groupNameView.setText(groupName); + groupNameView.setVisibility(VISIBLE); + groupDivider.setVisibility(VISIBLE); + } else { + groupNameView.setVisibility(GONE); + groupDivider.setVisibility(GONE); + } + } + + @Override + public boolean post(Runnable action) { + if (mSkipPost) { + action.run(); + return true; + } else { + return super.post(action); + } + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + } + + @Override + public void onFinishedClosing() { + // TODO: do we need to do anything here? + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + if (mGutsContainer != null && + event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { + if (mGutsContainer.isExposed()) { + event.getText().add(mContext.getString( + R.string.notification_channel_controls_opened_accessibility, mAppName)); + } else { + event.getText().add(mContext.getString( + R.string.notification_channel_controls_closed_accessibility, mAppName)); + } + } + } + + /** + * Closes the controls and commits the updated importance values (indirectly). + * + * <p><b>Note,</b> this will only get called once the view is dismissing. This means that the + * user does not have the ability to undo the action anymore. + */ + @VisibleForTesting + void closeControls(View v, boolean save) { + int[] parentLoc = new int[2]; + int[] targetLoc = new int[2]; + mGutsContainer.getLocationOnScreen(parentLoc); + v.getLocationOnScreen(targetLoc); + final int centerX = v.getWidth() / 2; + final int centerY = v.getHeight() / 2; + final int x = targetLoc[0] - parentLoc[0] + centerX; + final int y = targetLoc[1] - parentLoc[1] + centerY; + mGutsContainer.closeControls(x, y, save, false /* force */); + } + + @Override + public void setGutsParent(NotificationGuts guts) { + mGutsContainer = guts; + } + + @Override + public boolean willBeRemoved() { + return false; + } + + @Override + public boolean shouldBeSaved() { + return mPressedApply; + } + + @Override + public View getContentView() { + return this; + } + + @Override + public boolean handleCloseControls(boolean save, boolean force) { + return false; + } + + @Override + public int getActualHeight() { + return getHeight(); + } + + @VisibleForTesting + public boolean isAnimating() { + return false; + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java index 3847028a25df..dbf40e467c95 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java @@ -386,7 +386,10 @@ public class NotificationConversationInfoTest extends SysuiTestCase { applicationInfo); when(mMockPackageManager.getApplicationLabel(any())).thenReturn("Other"); - NotificationEntry entry = new NotificationEntryBuilder().setSbn(mSbn).build(); + NotificationEntry entry = new NotificationEntryBuilder() + .setSbn(mSbn) + .setShortcutInfo(mShortcutInfo) + .build(); mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java new file mode 100644 index 000000000000..c390e3933d7a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java @@ -0,0 +1,397 @@ +/* + * 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.statusbar.notification.row; + +import static android.app.Notification.EXTRA_IS_GROUP_CONVERSATION; +import static android.app.NotificationManager.IMPORTANCE_LOW; +import static android.print.PrintManager.PRINT_SPOOLER_PACKAGE_NAME; +import static android.view.View.GONE; +import static android.view.View.VISIBLE; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.app.INotificationManager; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; +import android.app.Person; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.os.UserHandle; +import android.service.notification.StatusBarNotification; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.internal.logging.MetricsLogger; +import com.android.systemui.Dependency; +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CountDownLatch; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class PartialConversationInfoTest extends SysuiTestCase { + private static final String TEST_PACKAGE_NAME = "test_package"; + private static final String TEST_SYSTEM_PACKAGE_NAME = PRINT_SPOOLER_PACKAGE_NAME; + private static final int TEST_UID = 1; + private static final String TEST_CHANNEL = "test_channel"; + private static final String TEST_CHANNEL_NAME = "TEST CHANNEL NAME"; + + private TestableLooper mTestableLooper; + private PartialConversationInfo mInfo; + private NotificationChannel mNotificationChannel; + private NotificationChannel mDefaultNotificationChannel; + private Set<NotificationChannel> mNotificationChannelSet = new HashSet<>(); + private Set<NotificationChannel> mDefaultNotificationChannelSet = new HashSet<>(); + private StatusBarNotification mSbn; + private NotificationEntry mEntry; + + @Rule + public MockitoRule mockito = MockitoJUnit.rule(); + @Mock + private MetricsLogger mMetricsLogger; + @Mock + private INotificationManager mMockINotificationManager; + @Mock + private PackageManager mMockPackageManager; + + @Mock + private Icon mIcon; + @Mock + private Drawable mDrawable; + + @Before + public void setUp() throws Exception { + mTestableLooper = TestableLooper.get(this); + + mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper()); + mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger); + // Inflate the layout + final LayoutInflater layoutInflater = LayoutInflater.from(mContext); + mInfo = (PartialConversationInfo) layoutInflater.inflate(R.layout.partial_conversation_info, + null); + mInfo.setGutsParent(mock(NotificationGuts.class)); + // Our view is never attached to a window so the View#post methods in NotificationInfo never + // get called. Setting this will skip the post and do the action immediately. + mInfo.mSkipPost = true; + + // PackageManager must return a packageInfo and applicationInfo. + final PackageInfo packageInfo = new PackageInfo(); + packageInfo.packageName = TEST_PACKAGE_NAME; + when(mMockPackageManager.getPackageInfo(eq(TEST_PACKAGE_NAME), anyInt())) + .thenReturn(packageInfo); + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.uid = TEST_UID; // non-zero + when(mMockPackageManager.getApplicationInfo(eq(TEST_PACKAGE_NAME), anyInt())).thenReturn( + applicationInfo); + final PackageInfo systemPackageInfo = new PackageInfo(); + systemPackageInfo.packageName = TEST_SYSTEM_PACKAGE_NAME; + when(mMockPackageManager.getPackageInfo(eq(TEST_SYSTEM_PACKAGE_NAME), anyInt())) + .thenReturn(systemPackageInfo); + when(mMockPackageManager.getPackageInfo(eq("android"), anyInt())) + .thenReturn(packageInfo); + + // Package has one channel by default. + when(mMockINotificationManager.getNumNotificationChannelsForPackage( + eq(TEST_PACKAGE_NAME), eq(TEST_UID), anyBoolean())).thenReturn(1); + + when(mIcon.loadDrawable(any())).thenReturn(mDrawable); + + // Some test channels. + mNotificationChannel = new NotificationChannel( + TEST_CHANNEL, TEST_CHANNEL_NAME, IMPORTANCE_LOW); + mNotificationChannelSet.add(mNotificationChannel); + mDefaultNotificationChannel = new NotificationChannel( + NotificationChannel.DEFAULT_CHANNEL_ID, TEST_CHANNEL_NAME, + IMPORTANCE_LOW); + mDefaultNotificationChannelSet.add(mDefaultNotificationChannel); + mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0, + new Notification(), UserHandle.CURRENT, null, 0); + mEntry = new NotificationEntryBuilder().setSbn(mSbn).build(); + } + + @Test + public void testBindNotification_SetsTextApplicationName() throws Exception { + when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name"); + mInfo.bindNotification( + mMockPackageManager, + mMockINotificationManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mNotificationChannelSet, + mEntry, + null, + true, + false); + final TextView textView = mInfo.findViewById(R.id.pkg_name); + assertTrue(textView.getText().toString().contains("App Name")); + assertEquals(VISIBLE, mInfo.findViewById(R.id.header).getVisibility()); + } + + @Test + public void testBindNotification_groupSetsPackageIcon() { + mEntry.getSbn().getNotification().extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, true); + final Drawable iconDrawable = mock(Drawable.class); + when(mMockPackageManager.getApplicationIcon(any(ApplicationInfo.class))) + .thenReturn(iconDrawable); + mInfo.bindNotification( + mMockPackageManager, + mMockINotificationManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mNotificationChannelSet, + mEntry, + null, + true, + false); + final ImageView iconView = mInfo.findViewById(R.id.conversation_icon); + assertEquals(iconDrawable, iconView.getDrawable()); + } + + @Test + public void testBindNotification_notGroupSetsMessageIcon() { + Notification n = new Notification.Builder(mContext, TEST_CHANNEL_NAME) + .setStyle(new Notification.MessagingStyle( + new Person.Builder().setName("me").build()) + .addMessage(new Notification.MessagingStyle.Message("hello", 0, + new Person.Builder().setName("friend").setIcon(mIcon).build()))) + .build(); + mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0, + n, UserHandle.CURRENT, null, 0); + mEntry.setSbn(mSbn); + mEntry.getSbn().getNotification().extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, false); + mInfo.bindNotification( + mMockPackageManager, + mMockINotificationManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mNotificationChannelSet, + mEntry, + null, + true, + false); + final ImageView iconView = mInfo.findViewById(R.id.conversation_icon); + assertEquals(mDrawable.hashCode() + "", mDrawable, iconView.getDrawable()); + } + + @Test + public void testBindNotification_noDelegate() { + mInfo.bindNotification( + mMockPackageManager, + mMockINotificationManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mNotificationChannelSet, + mEntry, + null, + true, + false); + final TextView nameView = mInfo.findViewById(R.id.delegate_name); + assertEquals(GONE, nameView.getVisibility()); + final TextView dividerView = mInfo.findViewById(R.id.group_divider); + assertEquals(GONE, dividerView.getVisibility()); + } + + @Test + public void testBindNotification_delegate() throws Exception { + mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, "other", 0, null, TEST_UID, 0, + new Notification(), UserHandle.CURRENT, null, 0); + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.uid = 7; // non-zero + when(mMockPackageManager.getApplicationInfo(eq("other"), anyInt())).thenReturn( + applicationInfo); + when(mMockPackageManager.getApplicationLabel(any())).thenReturn("Other"); + + NotificationEntry entry = new NotificationEntryBuilder().setSbn(mSbn).build(); + mInfo.bindNotification( + mMockPackageManager, + mMockINotificationManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mNotificationChannelSet, + entry, + null, + true, + false); + final TextView nameView = mInfo.findViewById(R.id.delegate_name); + assertEquals(VISIBLE, nameView.getVisibility()); + assertTrue(nameView.getText().toString().contains("Proxied")); + } + + @Test + public void testBindNotification_GroupNameHiddenIfNoGroup() throws Exception { + mInfo.bindNotification( + mMockPackageManager, + mMockINotificationManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mNotificationChannelSet, + mEntry, + null, + true, + false); + final TextView groupNameView = mInfo.findViewById(R.id.group_name); + assertEquals(GONE, groupNameView.getVisibility()); + final TextView dividerView = mInfo.findViewById(R.id.group_divider); + assertEquals(GONE, dividerView.getVisibility()); + } + + @Test + public void testBindNotification_SetsGroupNameIfNonNull() throws Exception { + mNotificationChannel.setGroup("test_group_id"); + final NotificationChannelGroup notificationChannelGroup = + new NotificationChannelGroup("test_group_id", "Test Group Name"); + when(mMockINotificationManager.getNotificationChannelGroupForPackage( + eq("test_group_id"), eq(TEST_PACKAGE_NAME), eq(TEST_UID))) + .thenReturn(notificationChannelGroup); + mInfo.bindNotification( + mMockPackageManager, + mMockINotificationManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mNotificationChannelSet, + mEntry, + null, + true, + false); + final TextView groupNameView = mInfo.findViewById(R.id.group_name); + assertEquals(View.VISIBLE, groupNameView.getVisibility()); + assertEquals("Test Group Name", groupNameView.getText()); + final TextView dividerView = mInfo.findViewById(R.id.group_divider); + assertEquals(View.VISIBLE, dividerView.getVisibility()); + } + + @Test + public void testBindNotification_SetsTextChannelName() { + mInfo.bindNotification( + mMockPackageManager, + mMockINotificationManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mNotificationChannelSet, + mEntry, + null, + true, + false); + final TextView textView = mInfo.findViewById(R.id.parent_channel_name); + assertEquals(TEST_CHANNEL_NAME, textView.getText()); + } + + @Test + public void testBindNotification_SetsOnClickListenerForSettings() { + final CountDownLatch latch = new CountDownLatch(1); + mInfo.bindNotification( + mMockPackageManager, + mMockINotificationManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mNotificationChannelSet, + mEntry, + (View v, NotificationChannel c, int appUid) -> { + assertEquals(mNotificationChannel, c); + latch.countDown(); + }, + true, + false); + + final View settingsButton = mInfo.findViewById(R.id.info); + settingsButton.performClick(); + // Verify that listener was triggered. + assertEquals(0, latch.getCount()); + } + + @Test + public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() { + mInfo.bindNotification( + mMockPackageManager, + mMockINotificationManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mNotificationChannelSet, + mEntry, + null, + true, + false); + final View settingsButton = mInfo.findViewById(R.id.info); + assertTrue(settingsButton.getVisibility() != View.VISIBLE); + } + + @Test + public void testBindNotification_SettingsButtonInvisibleWhenDeviceUnprovisioned() { + mInfo.bindNotification( + mMockPackageManager, + mMockINotificationManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mNotificationChannelSet, + mEntry, + (View v, NotificationChannel c, int appUid) -> { + assertEquals(mNotificationChannel, c); + }, + false, + false); + final View settingsButton = mInfo.findViewById(R.id.info); + assertTrue(settingsButton.getVisibility() != View.VISIBLE); + } + + @Test + public void testBindNotification_whenAppUnblockable() { + mInfo.bindNotification( + mMockPackageManager, + mMockINotificationManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mNotificationChannelSet, + mEntry, + null, + true, + true); + + assertEquals(GONE, + mInfo.findViewById(R.id.turn_off_notifications).getVisibility()); + } +} diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java index 83ca69956033..2f60e426245d 100644 --- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java +++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java @@ -47,7 +47,7 @@ public class NotificationChannelExtractor implements NotificationSignalExtractor NotificationChannel updatedChannel = mConfig.getConversationNotificationChannel( record.getSbn().getPackageName(), record.getSbn().getUid(), record.getChannel().getId(), - record.getSbn().getShortcutId(mContext), true, false); + record.getSbn().getShortcutId(), true, false); record.updateNotificationChannel(updatedChannel); return null; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 6c3177fe253a..2f76a1f9e246 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2718,12 +2718,12 @@ public class NotificationManagerService extends SystemService { } return text == null ? null : String.valueOf(text); } - + protected void maybeRegisterMessageSent(NotificationRecord r) { Context appContext = r.getSbn().getPackageContext(getContext()); - Notification.Builder nb = + Notification.Builder nb = Notification.Builder.recoverBuilder(appContext, r.getNotification()); - if (nb.getStyle() instanceof Notification.MessagingStyle) { + if (nb.getStyle() instanceof Notification.MessagingStyle && r.getShortcutInfo() == null) { mPreferencesHelper.setMessageSent(r.getSbn().getPackageName(), r.getUid()); handleSavePolicyFile(); } @@ -5627,7 +5627,7 @@ public class NotificationManagerService extends SystemService { if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) { channelId = (new Notification.TvExtender(notification)).getChannelId(); } - String shortcutId = n.getShortcutId(getContext()); + String shortcutId = n.getShortcutId(); final NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel( pkg, notificationUid, channelId, shortcutId, true /* parent ok */, false /* includeDeleted */); diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index e45b41df38b2..8e3de1598275 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -1386,10 +1386,6 @@ public final class NotificationRecord { || !Notification.MessagingStyle.class.equals(notification.getNotificationStyle())) { return false; } - if (mShortcutInfo == null && Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 0) == 1) { - return false; - } if (mIsNotConversationOverride) { return false; } diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 6d7b410b0b99..a4b99b376b30 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -116,7 +116,7 @@ public class PreferencesHelper implements RankingConfig { private static final String ATT_ENABLED = "enabled"; private static final String ATT_USER_ALLOWED = "allowed"; private static final String ATT_HIDE_SILENT = "hide_gentle"; - private static final String ATT_SENT_MESSAGE = "sent_msg"; + private static final String ATT_SENT_MESSAGE = "sent_invalid_msg"; private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT; private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE; @@ -194,8 +194,6 @@ public class PreferencesHelper implements RankingConfig { updateBadgingEnabled(); updateBubblesEnabled(); syncChannelsBypassingDnd(mContext.getUserId()); - mAllowInvalidShortcuts = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 0) == 0; } public void readXml(XmlPullParser parser, boolean forRestore, int userId) @@ -1313,7 +1311,9 @@ public class PreferencesHelper implements RankingConfig { int N = r.channels.size(); for (int i = 0; i < N; i++) { final NotificationChannel nc = r.channels.valueAt(i); - if (!TextUtils.isEmpty(nc.getConversationId()) && !nc.isDeleted()) { + if (!TextUtils.isEmpty(nc.getConversationId()) + && !nc.isDeleted() + && !nc.isDemoted()) { ConversationChannelWrapper conversation = new ConversationChannelWrapper(); conversation.setPkg(r.pkg); conversation.setUid(r.uid); diff --git a/services/core/java/com/android/server/notification/ShortcutHelper.java b/services/core/java/com/android/server/notification/ShortcutHelper.java index 13892ba0e480..94f69e9c73b3 100644 --- a/services/core/java/com/android/server/notification/ShortcutHelper.java +++ b/services/core/java/com/android/server/notification/ShortcutHelper.java @@ -152,9 +152,13 @@ public class ShortcutHelper { if (shortcutInfo == null || !shortcutInfo.isLongLived() || !shortcutInfo.isEnabled()) { return false; } - return mShortcutServiceInternal.isSharingShortcut(callingUserId, "android", + // TODO (b/155016294) uncomment when sharing shortcuts are required + /* + mShortcutServiceInternal.isSharingShortcut(callingUserId, "android", shortcutInfo.getPackage(), shortcutInfo.getId(), shortcutInfo.getUserId(), SHARING_FILTER); + */ + return true; } /** diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java index a23ade68b344..77ce2f032a09 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java @@ -76,8 +76,6 @@ public class NotificationChannelExtractorTest extends UiServiceTestCase { @Test public void testInvalidShortcutFlagEnabled_looksUpCorrectChannel() { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 0); NotificationChannelExtractor extractor = new NotificationChannelExtractor(); extractor.setConfig(mConfig); @@ -96,7 +94,7 @@ public class NotificationChannelExtractorTest extends UiServiceTestCase { NotificationChannel updatedChannel = new NotificationChannel("a", "", IMPORTANCE_HIGH); when(mConfig.getConversationNotificationChannel( - any(), anyInt(), eq("a"), eq(r.getSbn().getShortcutId(mContext)), + any(), anyInt(), eq("a"), eq(r.getSbn().getShortcutId()), eq(true), eq(false))) .thenReturn(updatedChannel); @@ -106,8 +104,6 @@ public class NotificationChannelExtractorTest extends UiServiceTestCase { @Test public void testInvalidShortcutFlagDisabled_looksUpCorrectChannel() { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 1); NotificationChannelExtractor extractor = new NotificationChannelExtractor(); extractor.setConfig(mConfig); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 41748b8a893b..d5ecfeb55e95 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -6589,14 +6589,45 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testRecordMessages() throws RemoteException { + public void testRecordMessages_invalidMsg() throws RemoteException { NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, - "testRecordMessages"); + "testRecordMessages_invalidMsg"); mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); assertTrue(mBinderService.hasSentMessage(PKG, mUid)); } + + @Test + public void testRecordMessages_validMsg() throws RemoteException { + // Messaging notification with shortcut info + Notification.BubbleMetadata metadata = + new Notification.BubbleMetadata.Builder("id").build(); + Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */, + null /* groupKey */, false /* isSummary */); + nb.setShortcutId("id"); + nb.setBubbleMetadata(metadata); + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, + "testRecordMessages_validMsg", mUid, 0, nb.build(), new UserHandle(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + // Pretend the shortcut exists + List<ShortcutInfo> shortcutInfos = new ArrayList<>(); + ShortcutInfo info = mock(ShortcutInfo.class); + when(info.getPackage()).thenReturn(PKG); + when(info.getId()).thenReturn("id"); + when(info.getUserId()).thenReturn(USER_SYSTEM); + when(info.isLongLived()).thenReturn(true); + when(info.isEnabled()).thenReturn(true); + shortcutInfos.add(info); + when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcutInfos); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); + waitForIdle(); + + assertFalse(mBinderService.hasSentMessage(PKG, mUid)); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java index 3139bfaaf1f5..9f593ce42741 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java @@ -123,8 +123,6 @@ public class NotificationRecordTest extends UiServiceTestCase { when(mMockContext.getResources()).thenReturn(getContext().getResources()); when(mMockContext.getPackageManager()).thenReturn(mPm); when(mMockContext.getContentResolver()).thenReturn(mContentResolver); - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 1); ApplicationInfo appInfo = new ApplicationInfo(); appInfo.targetSdkVersion = Build.VERSION_CODES.O; when(mMockContext.getApplicationInfo()).thenReturn(appInfo); @@ -1127,18 +1125,7 @@ public class NotificationRecordTest extends UiServiceTestCase { } @Test - public void testIsConversation_nullShortcut() { - StatusBarNotification sbn = getMessagingStyleNotification(); - NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); - record.setShortcutInfo(null); - - assertFalse(record.isConversation()); - } - - @Test - public void testIsConversation_bypassShortcutFlagEnabled() { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 0); + public void testIsConversation_noShortcut() { StatusBarNotification sbn = getMessagingStyleNotification(); NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); record.setShortcutInfo(null); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index e11392bde993..4320f1c3c896 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -3023,31 +3023,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testPlaceholderConversationId_shortcutNotRequired() throws Exception { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 0); - - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger, - mAppOpsManager); - - final String xml = "<ranking version=\"1\">\n" - + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" - + "<channel id=\"id\" name=\"hi\" importance=\"3\" conv_id=\"foo:placeholder_id\"/>" - + "</package>" - + "</ranking>"; - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())), - null); - parser.nextTag(); - mHelper.readXml(parser, false, UserHandle.USER_ALL); - - assertNotNull(mHelper.getNotificationChannel(PKG_O, UID_O, "id", true)); - } - - @Test public void testPlaceholderConversationId_shortcutRequired() throws Exception { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 1); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger, mAppOpsManager); @@ -3067,8 +3043,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testNormalConversationId_shortcutRequired() throws Exception { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 1); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger, mAppOpsManager); @@ -3088,8 +3062,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testNoConversationId_shortcutRequired() throws Exception { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.REQUIRE_SHORTCUTS_FOR_CONVERSATIONS, 1); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger, mAppOpsManager); @@ -3276,6 +3248,19 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test + public void testGetConversations_noDemoted() { + NotificationChannel parent = new NotificationChannel("parent", "p", 1); + mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false); + NotificationChannel channel = + new NotificationChannel("convo", "convo", IMPORTANCE_DEFAULT); + channel.setConversationId("parent", "convo"); + channel.setDemoted(true); + mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false); + + assertThat(mHelper.getConversations(PKG_O, UID_O)).isEmpty(); + } + + @Test public void testGetConversations() { NotificationChannelGroup group = new NotificationChannelGroup("acct", "account_name"); mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java index 3095c87ba2f6..eb2d9fed197f 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java @@ -40,6 +40,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.server.UiServiceTestCase; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -184,6 +185,7 @@ public class ShortcutHelperTest extends UiServiceTestCase { assertThat(mShortcutHelper.getValidShortcutInfo("a", "p", UserHandle.SYSTEM)).isNull(); } + @Ignore("b/155016294") @Test public void testGetValidShortcutInfo_notSharingShortcut() { ShortcutInfo si = mock(ShortcutInfo.class); @@ -229,8 +231,9 @@ public class ShortcutHelperTest extends UiServiceTestCase { ArrayList<ShortcutInfo> shortcuts = new ArrayList<>(); shortcuts.add(si); when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcuts); - when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(), - anyString(), anyInt(), any())).thenReturn(true); + // TODO: b/155016294 + //when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(), + // anyString(), anyInt(), any())).thenReturn(true); assertThat(mShortcutHelper.getValidShortcutInfo("a", "p", UserHandle.SYSTEM)).isSameAs(si); } |