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