1/*
2 * Copyright (C) 2015 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.v7.internal.app;
18
19import android.app.Notification;
20import android.app.PendingIntent;
21import android.content.Context;
22import android.content.res.Resources;
23import android.graphics.Bitmap;
24import android.os.Build;
25import android.os.SystemClock;
26import android.support.v7.appcompat.R;
27import android.support.v4.app.NotificationBuilderWithBuilderAccessor;
28import android.support.v4.app.NotificationCompatBase;
29import android.util.TypedValue;
30import android.view.View;
31import android.widget.RemoteViews;
32
33import java.text.NumberFormat;
34import java.util.List;
35
36/**
37 * Helper class to generate MediaStyle notifications for pre-Lollipop platforms. Overrides
38 * contentView and bigContentView of the notification.
39 * @hide
40 */
41public class NotificationCompatImplBase {
42
43    static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3;
44    static final int MAX_MEDIA_BUTTONS = 5;
45
46    public static <T extends NotificationCompatBase.Action> void overrideContentView(
47            NotificationBuilderWithBuilderAccessor builder,
48            Context context, CharSequence contentTitle, CharSequence contentText,
49            CharSequence contentInfo, int number, Bitmap largeIcon, CharSequence subText,
50            boolean useChronometer, long when, List<T> actions, int[] actionsToShowInCompact,
51            boolean showCancelButton, PendingIntent cancelButtonIntent) {
52        RemoteViews views = generateContentView(context, contentTitle, contentText, contentInfo,
53                number, largeIcon, subText, useChronometer, when, actions, actionsToShowInCompact,
54                showCancelButton, cancelButtonIntent);
55        builder.getBuilder().setContent(views);
56        if (showCancelButton) {
57            builder.getBuilder().setOngoing(true);
58        }
59    }
60
61    private static <T extends NotificationCompatBase.Action> RemoteViews generateContentView(
62            Context context, CharSequence contentTitle, CharSequence contentText,
63            CharSequence contentInfo, int number, Bitmap largeIcon, CharSequence subText,
64            boolean useChronometer, long when, List<T> actions, int[] actionsToShowInCompact,
65            boolean showCancelButton, PendingIntent cancelButtonIntent) {
66        RemoteViews view = applyStandardTemplate(context, contentTitle, contentText, contentInfo,
67                number, largeIcon, subText, useChronometer, when,
68                R.layout.notification_template_media, true /* fitIn1U */);
69
70        final int numActions = actions.size();
71        final int N = actionsToShowInCompact == null
72                ? 0
73                : Math.min(actionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
74        view.removeAllViews(R.id.media_actions);
75        if (N > 0) {
76            for (int i = 0; i < N; i++) {
77                if (i >= numActions) {
78                    throw new IllegalArgumentException(String.format(
79                            "setShowActionsInCompactView: action %d out of bounds (max %d)",
80                            i, numActions - 1));
81                }
82
83                final NotificationCompatBase.Action action = actions.get(actionsToShowInCompact[i]);
84                final RemoteViews button = generateMediaActionButton(context, action);
85                view.addView(R.id.media_actions, button);
86            }
87        }
88        if (showCancelButton) {
89            view.setViewVisibility(R.id.end_padder, View.GONE);
90            view.setViewVisibility(R.id.cancel_action, View.VISIBLE);
91            view.setOnClickPendingIntent(R.id.cancel_action, cancelButtonIntent);
92            view.setInt(R.id.cancel_action, "setAlpha",
93                    context.getResources().getInteger(R.integer.cancel_button_image_alpha));
94        } else {
95            view.setViewVisibility(R.id.end_padder, View.VISIBLE);
96            view.setViewVisibility(R.id.cancel_action, View.GONE);
97        }
98        return view;
99    }
100
101    public static <T extends NotificationCompatBase.Action> void overrideBigContentView(
102            Notification n, Context context, CharSequence contentTitle, CharSequence contentText,
103            CharSequence contentInfo, int number, Bitmap largeIcon, CharSequence subText,
104            boolean useChronometer, long when, List<T> actions, boolean showCancelButton,
105            PendingIntent cancelButtonIntent) {
106        n.bigContentView = generateBigContentView(context, contentTitle, contentText, contentInfo,
107                number, largeIcon, subText, useChronometer, when, actions, showCancelButton,
108                cancelButtonIntent);
109        if (showCancelButton) {
110            n.flags |= Notification.FLAG_ONGOING_EVENT;
111        }
112    }
113
114    private static <T extends NotificationCompatBase.Action> RemoteViews generateBigContentView(
115            Context context, CharSequence contentTitle, CharSequence contentText,
116            CharSequence contentInfo, int number, Bitmap largeIcon, CharSequence subText,
117            boolean useChronometer, long when, List<T> actions, boolean showCancelButton,
118            PendingIntent cancelButtonIntent) {
119        final int actionCount = Math.min(actions.size(), MAX_MEDIA_BUTTONS);
120        RemoteViews big = applyStandardTemplate(context, contentTitle, contentText, contentInfo,
121                number, largeIcon, subText, useChronometer, when,
122                getBigLayoutResource(actionCount), false /* fitIn1U */);
123
124        big.removeAllViews(R.id.media_actions);
125        if (actionCount > 0) {
126            for (int i = 0; i < actionCount; i++) {
127                final RemoteViews button = generateMediaActionButton(context, actions.get(i));
128                big.addView(R.id.media_actions, button);
129            }
130        }
131        if (showCancelButton) {
132            big.setViewVisibility(R.id.cancel_action, View.VISIBLE);
133            big.setInt(R.id.cancel_action, "setAlpha",
134                    context.getResources().getInteger(R.integer.cancel_button_image_alpha));
135            big.setOnClickPendingIntent(R.id.cancel_action, cancelButtonIntent);
136        } else {
137            big.setViewVisibility(R.id.cancel_action, View.GONE);
138        }
139        return big;
140    }
141
142    private static RemoteViews generateMediaActionButton(Context context,
143            NotificationCompatBase.Action action) {
144        final boolean tombstone = (action.getActionIntent() == null);
145        RemoteViews button = new RemoteViews(context.getPackageName(),
146                R.layout.notification_media_action);
147        button.setImageViewResource(R.id.action0, action.getIcon());
148        if (!tombstone) {
149            button.setOnClickPendingIntent(R.id.action0, action.getActionIntent());
150        }
151        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
152            button.setContentDescription(R.id.action0, action.getTitle());
153        }
154        return button;
155    }
156
157    private static int getBigLayoutResource(int actionCount) {
158        if (actionCount <= 3) {
159            return R.layout.notification_template_big_media_narrow;
160        } else {
161            return R.layout.notification_template_big_media;
162        }
163    }
164
165    private static RemoteViews applyStandardTemplate(Context context,
166            CharSequence contentTitle, CharSequence contentText, CharSequence contentInfo,
167            int number, Bitmap largeIcon, CharSequence subText, boolean useChronometer, long when,
168            int resId, boolean fitIn1U) {
169        RemoteViews contentView = new RemoteViews(context.getPackageName(), resId);
170        boolean showLine3 = false;
171        boolean showLine2 = false;
172
173        // On versions before Jellybean, the large icon was shown by SystemUI, so we need to hide
174        // it here.
175        if (largeIcon != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
176            contentView.setImageViewBitmap(R.id.icon, largeIcon);
177        } else {
178            contentView.setViewVisibility(R.id.icon, View.GONE);
179        }
180        if (contentTitle != null) {
181            contentView.setTextViewText(R.id.title, contentTitle);
182        }
183        if (contentText != null) {
184            contentView.setTextViewText(R.id.text, contentText);
185            showLine3 = true;
186        }
187        if (contentInfo != null) {
188            contentView.setTextViewText(R.id.info, contentInfo);
189            contentView.setViewVisibility(R.id.info, View.VISIBLE);
190            showLine3 = true;
191        } else if (number > 0) {
192            final int tooBig = context.getResources().getInteger(
193                    R.integer.status_bar_notification_info_maxnum);
194            if (number > tooBig) {
195                contentView.setTextViewText(R.id.info, context.getResources().getString(
196                        R.string.status_bar_notification_info_overflow));
197            } else {
198                NumberFormat f = NumberFormat.getIntegerInstance();
199                contentView.setTextViewText(R.id.info, f.format(number));
200            }
201            contentView.setViewVisibility(R.id.info, View.VISIBLE);
202            showLine3 = true;
203        } else {
204            contentView.setViewVisibility(R.id.info, View.GONE);
205        }
206
207        // Need to show three lines? Only allow on Jellybean+
208        if (subText != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
209            contentView.setTextViewText(R.id.text, subText);
210            if (contentText != null) {
211                contentView.setTextViewText(R.id.text2, contentText);
212                contentView.setViewVisibility(R.id.text2, View.VISIBLE);
213                showLine2 = true;
214            } else {
215                contentView.setViewVisibility(R.id.text2, View.GONE);
216            }
217        }
218
219        // RemoteViews.setViewPadding and RemoteViews.setTextViewTextSize is not available on ICS-
220        if (showLine2 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
221            if (fitIn1U) {
222                // need to shrink all the type to make sure everything fits
223                final Resources res = context.getResources();
224                final float subTextSize = res.getDimensionPixelSize(
225                        R.dimen.notification_subtext_size);
226                contentView.setTextViewTextSize(R.id.text, TypedValue.COMPLEX_UNIT_PX, subTextSize);
227            }
228            // vertical centering
229            contentView.setViewPadding(R.id.line1, 0, 0, 0, 0);
230        }
231
232        if (when != 0) {
233            if (useChronometer) {
234                contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
235                contentView.setLong(R.id.chronometer, "setBase",
236                        when + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
237                contentView.setBoolean(R.id.chronometer, "setStarted", true);
238            } else {
239                contentView.setViewVisibility(R.id.time, View.VISIBLE);
240                contentView.setLong(R.id.time, "setTime", when);
241            }
242        }
243        contentView.setViewVisibility(R.id.line3, showLine3 ? View.VISIBLE : View.GONE);
244        return contentView;
245    }
246}
247