blob: e8b346ed97ab6ffb8651e4da8d01a252d39f64c3 [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;
Mady Mellore8e07712019-01-23 12:45:33 -080041import android.graphics.drawable.Drawable;
Mady Mellor310c7c62019-02-21 17:03:08 -080042import android.graphics.drawable.GradientDrawable;
Mady Mellordea7ecf2018-12-10 15:47:40 -080043import android.graphics.drawable.ShapeDrawable;
Mady Mellore8e07712019-01-23 12:45:33 -080044import android.os.RemoteException;
45import android.os.ServiceManager;
Mady Mellor9801e852019-01-22 14:50:28 -080046import android.provider.Settings;
Steven Wub00225b2019-02-08 14:27:42 -050047import android.service.notification.StatusBarNotification;
Mady Mellordea7ecf2018-12-10 15:47:40 -080048import android.util.AttributeSet;
Mady Mellor9801e852019-01-22 14:50:28 -080049import android.util.Log;
Steven Wub00225b2019-02-08 14:27:42 -050050import android.util.StatsLog;
Mady Mellordea7ecf2018-12-10 15:47:40 -080051import android.view.View;
Mady Mellor5029fa62019-03-05 12:16:21 -080052import android.view.ViewGroup;
Mady Mellor3dff9e62019-02-05 18:12:53 -080053import android.view.WindowInsets;
Mady Mellore8e07712019-01-23 12:45:33 -080054import android.widget.FrameLayout;
Mady Mellor9801e852019-01-22 14:50:28 -080055import android.widget.ImageButton;
Mady Mellore8e07712019-01-23 12:45:33 -080056import android.widget.ImageView;
Mady Mellordea7ecf2018-12-10 15:47:40 -080057import android.widget.LinearLayout;
Mark Renouf89b1a4a2018-12-04 14:59:45 -050058import android.widget.TextView;
Mady Mellordea7ecf2018-12-10 15:47:40 -080059
Mady Mellor3dff9e62019-02-05 18:12:53 -080060import com.android.systemui.Dependency;
Mady Mellore8e07712019-01-23 12:45:33 -080061import com.android.systemui.Interpolators;
Mady Mellordea7ecf2018-12-10 15:47:40 -080062import com.android.systemui.R;
63import com.android.systemui.recents.TriangleShape;
Mady Mellor9801e852019-01-22 14:50:28 -080064import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Mady Mellor3dff9e62019-02-05 18:12:53 -080065import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
66import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
Mady Mellordea7ecf2018-12-10 15:47:40 -080067
68/**
69 * Container for the expanded bubble view, handles rendering the caret and header of the view.
70 */
Mady Mellor3d82e682019-02-05 13:34:48 -080071public class BubbleExpandedView extends LinearLayout implements View.OnClickListener {
Mady Mellor9801e852019-01-22 14:50:28 -080072 private static final String TAG = "BubbleExpandedView";
Mady Mellordea7ecf2018-12-10 15:47:40 -080073
Mady Mellor44ee2fe2019-01-30 17:51:16 -080074 // Configurable via bubble settings; just for testing
75 private boolean mUseFooter;
76 private boolean mShowOnTop;
77
Mady Mellordea7ecf2018-12-10 15:47:40 -080078 // The triangle pointing to the expanded view
79 private View mPointerView;
Mady Mellor44ee2fe2019-01-30 17:51:16 -080080 private int mPointerMargin;
Mady Mellore8e07712019-01-23 12:45:33 -080081
82 // Header
83 private View mHeaderView;
Mady Mellor9801e852019-01-22 14:50:28 -080084 private ImageButton mDeepLinkIcon;
Mady Mellor9801e852019-01-22 14:50:28 -080085 private ImageButton mSettingsIcon;
Mady Mellore8e07712019-01-23 12:45:33 -080086
87 // Permission view
88 private View mPermissionView;
89
Mady Mellor3dff9e62019-02-05 18:12:53 -080090 // Views for expanded state
91 private ExpandableNotificationRow mNotifRow;
92 private ActivityView mActivityView;
93
94 private boolean mActivityViewReady = false;
95 private PendingIntent mBubbleIntent;
96
Mady Mellorfe7ec032019-01-30 17:32:49 -080097 private int mMinHeight;
98 private int mHeaderHeight;
Mady Mellor44ee2fe2019-01-30 17:51:16 -080099 private int mBubbleHeight;
100 private int mPermissionHeight;
Mady Mellordea7ecf2018-12-10 15:47:40 -0800101
Mady Mellor9801e852019-01-22 14:50:28 -0800102 private NotificationEntry mEntry;
103 private PackageManager mPm;
104 private String mAppName;
Mady Mellore8e07712019-01-23 12:45:33 -0800105 private Drawable mAppIcon;
106
107 private INotificationManager mNotificationManagerService;
Mady Mellor3dff9e62019-02-05 18:12:53 -0800108 private BubbleController mBubbleController = Dependency.get(BubbleController.class);
Mady Mellor9801e852019-01-22 14:50:28 -0800109
Mady Mellor9801e852019-01-22 14:50:28 -0800110 private BubbleStackView mStackView;
111
Mady Mellor3dff9e62019-02-05 18:12:53 -0800112 private BubbleExpandedView.OnBubbleBlockedListener mOnBubbleBlockedListener;
113
114 private ActivityView.StateCallback mStateCallback = new ActivityView.StateCallback() {
115 @Override
116 public void onActivityViewReady(ActivityView view) {
Mady Mellor6d002032019-02-13 13:45:17 -0800117 if (!mActivityViewReady) {
118 mActivityViewReady = true;
119 mActivityView.startActivity(mBubbleIntent);
120 }
Mady Mellor3dff9e62019-02-05 18:12:53 -0800121 }
122
123 @Override
124 public void onActivityViewDestroyed(ActivityView view) {
125 mActivityViewReady = false;
126 }
127
128 /**
129 * This is only called for tasks on this ActivityView, which is also set to
130 * single-task mode -- meaning never more than one task on this display. If a task
131 * is being removed, it's the top Activity finishing and this bubble should
132 * be removed or collapsed.
133 */
134 @Override
135 public void onTaskRemovalStarted(int taskId) {
136 if (mEntry != null) {
137 // Must post because this is called from a binder thread.
Mark Renouf08bc42a2019-03-07 13:01:59 -0500138 post(() -> mBubbleController.removeBubble(mEntry.key,
139 BubbleController.DISMISS_TASK_FINISHED));
Mady Mellor3dff9e62019-02-05 18:12:53 -0800140 }
141 }
142 };
Mady Mellore8e07712019-01-23 12:45:33 -0800143
Mady Mellor3d82e682019-02-05 13:34:48 -0800144 public BubbleExpandedView(Context context) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800145 this(context, null);
146 }
147
Mady Mellor3d82e682019-02-05 13:34:48 -0800148 public BubbleExpandedView(Context context, AttributeSet attrs) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800149 this(context, attrs, 0);
150 }
151
Mady Mellor3d82e682019-02-05 13:34:48 -0800152 public BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800153 this(context, attrs, defStyleAttr, 0);
154 }
155
Mady Mellor3d82e682019-02-05 13:34:48 -0800156 public BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr,
Mady Mellordea7ecf2018-12-10 15:47:40 -0800157 int defStyleRes) {
158 super(context, attrs, defStyleAttr, defStyleRes);
Mady Mellor9801e852019-01-22 14:50:28 -0800159 mPm = context.getPackageManager();
Mady Mellorfe7ec032019-01-30 17:32:49 -0800160 mMinHeight = getResources().getDimensionPixelSize(
Mady Mellor3dff9e62019-02-05 18:12:53 -0800161 R.dimen.bubble_expanded_default_height);
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800162 mPointerMargin = getResources().getDimensionPixelSize(R.dimen.bubble_pointer_margin);
Mady Mellore8e07712019-01-23 12:45:33 -0800163 try {
164 mNotificationManagerService = INotificationManager.Stub.asInterface(
165 ServiceManager.getServiceOrThrow(Context.NOTIFICATION_SERVICE));
166 } catch (ServiceManager.ServiceNotFoundException e) {
167 Log.w(TAG, e);
168 }
Mady Mellordea7ecf2018-12-10 15:47:40 -0800169 }
170
171 @Override
172 protected void onFinishInflate() {
173 super.onFinishInflate();
174
175 Resources res = getResources();
176 mPointerView = findViewById(R.id.pointer_view);
177 int width = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
178 int height = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
Mady Mellordd497052019-01-30 17:23:48 -0800179
180 TypedArray ta = getContext().obtainStyledAttributes(
181 new int[] {android.R.attr.colorBackgroundFloating});
182 int bgColor = ta.getColor(0, Color.WHITE);
183 ta.recycle();
184
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800185 mShowOnTop = BubbleController.showBubblesAtTop(getContext());
186 mUseFooter = BubbleController.useFooter(getContext());
187
Mady Mellordea7ecf2018-12-10 15:47:40 -0800188 ShapeDrawable triangleDrawable = new ShapeDrawable(
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800189 TriangleShape.create(width, height, mShowOnTop /* pointUp */));
Mady Mellordd497052019-01-30 17:23:48 -0800190 triangleDrawable.setTint(bgColor);
Mady Mellordea7ecf2018-12-10 15:47:40 -0800191 mPointerView.setBackground(triangleDrawable);
Mady Mellor9801e852019-01-22 14:50:28 -0800192
Mady Mellore8e07712019-01-23 12:45:33 -0800193 FrameLayout viewWrapper = findViewById(R.id.header_permission_wrapper);
Mady Mellor310c7c62019-02-21 17:03:08 -0800194 viewWrapper.setBackground(createHeaderPermissionBackground(bgColor));
195
Mady Mellore8e07712019-01-23 12:45:33 -0800196 LayoutTransition transition = new LayoutTransition();
197 transition.setDuration(200);
198
199 ObjectAnimator appearAnimator = ObjectAnimator.ofFloat(null, View.ALPHA, 0f, 1f);
200 transition.setAnimator(LayoutTransition.APPEARING, appearAnimator);
201 transition.setInterpolator(LayoutTransition.APPEARING, Interpolators.ALPHA_IN);
202
203 ObjectAnimator disappearAnimator = ObjectAnimator.ofFloat(null, View.ALPHA, 1f, 0f);
204 transition.setAnimator(LayoutTransition.DISAPPEARING, disappearAnimator);
205 transition.setInterpolator(LayoutTransition.DISAPPEARING, Interpolators.ALPHA_OUT);
206
207 transition.setAnimateParentHierarchy(false);
208 viewWrapper.setLayoutTransition(transition);
209 viewWrapper.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
210
Mady Mellor310c7c62019-02-21 17:03:08 -0800211
Mady Mellorfe7ec032019-01-30 17:32:49 -0800212 mHeaderHeight = getContext().getResources().getDimensionPixelSize(
213 R.dimen.bubble_expanded_header_height);
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800214 mPermissionHeight = getContext().getResources().getDimensionPixelSize(
215 R.dimen.bubble_permission_height);
Mady Mellore8e07712019-01-23 12:45:33 -0800216 mHeaderView = findViewById(R.id.header_layout);
Mady Mellor9801e852019-01-22 14:50:28 -0800217 mDeepLinkIcon = findViewById(R.id.deep_link_button);
218 mSettingsIcon = findViewById(R.id.settings_button);
219 mDeepLinkIcon.setOnClickListener(this);
220 mSettingsIcon.setOnClickListener(this);
Mady Mellore8e07712019-01-23 12:45:33 -0800221
222 mPermissionView = findViewById(R.id.permission_layout);
223 findViewById(R.id.no_bubbles_button).setOnClickListener(this);
224 findViewById(R.id.yes_bubbles_button).setOnClickListener(this);
Mady Mellor3dff9e62019-02-05 18:12:53 -0800225
226 mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */,
227 true /* singleTaskInstance */);
228 addView(mActivityView);
229
230 mActivityView.setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
231 ActivityView activityView = (ActivityView) view;
232 // Here we assume that the position of the ActivityView on the screen
233 // remains regardless of IME status. When we move ActivityView, the
234 // forwardedInsets should be computed not against the current location
235 // and size, but against the post-moved location and size.
236 Point displaySize = new Point();
237 view.getContext().getDisplay().getSize(displaySize);
238 int[] windowLocation = view.getLocationOnScreen();
239 final int windowBottom = windowLocation[1] + view.getHeight();
240 final int keyboardHeight = insets.getSystemWindowInsetBottom()
241 - insets.getStableInsetBottom();
242 final int insetsBottom = Math.max(0,
243 windowBottom + keyboardHeight - displaySize.y);
244 activityView.setForwardedInsets(Insets.of(0, 0, 0, insetsBottom));
245 return view.onApplyWindowInsets(insets);
246 });
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800247
248 if (!mShowOnTop) {
249 removeView(mPointerView);
250 if (mUseFooter) {
Mady Mellor310c7c62019-02-21 17:03:08 -0800251 View divider = findViewById(R.id.divider);
252 viewWrapper.removeView(divider);
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800253 removeView(viewWrapper);
Mady Mellor310c7c62019-02-21 17:03:08 -0800254 addView(divider);
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800255 addView(viewWrapper);
256 }
257 addView(mPointerView);
258 }
Mady Mellore8e07712019-01-23 12:45:33 -0800259 }
260
261 /**
Mady Mellor310c7c62019-02-21 17:03:08 -0800262 * Creates a background with corners rounded based on how the view is configured to display
263 */
264 private Drawable createHeaderPermissionBackground(int bgColor) {
265 TypedArray ta2 = getContext().obtainStyledAttributes(
266 new int[] {android.R.attr.dialogCornerRadius});
267 final float cr = ta2.getDimension(0, 0f);
268 ta2.recycle();
269
270 float[] radii = mUseFooter
271 ? new float[] {0, 0, 0, 0, cr, cr, cr, cr}
272 : new float[] {cr, cr, cr, cr, 0, 0, 0, 0};
273 GradientDrawable chromeBackground = new GradientDrawable();
274 chromeBackground.setShape(GradientDrawable.RECTANGLE);
275 chromeBackground.setCornerRadii(radii);
276 chromeBackground.setColor(bgColor);
277 return chromeBackground;
278 }
279
280 /**
Mady Mellore8e07712019-01-23 12:45:33 -0800281 * Sets the listener to notify when a bubble has been blocked.
282 */
283 public void setOnBlockedListener(OnBubbleBlockedListener listener) {
284 mOnBubbleBlockedListener = listener;
Mady Mellor9801e852019-01-22 14:50:28 -0800285 }
286
287 /**
288 * Sets the notification entry used to populate this view.
289 */
290 public void setEntry(NotificationEntry entry, BubbleStackView stackView) {
291 mStackView = stackView;
292 mEntry = entry;
293
294 ApplicationInfo info;
295 try {
296 info = mPm.getApplicationInfo(
297 entry.notification.getPackageName(),
298 PackageManager.MATCH_UNINSTALLED_PACKAGES
299 | PackageManager.MATCH_DISABLED_COMPONENTS
300 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
301 | PackageManager.MATCH_DIRECT_BOOT_AWARE);
302 if (info != null) {
303 mAppName = String.valueOf(mPm.getApplicationLabel(info));
Mady Mellore8e07712019-01-23 12:45:33 -0800304 mAppIcon = mPm.getApplicationIcon(info);
Mady Mellor9801e852019-01-22 14:50:28 -0800305 }
306 } catch (PackageManager.NameNotFoundException e) {
307 // Ahh... just use package name
308 mAppName = entry.notification.getPackageName();
309 }
Mady Mellore8e07712019-01-23 12:45:33 -0800310 if (mAppIcon == null) {
311 mAppIcon = mPm.getDefaultActivityIcon();
312 }
Mady Mellor9801e852019-01-22 14:50:28 -0800313 updateHeaderView();
Mady Mellore8e07712019-01-23 12:45:33 -0800314 updatePermissionView();
Mady Mellor3dff9e62019-02-05 18:12:53 -0800315 updateExpandedView();
Mady Mellor6d002032019-02-13 13:45:17 -0800316 }
317
318 /**
319 * Lets activity view know it should be shown / populated.
320 */
Mady Mellor5029fa62019-03-05 12:16:21 -0800321 public void populateExpandedView() {
322 if (usingActivityView()) {
323 mActivityView.setCallback(mStateCallback);
324 } else {
325 // We're using notification template
326 ViewGroup parent = (ViewGroup) mNotifRow.getParent();
327 if (parent == this) {
328 // Already added
329 return;
330 } else if (parent != null) {
331 // Still in the shade... remove it
332 parent.removeView(mNotifRow);
333 }
334 if (mShowOnTop) {
335 addView(mNotifRow);
336 } else {
337 addView(mNotifRow, mUseFooter ? 0 : 1);
338 }
339 }
Mady Mellor9801e852019-01-22 14:50:28 -0800340 }
341
Mady Mellorfe7ec032019-01-30 17:32:49 -0800342 /**
343 * Updates the entry backing this view. This will not re-populate ActivityView, it will
344 * only update the deep-links in the header, the title, and the height of the view.
345 */
346 public void update(NotificationEntry entry) {
347 if (entry.key.equals(mEntry.key)) {
348 mEntry = entry;
349 updateHeaderView();
350 updateHeight();
351 } else {
352 Log.w(TAG, "Trying to update entry with different key, new entry: "
353 + entry.key + " old entry: " + mEntry.key);
354 }
355 }
356
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700357 /**
358 * Update bubble expanded view header when user toggles dark mode.
359 */
360 void updateHeaderColor() {
361 mHeaderView.setBackgroundColor(mContext.getColor(R.attr.colorAccent));
362 }
363
Mady Mellor9801e852019-01-22 14:50:28 -0800364 private void updateHeaderView() {
365 mSettingsIcon.setContentDescription(getResources().getString(
366 R.string.bubbles_settings_button_description, mAppName));
367 mDeepLinkIcon.setContentDescription(getResources().getString(
368 R.string.bubbles_deep_link_button_description, mAppName));
Mady Mellore8e07712019-01-23 12:45:33 -0800369 }
370
371 private void updatePermissionView() {
372 boolean hasUserApprovedBubblesForPackage = false;
373 try {
374 hasUserApprovedBubblesForPackage =
375 mNotificationManagerService.hasUserApprovedBubblesForPackage(
376 mEntry.notification.getPackageName(), mEntry.notification.getUid());
377 } catch (RemoteException e) {
378 Log.w(TAG, e);
379 }
380 if (hasUserApprovedBubblesForPackage) {
381 mHeaderView.setVisibility(VISIBLE);
382 mPermissionView.setVisibility(GONE);
383 } else {
384 mHeaderView.setVisibility(GONE);
385 mPermissionView.setVisibility(VISIBLE);
386 ((ImageView) mPermissionView.findViewById(R.id.pkgicon)).setImageDrawable(mAppIcon);
387 ((TextView) mPermissionView.findViewById(R.id.pkgname)).setText(mAppName);
Steven Wua62cb6a2019-02-15 17:12:51 -0500388 logBubbleClickEvent(mEntry.notification,
389 StatsLog.BUBBLE_UICHANGED__ACTION__PERMISSION_DIALOG_SHOWN);
Mady Mellor9801e852019-01-22 14:50:28 -0800390 }
391 }
392
Mady Mellor3dff9e62019-02-05 18:12:53 -0800393 private void updateExpandedView() {
394 mBubbleIntent = getBubbleIntent(mEntry);
395 if (mBubbleIntent != null) {
396 if (mNotifRow != null) {
397 // Clear out the row if we had it previously
398 removeView(mNotifRow);
399 mNotifRow = null;
400 }
Mady Mellor3dff9e62019-02-05 18:12:53 -0800401 mActivityView.setVisibility(VISIBLE);
402 } else {
403 // Hide activity view if we had it previously
404 mActivityView.setVisibility(GONE);
Mady Mellor3dff9e62019-02-05 18:12:53 -0800405 mNotifRow = mEntry.getRow();
Mady Mellor5029fa62019-03-05 12:16:21 -0800406
Mady Mellor3dff9e62019-02-05 18:12:53 -0800407 }
408 updateView();
409 }
410
Mark Renouf041d7262019-02-06 12:09:41 -0500411 boolean performBackPressIfNeeded() {
412 if (mActivityView == null || !usingActivityView()) {
413 return false;
414 }
415 mActivityView.performBackPress();
416 return true;
417 }
418
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800419 /**
420 * @return total height that the expanded view occupies.
421 */
422 int getExpandedSize() {
423 int chromeHeight = mPermissionView.getVisibility() != View.VISIBLE
424 ? mHeaderHeight
425 : mPermissionHeight;
426 return mBubbleHeight + mPointerView.getHeight() + mPointerMargin
427 + chromeHeight;
428 }
429
Mady Mellorfe7ec032019-01-30 17:32:49 -0800430 void updateHeight() {
431 if (usingActivityView()) {
432 Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
433 int desiredHeight;
434 if (data == null) {
435 // This is a contentIntent based bubble, lets allow it to be the max height
436 // as it was forced into this mode and not prepared to be small
437 desiredHeight = mStackView.getMaxExpandedHeight();
438 } else {
439 desiredHeight = data.getDesiredHeight() > 0
440 ? data.getDesiredHeight()
441 : mMinHeight;
442 }
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800443 int chromeHeight = mPermissionView.getVisibility() != View.VISIBLE
444 ? mHeaderHeight
445 : mPermissionHeight;
446 int max = mStackView.getMaxExpandedHeight() - chromeHeight - mPointerView.getHeight()
447 - mPointerMargin;
Mady Mellorfe7ec032019-01-30 17:32:49 -0800448 int height = Math.min(desiredHeight, max);
449 height = Math.max(height, mMinHeight);
450 LayoutParams lp = (LayoutParams) mActivityView.getLayoutParams();
451 lp.height = height;
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800452 mBubbleHeight = height;
Mady Mellorfe7ec032019-01-30 17:32:49 -0800453 mActivityView.setLayoutParams(lp);
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800454 } else {
455 mBubbleHeight = mNotifRow != null ? mNotifRow.getIntrinsicHeight() : mMinHeight;
Mady Mellorfe7ec032019-01-30 17:32:49 -0800456 }
457 }
458
Mady Mellor9801e852019-01-22 14:50:28 -0800459 @Override
460 public void onClick(View view) {
461 if (mEntry == null) {
462 return;
463 }
464 Notification n = mEntry.notification.getNotification();
465 int id = view.getId();
466 if (id == R.id.deep_link_button) {
467 mStackView.collapseStack(() -> {
468 try {
469 n.contentIntent.send();
Steven Wub00225b2019-02-08 14:27:42 -0500470 logBubbleClickEvent(mEntry.notification,
471 StatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_APP);
Mady Mellor9801e852019-01-22 14:50:28 -0800472 } catch (PendingIntent.CanceledException e) {
473 Log.w(TAG, "Failed to send intent for bubble with key: "
474 + (mEntry != null ? mEntry.key : " null entry"));
475 }
476 });
477 } else if (id == R.id.settings_button) {
478 Intent intent = getSettingsIntent(mEntry.notification.getPackageName(),
479 mEntry.notification.getUid());
Steven Wub00225b2019-02-08 14:27:42 -0500480 mStackView.collapseStack(() -> {
481 mContext.startActivity(intent);
482 logBubbleClickEvent(mEntry.notification,
483 StatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
484 });
Mady Mellore8e07712019-01-23 12:45:33 -0800485 } else if (id == R.id.no_bubbles_button) {
486 setBubblesAllowed(false);
487 } else if (id == R.id.yes_bubbles_button) {
488 setBubblesAllowed(true);
489 }
490 }
491
492 private void setBubblesAllowed(boolean allowed) {
493 try {
494 mNotificationManagerService.setBubblesAllowed(
495 mEntry.notification.getPackageName(),
496 mEntry.notification.getUid(),
497 allowed);
498 if (allowed) {
499 mPermissionView.setVisibility(GONE);
500 mHeaderView.setVisibility(VISIBLE);
501 } else if (mOnBubbleBlockedListener != null) {
502 mOnBubbleBlockedListener.onBubbleBlocked(mEntry);
503 }
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800504 mStackView.onExpandedHeightChanged();
Steven Wub00225b2019-02-08 14:27:42 -0500505 logBubbleClickEvent(mEntry.notification,
506 allowed ? StatsLog.BUBBLE_UICHANGED__ACTION__PERMISSION_OPT_IN :
507 StatsLog.BUBBLE_UICHANGED__ACTION__PERMISSION_OPT_OUT);
Mady Mellore8e07712019-01-23 12:45:33 -0800508 } catch (RemoteException e) {
509 Log.w(TAG, e);
Mady Mellor9801e852019-01-22 14:50:28 -0800510 }
Mady Mellordea7ecf2018-12-10 15:47:40 -0800511 }
512
513 /**
Mady Mellor3dff9e62019-02-05 18:12:53 -0800514 * Update appearance of the expanded view being displayed.
515 */
516 public void updateView() {
517 if (usingActivityView()
518 && mActivityView.getVisibility() == VISIBLE
519 && mActivityView.isAttachedToWindow()) {
520 mActivityView.onLocationChanged();
521 } else if (mNotifRow != null) {
522 applyRowState(mNotifRow);
523 }
Mady Mellorfe7ec032019-01-30 17:32:49 -0800524 updateHeight();
Mady Mellor3dff9e62019-02-05 18:12:53 -0800525 }
526
527 /**
Mady Mellordea7ecf2018-12-10 15:47:40 -0800528 * Set the x position that the tip of the triangle should point to.
529 */
Mady Mellor3dff9e62019-02-05 18:12:53 -0800530 public void setPointerPosition(float x) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800531 // Adjust for the pointer size
Mady Mellor3dff9e62019-02-05 18:12:53 -0800532 x -= (mPointerView.getWidth() / 2f);
Mady Mellordea7ecf2018-12-10 15:47:40 -0800533 mPointerView.setTranslationX(x);
534 }
535
536 /**
Mady Mellor3dff9e62019-02-05 18:12:53 -0800537 * Removes and releases an ActivityView if one was previously created for this bubble.
Mady Mellordea7ecf2018-12-10 15:47:40 -0800538 */
Mady Mellor94d94a72019-03-05 18:16:59 -0800539 public void cleanUpExpandedState() {
540 removeView(mNotifRow);
541
Mady Mellor3dff9e62019-02-05 18:12:53 -0800542 if (mActivityView == null) {
Mark Renouf89b1a4a2018-12-04 14:59:45 -0500543 return;
544 }
Mark Renouf28c250d2019-02-25 16:47:34 -0500545 if (mActivityViewReady) {
546 mActivityView.release();
Mady Mellordea7ecf2018-12-10 15:47:40 -0800547 }
Mark Renouf28c250d2019-02-25 16:47:34 -0500548 removeView(mActivityView);
Mady Mellor3dff9e62019-02-05 18:12:53 -0800549 mActivityView = null;
550 mActivityViewReady = false;
Mady Mellordea7ecf2018-12-10 15:47:40 -0800551 }
552
Mady Mellor3dff9e62019-02-05 18:12:53 -0800553 private boolean usingActivityView() {
554 return mBubbleIntent != null;
555 }
556
557 private void applyRowState(ExpandableNotificationRow view) {
558 view.reset();
559 view.setHeadsUp(false);
560 view.resetTranslation();
561 view.setOnKeyguard(false);
562 view.setOnAmbient(false);
563 view.setClipBottomAmount(0);
564 view.setClipTopAmount(0);
565 view.setContentTransformationAmount(0, false);
566 view.setIconsVisible(true);
567
568 // TODO - Need to reset this (and others) when view goes back in shade, leave for now
569 // view.setTopRoundness(1, false);
570 // view.setBottomRoundness(1, false);
571
572 ExpandableViewState viewState = view.getViewState();
573 viewState = viewState == null ? new ExpandableViewState() : viewState;
574 viewState.height = view.getIntrinsicHeight();
575 viewState.gone = false;
576 viewState.hidden = false;
577 viewState.dimmed = false;
578 viewState.dark = false;
579 viewState.alpha = 1f;
580 viewState.notGoneIndex = -1;
581 viewState.xTranslation = 0;
582 viewState.yTranslation = 0;
583 viewState.zTranslation = 0;
584 viewState.scaleX = 1;
585 viewState.scaleY = 1;
586 viewState.inShelf = true;
587 viewState.headsUpIsVisible = false;
588 viewState.applyToView(view);
Mady Mellordea7ecf2018-12-10 15:47:40 -0800589 }
Mady Mellor9801e852019-01-22 14:50:28 -0800590
591 private Intent getSettingsIntent(String packageName, final int appUid) {
592 final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
593 intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
594 intent.putExtra(Settings.EXTRA_APP_UID, appUid);
595 intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
596 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
597 return intent;
598 }
Mady Mellore8e07712019-01-23 12:45:33 -0800599
Mady Mellor3dff9e62019-02-05 18:12:53 -0800600 @Nullable
601 private PendingIntent getBubbleIntent(NotificationEntry entry) {
602 Notification notif = entry.notification.getNotification();
603 String packageName = entry.notification.getPackageName();
604 Notification.BubbleMetadata data = notif.getBubbleMetadata();
605 if (data != null && canLaunchInActivityView(data.getIntent(), true /* enableLogging */,
606 packageName)) {
607 return data.getIntent();
608 } else if (BubbleController.shouldUseContentIntent(mContext)
609 && canLaunchInActivityView(notif.contentIntent, false /* enableLogging */,
610 packageName)) {
611 return notif.contentIntent;
612 }
613 return null;
614 }
615
616 /**
617 * Whether an intent is properly configured to display in an {@link android.app.ActivityView}.
618 *
619 * @param intent the pending intent of the bubble.
620 * @param enableLogging whether bubble developer error should be logged.
621 * @param packageName the notification package name for this bubble.
622 * @return
623 */
624 private boolean canLaunchInActivityView(PendingIntent intent, boolean enableLogging,
625 String packageName) {
626 if (intent == null) {
627 return false;
628 }
629 ActivityInfo info =
630 intent.getIntent().resolveActivityInfo(mContext.getPackageManager(), 0);
631 if (info == null) {
632 if (enableLogging) {
633 StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
634 BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING);
635 }
636 return false;
637 }
638 if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
639 if (enableLogging) {
640 StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
641 BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE);
642 }
643 return false;
644 }
645 if (info.documentLaunchMode != DOCUMENT_LAUNCH_ALWAYS) {
646 if (enableLogging) {
647 StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
648 BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__DOCUMENT_LAUNCH_NOT_ALWAYS);
649 }
650 return false;
651 }
652 return (info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) != 0;
653 }
654
Mady Mellore8e07712019-01-23 12:45:33 -0800655 /**
656 * Listener that is notified when a bubble is blocked.
657 */
658 public interface OnBubbleBlockedListener {
659 /**
660 * Called when a bubble is blocked for the provided entry.
661 */
662 void onBubbleBlocked(NotificationEntry entry);
663 }
Steven Wub00225b2019-02-08 14:27:42 -0500664
665 /**
666 * Logs bubble UI click event.
667 *
668 * @param notification the bubble notification that user is interacting with.
669 * @param action the user interaction enum.
670 */
671 private void logBubbleClickEvent(StatusBarNotification notification, int action) {
672 StatsLog.write(StatsLog.BUBBLE_UI_CHANGED,
673 notification.getPackageName(),
674 notification.getNotification().getChannelId(),
675 notification.getId(),
676 mStackView.getBubbleIndex(mStackView.getExpandedBubble()),
677 mStackView.getBubbleCount(),
678 action,
679 mStackView.getNormalizedXPosition(),
680 mStackView.getNormalizedYPosition());
681 }
Mady Mellordea7ecf2018-12-10 15:47:40 -0800682}