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