blob: b635033ea7710d2020de3c978a8d3d85fcb8dc77 [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 Mellordea7ecf2018-12-10 15:47:40 -080042import android.graphics.drawable.ShapeDrawable;
Mady Mellore8e07712019-01-23 12:45:33 -080043import android.os.RemoteException;
44import android.os.ServiceManager;
Mady Mellor9801e852019-01-22 14:50:28 -080045import android.provider.Settings;
Steven Wub00225b2019-02-08 14:27:42 -050046import android.service.notification.StatusBarNotification;
Mady Mellordea7ecf2018-12-10 15:47:40 -080047import android.util.AttributeSet;
Mady Mellor9801e852019-01-22 14:50:28 -080048import android.util.Log;
Steven Wub00225b2019-02-08 14:27:42 -050049import android.util.StatsLog;
Mady Mellordea7ecf2018-12-10 15:47:40 -080050import android.view.View;
Mady Mellor3dff9e62019-02-05 18:12:53 -080051import android.view.ViewGroup;
52import android.view.WindowInsets;
Mady Mellore8e07712019-01-23 12:45:33 -080053import android.widget.FrameLayout;
Mady Mellor9801e852019-01-22 14:50:28 -080054import android.widget.ImageButton;
Mady Mellore8e07712019-01-23 12:45:33 -080055import android.widget.ImageView;
Mady Mellordea7ecf2018-12-10 15:47:40 -080056import android.widget.LinearLayout;
Mark Renouf89b1a4a2018-12-04 14:59:45 -050057import android.widget.TextView;
Mady Mellordea7ecf2018-12-10 15:47:40 -080058
Mady Mellor3dff9e62019-02-05 18:12:53 -080059import com.android.systemui.Dependency;
Mady Mellore8e07712019-01-23 12:45:33 -080060import com.android.systemui.Interpolators;
Mady Mellordea7ecf2018-12-10 15:47:40 -080061import com.android.systemui.R;
62import com.android.systemui.recents.TriangleShape;
Mady Mellor9801e852019-01-22 14:50:28 -080063import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Mady Mellor3dff9e62019-02-05 18:12:53 -080064import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
65import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
Mady Mellordea7ecf2018-12-10 15:47:40 -080066
67/**
68 * Container for the expanded bubble view, handles rendering the caret and header of the view.
69 */
Mady Mellor3d82e682019-02-05 13:34:48 -080070public class BubbleExpandedView extends LinearLayout implements View.OnClickListener {
Mady Mellor9801e852019-01-22 14:50:28 -080071 private static final String TAG = "BubbleExpandedView";
Mady Mellordea7ecf2018-12-10 15:47:40 -080072
Mady Mellor44ee2fe2019-01-30 17:51:16 -080073 // Configurable via bubble settings; just for testing
74 private boolean mUseFooter;
75 private boolean mShowOnTop;
76
Mady Mellordea7ecf2018-12-10 15:47:40 -080077 // 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
81 // Header
82 private View mHeaderView;
Mady Mellor9801e852019-01-22 14:50:28 -080083 private ImageButton mDeepLinkIcon;
Mady Mellor9801e852019-01-22 14:50:28 -080084 private ImageButton mSettingsIcon;
Mady Mellore8e07712019-01-23 12:45:33 -080085
86 // Permission view
87 private View mPermissionView;
88
Mady Mellor3dff9e62019-02-05 18:12:53 -080089 // Views for expanded state
90 private ExpandableNotificationRow mNotifRow;
91 private ActivityView mActivityView;
92
93 private boolean mActivityViewReady = false;
94 private PendingIntent mBubbleIntent;
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.
137 post(() -> mBubbleController.removeBubble(mEntry.key));
138 }
139 }
140 };
Mady Mellore8e07712019-01-23 12:45:33 -0800141
Mady Mellor3d82e682019-02-05 13:34:48 -0800142 public BubbleExpandedView(Context context) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800143 this(context, null);
144 }
145
Mady Mellor3d82e682019-02-05 13:34:48 -0800146 public BubbleExpandedView(Context context, AttributeSet attrs) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800147 this(context, attrs, 0);
148 }
149
Mady Mellor3d82e682019-02-05 13:34:48 -0800150 public BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800151 this(context, attrs, defStyleAttr, 0);
152 }
153
Mady Mellor3d82e682019-02-05 13:34:48 -0800154 public BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr,
Mady Mellordea7ecf2018-12-10 15:47:40 -0800155 int defStyleRes) {
156 super(context, attrs, defStyleAttr, defStyleRes);
Mady Mellor9801e852019-01-22 14:50:28 -0800157 mPm = context.getPackageManager();
Mady Mellorfe7ec032019-01-30 17:32:49 -0800158 mMinHeight = getResources().getDimensionPixelSize(
Mady Mellor3dff9e62019-02-05 18:12:53 -0800159 R.dimen.bubble_expanded_default_height);
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800160 mPointerMargin = getResources().getDimensionPixelSize(R.dimen.bubble_pointer_margin);
Mady Mellore8e07712019-01-23 12:45:33 -0800161 try {
162 mNotificationManagerService = INotificationManager.Stub.asInterface(
163 ServiceManager.getServiceOrThrow(Context.NOTIFICATION_SERVICE));
164 } catch (ServiceManager.ServiceNotFoundException e) {
165 Log.w(TAG, e);
166 }
Mady Mellordea7ecf2018-12-10 15:47:40 -0800167 }
168
169 @Override
170 protected void onFinishInflate() {
171 super.onFinishInflate();
172
173 Resources res = getResources();
174 mPointerView = findViewById(R.id.pointer_view);
175 int width = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
176 int height = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
Mady Mellordd497052019-01-30 17:23:48 -0800177
178 TypedArray ta = getContext().obtainStyledAttributes(
179 new int[] {android.R.attr.colorBackgroundFloating});
180 int bgColor = ta.getColor(0, Color.WHITE);
181 ta.recycle();
182
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800183 mShowOnTop = BubbleController.showBubblesAtTop(getContext());
184 mUseFooter = BubbleController.useFooter(getContext());
185
Mady Mellordea7ecf2018-12-10 15:47:40 -0800186 ShapeDrawable triangleDrawable = new ShapeDrawable(
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800187 TriangleShape.create(width, height, mShowOnTop /* pointUp */));
Mady Mellordd497052019-01-30 17:23:48 -0800188 triangleDrawable.setTint(bgColor);
Mady Mellordea7ecf2018-12-10 15:47:40 -0800189 mPointerView.setBackground(triangleDrawable);
Mady Mellor9801e852019-01-22 14:50:28 -0800190
Mady Mellore8e07712019-01-23 12:45:33 -0800191 FrameLayout viewWrapper = findViewById(R.id.header_permission_wrapper);
192 LayoutTransition transition = new LayoutTransition();
193 transition.setDuration(200);
194
195 ObjectAnimator appearAnimator = ObjectAnimator.ofFloat(null, View.ALPHA, 0f, 1f);
196 transition.setAnimator(LayoutTransition.APPEARING, appearAnimator);
197 transition.setInterpolator(LayoutTransition.APPEARING, Interpolators.ALPHA_IN);
198
199 ObjectAnimator disappearAnimator = ObjectAnimator.ofFloat(null, View.ALPHA, 1f, 0f);
200 transition.setAnimator(LayoutTransition.DISAPPEARING, disappearAnimator);
201 transition.setInterpolator(LayoutTransition.DISAPPEARING, Interpolators.ALPHA_OUT);
202
203 transition.setAnimateParentHierarchy(false);
204 viewWrapper.setLayoutTransition(transition);
205 viewWrapper.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
206
Mady Mellorfe7ec032019-01-30 17:32:49 -0800207 mHeaderHeight = getContext().getResources().getDimensionPixelSize(
208 R.dimen.bubble_expanded_header_height);
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800209 mPermissionHeight = getContext().getResources().getDimensionPixelSize(
210 R.dimen.bubble_permission_height);
Mady Mellore8e07712019-01-23 12:45:33 -0800211 mHeaderView = findViewById(R.id.header_layout);
Mady Mellor9801e852019-01-22 14:50:28 -0800212 mDeepLinkIcon = findViewById(R.id.deep_link_button);
213 mSettingsIcon = findViewById(R.id.settings_button);
214 mDeepLinkIcon.setOnClickListener(this);
215 mSettingsIcon.setOnClickListener(this);
Mady Mellore8e07712019-01-23 12:45:33 -0800216
217 mPermissionView = findViewById(R.id.permission_layout);
218 findViewById(R.id.no_bubbles_button).setOnClickListener(this);
219 findViewById(R.id.yes_bubbles_button).setOnClickListener(this);
Mady Mellor3dff9e62019-02-05 18:12:53 -0800220
221 mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */,
222 true /* singleTaskInstance */);
223 addView(mActivityView);
224
225 mActivityView.setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
226 ActivityView activityView = (ActivityView) view;
227 // Here we assume that the position of the ActivityView on the screen
228 // remains regardless of IME status. When we move ActivityView, the
229 // forwardedInsets should be computed not against the current location
230 // and size, but against the post-moved location and size.
231 Point displaySize = new Point();
232 view.getContext().getDisplay().getSize(displaySize);
233 int[] windowLocation = view.getLocationOnScreen();
234 final int windowBottom = windowLocation[1] + view.getHeight();
235 final int keyboardHeight = insets.getSystemWindowInsetBottom()
236 - insets.getStableInsetBottom();
237 final int insetsBottom = Math.max(0,
238 windowBottom + keyboardHeight - displaySize.y);
239 activityView.setForwardedInsets(Insets.of(0, 0, 0, insetsBottom));
240 return view.onApplyWindowInsets(insets);
241 });
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800242
243 if (!mShowOnTop) {
244 removeView(mPointerView);
245 if (mUseFooter) {
246 removeView(viewWrapper);
247 addView(viewWrapper);
248 }
249 addView(mPointerView);
250 }
Mady Mellore8e07712019-01-23 12:45:33 -0800251 }
252
253 /**
254 * Sets the listener to notify when a bubble has been blocked.
255 */
256 public void setOnBlockedListener(OnBubbleBlockedListener listener) {
257 mOnBubbleBlockedListener = listener;
Mady Mellor9801e852019-01-22 14:50:28 -0800258 }
259
260 /**
261 * Sets the notification entry used to populate this view.
262 */
263 public void setEntry(NotificationEntry entry, BubbleStackView stackView) {
264 mStackView = stackView;
265 mEntry = entry;
266
267 ApplicationInfo info;
268 try {
269 info = mPm.getApplicationInfo(
270 entry.notification.getPackageName(),
271 PackageManager.MATCH_UNINSTALLED_PACKAGES
272 | PackageManager.MATCH_DISABLED_COMPONENTS
273 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
274 | PackageManager.MATCH_DIRECT_BOOT_AWARE);
275 if (info != null) {
276 mAppName = String.valueOf(mPm.getApplicationLabel(info));
Mady Mellore8e07712019-01-23 12:45:33 -0800277 mAppIcon = mPm.getApplicationIcon(info);
Mady Mellor9801e852019-01-22 14:50:28 -0800278 }
279 } catch (PackageManager.NameNotFoundException e) {
280 // Ahh... just use package name
281 mAppName = entry.notification.getPackageName();
282 }
Mady Mellore8e07712019-01-23 12:45:33 -0800283 if (mAppIcon == null) {
284 mAppIcon = mPm.getDefaultActivityIcon();
285 }
Mady Mellor9801e852019-01-22 14:50:28 -0800286 updateHeaderView();
Mady Mellore8e07712019-01-23 12:45:33 -0800287 updatePermissionView();
Mady Mellor3dff9e62019-02-05 18:12:53 -0800288 updateExpandedView();
Mady Mellor6d002032019-02-13 13:45:17 -0800289 }
290
291 /**
292 * Lets activity view know it should be shown / populated.
293 */
294 public void populateActivityView() {
Mady Mellor3dff9e62019-02-05 18:12:53 -0800295 mActivityView.setCallback(mStateCallback);
Mady Mellor9801e852019-01-22 14:50:28 -0800296 }
297
Mady Mellorfe7ec032019-01-30 17:32:49 -0800298 /**
299 * Updates the entry backing this view. This will not re-populate ActivityView, it will
300 * only update the deep-links in the header, the title, and the height of the view.
301 */
302 public void update(NotificationEntry entry) {
303 if (entry.key.equals(mEntry.key)) {
304 mEntry = entry;
305 updateHeaderView();
306 updateHeight();
307 } else {
308 Log.w(TAG, "Trying to update entry with different key, new entry: "
309 + entry.key + " old entry: " + mEntry.key);
310 }
311 }
312
Mady Mellor9801e852019-01-22 14:50:28 -0800313 private void updateHeaderView() {
314 mSettingsIcon.setContentDescription(getResources().getString(
315 R.string.bubbles_settings_button_description, mAppName));
316 mDeepLinkIcon.setContentDescription(getResources().getString(
317 R.string.bubbles_deep_link_button_description, mAppName));
Mady Mellore8e07712019-01-23 12:45:33 -0800318 }
319
320 private void updatePermissionView() {
321 boolean hasUserApprovedBubblesForPackage = false;
322 try {
323 hasUserApprovedBubblesForPackage =
324 mNotificationManagerService.hasUserApprovedBubblesForPackage(
325 mEntry.notification.getPackageName(), mEntry.notification.getUid());
326 } catch (RemoteException e) {
327 Log.w(TAG, e);
328 }
329 if (hasUserApprovedBubblesForPackage) {
330 mHeaderView.setVisibility(VISIBLE);
331 mPermissionView.setVisibility(GONE);
332 } else {
333 mHeaderView.setVisibility(GONE);
334 mPermissionView.setVisibility(VISIBLE);
335 ((ImageView) mPermissionView.findViewById(R.id.pkgicon)).setImageDrawable(mAppIcon);
336 ((TextView) mPermissionView.findViewById(R.id.pkgname)).setText(mAppName);
Steven Wua62cb6a2019-02-15 17:12:51 -0500337 logBubbleClickEvent(mEntry.notification,
338 StatsLog.BUBBLE_UICHANGED__ACTION__PERMISSION_DIALOG_SHOWN);
Mady Mellor9801e852019-01-22 14:50:28 -0800339 }
340 }
341
Mady Mellor3dff9e62019-02-05 18:12:53 -0800342 private void updateExpandedView() {
343 mBubbleIntent = getBubbleIntent(mEntry);
344 if (mBubbleIntent != null) {
345 if (mNotifRow != null) {
346 // Clear out the row if we had it previously
347 removeView(mNotifRow);
348 mNotifRow = null;
349 }
Mady Mellor3dff9e62019-02-05 18:12:53 -0800350 mActivityView.setVisibility(VISIBLE);
351 } else {
352 // Hide activity view if we had it previously
353 mActivityView.setVisibility(GONE);
354
355 // Use notification view
356 mNotifRow = mEntry.getRow();
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800357 if (mShowOnTop) {
358 addView(mNotifRow);
359 } else {
360 addView(mNotifRow, mUseFooter ? 0 : 1);
361 }
Mady Mellor3dff9e62019-02-05 18:12:53 -0800362 }
363 updateView();
364 }
365
Mark Renouf041d7262019-02-06 12:09:41 -0500366 boolean performBackPressIfNeeded() {
367 if (mActivityView == null || !usingActivityView()) {
368 return false;
369 }
370 mActivityView.performBackPress();
371 return true;
372 }
373
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800374 /**
375 * @return total height that the expanded view occupies.
376 */
377 int getExpandedSize() {
378 int chromeHeight = mPermissionView.getVisibility() != View.VISIBLE
379 ? mHeaderHeight
380 : mPermissionHeight;
381 return mBubbleHeight + mPointerView.getHeight() + mPointerMargin
382 + chromeHeight;
383 }
384
Mady Mellorfe7ec032019-01-30 17:32:49 -0800385 void updateHeight() {
386 if (usingActivityView()) {
387 Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
388 int desiredHeight;
389 if (data == null) {
390 // This is a contentIntent based bubble, lets allow it to be the max height
391 // as it was forced into this mode and not prepared to be small
392 desiredHeight = mStackView.getMaxExpandedHeight();
393 } else {
394 desiredHeight = data.getDesiredHeight() > 0
395 ? data.getDesiredHeight()
396 : mMinHeight;
397 }
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800398 int chromeHeight = mPermissionView.getVisibility() != View.VISIBLE
399 ? mHeaderHeight
400 : mPermissionHeight;
401 int max = mStackView.getMaxExpandedHeight() - chromeHeight - mPointerView.getHeight()
402 - mPointerMargin;
Mady Mellorfe7ec032019-01-30 17:32:49 -0800403 int height = Math.min(desiredHeight, max);
404 height = Math.max(height, mMinHeight);
405 LayoutParams lp = (LayoutParams) mActivityView.getLayoutParams();
406 lp.height = height;
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800407 mBubbleHeight = height;
Mady Mellorfe7ec032019-01-30 17:32:49 -0800408 mActivityView.setLayoutParams(lp);
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800409 } else {
410 mBubbleHeight = mNotifRow != null ? mNotifRow.getIntrinsicHeight() : mMinHeight;
Mady Mellorfe7ec032019-01-30 17:32:49 -0800411 }
412 }
413
Mady Mellor9801e852019-01-22 14:50:28 -0800414 @Override
415 public void onClick(View view) {
416 if (mEntry == null) {
417 return;
418 }
419 Notification n = mEntry.notification.getNotification();
420 int id = view.getId();
421 if (id == R.id.deep_link_button) {
422 mStackView.collapseStack(() -> {
423 try {
424 n.contentIntent.send();
Steven Wub00225b2019-02-08 14:27:42 -0500425 logBubbleClickEvent(mEntry.notification,
426 StatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_APP);
Mady Mellor9801e852019-01-22 14:50:28 -0800427 } catch (PendingIntent.CanceledException e) {
428 Log.w(TAG, "Failed to send intent for bubble with key: "
429 + (mEntry != null ? mEntry.key : " null entry"));
430 }
431 });
432 } else if (id == R.id.settings_button) {
433 Intent intent = getSettingsIntent(mEntry.notification.getPackageName(),
434 mEntry.notification.getUid());
Steven Wub00225b2019-02-08 14:27:42 -0500435 mStackView.collapseStack(() -> {
436 mContext.startActivity(intent);
437 logBubbleClickEvent(mEntry.notification,
438 StatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
439 });
Mady Mellore8e07712019-01-23 12:45:33 -0800440 } else if (id == R.id.no_bubbles_button) {
441 setBubblesAllowed(false);
442 } else if (id == R.id.yes_bubbles_button) {
443 setBubblesAllowed(true);
444 }
445 }
446
447 private void setBubblesAllowed(boolean allowed) {
448 try {
449 mNotificationManagerService.setBubblesAllowed(
450 mEntry.notification.getPackageName(),
451 mEntry.notification.getUid(),
452 allowed);
453 if (allowed) {
454 mPermissionView.setVisibility(GONE);
455 mHeaderView.setVisibility(VISIBLE);
456 } else if (mOnBubbleBlockedListener != null) {
457 mOnBubbleBlockedListener.onBubbleBlocked(mEntry);
458 }
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800459 mStackView.onExpandedHeightChanged();
Steven Wub00225b2019-02-08 14:27:42 -0500460 logBubbleClickEvent(mEntry.notification,
461 allowed ? StatsLog.BUBBLE_UICHANGED__ACTION__PERMISSION_OPT_IN :
462 StatsLog.BUBBLE_UICHANGED__ACTION__PERMISSION_OPT_OUT);
Mady Mellore8e07712019-01-23 12:45:33 -0800463 } catch (RemoteException e) {
464 Log.w(TAG, e);
Mady Mellor9801e852019-01-22 14:50:28 -0800465 }
Mady Mellordea7ecf2018-12-10 15:47:40 -0800466 }
467
468 /**
Mady Mellor3dff9e62019-02-05 18:12:53 -0800469 * Update appearance of the expanded view being displayed.
470 */
471 public void updateView() {
472 if (usingActivityView()
473 && mActivityView.getVisibility() == VISIBLE
474 && mActivityView.isAttachedToWindow()) {
475 mActivityView.onLocationChanged();
476 } else if (mNotifRow != null) {
477 applyRowState(mNotifRow);
478 }
Mady Mellorfe7ec032019-01-30 17:32:49 -0800479 updateHeight();
Mady Mellor3dff9e62019-02-05 18:12:53 -0800480 }
481
482 /**
Mady Mellordea7ecf2018-12-10 15:47:40 -0800483 * Set the x position that the tip of the triangle should point to.
484 */
Mady Mellor3dff9e62019-02-05 18:12:53 -0800485 public void setPointerPosition(float x) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800486 // Adjust for the pointer size
Mady Mellor3dff9e62019-02-05 18:12:53 -0800487 x -= (mPointerView.getWidth() / 2f);
Mady Mellordea7ecf2018-12-10 15:47:40 -0800488 mPointerView.setTranslationX(x);
489 }
490
491 /**
Mady Mellor3dff9e62019-02-05 18:12:53 -0800492 * Removes and releases an ActivityView if one was previously created for this bubble.
Mady Mellordea7ecf2018-12-10 15:47:40 -0800493 */
Mady Mellor3dff9e62019-02-05 18:12:53 -0800494 public void destroyActivityView(ViewGroup tmpParent) {
495 if (mActivityView == null) {
Mark Renouf89b1a4a2018-12-04 14:59:45 -0500496 return;
497 }
Mady Mellor3dff9e62019-02-05 18:12:53 -0800498 if (!mActivityViewReady) {
499 // release not needed, never initialized?
500 mActivityView = null;
501 return;
Mady Mellordea7ecf2018-12-10 15:47:40 -0800502 }
Mady Mellor3dff9e62019-02-05 18:12:53 -0800503 // HACK: release() will crash if the view is not attached.
504 if (!isAttachedToWindow()) {
505 mActivityView.setVisibility(View.GONE);
506 tmpParent.addView(this);
Mady Mellordea7ecf2018-12-10 15:47:40 -0800507 }
Mady Mellor3dff9e62019-02-05 18:12:53 -0800508
509 mActivityView.release();
510 ((ViewGroup) getParent()).removeView(this);
511 mActivityView = null;
512 mActivityViewReady = false;
Mady Mellordea7ecf2018-12-10 15:47:40 -0800513 }
514
Mady Mellor3dff9e62019-02-05 18:12:53 -0800515 private boolean usingActivityView() {
516 return mBubbleIntent != null;
517 }
518
519 private void applyRowState(ExpandableNotificationRow view) {
520 view.reset();
521 view.setHeadsUp(false);
522 view.resetTranslation();
523 view.setOnKeyguard(false);
524 view.setOnAmbient(false);
525 view.setClipBottomAmount(0);
526 view.setClipTopAmount(0);
527 view.setContentTransformationAmount(0, false);
528 view.setIconsVisible(true);
529
530 // TODO - Need to reset this (and others) when view goes back in shade, leave for now
531 // view.setTopRoundness(1, false);
532 // view.setBottomRoundness(1, false);
533
534 ExpandableViewState viewState = view.getViewState();
535 viewState = viewState == null ? new ExpandableViewState() : viewState;
536 viewState.height = view.getIntrinsicHeight();
537 viewState.gone = false;
538 viewState.hidden = false;
539 viewState.dimmed = false;
540 viewState.dark = false;
541 viewState.alpha = 1f;
542 viewState.notGoneIndex = -1;
543 viewState.xTranslation = 0;
544 viewState.yTranslation = 0;
545 viewState.zTranslation = 0;
546 viewState.scaleX = 1;
547 viewState.scaleY = 1;
548 viewState.inShelf = true;
549 viewState.headsUpIsVisible = false;
550 viewState.applyToView(view);
Mady Mellordea7ecf2018-12-10 15:47:40 -0800551 }
Mady Mellor9801e852019-01-22 14:50:28 -0800552
553 private Intent getSettingsIntent(String packageName, final int appUid) {
554 final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
555 intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
556 intent.putExtra(Settings.EXTRA_APP_UID, appUid);
557 intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
558 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
559 return intent;
560 }
Mady Mellore8e07712019-01-23 12:45:33 -0800561
Mady Mellor3dff9e62019-02-05 18:12:53 -0800562 @Nullable
563 private PendingIntent getBubbleIntent(NotificationEntry entry) {
564 Notification notif = entry.notification.getNotification();
565 String packageName = entry.notification.getPackageName();
566 Notification.BubbleMetadata data = notif.getBubbleMetadata();
567 if (data != null && canLaunchInActivityView(data.getIntent(), true /* enableLogging */,
568 packageName)) {
569 return data.getIntent();
570 } else if (BubbleController.shouldUseContentIntent(mContext)
571 && canLaunchInActivityView(notif.contentIntent, false /* enableLogging */,
572 packageName)) {
573 return notif.contentIntent;
574 }
575 return null;
576 }
577
578 /**
579 * Whether an intent is properly configured to display in an {@link android.app.ActivityView}.
580 *
581 * @param intent the pending intent of the bubble.
582 * @param enableLogging whether bubble developer error should be logged.
583 * @param packageName the notification package name for this bubble.
584 * @return
585 */
586 private boolean canLaunchInActivityView(PendingIntent intent, boolean enableLogging,
587 String packageName) {
588 if (intent == null) {
589 return false;
590 }
591 ActivityInfo info =
592 intent.getIntent().resolveActivityInfo(mContext.getPackageManager(), 0);
593 if (info == null) {
594 if (enableLogging) {
595 StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
596 BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING);
597 }
598 return false;
599 }
600 if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
601 if (enableLogging) {
602 StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
603 BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE);
604 }
605 return false;
606 }
607 if (info.documentLaunchMode != DOCUMENT_LAUNCH_ALWAYS) {
608 if (enableLogging) {
609 StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
610 BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__DOCUMENT_LAUNCH_NOT_ALWAYS);
611 }
612 return false;
613 }
614 return (info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) != 0;
615 }
616
Mady Mellore8e07712019-01-23 12:45:33 -0800617 /**
618 * Listener that is notified when a bubble is blocked.
619 */
620 public interface OnBubbleBlockedListener {
621 /**
622 * Called when a bubble is blocked for the provided entry.
623 */
624 void onBubbleBlocked(NotificationEntry entry);
625 }
Steven Wub00225b2019-02-08 14:27:42 -0500626
627 /**
628 * Logs bubble UI click event.
629 *
630 * @param notification the bubble notification that user is interacting with.
631 * @param action the user interaction enum.
632 */
633 private void logBubbleClickEvent(StatusBarNotification notification, int action) {
634 StatsLog.write(StatsLog.BUBBLE_UI_CHANGED,
635 notification.getPackageName(),
636 notification.getNotification().getChannelId(),
637 notification.getId(),
638 mStackView.getBubbleIndex(mStackView.getExpandedBubble()),
639 mStackView.getBubbleCount(),
640 action,
641 mStackView.getNormalizedXPosition(),
642 mStackView.getNormalizedYPosition());
643 }
Mady Mellordea7ecf2018-12-10 15:47:40 -0800644}