blob: e8b11226539a26944b6b70fe21d5737aa7dfad37 [file] [log] [blame]
Mady Mellordea7ecf2018-12-10 15:47:40 -08001/*
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
17package com.android.systemui.bubbles;
18
Mady Mellor3dff9e62019-02-05 18:12:53 -080019import static android.content.pm.ActivityInfo.DOCUMENT_LAUNCH_ALWAYS;
20import static android.util.StatsLogInternal.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING;
21import static android.util.StatsLogInternal.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE;
22import static android.util.StatsLogInternal.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__DOCUMENT_LAUNCH_NOT_ALWAYS;
23
Mady Mellore8e07712019-01-23 12:45:33 -080024import android.animation.LayoutTransition;
25import android.animation.ObjectAnimator;
Mady Mellordea7ecf2018-12-10 15:47:40 -080026import android.annotation.Nullable;
Mady Mellor3dff9e62019-02-05 18:12:53 -080027import android.app.ActivityView;
Mady Mellore8e07712019-01-23 12:45:33 -080028import android.app.INotificationManager;
Mady Mellor9801e852019-01-22 14:50:28 -080029import android.app.Notification;
30import android.app.PendingIntent;
Mady Mellordea7ecf2018-12-10 15:47:40 -080031import android.content.Context;
Mady Mellor9801e852019-01-22 14:50:28 -080032import android.content.Intent;
Mady Mellor3dff9e62019-02-05 18:12:53 -080033import android.content.pm.ActivityInfo;
Mady Mellor9801e852019-01-22 14:50:28 -080034import android.content.pm.ApplicationInfo;
35import android.content.pm.PackageManager;
Mady Mellordea7ecf2018-12-10 15:47:40 -080036import android.content.res.Resources;
Mady Mellordd497052019-01-30 17:23:48 -080037import android.content.res.TypedArray;
Mady Mellordea7ecf2018-12-10 15:47:40 -080038import android.graphics.Color;
Mady Mellor3dff9e62019-02-05 18:12:53 -080039import android.graphics.Insets;
40import android.graphics.Point;
Lyn Hanc26ff122019-03-29 16:46:07 -070041import android.graphics.drawable.AdaptiveIconDrawable;
42import android.graphics.drawable.ColorDrawable;
Mady Mellore8e07712019-01-23 12:45:33 -080043import android.graphics.drawable.Drawable;
Mady Mellor310c7c62019-02-21 17:03:08 -080044import android.graphics.drawable.GradientDrawable;
Lyn Hanc26ff122019-03-29 16:46:07 -070045import android.graphics.drawable.InsetDrawable;
Mady Mellordea7ecf2018-12-10 15:47:40 -080046import android.graphics.drawable.ShapeDrawable;
Mady Mellore8e07712019-01-23 12:45:33 -080047import android.os.RemoteException;
48import android.os.ServiceManager;
Mady Mellor7af771a2019-03-07 15:04:54 -080049import android.os.UserHandle;
Mady Mellor9801e852019-01-22 14:50:28 -080050import android.provider.Settings;
Steven Wub00225b2019-02-08 14:27:42 -050051import android.service.notification.StatusBarNotification;
Mady Mellordea7ecf2018-12-10 15:47:40 -080052import android.util.AttributeSet;
Mady Mellor9801e852019-01-22 14:50:28 -080053import android.util.Log;
Steven Wub00225b2019-02-08 14:27:42 -050054import android.util.StatsLog;
Mady Mellordea7ecf2018-12-10 15:47:40 -080055import android.view.View;
Mady Mellor5029fa62019-03-05 12:16:21 -080056import android.view.ViewGroup;
Mady Mellor3dff9e62019-02-05 18:12:53 -080057import android.view.WindowInsets;
Mady Mellore8e07712019-01-23 12:45:33 -080058import android.widget.FrameLayout;
Mady Mellore8e07712019-01-23 12:45:33 -080059import android.widget.ImageView;
Mady Mellordea7ecf2018-12-10 15:47:40 -080060import android.widget.LinearLayout;
Mark Renouf89b1a4a2018-12-04 14:59:45 -050061import android.widget.TextView;
Mady Mellordea7ecf2018-12-10 15:47:40 -080062
Mady Mellor3dff9e62019-02-05 18:12:53 -080063import com.android.systemui.Dependency;
Mady Mellore8e07712019-01-23 12:45:33 -080064import com.android.systemui.Interpolators;
Mady Mellordea7ecf2018-12-10 15:47:40 -080065import com.android.systemui.R;
66import com.android.systemui.recents.TriangleShape;
Mady Mellor9801e852019-01-22 14:50:28 -080067import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Mady Mellor3dff9e62019-02-05 18:12:53 -080068import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
69import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
Mady Mellordea7ecf2018-12-10 15:47:40 -080070
71/**
72 * Container for the expanded bubble view, handles rendering the caret and header of the view.
73 */
Mady Mellor3d82e682019-02-05 13:34:48 -080074public class BubbleExpandedView extends LinearLayout implements View.OnClickListener {
Mady Mellor9801e852019-01-22 14:50:28 -080075 private static final String TAG = "BubbleExpandedView";
Mady Mellordea7ecf2018-12-10 15:47:40 -080076
77 // The triangle pointing to the expanded view
78 private View mPointerView;
Mady Mellor44ee2fe2019-01-30 17:51:16 -080079 private int mPointerMargin;
Mady Mellore8e07712019-01-23 12:45:33 -080080
Lyn Hanc26ff122019-03-29 16:46:07 -070081 private ImageView mSettingsIcon;
Mady Mellore8e07712019-01-23 12:45:33 -080082
83 // Permission view
84 private View mPermissionView;
85
Mady Mellor3dff9e62019-02-05 18:12:53 -080086 // Views for expanded state
87 private ExpandableNotificationRow mNotifRow;
88 private ActivityView mActivityView;
89
90 private boolean mActivityViewReady = false;
91 private PendingIntent mBubbleIntent;
92
Mady Mellor5d8f1402019-02-21 18:23:52 -080093 private boolean mKeyboardVisible;
94 private boolean mNeedsNewHeight;
95
Mady Mellorfe7ec032019-01-30 17:32:49 -080096 private int mMinHeight;
97 private int mHeaderHeight;
Mady Mellor44ee2fe2019-01-30 17:51:16 -080098 private int mBubbleHeight;
99 private int mPermissionHeight;
Mady Mellordea7ecf2018-12-10 15:47:40 -0800100
Mady Mellor9801e852019-01-22 14:50:28 -0800101 private NotificationEntry mEntry;
102 private PackageManager mPm;
103 private String mAppName;
Mady Mellore8e07712019-01-23 12:45:33 -0800104 private Drawable mAppIcon;
105
106 private INotificationManager mNotificationManagerService;
Mady Mellor3dff9e62019-02-05 18:12:53 -0800107 private BubbleController mBubbleController = Dependency.get(BubbleController.class);
Mady Mellor9801e852019-01-22 14:50:28 -0800108
Mady Mellor9801e852019-01-22 14:50:28 -0800109 private BubbleStackView mStackView;
110
Mady Mellor3dff9e62019-02-05 18:12:53 -0800111 private BubbleExpandedView.OnBubbleBlockedListener mOnBubbleBlockedListener;
112
113 private ActivityView.StateCallback mStateCallback = new ActivityView.StateCallback() {
114 @Override
115 public void onActivityViewReady(ActivityView view) {
Mady Mellor6d002032019-02-13 13:45:17 -0800116 if (!mActivityViewReady) {
117 mActivityViewReady = true;
118 mActivityView.startActivity(mBubbleIntent);
119 }
Mady Mellor3dff9e62019-02-05 18:12:53 -0800120 }
121
122 @Override
123 public void onActivityViewDestroyed(ActivityView view) {
124 mActivityViewReady = false;
125 }
126
127 /**
128 * This is only called for tasks on this ActivityView, which is also set to
129 * single-task mode -- meaning never more than one task on this display. If a task
130 * is being removed, it's the top Activity finishing and this bubble should
131 * be removed or collapsed.
132 */
133 @Override
134 public void onTaskRemovalStarted(int taskId) {
135 if (mEntry != null) {
136 // Must post because this is called from a binder thread.
Mark Renouf08bc42a2019-03-07 13:01:59 -0500137 post(() -> mBubbleController.removeBubble(mEntry.key,
138 BubbleController.DISMISS_TASK_FINISHED));
Mady Mellor3dff9e62019-02-05 18:12:53 -0800139 }
140 }
141 };
Mady Mellore8e07712019-01-23 12:45:33 -0800142
Mady Mellor3d82e682019-02-05 13:34:48 -0800143 public BubbleExpandedView(Context context) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800144 this(context, null);
145 }
146
Mady Mellor3d82e682019-02-05 13:34:48 -0800147 public BubbleExpandedView(Context context, AttributeSet attrs) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800148 this(context, attrs, 0);
149 }
150
Mady Mellor3d82e682019-02-05 13:34:48 -0800151 public BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800152 this(context, attrs, defStyleAttr, 0);
153 }
154
Mady Mellor3d82e682019-02-05 13:34:48 -0800155 public BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr,
Mady Mellordea7ecf2018-12-10 15:47:40 -0800156 int defStyleRes) {
157 super(context, attrs, defStyleAttr, defStyleRes);
Mady Mellor9801e852019-01-22 14:50:28 -0800158 mPm = context.getPackageManager();
Mady Mellorfe7ec032019-01-30 17:32:49 -0800159 mMinHeight = getResources().getDimensionPixelSize(
Mady Mellor3dff9e62019-02-05 18:12:53 -0800160 R.dimen.bubble_expanded_default_height);
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800161 mPointerMargin = getResources().getDimensionPixelSize(R.dimen.bubble_pointer_margin);
Mady Mellore8e07712019-01-23 12:45:33 -0800162 try {
163 mNotificationManagerService = INotificationManager.Stub.asInterface(
164 ServiceManager.getServiceOrThrow(Context.NOTIFICATION_SERVICE));
165 } catch (ServiceManager.ServiceNotFoundException e) {
166 Log.w(TAG, e);
167 }
Mady Mellordea7ecf2018-12-10 15:47:40 -0800168 }
169
170 @Override
171 protected void onFinishInflate() {
172 super.onFinishInflate();
173
174 Resources res = getResources();
175 mPointerView = findViewById(R.id.pointer_view);
176 int width = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
177 int height = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
Mady Mellordd497052019-01-30 17:23:48 -0800178
179 TypedArray ta = getContext().obtainStyledAttributes(
180 new int[] {android.R.attr.colorBackgroundFloating});
181 int bgColor = ta.getColor(0, Color.WHITE);
182 ta.recycle();
183
Mady Mellordea7ecf2018-12-10 15:47:40 -0800184 ShapeDrawable triangleDrawable = new ShapeDrawable(
Lyn Han3f5c3a42019-04-01 15:59:56 -0700185 TriangleShape.create(width, height, false /* pointUp */));
Mady Mellordd497052019-01-30 17:23:48 -0800186 triangleDrawable.setTint(bgColor);
Mady Mellordea7ecf2018-12-10 15:47:40 -0800187 mPointerView.setBackground(triangleDrawable);
Mady Mellor9801e852019-01-22 14:50:28 -0800188
Mady Mellore8e07712019-01-23 12:45:33 -0800189 FrameLayout viewWrapper = findViewById(R.id.header_permission_wrapper);
Mady Mellor310c7c62019-02-21 17:03:08 -0800190
Mady Mellore8e07712019-01-23 12:45:33 -0800191 LayoutTransition transition = new LayoutTransition();
192 transition.setDuration(200);
193
194 ObjectAnimator appearAnimator = ObjectAnimator.ofFloat(null, View.ALPHA, 0f, 1f);
195 transition.setAnimator(LayoutTransition.APPEARING, appearAnimator);
196 transition.setInterpolator(LayoutTransition.APPEARING, Interpolators.ALPHA_IN);
197
198 ObjectAnimator disappearAnimator = ObjectAnimator.ofFloat(null, View.ALPHA, 1f, 0f);
199 transition.setAnimator(LayoutTransition.DISAPPEARING, disappearAnimator);
200 transition.setInterpolator(LayoutTransition.DISAPPEARING, Interpolators.ALPHA_OUT);
201
202 transition.setAnimateParentHierarchy(false);
203 viewWrapper.setLayoutTransition(transition);
204 viewWrapper.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
205
Mady Mellorfe7ec032019-01-30 17:32:49 -0800206 mHeaderHeight = getContext().getResources().getDimensionPixelSize(
207 R.dimen.bubble_expanded_header_height);
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800208 mPermissionHeight = getContext().getResources().getDimensionPixelSize(
209 R.dimen.bubble_permission_height);
Mady Mellore8e07712019-01-23 12:45:33 -0800210
211 mPermissionView = findViewById(R.id.permission_layout);
Lyn Hanc26ff122019-03-29 16:46:07 -0700212 mSettingsIcon = findViewById(R.id.settings_button);
213 mSettingsIcon.setOnClickListener(this);
214 updateHeaderColor();
215
Mady Mellore8e07712019-01-23 12:45:33 -0800216 findViewById(R.id.no_bubbles_button).setOnClickListener(this);
217 findViewById(R.id.yes_bubbles_button).setOnClickListener(this);
Mady Mellor3dff9e62019-02-05 18:12:53 -0800218
219 mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */,
220 true /* singleTaskInstance */);
221 addView(mActivityView);
222
Mady Mellor5d8f1402019-02-21 18:23:52 -0800223 setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
224 // Keep track of IME displaying because we should not make any adjustments that might
225 // cause a config change while the IME is displayed otherwise it'll loose focus.
Mady Mellor3dff9e62019-02-05 18:12:53 -0800226 final int keyboardHeight = insets.getSystemWindowInsetBottom()
227 - insets.getStableInsetBottom();
Mady Mellor5d8f1402019-02-21 18:23:52 -0800228 mKeyboardVisible = keyboardHeight != 0;
229 if (!mKeyboardVisible && mNeedsNewHeight) {
230 updateHeight();
231 }
Mady Mellor3dff9e62019-02-05 18:12:53 -0800232 return view.onApplyWindowInsets(insets);
233 });
Mady Mellore8e07712019-01-23 12:45:33 -0800234 }
235
Mady Mellor5d8f1402019-02-21 18:23:52 -0800236 @Override
237 protected void onDetachedFromWindow() {
238 super.onDetachedFromWindow();
239 mKeyboardVisible = false;
240 mNeedsNewHeight = false;
241 if (mActivityView != null) {
242 mActivityView.setForwardedInsets(Insets.of(0, 0, 0, 0));
243 }
244 }
245
246 /**
247 * Called by {@link BubbleStackView} when the insets for the expanded state should be updated.
248 * This should be done post-move and post-animation.
249 */
250 void updateInsets(WindowInsets insets) {
251 if (usingActivityView()) {
252 Point displaySize = new Point();
253 mActivityView.getContext().getDisplay().getSize(displaySize);
254 int[] windowLocation = mActivityView.getLocationOnScreen();
255 final int windowBottom = windowLocation[1] + mActivityView.getHeight();
256 final int keyboardHeight = insets.getSystemWindowInsetBottom()
257 - insets.getStableInsetBottom();
258 final int insetsBottom = Math.max(0,
259 windowBottom + keyboardHeight - displaySize.y);
260 mActivityView.setForwardedInsets(Insets.of(0, 0, 0, insetsBottom));
261 }
262 }
263
Mady Mellore8e07712019-01-23 12:45:33 -0800264 /**
Mady Mellor310c7c62019-02-21 17:03:08 -0800265 * Creates a background with corners rounded based on how the view is configured to display
266 */
267 private Drawable createHeaderPermissionBackground(int bgColor) {
268 TypedArray ta2 = getContext().obtainStyledAttributes(
269 new int[] {android.R.attr.dialogCornerRadius});
270 final float cr = ta2.getDimension(0, 0f);
271 ta2.recycle();
272
Lyn Han3f5c3a42019-04-01 15:59:56 -0700273 float[] radii = new float[] {cr, cr, cr, cr, 0, 0, 0, 0};
Mady Mellor310c7c62019-02-21 17:03:08 -0800274 GradientDrawable chromeBackground = new GradientDrawable();
275 chromeBackground.setShape(GradientDrawable.RECTANGLE);
276 chromeBackground.setCornerRadii(radii);
277 chromeBackground.setColor(bgColor);
278 return chromeBackground;
279 }
280
281 /**
Mady Mellore8e07712019-01-23 12:45:33 -0800282 * Sets the listener to notify when a bubble has been blocked.
283 */
284 public void setOnBlockedListener(OnBubbleBlockedListener listener) {
285 mOnBubbleBlockedListener = listener;
Mady Mellor9801e852019-01-22 14:50:28 -0800286 }
287
288 /**
289 * Sets the notification entry used to populate this view.
290 */
291 public void setEntry(NotificationEntry entry, BubbleStackView stackView) {
292 mStackView = stackView;
293 mEntry = entry;
294
295 ApplicationInfo info;
296 try {
297 info = mPm.getApplicationInfo(
298 entry.notification.getPackageName(),
299 PackageManager.MATCH_UNINSTALLED_PACKAGES
300 | PackageManager.MATCH_DISABLED_COMPONENTS
301 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
302 | PackageManager.MATCH_DIRECT_BOOT_AWARE);
303 if (info != null) {
304 mAppName = String.valueOf(mPm.getApplicationLabel(info));
Mady Mellore8e07712019-01-23 12:45:33 -0800305 mAppIcon = mPm.getApplicationIcon(info);
Mady Mellor9801e852019-01-22 14:50:28 -0800306 }
307 } catch (PackageManager.NameNotFoundException e) {
308 // Ahh... just use package name
309 mAppName = entry.notification.getPackageName();
310 }
Mady Mellore8e07712019-01-23 12:45:33 -0800311 if (mAppIcon == null) {
312 mAppIcon = mPm.getDefaultActivityIcon();
313 }
Mady Mellor9801e852019-01-22 14:50:28 -0800314 updateHeaderView();
Mady Mellore8e07712019-01-23 12:45:33 -0800315 updatePermissionView();
Mady Mellor3dff9e62019-02-05 18:12:53 -0800316 updateExpandedView();
Mady Mellor6d002032019-02-13 13:45:17 -0800317 }
318
319 /**
320 * Lets activity view know it should be shown / populated.
321 */
Mady Mellor5029fa62019-03-05 12:16:21 -0800322 public void populateExpandedView() {
323 if (usingActivityView()) {
324 mActivityView.setCallback(mStateCallback);
325 } else {
326 // We're using notification template
327 ViewGroup parent = (ViewGroup) mNotifRow.getParent();
328 if (parent == this) {
329 // Already added
330 return;
331 } else if (parent != null) {
332 // Still in the shade... remove it
333 parent.removeView(mNotifRow);
334 }
Lyn Han3f5c3a42019-04-01 15:59:56 -0700335 addView(mNotifRow, 1 /* index */);
Mady Mellor5029fa62019-03-05 12:16:21 -0800336 }
Mady Mellor9801e852019-01-22 14:50:28 -0800337 }
338
Mady Mellorfe7ec032019-01-30 17:32:49 -0800339 /**
340 * Updates the entry backing this view. This will not re-populate ActivityView, it will
341 * only update the deep-links in the header, the title, and the height of the view.
342 */
343 public void update(NotificationEntry entry) {
344 if (entry.key.equals(mEntry.key)) {
345 mEntry = entry;
346 updateHeaderView();
347 updateHeight();
348 } else {
349 Log.w(TAG, "Trying to update entry with different key, new entry: "
350 + entry.key + " old entry: " + mEntry.key);
351 }
352 }
353
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700354 /**
Lyn Hanc26ff122019-03-29 16:46:07 -0700355 * Update header color and icon shape when theme changes.
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700356 */
357 void updateHeaderColor() {
Lyn Han76e803d2019-03-26 17:31:33 -0700358 TypedArray ta = mContext.obtainStyledAttributes(
359 new int[] {android.R.attr.colorBackgroundFloating, android.R.attr.colorForeground});
Lyn Hanc26ff122019-03-29 16:46:07 -0700360 int backgroundColor = ta.getColor(0, Color.WHITE /* default */);
361 int foregroundColor = ta.getColor(1, Color.BLACK /* default */);
Lyn Han76e803d2019-03-26 17:31:33 -0700362 ta.recycle();
363
Lyn Hanc26ff122019-03-29 16:46:07 -0700364 mPermissionView.setBackground(createHeaderPermissionBackground(backgroundColor));
365
366 Drawable settingsIcon = mSettingsIcon.getDrawable();
367 settingsIcon.setTint(foregroundColor);
368
369 int mIconInset = getResources().getDimensionPixelSize(R.dimen.bubble_icon_inset);
370 InsetDrawable foreground = new InsetDrawable(settingsIcon, mIconInset);
371 ColorDrawable background = new ColorDrawable(backgroundColor);
372 AdaptiveIconDrawable adaptiveIcon = new AdaptiveIconDrawable(background,
373 foreground);
374 mSettingsIcon.setImageDrawable(adaptiveIcon);
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700375 }
376
Mady Mellor9801e852019-01-22 14:50:28 -0800377 private void updateHeaderView() {
378 mSettingsIcon.setContentDescription(getResources().getString(
379 R.string.bubbles_settings_button_description, mAppName));
Mady Mellore8e07712019-01-23 12:45:33 -0800380 }
381
382 private void updatePermissionView() {
383 boolean hasUserApprovedBubblesForPackage = false;
384 try {
385 hasUserApprovedBubblesForPackage =
386 mNotificationManagerService.hasUserApprovedBubblesForPackage(
387 mEntry.notification.getPackageName(), mEntry.notification.getUid());
388 } catch (RemoteException e) {
389 Log.w(TAG, e);
390 }
391 if (hasUserApprovedBubblesForPackage) {
Lyn Hanc26ff122019-03-29 16:46:07 -0700392 showSettingsIcon();
Mady Mellore8e07712019-01-23 12:45:33 -0800393 } else {
Lyn Hanc26ff122019-03-29 16:46:07 -0700394 showPermissionView();
Mady Mellore8e07712019-01-23 12:45:33 -0800395 ((ImageView) mPermissionView.findViewById(R.id.pkgicon)).setImageDrawable(mAppIcon);
396 ((TextView) mPermissionView.findViewById(R.id.pkgname)).setText(mAppName);
Mady Mellor81f734c2019-03-20 18:42:50 -0700397 ((TextView) mPermissionView.findViewById(R.id.prompt)).setText(
398 getResources().getString(R.string.bubbles_prompt, mAppName));
Steven Wu45e38ae2019-03-25 16:16:59 -0400399 logBubbleClickEvent(mEntry,
Steven Wua62cb6a2019-02-15 17:12:51 -0500400 StatsLog.BUBBLE_UICHANGED__ACTION__PERMISSION_DIALOG_SHOWN);
Mady Mellor9801e852019-01-22 14:50:28 -0800401 }
402 }
403
Mady Mellor3dff9e62019-02-05 18:12:53 -0800404 private void updateExpandedView() {
405 mBubbleIntent = getBubbleIntent(mEntry);
406 if (mBubbleIntent != null) {
407 if (mNotifRow != null) {
408 // Clear out the row if we had it previously
409 removeView(mNotifRow);
410 mNotifRow = null;
411 }
Mady Mellor3dff9e62019-02-05 18:12:53 -0800412 mActivityView.setVisibility(VISIBLE);
413 } else {
414 // Hide activity view if we had it previously
415 mActivityView.setVisibility(GONE);
Mady Mellor3dff9e62019-02-05 18:12:53 -0800416 mNotifRow = mEntry.getRow();
Mady Mellor5029fa62019-03-05 12:16:21 -0800417
Mady Mellor3dff9e62019-02-05 18:12:53 -0800418 }
419 updateView();
420 }
421
Mark Renouf041d7262019-02-06 12:09:41 -0500422 boolean performBackPressIfNeeded() {
Mady Mellor323fb0b2019-03-25 12:15:22 -0700423 if (!usingActivityView()) {
Mark Renouf041d7262019-02-06 12:09:41 -0500424 return false;
425 }
426 mActivityView.performBackPress();
427 return true;
428 }
429
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800430 /**
431 * @return total height that the expanded view occupies.
432 */
433 int getExpandedSize() {
434 int chromeHeight = mPermissionView.getVisibility() != View.VISIBLE
435 ? mHeaderHeight
436 : mPermissionHeight;
437 return mBubbleHeight + mPointerView.getHeight() + mPointerMargin
438 + chromeHeight;
439 }
440
Mady Mellorfe7ec032019-01-30 17:32:49 -0800441 void updateHeight() {
442 if (usingActivityView()) {
443 Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
Mady Mellor7af771a2019-03-07 15:04:54 -0800444 float desiredHeight;
Mady Mellorfe7ec032019-01-30 17:32:49 -0800445 if (data == null) {
446 // This is a contentIntent based bubble, lets allow it to be the max height
447 // as it was forced into this mode and not prepared to be small
448 desiredHeight = mStackView.getMaxExpandedHeight();
449 } else {
Mady Mellor7af771a2019-03-07 15:04:54 -0800450 boolean useRes = data.getDesiredHeightResId() != 0;
451 float desiredPx;
452 if (useRes) {
453 desiredPx = getDimenForPackageUser(data.getDesiredHeightResId(),
454 mEntry.notification.getPackageName(),
455 mEntry.notification.getUser().getIdentifier());
456 } else {
457 desiredPx = data.getDesiredHeight()
458 * getContext().getResources().getDisplayMetrics().density;
459 }
460 desiredHeight = desiredPx > 0 ? desiredPx : mMinHeight;
Mady Mellorfe7ec032019-01-30 17:32:49 -0800461 }
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800462 int chromeHeight = mPermissionView.getVisibility() != View.VISIBLE
463 ? mHeaderHeight
464 : mPermissionHeight;
465 int max = mStackView.getMaxExpandedHeight() - chromeHeight - mPointerView.getHeight()
466 - mPointerMargin;
Mady Mellor7af771a2019-03-07 15:04:54 -0800467 float height = Math.min(desiredHeight, max);
Mady Mellorfe7ec032019-01-30 17:32:49 -0800468 height = Math.max(height, mMinHeight);
469 LayoutParams lp = (LayoutParams) mActivityView.getLayoutParams();
Mady Mellor5d8f1402019-02-21 18:23:52 -0800470 mNeedsNewHeight = lp.height != height;
471 if (!mKeyboardVisible) {
472 // If the keyboard is visible... don't adjust the height because that will cause
473 // a configuration change and the keyboard will be lost.
Mady Mellor7af771a2019-03-07 15:04:54 -0800474 lp.height = (int) height;
475 mBubbleHeight = (int) height;
Mady Mellor5d8f1402019-02-21 18:23:52 -0800476 mActivityView.setLayoutParams(lp);
477 mNeedsNewHeight = false;
478 }
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800479 } else {
480 mBubbleHeight = mNotifRow != null ? mNotifRow.getIntrinsicHeight() : mMinHeight;
Mady Mellorfe7ec032019-01-30 17:32:49 -0800481 }
482 }
483
Mady Mellor9801e852019-01-22 14:50:28 -0800484 @Override
485 public void onClick(View view) {
486 if (mEntry == null) {
487 return;
488 }
489 Notification n = mEntry.notification.getNotification();
490 int id = view.getId();
Lyn Hanc26ff122019-03-29 16:46:07 -0700491 if (id == R.id.settings_button) {
Mady Mellor9801e852019-01-22 14:50:28 -0800492 Intent intent = getSettingsIntent(mEntry.notification.getPackageName(),
493 mEntry.notification.getUid());
Steven Wub00225b2019-02-08 14:27:42 -0500494 mStackView.collapseStack(() -> {
495 mContext.startActivity(intent);
Steven Wu45e38ae2019-03-25 16:16:59 -0400496 logBubbleClickEvent(mEntry,
Steven Wub00225b2019-02-08 14:27:42 -0500497 StatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
498 });
Mady Mellore8e07712019-01-23 12:45:33 -0800499 } else if (id == R.id.no_bubbles_button) {
500 setBubblesAllowed(false);
501 } else if (id == R.id.yes_bubbles_button) {
502 setBubblesAllowed(true);
503 }
504 }
505
506 private void setBubblesAllowed(boolean allowed) {
507 try {
508 mNotificationManagerService.setBubblesAllowed(
509 mEntry.notification.getPackageName(),
510 mEntry.notification.getUid(),
511 allowed);
512 if (allowed) {
Lyn Hanc26ff122019-03-29 16:46:07 -0700513 showSettingsIcon();
Mady Mellore8e07712019-01-23 12:45:33 -0800514 } else if (mOnBubbleBlockedListener != null) {
515 mOnBubbleBlockedListener.onBubbleBlocked(mEntry);
516 }
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800517 mStackView.onExpandedHeightChanged();
Steven Wu45e38ae2019-03-25 16:16:59 -0400518 logBubbleClickEvent(mEntry,
Steven Wub00225b2019-02-08 14:27:42 -0500519 allowed ? StatsLog.BUBBLE_UICHANGED__ACTION__PERMISSION_OPT_IN :
520 StatsLog.BUBBLE_UICHANGED__ACTION__PERMISSION_OPT_OUT);
Mady Mellore8e07712019-01-23 12:45:33 -0800521 } catch (RemoteException e) {
522 Log.w(TAG, e);
Mady Mellor9801e852019-01-22 14:50:28 -0800523 }
Mady Mellordea7ecf2018-12-10 15:47:40 -0800524 }
525
Lyn Hanc26ff122019-03-29 16:46:07 -0700526 void showSettingsIcon() {
527 mPermissionView.setVisibility(GONE);
528 mSettingsIcon.setVisibility(VISIBLE);
529 }
530
531 void showPermissionView() {
532 mSettingsIcon.setVisibility(GONE);
533 mPermissionView.setVisibility(VISIBLE);
534
535 }
536
Mady Mellordea7ecf2018-12-10 15:47:40 -0800537 /**
Mady Mellor3dff9e62019-02-05 18:12:53 -0800538 * Update appearance of the expanded view being displayed.
539 */
540 public void updateView() {
541 if (usingActivityView()
542 && mActivityView.getVisibility() == VISIBLE
543 && mActivityView.isAttachedToWindow()) {
544 mActivityView.onLocationChanged();
545 } else if (mNotifRow != null) {
546 applyRowState(mNotifRow);
547 }
Mady Mellorfe7ec032019-01-30 17:32:49 -0800548 updateHeight();
Mady Mellor3dff9e62019-02-05 18:12:53 -0800549 }
550
551 /**
Mady Mellordea7ecf2018-12-10 15:47:40 -0800552 * Set the x position that the tip of the triangle should point to.
553 */
Mady Mellor3dff9e62019-02-05 18:12:53 -0800554 public void setPointerPosition(float x) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800555 // Adjust for the pointer size
Mady Mellor3dff9e62019-02-05 18:12:53 -0800556 x -= (mPointerView.getWidth() / 2f);
Mady Mellordea7ecf2018-12-10 15:47:40 -0800557 mPointerView.setTranslationX(x);
558 }
559
560 /**
Mady Mellor3dff9e62019-02-05 18:12:53 -0800561 * Removes and releases an ActivityView if one was previously created for this bubble.
Mady Mellordea7ecf2018-12-10 15:47:40 -0800562 */
Mady Mellor94d94a72019-03-05 18:16:59 -0800563 public void cleanUpExpandedState() {
564 removeView(mNotifRow);
565
Mady Mellor3dff9e62019-02-05 18:12:53 -0800566 if (mActivityView == null) {
Mark Renouf89b1a4a2018-12-04 14:59:45 -0500567 return;
568 }
Mark Renouf28c250d2019-02-25 16:47:34 -0500569 if (mActivityViewReady) {
570 mActivityView.release();
Mady Mellordea7ecf2018-12-10 15:47:40 -0800571 }
Mark Renouf28c250d2019-02-25 16:47:34 -0500572 removeView(mActivityView);
Mady Mellor3dff9e62019-02-05 18:12:53 -0800573 mActivityView = null;
574 mActivityViewReady = false;
Mady Mellordea7ecf2018-12-10 15:47:40 -0800575 }
576
Mady Mellor3dff9e62019-02-05 18:12:53 -0800577 private boolean usingActivityView() {
Mady Mellor323fb0b2019-03-25 12:15:22 -0700578 return mBubbleIntent != null && mActivityView != null;
Mady Mellor3dff9e62019-02-05 18:12:53 -0800579 }
580
581 private void applyRowState(ExpandableNotificationRow view) {
582 view.reset();
583 view.setHeadsUp(false);
584 view.resetTranslation();
585 view.setOnKeyguard(false);
586 view.setOnAmbient(false);
587 view.setClipBottomAmount(0);
588 view.setClipTopAmount(0);
589 view.setContentTransformationAmount(0, false);
590 view.setIconsVisible(true);
591
592 // TODO - Need to reset this (and others) when view goes back in shade, leave for now
593 // view.setTopRoundness(1, false);
594 // view.setBottomRoundness(1, false);
595
596 ExpandableViewState viewState = view.getViewState();
597 viewState = viewState == null ? new ExpandableViewState() : viewState;
598 viewState.height = view.getIntrinsicHeight();
599 viewState.gone = false;
600 viewState.hidden = false;
601 viewState.dimmed = false;
602 viewState.dark = false;
603 viewState.alpha = 1f;
604 viewState.notGoneIndex = -1;
605 viewState.xTranslation = 0;
606 viewState.yTranslation = 0;
607 viewState.zTranslation = 0;
608 viewState.scaleX = 1;
609 viewState.scaleY = 1;
610 viewState.inShelf = true;
611 viewState.headsUpIsVisible = false;
612 viewState.applyToView(view);
Mady Mellordea7ecf2018-12-10 15:47:40 -0800613 }
Mady Mellor9801e852019-01-22 14:50:28 -0800614
615 private Intent getSettingsIntent(String packageName, final int appUid) {
616 final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
617 intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
618 intent.putExtra(Settings.EXTRA_APP_UID, appUid);
619 intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
620 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
621 return intent;
622 }
Mady Mellore8e07712019-01-23 12:45:33 -0800623
Mady Mellor3dff9e62019-02-05 18:12:53 -0800624 @Nullable
625 private PendingIntent getBubbleIntent(NotificationEntry entry) {
626 Notification notif = entry.notification.getNotification();
627 String packageName = entry.notification.getPackageName();
628 Notification.BubbleMetadata data = notif.getBubbleMetadata();
629 if (data != null && canLaunchInActivityView(data.getIntent(), true /* enableLogging */,
630 packageName)) {
631 return data.getIntent();
632 } else if (BubbleController.shouldUseContentIntent(mContext)
633 && canLaunchInActivityView(notif.contentIntent, false /* enableLogging */,
634 packageName)) {
635 return notif.contentIntent;
636 }
637 return null;
638 }
639
640 /**
641 * Whether an intent is properly configured to display in an {@link android.app.ActivityView}.
642 *
643 * @param intent the pending intent of the bubble.
644 * @param enableLogging whether bubble developer error should be logged.
645 * @param packageName the notification package name for this bubble.
646 * @return
647 */
648 private boolean canLaunchInActivityView(PendingIntent intent, boolean enableLogging,
649 String packageName) {
650 if (intent == null) {
651 return false;
652 }
653 ActivityInfo info =
654 intent.getIntent().resolveActivityInfo(mContext.getPackageManager(), 0);
655 if (info == null) {
656 if (enableLogging) {
657 StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
658 BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING);
659 }
660 return false;
661 }
662 if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
663 if (enableLogging) {
664 StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
665 BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE);
666 }
667 return false;
668 }
669 if (info.documentLaunchMode != DOCUMENT_LAUNCH_ALWAYS) {
670 if (enableLogging) {
671 StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
672 BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__DOCUMENT_LAUNCH_NOT_ALWAYS);
673 }
674 return false;
675 }
676 return (info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) != 0;
677 }
678
Mady Mellore8e07712019-01-23 12:45:33 -0800679 /**
680 * Listener that is notified when a bubble is blocked.
681 */
682 public interface OnBubbleBlockedListener {
683 /**
684 * Called when a bubble is blocked for the provided entry.
685 */
686 void onBubbleBlocked(NotificationEntry entry);
687 }
Steven Wub00225b2019-02-08 14:27:42 -0500688
689 /**
690 * Logs bubble UI click event.
691 *
Steven Wu45e38ae2019-03-25 16:16:59 -0400692 * @param entry the bubble notification entry that user is interacting with.
Steven Wub00225b2019-02-08 14:27:42 -0500693 * @param action the user interaction enum.
694 */
Steven Wu45e38ae2019-03-25 16:16:59 -0400695 private void logBubbleClickEvent(NotificationEntry entry, int action) {
696 StatusBarNotification notification = entry.notification;
Steven Wub00225b2019-02-08 14:27:42 -0500697 StatsLog.write(StatsLog.BUBBLE_UI_CHANGED,
698 notification.getPackageName(),
699 notification.getNotification().getChannelId(),
700 notification.getId(),
701 mStackView.getBubbleIndex(mStackView.getExpandedBubble()),
702 mStackView.getBubbleCount(),
703 action,
704 mStackView.getNormalizedXPosition(),
Steven Wu45e38ae2019-03-25 16:16:59 -0400705 mStackView.getNormalizedYPosition(),
706 entry.showInShadeWhenBubble());
Steven Wub00225b2019-02-08 14:27:42 -0500707 }
Mady Mellor7af771a2019-03-07 15:04:54 -0800708
709 private int getDimenForPackageUser(int resId, String pkg, int userId) {
710 Resources r;
711 if (pkg != null) {
712 try {
713 if (userId == UserHandle.USER_ALL) {
714 userId = UserHandle.USER_SYSTEM;
715 }
716 r = mPm.getResourcesForApplicationAsUser(pkg, userId);
717 return r.getDimensionPixelSize(resId);
718 } catch (PackageManager.NameNotFoundException ex) {
719 // Uninstalled, don't care
720 } catch (Resources.NotFoundException e) {
721 // Invalid res id, return 0 and user our default
722 Log.e(TAG, "Couldn't find desired height res id", e);
723 }
724 }
725 return 0;
726 }
Mady Mellordea7ecf2018-12-10 15:47:40 -0800727}