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