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