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