• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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 com.android.car.notification.template;
17 
18 import static android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME;
19 
20 import android.annotation.ColorInt;
21 import android.annotation.Nullable;
22 import android.app.Notification;
23 import android.content.Context;
24 import android.content.pm.PackageManager;
25 import android.content.res.TypedArray;
26 import android.graphics.drawable.Icon;
27 import android.os.Bundle;
28 import android.os.Handler;
29 import android.os.Looper;
30 import android.service.notification.StatusBarNotification;
31 import android.text.BidiFormatter;
32 import android.text.TextDirectionHeuristics;
33 import android.text.TextUtils;
34 import android.util.AttributeSet;
35 import android.util.Log;
36 import android.view.View;
37 import android.widget.DateTimeView;
38 import android.widget.ImageView;
39 import android.widget.LinearLayout;
40 import android.widget.TextView;
41 
42 
43 import com.android.car.notification.AlertEntry;
44 import com.android.car.notification.R;
45 
46 /**
47  * Notification header view that contains the issuer app icon and name, and extra information.
48  */
49 public class CarNotificationHeaderView extends LinearLayout {
50 
51     private static final String TAG = "car_notification_header";
52 
53     private final int mDefaultTextColor;
54     private final String mSeparatorText;
55     private final boolean mUseLauncherIcon;
56 
57     private boolean mIsHeadsUp;
58     @Nullable
59     private ImageView mIconView;
60     @Nullable
61     private TextView mHeaderTextView;
62     @Nullable
63     private DateTimeView mTimeView;
64 
CarNotificationHeaderView(Context context)65     public CarNotificationHeaderView(Context context) {
66         super(context);
67     }
68 
CarNotificationHeaderView(Context context, AttributeSet attrs)69     public CarNotificationHeaderView(Context context, AttributeSet attrs) {
70         super(context, attrs);
71         init(attrs);
72     }
73 
CarNotificationHeaderView(Context context, AttributeSet attrs, int defStyleAttr)74     public CarNotificationHeaderView(Context context, AttributeSet attrs, int defStyleAttr) {
75         super(context, attrs, defStyleAttr);
76         init(attrs);
77     }
78 
CarNotificationHeaderView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)79     public CarNotificationHeaderView(Context context, AttributeSet attrs, int defStyleAttr,
80             int defStyleRes) {
81         super(context, attrs, defStyleAttr, defStyleRes);
82         init(attrs);
83     }
84 
85     {
86         mDefaultTextColor = getContext().getColor(R.color.primary_text_color);
87         mSeparatorText = getContext().getString(R.string.header_text_separator);
88         mUseLauncherIcon = getResources().getBoolean(R.bool.config_useLauncherIcon);
89     }
90 
init(AttributeSet attrs)91     private void init(AttributeSet attrs) {
92         TypedArray attributes =
93                 getContext().obtainStyledAttributes(attrs, R.styleable.CarNotificationHeaderView);
94         mIsHeadsUp =
95                 attributes.getBoolean(R.styleable.CarNotificationHeaderView_isHeadsUp,
96                         /* defValue= */ false);
97         inflate(getContext(), mIsHeadsUp ? R.layout.car_headsup_notification_header_view
98                 : R.layout.car_notification_header_view, this);
99         attributes.recycle();
100     }
101 
102     @Override
onFinishInflate()103     protected void onFinishInflate() {
104         super.onFinishInflate();
105         mIconView = findViewById(R.id.app_icon);
106         mHeaderTextView = findViewById(R.id.header_text);
107         mTimeView = findViewById(R.id.time);
108         if (mTimeView != null) {
109             mTimeView.setShowRelativeTime(true);
110         }
111     }
112 
113     /**
114      * Binds the notification header that contains the issuer app icon and name.
115      *
116      * @param alertEntry the notification to be bound.
117      * @param isInGroup  whether this notification is part of a grouped notification.
118      */
bind(AlertEntry alertEntry, boolean isInGroup)119     public void bind(AlertEntry alertEntry, boolean isInGroup) {
120         if (mUseLauncherIcon || isInGroup) {
121             // If the notification is part of a group, individual headers are not shown
122             // instead, there is a header for the entire group in the group notification template
123             // OR
124             // If launcher icon is used then hide header
125             setVisibility(View.GONE);
126             return;
127         }
128 
129         setVisibility(View.VISIBLE);
130 
131         Notification notification = alertEntry.getNotification();
132         StatusBarNotification sbn = alertEntry.getStatusBarNotification();
133 
134 
135         // App icon
136         if (mIconView != null) {
137             if (notification.getSmallIcon() != null) {
138                 Context packageContext = sbn.getPackageContext(getContext());
139                 Icon.OnDrawableLoadedListener loadedListener = drawable -> {
140                     mIconView.setVisibility(View.VISIBLE);
141                     mIconView.setImageDrawable(drawable);
142                 };
143                 Handler handler = Handler.createAsync(Looper.myLooper());
144                 notification.getSmallIcon().loadDrawableAsync(packageContext, loadedListener,
145                         handler);
146             } else {
147                 mIconView.setVisibility(View.GONE);
148                 mIconView.setImageDrawable(null);
149             }
150         }
151 
152         StringBuilder stringBuilder = new StringBuilder();
153 
154         // App name
155         if (mHeaderTextView != null) {
156             mHeaderTextView.setVisibility(View.VISIBLE);
157         }
158         String appName = loadHeaderAppName(sbn);
159 
160         if (mIsHeadsUp) {
161             if (mHeaderTextView != null) {
162                 mHeaderTextView.setText(appName);
163             }
164             if (mTimeView != null) {
165                 mTimeView.setVisibility(View.GONE);
166             }
167             return;
168         }
169 
170         stringBuilder.append(appName);
171         Bundle extras = notification.extras;
172 
173         // Optional field: sub text
174         if (!TextUtils.isEmpty(extras.getCharSequence(Notification.EXTRA_SUB_TEXT))) {
175             stringBuilder.append(mSeparatorText);
176             stringBuilder.append(extras.getCharSequence(Notification.EXTRA_SUB_TEXT));
177         }
178 
179         // Optional field: content info
180         if (!TextUtils.isEmpty(extras.getCharSequence(Notification.EXTRA_INFO_TEXT))) {
181             stringBuilder.append(mSeparatorText);
182             stringBuilder.append(extras.getCharSequence(Notification.EXTRA_INFO_TEXT));
183         }
184 
185         // Optional field: time
186         if (notification.showsTime()) {
187             stringBuilder.append(mSeparatorText);
188             if (mTimeView != null) {
189                 mTimeView.setVisibility(View.VISIBLE);
190                 mTimeView.setTime(notification.when);
191             }
192         }
193 
194         mHeaderTextView.setText(BidiFormatter.getInstance().unicodeWrap(stringBuilder,
195                 TextDirectionHeuristics.LOCALE));
196     }
197 
198     /**
199      * Sets the color for the small icon.
200      */
setSmallIconColor(@olorInt int color)201     public void setSmallIconColor(@ColorInt int color) {
202         if (mIconView != null) {
203             mIconView.setColorFilter(color);
204         }
205     }
206 
207     /**
208      * Sets the header text color.
209      */
setHeaderTextColor(@olorInt int color)210     public void setHeaderTextColor(@ColorInt int color) {
211         if (mHeaderTextView != null) {
212             mHeaderTextView.setTextColor(color);
213         }
214     }
215 
216     /**
217      * Sets the text color for the time field.
218      */
setTimeTextColor(@olorInt int color)219     public void setTimeTextColor(@ColorInt int color) {
220         if (mTimeView != null) {
221             mTimeView.setTextColor(color);
222         }
223     }
224 
225     /**
226      * Resets the notification header empty.
227      */
reset()228     public void reset() {
229         if (mIconView != null) {
230             setSmallIconColor(mDefaultTextColor);
231         }
232 
233         if (mHeaderTextView != null) {
234             mHeaderTextView.setVisibility(View.GONE);
235             mHeaderTextView.setText(null);
236             setHeaderTextColor(mDefaultTextColor);
237         }
238 
239         if (mTimeView != null) {
240             mTimeView.setVisibility(View.GONE);
241             mTimeView.setTime(0);
242             setTimeTextColor(mDefaultTextColor);
243         }
244     }
245 
246     /**
247      * Fetches the application label given the notification. If the notification is a system
248      * generated message notification that is posting on behalf of another application, that
249      * application's name is used.
250      *
251      * The system permission {@link android.Manifest.permission#SUBSTITUTE_NOTIFICATION_APP_NAME}
252      * is required to post on behalf of another application. The notification extra should also
253      * contain a key {@link Notification#EXTRA_SUBSTITUTE_APP_NAME} with the value of
254      * the appropriate application name.
255      *
256      * @return application label. Returns {@code null} when application name is not found.
257      */
258     @Nullable
loadHeaderAppName(StatusBarNotification sbn)259     private String loadHeaderAppName(StatusBarNotification sbn) {
260         final Context packageContext = sbn.getPackageContext(mContext);
261         final PackageManager pm = packageContext.getPackageManager();
262         final Notification notification = sbn.getNotification();
263         CharSequence name = pm.getApplicationLabel(packageContext.getApplicationInfo());
264         if (notification.extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) {
265             // Only system packages which lump together a bunch of unrelated stuff may substitute a
266             // different name to make the purpose of the notification more clear
267             // The correct package label should always be accessible via SystemUI
268             final String subName = notification.extras.getString(EXTRA_SUBSTITUTE_APP_NAME);
269             final String pkg = sbn.getPackageName();
270             if (PackageManager.PERMISSION_GRANTED == pm.checkPermission(
271                     android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME, pkg)) {
272                 name = subName;
273             } else {
274                 Log.w(TAG, "warning: pkg "
275                         + pkg + " attempting to substitute app name '" + subName
276                         + "' without holding perm "
277                         + android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME);
278             }
279         }
280         if (TextUtils.isEmpty(name)) {
281             return null;
282         }
283         return String.valueOf(name);
284     }
285 }
286