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