• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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;
18 
19 import static android.app.Flags.lifetimeExtensionRefactor;
20 
21 import android.annotation.NonNull;
22 import android.app.Notification;
23 import android.app.RemoteInputHistoryItem;
24 import android.content.Context;
25 import android.net.Uri;
26 import android.os.Parcelable;
27 import android.service.notification.StatusBarNotification;
28 import android.text.TextUtils;
29 
30 import com.android.internal.annotations.VisibleForTesting;
31 import com.android.systemui.dagger.SysUISingleton;
32 import com.android.systemui.shade.ShadeDisplayAware;
33 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
34 
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import java.util.stream.Stream;
38 
39 import javax.inject.Inject;
40 
41 /**
42  * A helper class which will augment the notifications using arguments and other information
43  * accessible to the entry in order to provide intermediate remote input states.
44  */
45 @SysUISingleton
46 public class RemoteInputNotificationRebuilder {
47 
48     private final Context mContext;
49 
50     @Inject
RemoteInputNotificationRebuilder(@hadeDisplayAware Context context)51     RemoteInputNotificationRebuilder(@ShadeDisplayAware Context context) {
52         mContext = context;
53     }
54 
55     /**
56      * When a smart reply is sent off to the app, we insert the text into the remote input history,
57      * and show a spinner to indicate that the app has yet to respond.
58      */
59     @NonNull
rebuildForSendingSmartReply(NotificationEntry entry, CharSequence reply)60     public StatusBarNotification rebuildForSendingSmartReply(NotificationEntry entry,
61             CharSequence reply) {
62         return rebuildWithRemoteInputInserted(entry, reply,
63                 true /* showSpinner */,
64                 null /* mimeType */, null /* uri */);
65     }
66 
67     /**
68      * When the app cancels a notification in response to a smart reply, we remove the spinner
69      * and leave the previously-added reply.  This is the lifetime-extended appearance of the
70      * notification.
71      */
72     @NonNull
rebuildForCanceledSmartReplies( NotificationEntry entry)73     public StatusBarNotification rebuildForCanceledSmartReplies(
74             NotificationEntry entry) {
75         return rebuildWithExistingReplies(entry);
76     }
77 
78     /**
79      * Rebuilds to include any previously-added remote input replies.
80      * For when the app cancels a notification that has already been lifetime extended.
81      */
82     @NonNull
rebuildWithExistingReplies(NotificationEntry entry)83     public StatusBarNotification rebuildWithExistingReplies(NotificationEntry entry) {
84         return rebuildWithRemoteInputInserted(entry, null /* remoteInputText */,
85                 false /* showSpinner */, null /* mimeType */, null /* uri */);
86     }
87 
88     /**
89      * When the app cancels a notification in response to a remote input reply, we update the
90      * notification with the reply text and/or attachment. This is the lifetime-extended
91      * appearance of the notification.
92      */
93     @NonNull
rebuildForRemoteInputReply(NotificationEntry entry)94     public StatusBarNotification rebuildForRemoteInputReply(NotificationEntry entry) {
95         CharSequence remoteInputText = entry.remoteInputText;
96         if (TextUtils.isEmpty(remoteInputText)) {
97             remoteInputText = entry.remoteInputTextWhenReset;
98         }
99         String remoteInputMimeType = entry.remoteInputMimeType;
100         Uri remoteInputUri = entry.remoteInputUri;
101         StatusBarNotification newSbn = rebuildWithRemoteInputInserted(entry,
102                 remoteInputText, false /* showSpinner */, remoteInputMimeType,
103                 remoteInputUri);
104         return newSbn;
105     }
106 
107     /** Inner method for generating the SBN */
108     @VisibleForTesting
109     @NonNull
rebuildWithRemoteInputInserted(NotificationEntry entry, CharSequence remoteInputText, boolean showSpinner, String mimeType, Uri uri)110     StatusBarNotification rebuildWithRemoteInputInserted(NotificationEntry entry,
111             CharSequence remoteInputText, boolean showSpinner, String mimeType, Uri uri) {
112         StatusBarNotification sbn = entry.getSbn();
113         Notification.Builder b = Notification.Builder
114                 .recoverBuilder(mContext, sbn.getNotification().clone());
115 
116         if (lifetimeExtensionRefactor()) {
117             if (entry.remoteInputs == null) {
118                 entry.remoteInputs = new ArrayList<RemoteInputHistoryItem>();
119             }
120 
121             // Append new remote input information to remoteInputs list
122             if (remoteInputText != null || uri != null) {
123                 RemoteInputHistoryItem newItem = uri != null
124                         ? new RemoteInputHistoryItem(mimeType, uri, remoteInputText)
125                         : new RemoteInputHistoryItem(remoteInputText);
126                 // The list is latest-first, so new elements should be added as the first element.
127                 entry.remoteInputs.add(0, newItem);
128             }
129 
130             // Read the whole remoteInputs list from the entry, then append all of those to the sbn.
131             Parcelable[] oldHistoryItems = sbn.getNotification().extras
132                     .getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
133 
134             RemoteInputHistoryItem[] newHistoryItems = oldHistoryItems != null
135                     ? Stream.concat(
136                             entry.remoteInputs.stream(),
137                             Arrays.stream(oldHistoryItems).map(p -> (RemoteInputHistoryItem) p))
138                     .toArray(RemoteInputHistoryItem[]::new)
139                     : entry.remoteInputs.toArray(RemoteInputHistoryItem[]::new);
140             b.setRemoteInputHistory(newHistoryItems);
141 
142         } else {
143             if (remoteInputText != null || uri != null) {
144                 RemoteInputHistoryItem newItem = uri != null
145                         ? new RemoteInputHistoryItem(mimeType, uri, remoteInputText)
146                         : new RemoteInputHistoryItem(remoteInputText);
147                 Parcelable[] oldHistoryItems = sbn.getNotification().extras
148                         .getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
149                 RemoteInputHistoryItem[] newHistoryItems = oldHistoryItems != null
150                         ? Stream.concat(
151                                 Stream.of(newItem),
152                                 Arrays.stream(oldHistoryItems).map(p -> (RemoteInputHistoryItem) p))
153                         .toArray(RemoteInputHistoryItem[]::new)
154                         : new RemoteInputHistoryItem[]{newItem};
155                 b.setRemoteInputHistory(newHistoryItems);
156             }
157         }
158         b.setShowRemoteInputSpinner(showSpinner);
159         b.setHideSmartReplies(true);
160 
161         Notification newNotification = b.build();
162 
163         // Undo any compatibility view inflation
164         newNotification.contentView = sbn.getNotification().contentView;
165         newNotification.bigContentView = sbn.getNotification().bigContentView;
166         newNotification.headsUpContentView = sbn.getNotification().headsUpContentView;
167 
168         return new StatusBarNotification(
169                 sbn.getPackageName(),
170                 sbn.getOpPkg(),
171                 sbn.getId(),
172                 sbn.getTag(),
173                 sbn.getUid(),
174                 sbn.getInitialPid(),
175                 newNotification,
176                 sbn.getUser(),
177                 sbn.getOverrideGroupKey(),
178                 sbn.getPostTime());
179     }
180 
181 
182 }
183