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.res.R; 38 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 39 40 /** 41 * The guts of a conversation notification that doesn't use valid shortcuts that is revealed when 42 * performing a long press. 43 */ 44 public class PartialConversationInfo extends LinearLayout implements 45 NotificationGuts.GutsContent { 46 private static final String TAG = "PartialConvoGuts"; 47 48 private INotificationManager mINotificationManager; 49 private PackageManager mPm; 50 private String mPackageName; 51 private String mAppName; 52 private int mAppUid; 53 private String mDelegatePkg; 54 private NotificationChannel mNotificationChannel; 55 private StatusBarNotification mSbn; 56 private boolean mIsDeviceProvisioned; 57 private boolean mIsNonBlockable; 58 private Drawable mPkgIcon; 59 60 private boolean mPresentingChannelEditorDialog = false; 61 62 private NotificationInfo.OnSettingsClickListener mOnSettingsClickListener; 63 private NotificationGuts mGutsContainer; 64 private ChannelEditorDialogController mChannelEditorDialogController; 65 66 @VisibleForTesting 67 boolean mSkipPost = false; 68 69 private OnClickListener mOnDone = v -> { 70 mGutsContainer.closeControls(v, /* save= */ false); 71 }; 72 PartialConversationInfo(Context context, AttributeSet attrs)73 public PartialConversationInfo(Context context, AttributeSet attrs) { 74 super(context, attrs); 75 } 76 bindNotification( PackageManager pm, INotificationManager iNotificationManager, ChannelEditorDialogController channelEditorDialogController, String pkg, NotificationChannel notificationChannel, NotificationEntry entry, NotificationInfo.OnSettingsClickListener onSettingsClick, boolean isDeviceProvisioned, boolean isNonBlockable)77 public void bindNotification( 78 PackageManager pm, 79 INotificationManager iNotificationManager, 80 ChannelEditorDialogController channelEditorDialogController, 81 String pkg, 82 NotificationChannel notificationChannel, 83 NotificationEntry entry, 84 NotificationInfo.OnSettingsClickListener onSettingsClick, 85 boolean isDeviceProvisioned, 86 boolean isNonBlockable) { 87 mINotificationManager = iNotificationManager; 88 mPackageName = pkg; 89 mSbn = entry.getSbn(); 90 mPm = pm; 91 mAppName = mPackageName; 92 mOnSettingsClickListener = onSettingsClick; 93 mNotificationChannel = notificationChannel; 94 mAppUid = mSbn.getUid(); 95 mDelegatePkg = mSbn.getOpPkg(); 96 mIsDeviceProvisioned = isDeviceProvisioned; 97 mIsNonBlockable = isNonBlockable; 98 mChannelEditorDialogController = channelEditorDialogController; 99 100 bindHeader(); 101 bindActions(); 102 103 View turnOffButton = findViewById(R.id.turn_off_notifications); 104 turnOffButton.setOnClickListener(getTurnOffNotificationsClickListener()); 105 turnOffButton.setVisibility(turnOffButton.hasOnClickListeners() && !mIsNonBlockable 106 ? VISIBLE : GONE); 107 108 View done = findViewById(R.id.done); 109 done.setOnClickListener(mOnDone); 110 done.setAccessibilityDelegate(mGutsContainer.getAccessibilityDelegate()); 111 } 112 bindActions()113 private void bindActions() { 114 final OnClickListener settingsOnClickListener = getSettingsOnClickListener(); 115 final View settingsButton = findViewById(R.id.info); 116 settingsButton.setOnClickListener(settingsOnClickListener); 117 settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE); 118 119 findViewById(R.id.settings_link).setOnClickListener(settingsOnClickListener); 120 121 TextView msg = findViewById(R.id.non_configurable_text); 122 msg.setText(getResources().getString(R.string.no_shortcut, mAppName)); 123 } 124 bindHeader()125 private void bindHeader() { 126 bindPackage(); 127 // Delegate 128 bindDelegate(); 129 } 130 getSettingsOnClickListener()131 private OnClickListener getSettingsOnClickListener() { 132 if (mAppUid >= 0 && mOnSettingsClickListener != null && mIsDeviceProvisioned) { 133 final int appUidF = mAppUid; 134 return ((View view) -> { 135 mOnSettingsClickListener.onClick(view, mNotificationChannel, appUidF); 136 }); 137 } 138 return null; 139 } 140 getTurnOffNotificationsClickListener()141 private OnClickListener getTurnOffNotificationsClickListener() { 142 return ((View view) -> { 143 if (!mPresentingChannelEditorDialog && mChannelEditorDialogController != null) { 144 mPresentingChannelEditorDialog = true; 145 146 mChannelEditorDialogController.prepareDialogForApp(mAppName, mPackageName, mAppUid, 147 mNotificationChannel, mPkgIcon, mOnSettingsClickListener); 148 mChannelEditorDialogController.setOnFinishListener(() -> { 149 mPresentingChannelEditorDialog = false; 150 mGutsContainer.closeControls(this, false); 151 }); 152 mChannelEditorDialogController.show(); 153 } 154 }); 155 } 156 157 private void bindPackage() { 158 ApplicationInfo info; 159 try { 160 info = mPm.getApplicationInfo( 161 mPackageName, 162 PackageManager.MATCH_UNINSTALLED_PACKAGES 163 | PackageManager.MATCH_DISABLED_COMPONENTS 164 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE 165 | PackageManager.MATCH_DIRECT_BOOT_AWARE); 166 if (info != null) { 167 mAppName = String.valueOf(mPm.getApplicationLabel(info)); 168 mPkgIcon = mPm.getApplicationIcon(info); 169 } 170 } catch (PackageManager.NameNotFoundException e) { 171 mPkgIcon = mPm.getDefaultActivityIcon(); 172 } 173 TextView name = findViewById(R.id.name); 174 name.setText(mAppName); 175 ImageView image = findViewById(R.id.icon); 176 image.setImageDrawable(mPkgIcon); 177 } 178 179 private void bindDelegate() { 180 TextView delegateView = findViewById(R.id.delegate_name); 181 182 if (!TextUtils.equals(mPackageName, mDelegatePkg)) { 183 // this notification was posted by a delegate! 184 delegateView.setVisibility(View.VISIBLE); 185 } else { 186 delegateView.setVisibility(View.GONE); 187 } 188 } 189 190 private void bindGroup() { 191 // Set group information if this channel has an associated group. 192 CharSequence groupName = null; 193 if (mNotificationChannel != null && mNotificationChannel.getGroup() != null) { 194 try { 195 final NotificationChannelGroup notificationChannelGroup = 196 mINotificationManager.getNotificationChannelGroupForPackage( 197 mNotificationChannel.getGroup(), mPackageName, mAppUid); 198 if (notificationChannelGroup != null) { 199 groupName = notificationChannelGroup.getName(); 200 } 201 } catch (RemoteException e) { 202 } 203 } 204 TextView groupNameView = findViewById(R.id.group_name); 205 if (groupName != null) { 206 groupNameView.setText(groupName); 207 groupNameView.setVisibility(VISIBLE); 208 } else { 209 groupNameView.setVisibility(GONE); 210 } 211 } 212 213 @Override 214 public boolean post(Runnable action) { 215 if (mSkipPost) { 216 action.run(); 217 return true; 218 } else { 219 return super.post(action); 220 } 221 } 222 223 @Override 224 protected void onFinishInflate() { 225 super.onFinishInflate(); 226 } 227 228 @Override 229 public void onFinishedClosing() { 230 // TODO: do we need to do anything here? 231 } 232 233 @Override 234 public boolean needsFalsingProtection() { 235 return true; 236 } 237 238 @Override 239 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 240 super.onInitializeAccessibilityEvent(event); 241 if (mGutsContainer != null && 242 event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { 243 if (mGutsContainer.isExposed()) { 244 event.getText().add(mContext.getString( 245 R.string.notification_channel_controls_opened_accessibility, mAppName)); 246 } else { 247 event.getText().add(mContext.getString( 248 R.string.notification_channel_controls_closed_accessibility, mAppName)); 249 } 250 } 251 } 252 253 @Override 254 public void setGutsParent(NotificationGuts guts) { 255 mGutsContainer = guts; 256 } 257 258 @Override 259 public boolean willBeRemoved() { 260 return false; 261 } 262 263 @Override 264 public boolean shouldBeSavedOnClose() { 265 return false; 266 } 267 268 @Override 269 public View getContentView() { 270 return this; 271 } 272 273 @Override 274 public boolean handleCloseControls(boolean save, boolean force) { 275 return false; 276 } 277 278 @Override 279 public int getActualHeight() { 280 return getHeight(); 281 } 282 283 @VisibleForTesting 284 public boolean isAnimating() { 285 return false; 286 } 287 } 288