blob: c2327ad41d3e97410f3adeaac7c78821dcad25c5 [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
73 // The triangle pointing to the expanded view
74 private View mPointerView;
Mady Mellore8e07712019-01-23 12:45:33 -080075
76 // Header
77 private View mHeaderView;
78 private TextView mHeaderTextView;
Mady Mellor9801e852019-01-22 14:50:28 -080079 private ImageButton mDeepLinkIcon;
Mady Mellor9801e852019-01-22 14:50:28 -080080 private ImageButton mSettingsIcon;
Mady Mellore8e07712019-01-23 12:45:33 -080081
82 // Permission view
83 private View mPermissionView;
84
Mady Mellor3dff9e62019-02-05 18:12:53 -080085 // Views for expanded state
86 private ExpandableNotificationRow mNotifRow;
87 private ActivityView mActivityView;
88
89 private boolean mActivityViewReady = false;
90 private PendingIntent mBubbleIntent;
91
Mady Mellorfe7ec032019-01-30 17:32:49 -080092 private int mMinHeight;
93 private int mHeaderHeight;
Mady Mellordea7ecf2018-12-10 15:47:40 -080094
Mady Mellor9801e852019-01-22 14:50:28 -080095 private NotificationEntry mEntry;
96 private PackageManager mPm;
97 private String mAppName;
Mady Mellore8e07712019-01-23 12:45:33 -080098 private Drawable mAppIcon;
99
100 private INotificationManager mNotificationManagerService;
Mady Mellor3dff9e62019-02-05 18:12:53 -0800101 private BubbleController mBubbleController = Dependency.get(BubbleController.class);
Mady Mellor9801e852019-01-22 14:50:28 -0800102
Mady Mellor9801e852019-01-22 14:50:28 -0800103 private BubbleStackView mStackView;
104
Mady Mellor3dff9e62019-02-05 18:12:53 -0800105 private BubbleExpandedView.OnBubbleBlockedListener mOnBubbleBlockedListener;
106
107 private ActivityView.StateCallback mStateCallback = new ActivityView.StateCallback() {
108 @Override
109 public void onActivityViewReady(ActivityView view) {
Mady Mellor6d002032019-02-13 13:45:17 -0800110 if (!mActivityViewReady) {
111 mActivityViewReady = true;
112 mActivityView.startActivity(mBubbleIntent);
113 }
Mady Mellor3dff9e62019-02-05 18:12:53 -0800114 }
115
116 @Override
117 public void onActivityViewDestroyed(ActivityView view) {
118 mActivityViewReady = false;
119 }
120
121 /**
122 * This is only called for tasks on this ActivityView, which is also set to
123 * single-task mode -- meaning never more than one task on this display. If a task
124 * is being removed, it's the top Activity finishing and this bubble should
125 * be removed or collapsed.
126 */
127 @Override
128 public void onTaskRemovalStarted(int taskId) {
129 if (mEntry != null) {
130 // Must post because this is called from a binder thread.
131 post(() -> mBubbleController.removeBubble(mEntry.key));
132 }
133 }
134 };
Mady Mellore8e07712019-01-23 12:45:33 -0800135
Mady Mellor3d82e682019-02-05 13:34:48 -0800136 public BubbleExpandedView(Context context) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800137 this(context, null);
138 }
139
Mady Mellor3d82e682019-02-05 13:34:48 -0800140 public BubbleExpandedView(Context context, AttributeSet attrs) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800141 this(context, attrs, 0);
142 }
143
Mady Mellor3d82e682019-02-05 13:34:48 -0800144 public BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800145 this(context, attrs, defStyleAttr, 0);
146 }
147
Mady Mellor3d82e682019-02-05 13:34:48 -0800148 public BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr,
Mady Mellordea7ecf2018-12-10 15:47:40 -0800149 int defStyleRes) {
150 super(context, attrs, defStyleAttr, defStyleRes);
Mady Mellor9801e852019-01-22 14:50:28 -0800151 mPm = context.getPackageManager();
Mady Mellorfe7ec032019-01-30 17:32:49 -0800152 mMinHeight = getResources().getDimensionPixelSize(
Mady Mellor3dff9e62019-02-05 18:12:53 -0800153 R.dimen.bubble_expanded_default_height);
Mady Mellore8e07712019-01-23 12:45:33 -0800154 try {
155 mNotificationManagerService = INotificationManager.Stub.asInterface(
156 ServiceManager.getServiceOrThrow(Context.NOTIFICATION_SERVICE));
157 } catch (ServiceManager.ServiceNotFoundException e) {
158 Log.w(TAG, e);
159 }
Mady Mellordea7ecf2018-12-10 15:47:40 -0800160 }
161
162 @Override
163 protected void onFinishInflate() {
164 super.onFinishInflate();
165
166 Resources res = getResources();
167 mPointerView = findViewById(R.id.pointer_view);
168 int width = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
169 int height = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
Mady Mellordd497052019-01-30 17:23:48 -0800170
171 TypedArray ta = getContext().obtainStyledAttributes(
172 new int[] {android.R.attr.colorBackgroundFloating});
173 int bgColor = ta.getColor(0, Color.WHITE);
174 ta.recycle();
175
Mady Mellordea7ecf2018-12-10 15:47:40 -0800176 ShapeDrawable triangleDrawable = new ShapeDrawable(
177 TriangleShape.create(width, height, true /* pointUp */));
Mady Mellordd497052019-01-30 17:23:48 -0800178 triangleDrawable.setTint(bgColor);
Mady Mellordea7ecf2018-12-10 15:47:40 -0800179 mPointerView.setBackground(triangleDrawable);
Mady Mellor9801e852019-01-22 14:50:28 -0800180
Mady Mellore8e07712019-01-23 12:45:33 -0800181 FrameLayout viewWrapper = findViewById(R.id.header_permission_wrapper);
182 LayoutTransition transition = new LayoutTransition();
183 transition.setDuration(200);
184
185 ObjectAnimator appearAnimator = ObjectAnimator.ofFloat(null, View.ALPHA, 0f, 1f);
186 transition.setAnimator(LayoutTransition.APPEARING, appearAnimator);
187 transition.setInterpolator(LayoutTransition.APPEARING, Interpolators.ALPHA_IN);
188
189 ObjectAnimator disappearAnimator = ObjectAnimator.ofFloat(null, View.ALPHA, 1f, 0f);
190 transition.setAnimator(LayoutTransition.DISAPPEARING, disappearAnimator);
191 transition.setInterpolator(LayoutTransition.DISAPPEARING, Interpolators.ALPHA_OUT);
192
193 transition.setAnimateParentHierarchy(false);
194 viewWrapper.setLayoutTransition(transition);
195 viewWrapper.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
196
Mady Mellorfe7ec032019-01-30 17:32:49 -0800197 mHeaderHeight = getContext().getResources().getDimensionPixelSize(
198 R.dimen.bubble_expanded_header_height);
Mady Mellore8e07712019-01-23 12:45:33 -0800199 mHeaderView = findViewById(R.id.header_layout);
200 mHeaderTextView = findViewById(R.id.header_text);
Mady Mellor9801e852019-01-22 14:50:28 -0800201 mDeepLinkIcon = findViewById(R.id.deep_link_button);
202 mSettingsIcon = findViewById(R.id.settings_button);
203 mDeepLinkIcon.setOnClickListener(this);
204 mSettingsIcon.setOnClickListener(this);
Mady Mellore8e07712019-01-23 12:45:33 -0800205
206 mPermissionView = findViewById(R.id.permission_layout);
207 findViewById(R.id.no_bubbles_button).setOnClickListener(this);
208 findViewById(R.id.yes_bubbles_button).setOnClickListener(this);
Mady Mellor3dff9e62019-02-05 18:12:53 -0800209
210 mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */,
211 true /* singleTaskInstance */);
212 addView(mActivityView);
213
214 mActivityView.setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
215 ActivityView activityView = (ActivityView) view;
216 // Here we assume that the position of the ActivityView on the screen
217 // remains regardless of IME status. When we move ActivityView, the
218 // forwardedInsets should be computed not against the current location
219 // and size, but against the post-moved location and size.
220 Point displaySize = new Point();
221 view.getContext().getDisplay().getSize(displaySize);
222 int[] windowLocation = view.getLocationOnScreen();
223 final int windowBottom = windowLocation[1] + view.getHeight();
224 final int keyboardHeight = insets.getSystemWindowInsetBottom()
225 - insets.getStableInsetBottom();
226 final int insetsBottom = Math.max(0,
227 windowBottom + keyboardHeight - displaySize.y);
228 activityView.setForwardedInsets(Insets.of(0, 0, 0, insetsBottom));
229 return view.onApplyWindowInsets(insets);
230 });
Mady Mellore8e07712019-01-23 12:45:33 -0800231 }
232
233 /**
234 * Sets the listener to notify when a bubble has been blocked.
235 */
236 public void setOnBlockedListener(OnBubbleBlockedListener listener) {
237 mOnBubbleBlockedListener = listener;
Mady Mellor9801e852019-01-22 14:50:28 -0800238 }
239
240 /**
241 * Sets the notification entry used to populate this view.
242 */
243 public void setEntry(NotificationEntry entry, BubbleStackView stackView) {
244 mStackView = stackView;
245 mEntry = entry;
246
247 ApplicationInfo info;
248 try {
249 info = mPm.getApplicationInfo(
250 entry.notification.getPackageName(),
251 PackageManager.MATCH_UNINSTALLED_PACKAGES
252 | PackageManager.MATCH_DISABLED_COMPONENTS
253 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
254 | PackageManager.MATCH_DIRECT_BOOT_AWARE);
255 if (info != null) {
256 mAppName = String.valueOf(mPm.getApplicationLabel(info));
Mady Mellore8e07712019-01-23 12:45:33 -0800257 mAppIcon = mPm.getApplicationIcon(info);
Mady Mellor9801e852019-01-22 14:50:28 -0800258 }
259 } catch (PackageManager.NameNotFoundException e) {
260 // Ahh... just use package name
261 mAppName = entry.notification.getPackageName();
262 }
Mady Mellore8e07712019-01-23 12:45:33 -0800263 if (mAppIcon == null) {
264 mAppIcon = mPm.getDefaultActivityIcon();
265 }
Mady Mellor9801e852019-01-22 14:50:28 -0800266 updateHeaderView();
Mady Mellore8e07712019-01-23 12:45:33 -0800267 updatePermissionView();
Mady Mellor3dff9e62019-02-05 18:12:53 -0800268 updateExpandedView();
Mady Mellor6d002032019-02-13 13:45:17 -0800269 }
270
271 /**
272 * Lets activity view know it should be shown / populated.
273 */
274 public void populateActivityView() {
Mady Mellor3dff9e62019-02-05 18:12:53 -0800275 mActivityView.setCallback(mStateCallback);
Mady Mellor9801e852019-01-22 14:50:28 -0800276 }
277
Mady Mellorfe7ec032019-01-30 17:32:49 -0800278 /**
279 * Updates the entry backing this view. This will not re-populate ActivityView, it will
280 * only update the deep-links in the header, the title, and the height of the view.
281 */
282 public void update(NotificationEntry entry) {
283 if (entry.key.equals(mEntry.key)) {
284 mEntry = entry;
285 updateHeaderView();
286 updateHeight();
287 } else {
288 Log.w(TAG, "Trying to update entry with different key, new entry: "
289 + entry.key + " old entry: " + mEntry.key);
290 }
291 }
292
Mady Mellor9801e852019-01-22 14:50:28 -0800293 private void updateHeaderView() {
294 mSettingsIcon.setContentDescription(getResources().getString(
295 R.string.bubbles_settings_button_description, mAppName));
296 mDeepLinkIcon.setContentDescription(getResources().getString(
297 R.string.bubbles_deep_link_button_description, mAppName));
298 if (mEntry != null && mEntry.getBubbleMetadata() != null) {
Mady Mellore8e07712019-01-23 12:45:33 -0800299 mHeaderTextView.setText(mEntry.getBubbleMetadata().getTitle());
Mady Mellor9801e852019-01-22 14:50:28 -0800300 } else {
301 // This should only happen if we're auto-bubbling notification content that isn't
302 // explicitly a bubble
Mady Mellore8e07712019-01-23 12:45:33 -0800303 mHeaderTextView.setText(mAppName);
304 }
305 }
306
307 private void updatePermissionView() {
308 boolean hasUserApprovedBubblesForPackage = false;
309 try {
310 hasUserApprovedBubblesForPackage =
311 mNotificationManagerService.hasUserApprovedBubblesForPackage(
312 mEntry.notification.getPackageName(), mEntry.notification.getUid());
313 } catch (RemoteException e) {
314 Log.w(TAG, e);
315 }
316 if (hasUserApprovedBubblesForPackage) {
317 mHeaderView.setVisibility(VISIBLE);
318 mPermissionView.setVisibility(GONE);
319 } else {
320 mHeaderView.setVisibility(GONE);
321 mPermissionView.setVisibility(VISIBLE);
322 ((ImageView) mPermissionView.findViewById(R.id.pkgicon)).setImageDrawable(mAppIcon);
323 ((TextView) mPermissionView.findViewById(R.id.pkgname)).setText(mAppName);
Mady Mellor9801e852019-01-22 14:50:28 -0800324 }
325 }
326
Mady Mellor3dff9e62019-02-05 18:12:53 -0800327 private void updateExpandedView() {
328 mBubbleIntent = getBubbleIntent(mEntry);
329 if (mBubbleIntent != null) {
330 if (mNotifRow != null) {
331 // Clear out the row if we had it previously
332 removeView(mNotifRow);
333 mNotifRow = null;
334 }
Mady Mellor3dff9e62019-02-05 18:12:53 -0800335 mActivityView.setVisibility(VISIBLE);
336 } else {
337 // Hide activity view if we had it previously
338 mActivityView.setVisibility(GONE);
339
340 // Use notification view
341 mNotifRow = mEntry.getRow();
342 addView(mNotifRow);
343 }
344 updateView();
345 }
346
Mark Renouf041d7262019-02-06 12:09:41 -0500347 boolean performBackPressIfNeeded() {
348 if (mActivityView == null || !usingActivityView()) {
349 return false;
350 }
351 mActivityView.performBackPress();
352 return true;
353 }
354
Mady Mellorfe7ec032019-01-30 17:32:49 -0800355 void updateHeight() {
356 if (usingActivityView()) {
357 Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
358 int desiredHeight;
359 if (data == null) {
360 // This is a contentIntent based bubble, lets allow it to be the max height
361 // as it was forced into this mode and not prepared to be small
362 desiredHeight = mStackView.getMaxExpandedHeight();
363 } else {
364 desiredHeight = data.getDesiredHeight() > 0
365 ? data.getDesiredHeight()
366 : mMinHeight;
367 }
368 int max = mStackView.getMaxExpandedHeight() - mHeaderHeight;
369 int height = Math.min(desiredHeight, max);
370 height = Math.max(height, mMinHeight);
371 LayoutParams lp = (LayoutParams) mActivityView.getLayoutParams();
372 lp.height = height;
373 mActivityView.setLayoutParams(lp);
374 }
375 }
376
Mady Mellor9801e852019-01-22 14:50:28 -0800377 @Override
378 public void onClick(View view) {
379 if (mEntry == null) {
380 return;
381 }
382 Notification n = mEntry.notification.getNotification();
383 int id = view.getId();
384 if (id == R.id.deep_link_button) {
385 mStackView.collapseStack(() -> {
386 try {
387 n.contentIntent.send();
Steven Wub00225b2019-02-08 14:27:42 -0500388 logBubbleClickEvent(mEntry.notification,
389 StatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_APP);
Mady Mellor9801e852019-01-22 14:50:28 -0800390 } catch (PendingIntent.CanceledException e) {
391 Log.w(TAG, "Failed to send intent for bubble with key: "
392 + (mEntry != null ? mEntry.key : " null entry"));
393 }
394 });
395 } else if (id == R.id.settings_button) {
396 Intent intent = getSettingsIntent(mEntry.notification.getPackageName(),
397 mEntry.notification.getUid());
Steven Wub00225b2019-02-08 14:27:42 -0500398 mStackView.collapseStack(() -> {
399 mContext.startActivity(intent);
400 logBubbleClickEvent(mEntry.notification,
401 StatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
402 });
Mady Mellore8e07712019-01-23 12:45:33 -0800403 } else if (id == R.id.no_bubbles_button) {
404 setBubblesAllowed(false);
405 } else if (id == R.id.yes_bubbles_button) {
406 setBubblesAllowed(true);
407 }
408 }
409
410 private void setBubblesAllowed(boolean allowed) {
411 try {
412 mNotificationManagerService.setBubblesAllowed(
413 mEntry.notification.getPackageName(),
414 mEntry.notification.getUid(),
415 allowed);
416 if (allowed) {
417 mPermissionView.setVisibility(GONE);
418 mHeaderView.setVisibility(VISIBLE);
419 } else if (mOnBubbleBlockedListener != null) {
420 mOnBubbleBlockedListener.onBubbleBlocked(mEntry);
421 }
Steven Wub00225b2019-02-08 14:27:42 -0500422 logBubbleClickEvent(mEntry.notification,
423 allowed ? StatsLog.BUBBLE_UICHANGED__ACTION__PERMISSION_OPT_IN :
424 StatsLog.BUBBLE_UICHANGED__ACTION__PERMISSION_OPT_OUT);
Mady Mellore8e07712019-01-23 12:45:33 -0800425 } catch (RemoteException e) {
426 Log.w(TAG, e);
Mady Mellor9801e852019-01-22 14:50:28 -0800427 }
Mady Mellordea7ecf2018-12-10 15:47:40 -0800428 }
429
430 /**
Mady Mellor3dff9e62019-02-05 18:12:53 -0800431 * Update appearance of the expanded view being displayed.
432 */
433 public void updateView() {
434 if (usingActivityView()
435 && mActivityView.getVisibility() == VISIBLE
436 && mActivityView.isAttachedToWindow()) {
437 mActivityView.onLocationChanged();
438 } else if (mNotifRow != null) {
439 applyRowState(mNotifRow);
440 }
Mady Mellorfe7ec032019-01-30 17:32:49 -0800441 updateHeight();
Mady Mellor3dff9e62019-02-05 18:12:53 -0800442 }
443
444 /**
Mady Mellordea7ecf2018-12-10 15:47:40 -0800445 * Set the x position that the tip of the triangle should point to.
446 */
Mady Mellor3dff9e62019-02-05 18:12:53 -0800447 public void setPointerPosition(float x) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800448 // Adjust for the pointer size
Mady Mellor3dff9e62019-02-05 18:12:53 -0800449 x -= (mPointerView.getWidth() / 2f);
Mady Mellordea7ecf2018-12-10 15:47:40 -0800450 mPointerView.setTranslationX(x);
451 }
452
453 /**
Mady Mellor3dff9e62019-02-05 18:12:53 -0800454 * Removes and releases an ActivityView if one was previously created for this bubble.
Mady Mellordea7ecf2018-12-10 15:47:40 -0800455 */
Mady Mellor3dff9e62019-02-05 18:12:53 -0800456 public void destroyActivityView(ViewGroup tmpParent) {
457 if (mActivityView == null) {
Mark Renouf89b1a4a2018-12-04 14:59:45 -0500458 return;
459 }
Mady Mellor3dff9e62019-02-05 18:12:53 -0800460 if (!mActivityViewReady) {
461 // release not needed, never initialized?
462 mActivityView = null;
463 return;
Mady Mellordea7ecf2018-12-10 15:47:40 -0800464 }
Mady Mellor3dff9e62019-02-05 18:12:53 -0800465 // HACK: release() will crash if the view is not attached.
466 if (!isAttachedToWindow()) {
467 mActivityView.setVisibility(View.GONE);
468 tmpParent.addView(this);
Mady Mellordea7ecf2018-12-10 15:47:40 -0800469 }
Mady Mellor3dff9e62019-02-05 18:12:53 -0800470
471 mActivityView.release();
472 ((ViewGroup) getParent()).removeView(this);
473 mActivityView = null;
474 mActivityViewReady = false;
Mady Mellordea7ecf2018-12-10 15:47:40 -0800475 }
476
Mady Mellor3dff9e62019-02-05 18:12:53 -0800477 private boolean usingActivityView() {
478 return mBubbleIntent != null;
479 }
480
481 private void applyRowState(ExpandableNotificationRow view) {
482 view.reset();
483 view.setHeadsUp(false);
484 view.resetTranslation();
485 view.setOnKeyguard(false);
486 view.setOnAmbient(false);
487 view.setClipBottomAmount(0);
488 view.setClipTopAmount(0);
489 view.setContentTransformationAmount(0, false);
490 view.setIconsVisible(true);
491
492 // TODO - Need to reset this (and others) when view goes back in shade, leave for now
493 // view.setTopRoundness(1, false);
494 // view.setBottomRoundness(1, false);
495
496 ExpandableViewState viewState = view.getViewState();
497 viewState = viewState == null ? new ExpandableViewState() : viewState;
498 viewState.height = view.getIntrinsicHeight();
499 viewState.gone = false;
500 viewState.hidden = false;
501 viewState.dimmed = false;
502 viewState.dark = false;
503 viewState.alpha = 1f;
504 viewState.notGoneIndex = -1;
505 viewState.xTranslation = 0;
506 viewState.yTranslation = 0;
507 viewState.zTranslation = 0;
508 viewState.scaleX = 1;
509 viewState.scaleY = 1;
510 viewState.inShelf = true;
511 viewState.headsUpIsVisible = false;
512 viewState.applyToView(view);
Mady Mellordea7ecf2018-12-10 15:47:40 -0800513 }
Mady Mellor9801e852019-01-22 14:50:28 -0800514
515 private Intent getSettingsIntent(String packageName, final int appUid) {
516 final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
517 intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
518 intent.putExtra(Settings.EXTRA_APP_UID, appUid);
519 intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
520 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
521 return intent;
522 }
Mady Mellore8e07712019-01-23 12:45:33 -0800523
Mady Mellor3dff9e62019-02-05 18:12:53 -0800524 @Nullable
525 private PendingIntent getBubbleIntent(NotificationEntry entry) {
526 Notification notif = entry.notification.getNotification();
527 String packageName = entry.notification.getPackageName();
528 Notification.BubbleMetadata data = notif.getBubbleMetadata();
529 if (data != null && canLaunchInActivityView(data.getIntent(), true /* enableLogging */,
530 packageName)) {
531 return data.getIntent();
532 } else if (BubbleController.shouldUseContentIntent(mContext)
533 && canLaunchInActivityView(notif.contentIntent, false /* enableLogging */,
534 packageName)) {
535 return notif.contentIntent;
536 }
537 return null;
538 }
539
540 /**
541 * Whether an intent is properly configured to display in an {@link android.app.ActivityView}.
542 *
543 * @param intent the pending intent of the bubble.
544 * @param enableLogging whether bubble developer error should be logged.
545 * @param packageName the notification package name for this bubble.
546 * @return
547 */
548 private boolean canLaunchInActivityView(PendingIntent intent, boolean enableLogging,
549 String packageName) {
550 if (intent == null) {
551 return false;
552 }
553 ActivityInfo info =
554 intent.getIntent().resolveActivityInfo(mContext.getPackageManager(), 0);
555 if (info == null) {
556 if (enableLogging) {
557 StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
558 BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING);
559 }
560 return false;
561 }
562 if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
563 if (enableLogging) {
564 StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
565 BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE);
566 }
567 return false;
568 }
569 if (info.documentLaunchMode != DOCUMENT_LAUNCH_ALWAYS) {
570 if (enableLogging) {
571 StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
572 BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__DOCUMENT_LAUNCH_NOT_ALWAYS);
573 }
574 return false;
575 }
576 return (info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) != 0;
577 }
578
Mady Mellore8e07712019-01-23 12:45:33 -0800579 /**
580 * Listener that is notified when a bubble is blocked.
581 */
582 public interface OnBubbleBlockedListener {
583 /**
584 * Called when a bubble is blocked for the provided entry.
585 */
586 void onBubbleBlocked(NotificationEntry entry);
587 }
Steven Wub00225b2019-02-08 14:27:42 -0500588
589 /**
590 * Logs bubble UI click event.
591 *
592 * @param notification the bubble notification that user is interacting with.
593 * @param action the user interaction enum.
594 */
595 private void logBubbleClickEvent(StatusBarNotification notification, int action) {
596 StatsLog.write(StatsLog.BUBBLE_UI_CHANGED,
597 notification.getPackageName(),
598 notification.getNotification().getChannelId(),
599 notification.getId(),
600 mStackView.getBubbleIndex(mStackView.getExpandedBubble()),
601 mStackView.getBubbleCount(),
602 action,
603 mStackView.getNormalizedXPosition(),
604 mStackView.getNormalizedYPosition());
605 }
Mady Mellordea7ecf2018-12-10 15:47:40 -0800606}