1 /* 2 * Copyright (C) 2020 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.systemui.statusbar.notification.row; 18 19 import static android.service.notification.NotificationAssistantService.FEEDBACK_RATING; 20 21 import static com.android.systemui.statusbar.notification.AssistantFeedbackController.STATUS_ALERTED; 22 import static com.android.systemui.statusbar.notification.AssistantFeedbackController.STATUS_DEMOTED; 23 import static com.android.systemui.statusbar.notification.AssistantFeedbackController.STATUS_PROMOTED; 24 import static com.android.systemui.statusbar.notification.AssistantFeedbackController.STATUS_SILENCED; 25 26 import android.annotation.SuppressLint; 27 import android.content.Context; 28 import android.content.pm.ApplicationInfo; 29 import android.content.pm.PackageManager; 30 import android.graphics.drawable.Drawable; 31 import android.os.Bundle; 32 import android.os.RemoteException; 33 import android.service.notification.NotificationListenerService; 34 import android.service.notification.StatusBarNotification; 35 import android.text.Html; 36 import android.util.AttributeSet; 37 import android.util.Log; 38 import android.view.View; 39 import android.view.accessibility.AccessibilityEvent; 40 import android.widget.ImageView; 41 import android.widget.LinearLayout; 42 import android.widget.TextView; 43 44 import com.android.internal.statusbar.IStatusBarService; 45 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; 46 import com.android.systemui.res.R; 47 import com.android.systemui.statusbar.notification.AssistantFeedbackController; 48 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 49 import com.android.systemui.util.Compile; 50 51 public class FeedbackInfo extends LinearLayout implements NotificationGuts.GutsContent { 52 53 private static final String TAG = "FeedbackInfo"; 54 private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG); 55 56 private NotificationGuts mGutsContainer; 57 private NotificationListenerService.Ranking mRanking; 58 private PackageManager mPm; 59 private String mAppName; 60 private String mPkg; 61 private NotificationEntry mEntry; 62 63 private IStatusBarService mStatusBarService; 64 private AssistantFeedbackController mFeedbackController; 65 private NotificationGutsManager mNotificationGutsManager; 66 private NotificationMenuRowPlugin mMenuRowPlugin; 67 private ExpandableNotificationRow mExpandableNotificationRow; 68 FeedbackInfo(Context context, AttributeSet attrs)69 public FeedbackInfo(Context context, AttributeSet attrs) { 70 super(context, attrs); 71 } 72 bindGuts( final PackageManager pm, final StatusBarNotification sbn, final NotificationEntry entry, final ExpandableNotificationRow row, final AssistantFeedbackController controller, final IStatusBarService statusBarService, final NotificationGutsManager notificationGutsManager)73 public void bindGuts( 74 final PackageManager pm, 75 final StatusBarNotification sbn, 76 final NotificationEntry entry, 77 final ExpandableNotificationRow row, 78 final AssistantFeedbackController controller, 79 final IStatusBarService statusBarService, 80 final NotificationGutsManager notificationGutsManager) { 81 mPkg = sbn.getPackageName(); 82 mPm = pm; 83 mEntry = entry; 84 mExpandableNotificationRow = row; 85 mRanking = entry.getRanking(); 86 mFeedbackController = controller; 87 mAppName = mPkg; 88 mStatusBarService = statusBarService; 89 mNotificationGutsManager = notificationGutsManager; 90 91 bindHeader(); 92 bindPrompt(); 93 } 94 bindHeader()95 private void bindHeader() { 96 // Package name 97 Drawable pkgicon = null; 98 ApplicationInfo info; 99 try { 100 info = mPm.getApplicationInfo(mPkg, 101 PackageManager.MATCH_UNINSTALLED_PACKAGES 102 | PackageManager.MATCH_DISABLED_COMPONENTS 103 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE 104 | PackageManager.MATCH_DIRECT_BOOT_AWARE); 105 if (info != null) { 106 mAppName = String.valueOf(mPm.getApplicationLabel(info)); 107 pkgicon = mPm.getApplicationIcon(info); 108 } 109 } catch (PackageManager.NameNotFoundException e) { 110 // app is gone, just show package name and generic icon 111 pkgicon = mPm.getDefaultActivityIcon(); 112 } 113 ((ImageView) findViewById(R.id.pkg_icon)).setImageDrawable(pkgicon); 114 ((TextView) findViewById(R.id.pkg_name)).setText(mAppName); 115 } 116 117 @Override onInitializeAccessibilityEvent(AccessibilityEvent event)118 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 119 super.onInitializeAccessibilityEvent(event); 120 if (mGutsContainer != null 121 && event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { 122 if (mGutsContainer.isExposed()) { 123 event.getText().add(mContext.getString( 124 R.string.notification_channel_controls_opened_accessibility, mAppName)); 125 } else { 126 event.getText().add(mContext.getString( 127 R.string.notification_channel_controls_closed_accessibility, mAppName)); 128 } 129 } 130 } 131 bindPrompt()132 private void bindPrompt() { 133 final TextView prompt = findViewById(R.id.prompt); 134 final TextView yes = findViewById(R.id.yes); 135 final TextView no = findViewById(R.id.no); 136 yes.setVisibility(View.VISIBLE); 137 no.setVisibility(View.VISIBLE); 138 yes.setOnClickListener(this::positiveFeedback); 139 no.setOnClickListener(this::negativeFeedback); 140 prompt.setText(Html.fromHtml(getPrompt())); 141 } 142 143 @SuppressLint("DefaultLocale") getPrompt()144 private String getPrompt() { 145 StringBuilder sb = new StringBuilder(); 146 int status = mFeedbackController.getFeedbackStatus(mEntry); 147 if (DEBUG) { 148 sb.append(String.format( 149 "[DEBUG]: oldImportance=%d, newImportance=%d, ranking=%f\n\n", 150 mRanking.getChannel().getImportance(), mRanking.getImportance(), 151 mRanking.getRankingScore())); 152 } 153 if (status == STATUS_ALERTED) { 154 sb.append(mContext.getText(R.string.feedback_alerted)); 155 } else if (status == STATUS_SILENCED) { 156 sb.append(mContext.getText(R.string.feedback_silenced)); 157 } else if (status == STATUS_PROMOTED) { 158 sb.append(mContext.getText(R.string.feedback_promoted)); 159 } else if (status == STATUS_DEMOTED) { 160 sb.append(mContext.getText(R.string.feedback_demoted)); 161 } 162 sb.append(" "); 163 sb.append(mContext.getText(R.string.feedback_prompt)); 164 165 return sb.toString(); 166 } 167 positiveFeedback(View v)168 private void positiveFeedback(View v) { 169 mGutsContainer.closeControls(v, /* save= */ false); 170 handleFeedback(true); 171 } 172 negativeFeedback(View v)173 private void negativeFeedback(View v) { 174 mMenuRowPlugin = mExpandableNotificationRow.getProvider(); 175 NotificationMenuRowPlugin.MenuItem menuItem = null; 176 if (mMenuRowPlugin != null) { 177 menuItem = mMenuRowPlugin.getLongpressMenuItem(mContext); 178 } 179 180 mGutsContainer.closeControls(v, /* save= */ false); 181 mNotificationGutsManager.openGuts(mExpandableNotificationRow, 0, 0, menuItem); 182 handleFeedback(false); 183 } 184 handleFeedback(boolean positive)185 private void handleFeedback(boolean positive) { 186 Bundle feedback = new Bundle(); 187 feedback.putInt(FEEDBACK_RATING, positive ? 1 : -1); 188 189 sendFeedbackToAssistant(feedback); 190 } 191 sendFeedbackToAssistant(Bundle feedback)192 private void sendFeedbackToAssistant(Bundle feedback) { 193 if (!mFeedbackController.isFeedbackEnabled()) { 194 return; 195 } 196 197 try { 198 mStatusBarService.onNotificationFeedbackReceived(mRanking.getKey(), feedback); 199 } catch (RemoteException e) { 200 if (DEBUG) { 201 Log.e(TAG, "Failed to send feedback to assistant", e); 202 } 203 } 204 } 205 closeControls(View v)206 private void closeControls(View v) { 207 mGutsContainer.closeControls(v, /* save= */ false); 208 } 209 210 @Override setGutsParent(NotificationGuts guts)211 public void setGutsParent(NotificationGuts guts) { 212 mGutsContainer = guts; 213 } 214 215 @Override getContentView()216 public View getContentView() { 217 return this; 218 } 219 220 @Override getActualHeight()221 public int getActualHeight() { 222 return getHeight(); 223 } 224 225 @Override handleCloseControls(boolean save, boolean force)226 public boolean handleCloseControls(boolean save, boolean force) { 227 return false; 228 } 229 230 @Override willBeRemoved()231 public boolean willBeRemoved() { 232 return false; 233 } 234 235 @Override shouldBeSavedOnClose()236 public boolean shouldBeSavedOnClose() { 237 return false; 238 } 239 240 @Override needsFalsingProtection()241 public boolean needsFalsingProtection() { 242 return false; 243 } 244 } 245