1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.systemui.statusbar.notification.row; 18 19 import android.app.INotificationManager; 20 import android.app.NotificationChannel; 21 import android.app.NotificationChannelGroup; 22 import android.content.Context; 23 import android.content.pm.ApplicationInfo; 24 import android.content.pm.PackageManager; 25 import android.graphics.drawable.Drawable; 26 import android.os.RemoteException; 27 import android.service.notification.StatusBarNotification; 28 import android.text.TextUtils; 29 import android.util.AttributeSet; 30 import android.view.View; 31 import android.view.accessibility.AccessibilityEvent; 32 import android.widget.ImageView; 33 import android.widget.LinearLayout; 34 import android.widget.TextView; 35 36 import com.android.internal.annotations.VisibleForTesting; 37 import com.android.systemui.R; 38 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 39 40 import java.util.Set; 41 42 /** 43 * The guts of a conversation notification that doesn't use valid shortcuts that is revealed when 44 * performing a long press. 45 */ 46 public class PartialConversationInfo extends LinearLayout implements 47 NotificationGuts.GutsContent { 48 private static final String TAG = "PartialConvoGuts"; 49 50 private INotificationManager mINotificationManager; 51 private PackageManager mPm; 52 private String mPackageName; 53 private String mAppName; 54 private int mAppUid; 55 private String mDelegatePkg; 56 private NotificationChannel mNotificationChannel; 57 private StatusBarNotification mSbn; 58 private boolean mIsDeviceProvisioned; 59 private boolean mIsNonBlockable; 60 private Set<NotificationChannel> mUniqueChannelsInRow; 61 private Drawable mPkgIcon; 62 63 private boolean mPresentingChannelEditorDialog = false; 64 65 private NotificationInfo.OnSettingsClickListener mOnSettingsClickListener; 66 private NotificationGuts mGutsContainer; 67 private ChannelEditorDialogController mChannelEditorDialogController; 68 69 @VisibleForTesting 70 boolean mSkipPost = false; 71 72 private OnClickListener mOnDone = v -> { 73 mGutsContainer.closeControls(v, /* save= */ false); 74 }; 75 PartialConversationInfo(Context context, AttributeSet attrs)76 public PartialConversationInfo(Context context, AttributeSet attrs) { 77 super(context, attrs); 78 } 79 bindNotification( PackageManager pm, INotificationManager iNotificationManager, ChannelEditorDialogController channelEditorDialogController, String pkg, NotificationChannel notificationChannel, Set<NotificationChannel> uniqueChannelsInRow, NotificationEntry entry, NotificationInfo.OnSettingsClickListener onSettingsClick, boolean isDeviceProvisioned, boolean isNonBlockable)80 public void bindNotification( 81 PackageManager pm, 82 INotificationManager iNotificationManager, 83 ChannelEditorDialogController channelEditorDialogController, 84 String pkg, 85 NotificationChannel notificationChannel, 86 Set<NotificationChannel> uniqueChannelsInRow, 87 NotificationEntry entry, 88 NotificationInfo.OnSettingsClickListener onSettingsClick, 89 boolean isDeviceProvisioned, 90 boolean isNonBlockable) { 91 mINotificationManager = iNotificationManager; 92 mPackageName = pkg; 93 mSbn = entry.getSbn(); 94 mPm = pm; 95 mAppName = mPackageName; 96 mOnSettingsClickListener = onSettingsClick; 97 mNotificationChannel = notificationChannel; 98 mAppUid = mSbn.getUid(); 99 mDelegatePkg = mSbn.getOpPkg(); 100 mIsDeviceProvisioned = isDeviceProvisioned; 101 mIsNonBlockable = isNonBlockable; 102 mChannelEditorDialogController = channelEditorDialogController; 103 mUniqueChannelsInRow = uniqueChannelsInRow; 104 105 bindHeader(); 106 bindActions(); 107 108 View turnOffButton = findViewById(R.id.turn_off_notifications); 109 turnOffButton.setOnClickListener(getTurnOffNotificationsClickListener()); 110 turnOffButton.setVisibility(turnOffButton.hasOnClickListeners() && !mIsNonBlockable 111 ? VISIBLE : GONE); 112 113 View done = findViewById(R.id.done); 114 done.setOnClickListener(mOnDone); 115 done.setAccessibilityDelegate(mGutsContainer.getAccessibilityDelegate()); 116 } 117 bindActions()118 private void bindActions() { 119 final OnClickListener settingsOnClickListener = getSettingsOnClickListener(); 120 final View settingsButton = findViewById(R.id.info); 121 settingsButton.setOnClickListener(settingsOnClickListener); 122 settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE); 123 124 findViewById(R.id.settings_link).setOnClickListener(settingsOnClickListener); 125 126 TextView msg = findViewById(R.id.non_configurable_text); 127 msg.setText(getResources().getString(R.string.no_shortcut, mAppName)); 128 } 129 bindHeader()130 private void bindHeader() { 131 bindPackage(); 132 // Delegate 133 bindDelegate(); 134 } 135 getSettingsOnClickListener()136 private OnClickListener getSettingsOnClickListener() { 137 if (mAppUid >= 0 && mOnSettingsClickListener != null && mIsDeviceProvisioned) { 138 final int appUidF = mAppUid; 139 return ((View view) -> { 140 mOnSettingsClickListener.onClick(view, mNotificationChannel, appUidF); 141 }); 142 } 143 return null; 144 } 145 getTurnOffNotificationsClickListener()146 private OnClickListener getTurnOffNotificationsClickListener() { 147 return ((View view) -> { 148 if (!mPresentingChannelEditorDialog && mChannelEditorDialogController != null) { 149 mPresentingChannelEditorDialog = true; 150 151 mChannelEditorDialogController.prepareDialogForApp(mAppName, mPackageName, mAppUid, 152 mUniqueChannelsInRow, mPkgIcon, mOnSettingsClickListener); 153 mChannelEditorDialogController.setOnFinishListener(() -> { 154 mPresentingChannelEditorDialog = false; 155 mGutsContainer.closeControls(this, false); 156 }); 157 mChannelEditorDialogController.show(); 158 } 159 }); 160 } 161 162 private void bindPackage() { 163 ApplicationInfo info; 164 try { 165 info = mPm.getApplicationInfo( 166 mPackageName, 167 PackageManager.MATCH_UNINSTALLED_PACKAGES 168 | PackageManager.MATCH_DISABLED_COMPONENTS 169 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE 170 | PackageManager.MATCH_DIRECT_BOOT_AWARE); 171 if (info != null) { 172 mAppName = String.valueOf(mPm.getApplicationLabel(info)); 173 mPkgIcon = mPm.getApplicationIcon(info); 174 } 175 } catch (PackageManager.NameNotFoundException e) { 176 mPkgIcon = mPm.getDefaultActivityIcon(); 177 } 178 TextView name = findViewById(R.id.name); 179 name.setText(mAppName); 180 ImageView image = findViewById(R.id.icon); 181 image.setImageDrawable(mPkgIcon); 182 } 183 184 private void bindDelegate() { 185 TextView delegateView = findViewById(R.id.delegate_name); 186 187 if (!TextUtils.equals(mPackageName, mDelegatePkg)) { 188 // this notification was posted by a delegate! 189 delegateView.setVisibility(View.VISIBLE); 190 } else { 191 delegateView.setVisibility(View.GONE); 192 } 193 } 194 195 private void bindGroup() { 196 // Set group information if this channel has an associated group. 197 CharSequence groupName = null; 198 if (mNotificationChannel != null && mNotificationChannel.getGroup() != null) { 199 try { 200 final NotificationChannelGroup notificationChannelGroup = 201 mINotificationManager.getNotificationChannelGroupForPackage( 202 mNotificationChannel.getGroup(), mPackageName, mAppUid); 203 if (notificationChannelGroup != null) { 204 groupName = notificationChannelGroup.getName(); 205 } 206 } catch (RemoteException e) { 207 } 208 } 209 TextView groupNameView = findViewById(R.id.group_name); 210 if (groupName != null) { 211 groupNameView.setText(groupName); 212 groupNameView.setVisibility(VISIBLE); 213 } else { 214 groupNameView.setVisibility(GONE); 215 } 216 } 217 218 @Override 219 public boolean post(Runnable action) { 220 if (mSkipPost) { 221 action.run(); 222 return true; 223 } else { 224 return super.post(action); 225 } 226 } 227 228 @Override 229 protected void onFinishInflate() { 230 super.onFinishInflate(); 231 } 232 233 @Override 234 public void onFinishedClosing() { 235 // TODO: do we need to do anything here? 236 } 237 238 @Override 239 public boolean needsFalsingProtection() { 240 return true; 241 } 242 243 @Override 244 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 245 super.onInitializeAccessibilityEvent(event); 246 if (mGutsContainer != null && 247 event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { 248 if (mGutsContainer.isExposed()) { 249 event.getText().add(mContext.getString( 250 R.string.notification_channel_controls_opened_accessibility, mAppName)); 251 } else { 252 event.getText().add(mContext.getString( 253 R.string.notification_channel_controls_closed_accessibility, mAppName)); 254 } 255 } 256 } 257 258 @Override 259 public void setGutsParent(NotificationGuts guts) { 260 mGutsContainer = guts; 261 } 262 263 @Override 264 public boolean willBeRemoved() { 265 return false; 266 } 267 268 @Override 269 public boolean shouldBeSavedOnClose() { 270 return false; 271 } 272 273 @Override 274 public View getContentView() { 275 return this; 276 } 277 278 @Override 279 public boolean handleCloseControls(boolean save, boolean force) { 280 return false; 281 } 282 283 @Override 284 public int getActualHeight() { 285 return getHeight(); 286 } 287 288 @VisibleForTesting 289 public boolean isAnimating() { 290 return false; 291 } 292 } 293