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