1/*
2 * Copyright (C) 2017 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
17package android.support.v4.app;
18
19import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
20import static android.support.v4.app.NotificationCompat.DEFAULT_SOUND;
21import static android.support.v4.app.NotificationCompat.DEFAULT_VIBRATE;
22import static android.support.v4.app.NotificationCompat.FLAG_GROUP_SUMMARY;
23import static android.support.v4.app.NotificationCompat.GROUP_ALERT_ALL;
24import static android.support.v4.app.NotificationCompat.GROUP_ALERT_CHILDREN;
25import static android.support.v4.app.NotificationCompat.GROUP_ALERT_SUMMARY;
26
27import android.app.Notification;
28import android.os.Build;
29import android.os.Bundle;
30import android.support.annotation.RestrictTo;
31import android.util.SparseArray;
32import android.widget.RemoteViews;
33
34import java.util.ArrayList;
35import java.util.List;
36
37/**
38 * Wrapper around {@link Notification.Builder} that works in a backwards compatible way.
39 *
40 * @hide
41 */
42@RestrictTo(LIBRARY_GROUP)
43class NotificationCompatBuilder implements NotificationBuilderWithBuilderAccessor {
44    private final Notification.Builder mBuilder;
45    private final NotificationCompat.Builder mBuilderCompat;
46
47    // @RequiresApi(16) - uncomment when lint bug is fixed.
48    private RemoteViews mContentView;
49    // @RequiresApi(16) - uncomment when lint bug is fixed.
50    private RemoteViews mBigContentView;
51    // @RequiresApi(16) - uncomment when lint bug is fixed.
52    private final List<Bundle> mActionExtrasList = new ArrayList<>();
53    // @RequiresApi(16) - uncomment when lint bug is fixed.
54    private final Bundle mExtras = new Bundle();
55    // @RequiresApi(20) - uncomment when lint bug is fixed.
56    private int mGroupAlertBehavior;
57    // @RequiresApi(21) - uncomment when lint bug is fixed.
58    private RemoteViews mHeadsUpContentView;
59
60    NotificationCompatBuilder(NotificationCompat.Builder b) {
61        mBuilderCompat = b;
62        if (Build.VERSION.SDK_INT >= 26) {
63            mBuilder = new Notification.Builder(b.mContext, b.mChannelId);
64        } else {
65            mBuilder = new Notification.Builder(b.mContext);
66        }
67        Notification n = b.mNotification;
68        mBuilder.setWhen(n.when)
69                .setSmallIcon(n.icon, n.iconLevel)
70                .setContent(n.contentView)
71                .setTicker(n.tickerText, b.mTickerView)
72                .setSound(n.sound, n.audioStreamType)
73                .setVibrate(n.vibrate)
74                .setLights(n.ledARGB, n.ledOnMS, n.ledOffMS)
75                .setOngoing((n.flags & Notification.FLAG_ONGOING_EVENT) != 0)
76                .setOnlyAlertOnce((n.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0)
77                .setAutoCancel((n.flags & Notification.FLAG_AUTO_CANCEL) != 0)
78                .setDefaults(n.defaults)
79                .setContentTitle(b.mContentTitle)
80                .setContentText(b.mContentText)
81                .setContentInfo(b.mContentInfo)
82                .setContentIntent(b.mContentIntent)
83                .setDeleteIntent(n.deleteIntent)
84                .setFullScreenIntent(b.mFullScreenIntent,
85                        (n.flags & Notification.FLAG_HIGH_PRIORITY) != 0)
86                .setLargeIcon(b.mLargeIcon)
87                .setNumber(b.mNumber)
88                .setProgress(b.mProgressMax, b.mProgress, b.mProgressIndeterminate);
89        if (Build.VERSION.SDK_INT >= 16) {
90            mBuilder.setSubText(b.mSubText)
91                    .setUsesChronometer(b.mUseChronometer)
92                    .setPriority(b.mPriority);
93
94            for (NotificationCompat.Action action : b.mActions) {
95                addAction(action);
96            }
97
98            if (b.mExtras != null) {
99                mExtras.putAll(b.mExtras);
100            }
101            if (Build.VERSION.SDK_INT < 20) {
102                if (b.mLocalOnly) {
103                    mExtras.putBoolean(NotificationCompatExtras.EXTRA_LOCAL_ONLY, true);
104                }
105                if (b.mGroupKey != null) {
106                    mExtras.putString(NotificationCompatExtras.EXTRA_GROUP_KEY, b.mGroupKey);
107                    if (b.mGroupSummary) {
108                        mExtras.putBoolean(NotificationCompatExtras.EXTRA_GROUP_SUMMARY, true);
109                    } else {
110                        mExtras.putBoolean(
111                                NotificationManagerCompat.EXTRA_USE_SIDE_CHANNEL, true);
112                    }
113                }
114                if (b.mSortKey != null) {
115                    mExtras.putString(NotificationCompatExtras.EXTRA_SORT_KEY, b.mSortKey);
116                }
117            }
118
119            mContentView = b.mContentView;
120            mBigContentView = b.mBigContentView;
121        }
122        if (Build.VERSION.SDK_INT >= 19) {
123            mBuilder.setShowWhen(b.mShowWhen);
124
125            if (Build.VERSION.SDK_INT < 21) {
126                if (b.mPeople != null && !b.mPeople.isEmpty()) {
127                    mExtras.putStringArray(Notification.EXTRA_PEOPLE,
128                            b.mPeople.toArray(new String[b.mPeople.size()]));
129                }
130            }
131        }
132        if (Build.VERSION.SDK_INT >= 20) {
133            mBuilder.setLocalOnly(b.mLocalOnly)
134                    .setGroup(b.mGroupKey)
135                    .setGroupSummary(b.mGroupSummary)
136                    .setSortKey(b.mSortKey);
137
138            mGroupAlertBehavior = b.mGroupAlertBehavior;
139        }
140        if (Build.VERSION.SDK_INT >= 21) {
141            mBuilder.setCategory(b.mCategory)
142                    .setColor(b.mColor)
143                    .setVisibility(b.mVisibility)
144                    .setPublicVersion(b.mPublicVersion);
145
146            for (String person: b.mPeople) {
147                mBuilder.addPerson(person);
148            }
149            mHeadsUpContentView = b.mHeadsUpContentView;
150        }
151        if (Build.VERSION.SDK_INT >= 24) {
152            mBuilder.setExtras(b.mExtras)
153                    .setRemoteInputHistory(b.mRemoteInputHistory);
154            if (b.mContentView != null) {
155                mBuilder.setCustomContentView(b.mContentView);
156            }
157            if (b.mBigContentView != null) {
158                mBuilder.setCustomBigContentView(b.mBigContentView);
159            }
160            if (b.mHeadsUpContentView != null) {
161                mBuilder.setCustomHeadsUpContentView(b.mHeadsUpContentView);
162            }
163        }
164        if (Build.VERSION.SDK_INT >= 26) {
165            mBuilder.setBadgeIconType(b.mBadgeIcon)
166                    .setShortcutId(b.mShortcutId)
167                    .setTimeoutAfter(b.mTimeout)
168                    .setGroupAlertBehavior(b.mGroupAlertBehavior);
169            if (b.mColorizedSet) {
170                mBuilder.setColorized(b.mColorized);
171            }
172        }
173    }
174
175    @Override
176    public Notification.Builder getBuilder() {
177        return mBuilder;
178    }
179
180    public Notification build() {
181        final NotificationCompat.Style style = mBuilderCompat.mStyle;
182        if (style != null) {
183            style.apply(this);
184        }
185
186        RemoteViews styleContentView = style != null
187                ? style.makeContentView(this)
188                : null;
189        Notification n = buildInternal();
190        if (styleContentView != null) {
191            n.contentView = styleContentView;
192        } else if (mBuilderCompat.mContentView != null) {
193            n.contentView = mBuilderCompat.mContentView;
194        }
195        if (Build.VERSION.SDK_INT >= 16 && style != null) {
196            RemoteViews styleBigContentView = style.makeBigContentView(this);
197            if (styleBigContentView != null) {
198                n.bigContentView = styleBigContentView;
199            }
200        }
201        if (Build.VERSION.SDK_INT >= 21 && style != null) {
202            RemoteViews styleHeadsUpContentView =
203                    mBuilderCompat.mStyle.makeHeadsUpContentView(this);
204            if (styleHeadsUpContentView != null) {
205                n.headsUpContentView = styleHeadsUpContentView;
206            }
207        }
208
209        if (Build.VERSION.SDK_INT >= 16 && style != null) {
210            Bundle extras = NotificationCompat.getExtras(n);
211            if (extras != null) {
212                style.addCompatExtras(extras);
213            }
214        }
215
216        return n;
217    }
218
219    private void addAction(NotificationCompat.Action action) {
220        if (Build.VERSION.SDK_INT >= 20) {
221            Notification.Action.Builder actionBuilder = new Notification.Action.Builder(
222                    action.getIcon(), action.getTitle(), action.getActionIntent());
223            if (action.getRemoteInputs() != null) {
224                for (android.app.RemoteInput remoteInput : RemoteInput.fromCompat(
225                        action.getRemoteInputs())) {
226                    actionBuilder.addRemoteInput(remoteInput);
227                }
228            }
229            Bundle actionExtras;
230            if (action.getExtras() != null) {
231                actionExtras = new Bundle(action.getExtras());
232            } else {
233                actionExtras = new Bundle();
234            }
235            actionExtras.putBoolean(NotificationCompatJellybean.EXTRA_ALLOW_GENERATED_REPLIES,
236                    action.getAllowGeneratedReplies());
237            if (Build.VERSION.SDK_INT >= 24) {
238                actionBuilder.setAllowGeneratedReplies(action.getAllowGeneratedReplies());
239            }
240            actionBuilder.addExtras(actionExtras);
241            mBuilder.addAction(actionBuilder.build());
242        } else if (Build.VERSION.SDK_INT >= 16) {
243            mActionExtrasList.add(
244                    NotificationCompatJellybean.writeActionAndGetExtras(mBuilder, action));
245        }
246    }
247
248    protected Notification buildInternal() {
249        if (Build.VERSION.SDK_INT >= 26) {
250            return mBuilder.build();
251        } else if (Build.VERSION.SDK_INT >= 24) {
252            Notification notification =  mBuilder.build();
253
254            if (mGroupAlertBehavior != GROUP_ALERT_ALL) {
255                // if is summary and only children should alert
256                if (notification.getGroup() != null
257                        && (notification.flags & FLAG_GROUP_SUMMARY) != 0
258                        && mGroupAlertBehavior == GROUP_ALERT_CHILDREN) {
259                    removeSoundAndVibration(notification);
260                }
261                // if is group child and only summary should alert
262                if (notification.getGroup() != null
263                        && (notification.flags & FLAG_GROUP_SUMMARY) == 0
264                        && mGroupAlertBehavior == GROUP_ALERT_SUMMARY) {
265                    removeSoundAndVibration(notification);
266                }
267            }
268
269            return notification;
270        } else if (Build.VERSION.SDK_INT >= 21) {
271            mBuilder.setExtras(mExtras);
272            Notification notification = mBuilder.build();
273            if (mContentView != null) {
274                notification.contentView = mContentView;
275            }
276            if (mBigContentView != null) {
277                notification.bigContentView = mBigContentView;
278            }
279            if (mHeadsUpContentView != null) {
280                notification.headsUpContentView = mHeadsUpContentView;
281            }
282
283            if (mGroupAlertBehavior != GROUP_ALERT_ALL) {
284                // if is summary and only children should alert
285                if (notification.getGroup() != null
286                        && (notification.flags & FLAG_GROUP_SUMMARY) != 0
287                        && mGroupAlertBehavior == GROUP_ALERT_CHILDREN) {
288                    removeSoundAndVibration(notification);
289                }
290                // if is group child and only summary should alert
291                if (notification.getGroup() != null
292                        && (notification.flags & FLAG_GROUP_SUMMARY) == 0
293                        && mGroupAlertBehavior == GROUP_ALERT_SUMMARY) {
294                    removeSoundAndVibration(notification);
295                }
296            }
297            return notification;
298        } else if (Build.VERSION.SDK_INT >= 20) {
299            mBuilder.setExtras(mExtras);
300            Notification notification = mBuilder.build();
301            if (mContentView != null) {
302                notification.contentView = mContentView;
303            }
304            if (mBigContentView != null) {
305                notification.bigContentView = mBigContentView;
306            }
307
308            if (mGroupAlertBehavior != GROUP_ALERT_ALL) {
309                // if is summary and only children should alert
310                if (notification.getGroup() != null
311                        && (notification.flags & FLAG_GROUP_SUMMARY) != 0
312                        && mGroupAlertBehavior == GROUP_ALERT_CHILDREN) {
313                    removeSoundAndVibration(notification);
314                }
315                // if is group child and only summary should alert
316                if (notification.getGroup() != null
317                        && (notification.flags & FLAG_GROUP_SUMMARY) == 0
318                        && mGroupAlertBehavior == GROUP_ALERT_SUMMARY) {
319                    removeSoundAndVibration(notification);
320                }
321            }
322
323            return notification;
324        } else if (Build.VERSION.SDK_INT >= 19) {
325            SparseArray<Bundle> actionExtrasMap =
326                    NotificationCompatJellybean.buildActionExtrasMap(mActionExtrasList);
327            if (actionExtrasMap != null) {
328                // Add the action extras sparse array if any action was added with extras.
329                mExtras.putSparseParcelableArray(
330                        NotificationCompatExtras.EXTRA_ACTION_EXTRAS, actionExtrasMap);
331            }
332            mBuilder.setExtras(mExtras);
333            Notification notification = mBuilder.build();
334            if (mContentView != null) {
335                notification.contentView = mContentView;
336            }
337            if (mBigContentView != null) {
338                notification.bigContentView = mBigContentView;
339            }
340            return notification;
341        } else if (Build.VERSION.SDK_INT >= 16) {
342            Notification notification = mBuilder.build();
343            // Merge in developer provided extras, but let the values already set
344            // for keys take precedence.
345            Bundle extras = NotificationCompat.getExtras(notification);
346            Bundle mergeBundle = new Bundle(mExtras);
347            for (String key : mExtras.keySet()) {
348                if (extras.containsKey(key)) {
349                    mergeBundle.remove(key);
350                }
351            }
352            extras.putAll(mergeBundle);
353            SparseArray<Bundle> actionExtrasMap =
354                    NotificationCompatJellybean.buildActionExtrasMap(mActionExtrasList);
355            if (actionExtrasMap != null) {
356                // Add the action extras sparse array if any action was added with extras.
357                NotificationCompat.getExtras(notification).putSparseParcelableArray(
358                        NotificationCompatExtras.EXTRA_ACTION_EXTRAS, actionExtrasMap);
359            }
360            if (mContentView != null) {
361                notification.contentView = mContentView;
362            }
363            if (mBigContentView != null) {
364                notification.bigContentView = mBigContentView;
365            }
366            return notification;
367        } else {
368            //noinspection deprecation
369            return mBuilder.getNotification();
370        }
371    }
372
373    private void removeSoundAndVibration(Notification notification) {
374        notification.sound = null;
375        notification.vibrate = null;
376        notification.defaults &= ~DEFAULT_SOUND;
377        notification.defaults &= ~DEFAULT_VIBRATE;
378    }
379}
380