NotificationInflater.java revision 1a1ecfcf5ae32482aee23ebc7c4389daf164cadd
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 com.android.systemui.statusbar.notification;
18
19import android.app.Notification;
20import android.content.Context;
21import android.service.notification.StatusBarNotification;
22import android.util.Log;
23import android.view.View;
24import android.view.ViewGroup;
25import android.view.ViewParent;
26import android.widget.RemoteViews;
27
28import com.android.internal.annotations.VisibleForTesting;
29import com.android.systemui.statusbar.ExpandableNotificationRow;
30import com.android.systemui.statusbar.NotificationContentView;
31import com.android.systemui.statusbar.NotificationData;
32import com.android.systemui.statusbar.phone.StatusBar;
33
34/**
35 * A utility that inflates the right kind of contentView based on the state
36 */
37public class NotificationInflater {
38
39    @VisibleForTesting
40    static final int FLAG_REINFLATE_ALL = ~0;
41    private static final int FLAG_REINFLATE_CONTENT_VIEW = 1<<0;
42    private static final int FLAG_REINFLATE_EXPANDED_VIEW = 1<<1;
43    private static final int FLAG_REINFLATE_HEADS_UP_VIEW = 1<<2;
44    private static final int FLAG_REINFLATE_PUBLIC_VIEW = 1<<3;
45    private static final int FLAG_REINFLATE_AMBIENT_VIEW = 1<<4;
46
47    private final ExpandableNotificationRow mRow;
48    private boolean mIsLowPriority;
49    private boolean mUsesIncreasedHeight;
50    private boolean mUsesIncreasedHeadsUpHeight;
51    private RemoteViews.OnClickHandler mRemoteViewClickHandler;
52    private boolean mIsChildInGroup;
53    private InflationExceptionHandler mInflateExceptionHandler;
54    private boolean mRedactAmbient;
55
56    public NotificationInflater(ExpandableNotificationRow row) {
57        mRow = row;
58    }
59
60    public void setIsLowPriority(boolean isLowPriority) {
61        mIsLowPriority = isLowPriority;
62    }
63
64    /**
65     * Set whether the notification is a child in a group
66     *
67     * @return whether the view was re-inflated
68     */
69    public boolean setIsChildInGroup(boolean childInGroup) {
70        if (childInGroup != mIsChildInGroup) {
71            mIsChildInGroup = childInGroup;
72            if (mIsLowPriority) {
73                try {
74                    int flags = FLAG_REINFLATE_CONTENT_VIEW | FLAG_REINFLATE_EXPANDED_VIEW;
75                    inflateNotificationViews(flags);
76                } catch (InflationException e) {
77                    mInflateExceptionHandler.handleInflationException(
78                            mRow.getStatusBarNotification(), e);
79                }
80            }
81            return true;
82        }
83        return false;
84    }
85
86    public void setUsesIncreasedHeight(boolean usesIncreasedHeight) {
87        mUsesIncreasedHeight = usesIncreasedHeight;
88    }
89
90    public void setUsesIncreasedHeadsUpHeight(boolean usesIncreasedHeight) {
91        mUsesIncreasedHeadsUpHeight = usesIncreasedHeight;
92    }
93
94    public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) {
95        mRemoteViewClickHandler = remoteViewClickHandler;
96    }
97
98    public void setRedactAmbient(boolean redactAmbient) {
99        if (mRedactAmbient != redactAmbient) {
100            mRedactAmbient = redactAmbient;
101            if (mRow.getEntry() == null) {
102                return;
103            }
104            try {
105                inflateNotificationViews(FLAG_REINFLATE_AMBIENT_VIEW);
106            } catch (InflationException e) {
107                mInflateExceptionHandler.handleInflationException(
108                        mRow.getStatusBarNotification(), e);
109            }
110        }
111    }
112
113    public void inflateNotificationViews() throws InflationException {
114        inflateNotificationViews(FLAG_REINFLATE_ALL);
115    }
116
117    /**
118     * reinflate all views for the specified flags
119     * @param reInflateFlags flags which views should be reinflated. Use {@link #FLAG_REINFLATE_ALL}
120     *                       to reinflate all of views.
121     * @throws InflationException
122     */
123    private void inflateNotificationViews(int reInflateFlags)
124            throws InflationException {
125        StatusBarNotification sbn = mRow.getEntry().notification;
126        try {
127            final Notification.Builder recoveredBuilder
128                    = Notification.Builder.recoverBuilder(mRow.getContext(), sbn.getNotification());
129            Context packageContext = sbn.getPackageContext(mRow.getContext());
130            inflateNotificationViews(reInflateFlags, recoveredBuilder, packageContext);
131
132        } catch (RuntimeException e) {
133            final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId());
134            Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e);
135            throw new InflationException("Couldn't inflate contentViews");
136        }
137    }
138
139    @VisibleForTesting
140    void inflateNotificationViews(int reInflateFlags,
141            Notification.Builder builder, Context packageContext) {
142        NotificationData.Entry entry = mRow.getEntry();
143        NotificationContentView privateLayout = mRow.getPrivateLayout();
144        NotificationContentView publicLayout = mRow.getPublicLayout();
145
146        boolean isLowPriority = mIsLowPriority && !mIsChildInGroup;
147        if ((reInflateFlags & FLAG_REINFLATE_CONTENT_VIEW) != 0) {
148            final RemoteViews newContentView = createContentView(builder,
149                    isLowPriority, mUsesIncreasedHeight);
150            if (!compareRemoteViews(newContentView,
151                    entry.cachedContentView)) {
152                View contentViewLocal = newContentView.apply(
153                        packageContext,
154                        privateLayout,
155                        mRemoteViewClickHandler);
156                contentViewLocal.setIsRootNamespace(true);
157                privateLayout.setContractedChild(contentViewLocal);
158            } else {
159                newContentView.reapply(packageContext,
160                        privateLayout.getContractedChild(),
161                        mRemoteViewClickHandler);
162            }
163            entry.cachedContentView = newContentView;
164        }
165
166        if ((reInflateFlags & FLAG_REINFLATE_EXPANDED_VIEW) != 0) {
167            final RemoteViews newBigContentView = createBigContentView(
168                    builder, isLowPriority);
169            if (newBigContentView != null) {
170                if (!compareRemoteViews(newBigContentView, entry.cachedBigContentView)) {
171                    View bigContentViewLocal = newBigContentView.apply(
172                            packageContext,
173                            privateLayout,
174                            mRemoteViewClickHandler);
175                    bigContentViewLocal.setIsRootNamespace(true);
176                    privateLayout.setExpandedChild(bigContentViewLocal);
177                } else {
178                    newBigContentView.reapply(packageContext,
179                            privateLayout.getExpandedChild(),
180                            mRemoteViewClickHandler);
181                }
182            } else if (entry.cachedBigContentView != null) {
183                privateLayout.setExpandedChild(null);
184            }
185            entry.cachedBigContentView = newBigContentView;
186            mRow.setExpandable(newBigContentView != null);
187        }
188
189        if ((reInflateFlags & FLAG_REINFLATE_HEADS_UP_VIEW) != 0) {
190            final RemoteViews newHeadsUpContentView =
191                    builder.createHeadsUpContentView(mUsesIncreasedHeadsUpHeight);
192            if (newHeadsUpContentView != null) {
193                if (!compareRemoteViews(newHeadsUpContentView,
194                        entry.cachedHeadsUpContentView)) {
195                    View headsUpContentViewLocal = newHeadsUpContentView.apply(
196                            packageContext,
197                            privateLayout,
198                            mRemoteViewClickHandler);
199                    headsUpContentViewLocal.setIsRootNamespace(true);
200                    privateLayout.setHeadsUpChild(headsUpContentViewLocal);
201                } else {
202                    newHeadsUpContentView.reapply(packageContext,
203                            privateLayout.getHeadsUpChild(),
204                            mRemoteViewClickHandler);
205                }
206            } else if (entry.cachedHeadsUpContentView != null) {
207                privateLayout.setHeadsUpChild(null);
208            }
209            entry.cachedHeadsUpContentView = newHeadsUpContentView;
210        }
211
212        if ((reInflateFlags & FLAG_REINFLATE_PUBLIC_VIEW) != 0) {
213            final RemoteViews newPublicNotification
214                    = builder.makePublicContentView();
215            if (!compareRemoteViews(newPublicNotification, entry.cachedPublicContentView)) {
216                View publicContentView = newPublicNotification.apply(
217                        packageContext,
218                        publicLayout,
219                        mRemoteViewClickHandler);
220                publicContentView.setIsRootNamespace(true);
221                publicLayout.setContractedChild(publicContentView);
222            } else {
223                newPublicNotification.reapply(packageContext,
224                        publicLayout.getContractedChild(),
225                        mRemoteViewClickHandler);
226            }
227            entry.cachedPublicContentView = newPublicNotification;
228        }
229
230        if ((reInflateFlags & FLAG_REINFLATE_AMBIENT_VIEW) != 0) {
231            final RemoteViews newAmbientNotification = mRedactAmbient
232                    ? builder.makePublicAmbientNotification()
233                    : builder.makeAmbientNotification();
234            NotificationContentView newParent = mRedactAmbient ? publicLayout : privateLayout;
235            NotificationContentView otherParent = !mRedactAmbient ? publicLayout : privateLayout;
236
237            if (newParent.getAmbientChild() == null ||
238                    !compareRemoteViews(newAmbientNotification, entry.cachedAmbientContentView)) {
239                View ambientContentView = newAmbientNotification.apply(
240                        packageContext,
241                        newParent,
242                        mRemoteViewClickHandler);
243                ambientContentView.setIsRootNamespace(true);
244                newParent.setAmbientChild(ambientContentView);
245                otherParent.setAmbientChild(null);
246            } else {
247                newAmbientNotification.reapply(packageContext,
248                        newParent.getAmbientChild(),
249                        mRemoteViewClickHandler);
250            }
251            entry.cachedAmbientContentView = newAmbientNotification;
252        }
253    }
254
255    private RemoteViews createBigContentView(Notification.Builder builder,
256            boolean isLowPriority) {
257        RemoteViews bigContentView = builder.createBigContentView();
258        if (bigContentView != null) {
259            return bigContentView;
260        }
261        if (isLowPriority) {
262            RemoteViews contentView = builder.createContentView();
263            Notification.Builder.makeHeaderExpanded(contentView);
264            return contentView;
265        }
266        return null;
267    }
268
269    private RemoteViews createContentView(Notification.Builder builder,
270            boolean isLowPriority, boolean useLarge) {
271        if (isLowPriority) {
272            return builder.makeLowPriorityContentView(false /* useRegularSubtext */);
273        }
274        return builder.createContentView(useLarge);
275    }
276
277    // Returns true if the RemoteViews are the same.
278    private boolean compareRemoteViews(final RemoteViews a, final RemoteViews b) {
279        return (a == null && b == null) ||
280                (a != null && b != null
281                        && b.getPackage() != null
282                        && a.getPackage() != null
283                        && a.getPackage().equals(b.getPackage())
284                        && a.getLayoutId() == b.getLayoutId());
285    }
286
287    public void setInflateExceptionHandler(InflationExceptionHandler inflateExceptionHandler) {
288        mInflateExceptionHandler = inflateExceptionHandler;
289    }
290
291    public interface InflationExceptionHandler {
292        void handleInflationException(StatusBarNotification notification, InflationException e);
293    }
294
295    public void onDensityOrFontScaleChanged() {
296        NotificationData.Entry entry = mRow.getEntry();
297        entry.cachedAmbientContentView = null;
298        entry.cachedBigContentView = null;
299        entry.cachedContentView = null;
300        entry.cachedHeadsUpContentView = null;
301        entry.cachedPublicContentView = null;
302        try {
303            inflateNotificationViews();
304        } catch (InflationException e) {
305            mInflateExceptionHandler.handleInflationException(
306                    mRow.getStatusBarNotification(), e);
307        }
308    }
309
310}
311