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