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