1 /*
2  * Copyright (C) 2016 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 package android.service.notification;
17 
18 import android.annotation.NonNull;
19 import android.annotation.Nullable;
20 import android.annotation.StringDef;
21 import android.annotation.SystemApi;
22 import android.app.Notification;
23 import android.os.Build;
24 import android.os.Bundle;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 import android.os.UserHandle;
28 
29 import java.lang.annotation.Retention;
30 import java.lang.annotation.RetentionPolicy;
31 
32 /**
33  * Ranking updates from the Assistant.
34  *
35  * The updates are provides as a {@link Bundle} of signals, using the keys provided in this
36  * class.
37  * Each {@code KEY} specifies what type of data it supports and what kind of Adjustment it
38  * realizes on the notification rankings.
39  *
40  * Notifications affected by the Adjustment will be re-ranked if necessary.
41  *
42  * @hide
43  */
44 @SystemApi
45 public final class Adjustment implements Parcelable {
46     private final String mPackage;
47     private final String mKey;
48     private final CharSequence mExplanation;
49     private final Bundle mSignals;
50     private final int mUser;
51     @Nullable private String mIssuer;
52 
53     /** @hide */
54     @StringDef (prefix = { "KEY_" }, value = {
55             KEY_PEOPLE,
56             KEY_SNOOZE_CRITERIA,
57             KEY_GROUP_KEY,
58             KEY_USER_SENTIMENT,
59             KEY_CONTEXTUAL_ACTIONS,
60             KEY_TEXT_REPLIES,
61             KEY_IMPORTANCE,
62             KEY_IMPORTANCE_PROPOSAL,
63             KEY_SENSITIVE_CONTENT,
64             KEY_RANKING_SCORE,
65             KEY_NOT_CONVERSATION
66     })
67     @Retention(RetentionPolicy.SOURCE)
68     public @interface Keys {}
69 
70     /**
71      * Data type: ArrayList of {@code String}, where each is a representation of a
72      * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
73      * See {@link android.app.Notification.Builder#addPerson(String)}.
74      * @hide
75      */
76     @SystemApi
77     public static final String KEY_PEOPLE = "key_people";
78 
79     /**
80      * Parcelable {@code ArrayList} of {@link SnoozeCriterion}. These criteria may be visible to
81      * users. If a user chooses to snooze a notification until one of these criterion, the
82      * assistant will be notified via
83      * {@link NotificationAssistantService#onNotificationSnoozedUntilContext}.
84      */
85     public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
86 
87     /**
88      * Data type: String. Used to change what {@link Notification#getGroup() group} a notification
89      * belongs to.
90      * @hide
91      */
92     public static final String KEY_GROUP_KEY = "key_group_key";
93 
94     /**
95      * Data type: int, one of {@link NotificationListenerService.Ranking#USER_SENTIMENT_POSITIVE},
96      * {@link NotificationListenerService.Ranking#USER_SENTIMENT_NEUTRAL},
97      * {@link NotificationListenerService.Ranking#USER_SENTIMENT_NEGATIVE}. Used to express how
98      * a user feels about notifications in the same {@link android.app.NotificationChannel} as
99      * the notification represented by {@link #getKey()}.
100      */
101     public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
102 
103     /**
104      * Data type: ArrayList of {@link android.app.Notification.Action}.
105      * Used to suggest contextual actions for a notification.
106      *
107      * @see Notification.Action.Builder#setContextual(boolean)
108      */
109     public static final String KEY_CONTEXTUAL_ACTIONS = "key_contextual_actions";
110 
111     /**
112      * Data type: ArrayList of {@link CharSequence}.
113      * Used to suggest smart replies for a notification.
114      */
115     public static final String KEY_TEXT_REPLIES = "key_text_replies";
116 
117     /**
118      * Data type: int, one of importance values e.g.
119      * {@link android.app.NotificationManager#IMPORTANCE_MIN}.
120      *
121      * <p> If used from
122      * {@link NotificationAssistantService#onNotificationEnqueued(StatusBarNotification)}, and
123      * received before the notification is posted, it can block a notification from appearing or
124      * silence it. Importance adjustments received too late from
125      * {@link NotificationAssistantService#onNotificationEnqueued(StatusBarNotification)} will be
126      * ignored.
127      * </p>
128      * <p>If used from
129      * {@link NotificationAssistantService#adjustNotification(Adjustment)}, it can
130      * visually demote or cancel a notification, but use this with care if they notification was
131      * recently posted because the notification may already have made noise.
132      * </p>
133      */
134     public static final String KEY_IMPORTANCE = "key_importance";
135 
136     /**
137      * Weaker than {@link #KEY_IMPORTANCE}, this adjustment suggests an importance rather than
138      * mandates an importance change.
139      *
140      * A notification listener can interpet this suggestion to show the user a prompt to change
141      * notification importance for the notification (or type, or app) moving forward.
142      *
143      * Data type: int, one of importance values e.g.
144      * {@link android.app.NotificationManager#IMPORTANCE_MIN}.
145      */
146     public static final String KEY_IMPORTANCE_PROPOSAL = "key_importance_proposal";
147 
148     /**
149      * Data type: boolean, when true it suggests that the content text of this notification is
150      * sensitive. The system uses this information to improve privacy around the notification
151      * content. In {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, sensitive notification content is
152      * redacted from updates to most {@link NotificationListenerService
153      * NotificationListenerServices}. Also if an app posts a sensitive notification while
154      * {@link android.media.projection.MediaProjection screen-sharing} is active, that app's windows
155      * are blocked from screen-sharing and a {@link android.widget.Toast Toast} is shown to inform
156      * the user about this.
157      */
158     public static final String KEY_SENSITIVE_CONTENT = "key_sensitive_content";
159 
160     /**
161      * Data type: float, a ranking score from 0 (lowest) to 1 (highest).
162      * Used to rank notifications inside that fall under the same classification (i.e. alerting,
163      * silenced).
164      */
165     public static final String KEY_RANKING_SCORE = "key_ranking_score";
166 
167     /**
168      * Data type: boolean, when true it suggests this is NOT a conversation notification.
169      * @hide
170      */
171     @SystemApi
172     public static final String KEY_NOT_CONVERSATION = "key_not_conversation";
173 
174     /**
175      * Create a notification adjustment.
176      *
177      * @param pkg The package of the notification.
178      * @param key The notification key.
179      * @param signals A bundle of signals that should inform notification display, ordering, and
180      *                interruptiveness.
181      * @param explanation A human-readable justification for the adjustment.
182      * @hide
183      */
184     @SystemApi
Adjustment(String pkg, String key, Bundle signals, CharSequence explanation, int user)185     public Adjustment(String pkg, String key, Bundle signals, CharSequence explanation, int user) {
186         mPackage = pkg;
187         mKey = key;
188         mSignals = signals;
189         mExplanation = explanation;
190         mUser = user;
191     }
192 
193     /**
194      * Create a notification adjustment.
195      *
196      * @param pkg The package of the notification.
197      * @param key The notification key.
198      * @param signals A bundle of signals that should inform notification display, ordering, and
199      *                interruptiveness.
200      * @param explanation A human-readable justification for the adjustment.
201      * @param userHandle User handle for for whose the adjustments will be applied.
202      */
Adjustment(@onNull String pkg, @NonNull String key, @NonNull Bundle signals, @NonNull CharSequence explanation, @NonNull UserHandle userHandle)203     public Adjustment(@NonNull String pkg, @NonNull String key, @NonNull Bundle signals,
204             @NonNull CharSequence explanation,
205             @NonNull UserHandle userHandle) {
206         mPackage = pkg;
207         mKey = key;
208         mSignals = signals;
209         mExplanation = explanation;
210         mUser = userHandle.getIdentifier();
211     }
212 
213     /**
214      * @hide
215      */
216     @SystemApi
Adjustment(Parcel in)217     protected Adjustment(Parcel in) {
218         if (in.readInt() == 1) {
219             mPackage = in.readString();
220         } else {
221             mPackage = null;
222         }
223         if (in.readInt() == 1) {
224             mKey = in.readString();
225         } else {
226             mKey = null;
227         }
228         if (in.readInt() == 1) {
229             mExplanation = in.readCharSequence();
230         } else {
231             mExplanation = null;
232         }
233         mSignals = in.readBundle();
234         mUser = in.readInt();
235         mIssuer = in.readString();
236     }
237 
238     public static final @android.annotation.NonNull Creator<Adjustment> CREATOR = new Creator<Adjustment>() {
239         @Override
240         public Adjustment createFromParcel(Parcel in) {
241             return new Adjustment(in);
242         }
243 
244         @Override
245         public Adjustment[] newArray(int size) {
246             return new Adjustment[size];
247         }
248     };
249 
getPackage()250     public @NonNull String getPackage() {
251         return mPackage;
252     }
253 
getKey()254     public @NonNull String getKey() {
255         return mKey;
256     }
257 
getExplanation()258     public @NonNull CharSequence getExplanation() {
259         return mExplanation;
260     }
261 
getSignals()262     public @NonNull Bundle getSignals() {
263         return mSignals;
264     }
265 
266     /** @hide */
267     @SystemApi
getUser()268     public int getUser() {
269         return mUser;
270     }
271 
getUserHandle()272     public @NonNull UserHandle getUserHandle() {
273         return UserHandle.of(mUser);
274     }
275 
276     @Override
describeContents()277     public int describeContents() {
278         return 0;
279     }
280 
281     @Override
writeToParcel(Parcel dest, int flags)282     public void writeToParcel(Parcel dest, int flags) {
283         if (mPackage != null) {
284             dest.writeInt(1);
285             dest.writeString(mPackage);
286         } else {
287             dest.writeInt(0);
288         }
289         if (mKey != null) {
290             dest.writeInt(1);
291             dest.writeString(mKey);
292         } else {
293             dest.writeInt(0);
294         }
295         if (mExplanation != null) {
296             dest.writeInt(1);
297             dest.writeCharSequence(mExplanation);
298         } else {
299             dest.writeInt(0);
300         }
301         dest.writeBundle(mSignals);
302         dest.writeInt(mUser);
303         dest.writeString(mIssuer);
304     }
305 
306     @NonNull
307     @Override
toString()308     public String toString() {
309         return "Adjustment{"
310                 + "mSignals=" + mSignals
311                 + '}';
312     }
313 
314     /** @hide */
setIssuer(@ullable String issuer)315     public void setIssuer(@Nullable String issuer) {
316         mIssuer = issuer;
317     }
318 
319     /** @hide */
getIssuer()320     public @Nullable String getIssuer() {
321         return mIssuer;
322     }
323 }
324