1 /* 2 * Copyright (C) 2022 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 17 package com.android.server.nearby.fastpair.notification; 18 19 import static com.android.server.nearby.fastpair.Constant.TAG; 20 21 import android.annotation.LayoutRes; 22 import android.annotation.Nullable; 23 import android.app.Notification; 24 import android.content.Context; 25 import android.content.res.Resources; 26 import android.graphics.Bitmap; 27 import android.util.Log; 28 import android.view.View; 29 import android.widget.LinearLayout; 30 import android.widget.RemoteViews; 31 32 import com.android.nearby.halfsheet.R; 33 34 /** Wrapper class for creating larger heads up notifications. */ 35 public class LargeHeadsUpNotificationBuilder extends FastPairNotificationBuilder { 36 private final boolean mLargeIcon; 37 private final RemoteViews mNotification; 38 private final RemoteViews mNotificationCollapsed; 39 40 @Nullable private Runnable mLargeIconAction; 41 @Nullable private Runnable mProgressAction; 42 LargeHeadsUpNotificationBuilder(Context context, String channelId, boolean largeIcon)43 public LargeHeadsUpNotificationBuilder(Context context, String channelId, boolean largeIcon) { 44 super(context, channelId); 45 46 this.mLargeIcon = largeIcon; 47 this.mNotification = getRemoteViews(R.layout.fast_pair_heads_up_notification); 48 this.mNotificationCollapsed = getRemoteViews(R.layout.fast_pair_heads_up_notification); 49 50 if (mNotification != null) { 51 mNotificationCollapsed.setViewVisibility(android.R.id.secondaryProgress, View.GONE); 52 } 53 } 54 55 /** 56 * Create a new RemoteViews object that will display the views contained 57 * fast_pair_heads_up_notification layout. 58 */ 59 @Nullable getRemoteViews(@ayoutRes int layoutId)60 public RemoteViews getRemoteViews(@LayoutRes int layoutId) { 61 return new RemoteViews(mPackageName, layoutId); 62 } 63 64 @Override setContentTitle(@ullable CharSequence title)65 public Notification.Builder setContentTitle(@Nullable CharSequence title) { 66 if (mNotification != null) { 67 mNotification.setTextViewText(android.R.id.title, title); 68 } 69 if (mNotificationCollapsed != null) { 70 mNotificationCollapsed.setTextViewText(android.R.id.title, title); 71 // Collapsed mode does not need additional lines. 72 mNotificationCollapsed.setInt(android.R.id.title, "setMaxLines", 1); 73 } 74 return super.setContentTitle(title); 75 } 76 77 @Override setContentText(@ullable CharSequence text)78 public Notification.Builder setContentText(@Nullable CharSequence text) { 79 if (mNotification != null) { 80 mNotification.setTextViewText(android.R.id.text1, text); 81 } 82 if (mNotificationCollapsed != null) { 83 mNotificationCollapsed.setTextViewText(android.R.id.text1, text); 84 // Collapsed mode does not need additional lines. 85 mNotificationCollapsed.setInt(android.R.id.text1, "setMaxLines", 1); 86 } 87 88 return super.setContentText(text); 89 } 90 91 @Override setSubText(@ullable CharSequence subText)92 public Notification.Builder setSubText(@Nullable CharSequence subText) { 93 if (mNotification != null) { 94 mNotification.setTextViewText(android.R.id.text2, subText); 95 } 96 if (mNotificationCollapsed != null) { 97 mNotificationCollapsed.setTextViewText(android.R.id.text2, subText); 98 } 99 return super.setSubText(subText); 100 } 101 102 @Override setLargeIcon(@ullable Bitmap bitmap)103 public Notification.Builder setLargeIcon(@Nullable Bitmap bitmap) { 104 RemoteViews image = 105 getRemoteViews( 106 useLargeIcon() 107 ? R.layout.fast_pair_heads_up_notification_large_image 108 : R.layout.fast_pair_heads_up_notification_small_image); 109 if (image == null) { 110 return super.setLargeIcon(bitmap); 111 } 112 image.setImageViewBitmap(android.R.id.icon, bitmap); 113 114 if (mNotification != null) { 115 mNotification.removeAllViews(android.R.id.icon1); 116 mNotification.addView(android.R.id.icon1, image); 117 } 118 if (mNotificationCollapsed != null) { 119 mNotificationCollapsed.removeAllViews(android.R.id.icon1); 120 mNotificationCollapsed.addView(android.R.id.icon1, image); 121 // In Android S, if super.setLargeIcon() is called, there will be an extra icon on 122 // top-right. 123 // But we still need to store this setting for the default UI when something wrong. 124 mLargeIconAction = () -> super.setLargeIcon(bitmap); 125 return this; 126 } 127 return super.setLargeIcon(bitmap); 128 } 129 130 @Override setProgress(int max, int progress, boolean indeterminate)131 public Notification.Builder setProgress(int max, int progress, boolean indeterminate) { 132 if (mNotification != null) { 133 mNotification.setViewVisibility(android.R.id.secondaryProgress, View.VISIBLE); 134 mNotification.setProgressBar(android.R.id.progress, max, progress, indeterminate); 135 } 136 if (mNotificationCollapsed != null) { 137 mNotificationCollapsed.setViewVisibility(android.R.id.secondaryProgress, View.VISIBLE); 138 mNotificationCollapsed.setProgressBar(android.R.id.progress, max, progress, 139 indeterminate); 140 // In Android S, if super.setProgress() is called, there will be an extra progress bar. 141 // But we still need to store this setting for the default UI when something wrong. 142 mProgressAction = () -> super.setProgress(max, progress, indeterminate); 143 return this; 144 } 145 return super.setProgress(max, progress, indeterminate); 146 } 147 148 @Override build()149 public Notification build() { 150 if (mNotification != null) { 151 boolean buildSuccess = false; 152 try { 153 // Attempt to apply the remote views. This verifies that all of the resources are 154 // correctly available. 155 // If it fails, fall back to a non-custom notification. 156 mNotification.apply(mContext, new LinearLayout(mContext)); 157 if (mNotificationCollapsed != null) { 158 mNotificationCollapsed.apply(mContext, new LinearLayout(mContext)); 159 } 160 buildSuccess = true; 161 } catch (Resources.NotFoundException e) { 162 Log.w(TAG, "Failed to build notification, not setting custom view.", e); 163 } 164 165 if (buildSuccess) { 166 if (mNotificationCollapsed != null) { 167 setStyle(new Notification.DecoratedCustomViewStyle()); 168 setCustomContentView(mNotificationCollapsed); 169 setCustomBigContentView(mNotification); 170 } else { 171 setCustomHeadsUpContentView(mNotification); 172 } 173 } else { 174 if (mLargeIconAction != null) { 175 mLargeIconAction.run(); 176 } 177 if (mProgressAction != null) { 178 mProgressAction.run(); 179 } 180 } 181 } 182 return super.build(); 183 } 184 useLargeIcon()185 private boolean useLargeIcon() { 186 return mLargeIcon; 187 } 188 } 189