Removing PIP notification for menu action
Bug: 65482444 Test: Launch PiP, tap to show menu, tap settings to go to settings Change-Id: I204e18ffa0b63acbfef899bda91f5da9daf9f0a5
This commit is contained in:
@@ -60,14 +60,24 @@
|
||||
</FrameLayout>
|
||||
</FrameLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/dismiss"
|
||||
android:layout_width="@dimen/pip_action_size"
|
||||
android:layout_height="@dimen/pip_action_size"
|
||||
android:layout_gravity="top|end"
|
||||
android:padding="@dimen/pip_action_padding"
|
||||
android:contentDescription="@string/pip_phone_close"
|
||||
android:src="@drawable/ic_close_white"
|
||||
android:background="?android:selectableItemBackgroundBorderless" />
|
||||
<ImageView
|
||||
android:id="@+id/settings"
|
||||
android:layout_width="@dimen/pip_action_size"
|
||||
android:layout_height="@dimen/pip_action_size"
|
||||
android:layout_gravity="top|start"
|
||||
android:padding="@dimen/pip_action_padding"
|
||||
android:contentDescription="@string/pip_phone_settings"
|
||||
android:src="@drawable/ic_settings"
|
||||
android:background="?android:selectableItemBackgroundBorderless" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/dismiss"
|
||||
android:layout_width="@dimen/pip_action_size"
|
||||
android:layout_height="@dimen/pip_action_size"
|
||||
android:layout_gravity="top|end"
|
||||
android:padding="@dimen/pip_action_padding"
|
||||
android:contentDescription="@string/pip_phone_close"
|
||||
android:src="@drawable/ic_close_white"
|
||||
android:background="?android:selectableItemBackgroundBorderless" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
@@ -1887,6 +1887,9 @@
|
||||
<!-- Label for PIP close button [CHAR LIMIT=NONE]-->
|
||||
<string name="pip_phone_close">Close</string>
|
||||
|
||||
<!-- Label for PIP settings button [CHAR LIMIT=NONE]-->
|
||||
<string name="pip_phone_settings">Settings</string>
|
||||
|
||||
<!-- Label for PIP the drag to dismiss hint [CHAR LIMIT=NONE]-->
|
||||
<string name="pip_phone_dismiss_hint">Drag down to dismiss</string>
|
||||
|
||||
|
||||
@@ -64,7 +64,6 @@ public class PipManager implements BasePipManager {
|
||||
private InputConsumerController mInputConsumerController;
|
||||
private PipMenuActivityController mMenuController;
|
||||
private PipMediaController mMediaController;
|
||||
private PipNotificationController mNotificationController;
|
||||
private PipTouchHandler mTouchHandler;
|
||||
|
||||
/**
|
||||
@@ -76,8 +75,6 @@ public class PipManager implements BasePipManager {
|
||||
mTouchHandler.onActivityPinned();
|
||||
mMediaController.onActivityPinned();
|
||||
mMenuController.onActivityPinned();
|
||||
mNotificationController.onActivityPinned(packageName, userId,
|
||||
true /* deferUntilAnimationEnds */);
|
||||
|
||||
SystemServicesProxy.getInstance(mContext).setPipVisibility(true);
|
||||
}
|
||||
@@ -90,7 +87,6 @@ public class PipManager implements BasePipManager {
|
||||
final int userId = topActivity != null ? topPipActivityInfo.second : 0;
|
||||
mMenuController.onActivityUnpinned();
|
||||
mTouchHandler.onActivityUnpinned(topActivity);
|
||||
mNotificationController.onActivityUnpinned(topActivity, userId);
|
||||
|
||||
SystemServicesProxy.getInstance(mContext).setPipVisibility(topActivity != null);
|
||||
}
|
||||
@@ -107,7 +103,6 @@ public class PipManager implements BasePipManager {
|
||||
mTouchHandler.setTouchEnabled(true);
|
||||
mTouchHandler.onPinnedStackAnimationEnded();
|
||||
mMenuController.onPinnedStackAnimationEnded();
|
||||
mNotificationController.onPinnedStackAnimationEnded();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -182,8 +177,6 @@ public class PipManager implements BasePipManager {
|
||||
mInputConsumerController);
|
||||
mTouchHandler = new PipTouchHandler(context, mActivityManager, mMenuController,
|
||||
mInputConsumerController);
|
||||
mNotificationController = new PipNotificationController(context, mActivityManager,
|
||||
mTouchHandler.getMotionHelper());
|
||||
EventBus.getDefault().register(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
package com.android.systemui.pip.phone;
|
||||
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||
import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;
|
||||
|
||||
import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ACTIONS;
|
||||
import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ALLOW_TIMEOUT;
|
||||
import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER;
|
||||
@@ -39,6 +43,7 @@ import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.PendingIntent.CanceledException;
|
||||
import android.app.RemoteAction;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.graphics.Color;
|
||||
@@ -46,12 +51,15 @@ import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
@@ -105,6 +113,7 @@ public class PipMenuActivity extends Activity {
|
||||
private Drawable mBackgroundDrawable;
|
||||
private View mMenuContainer;
|
||||
private LinearLayout mActionsGroup;
|
||||
private View mSettingsButton;
|
||||
private View mDismissButton;
|
||||
private ImageView mExpandButton;
|
||||
private int mBetweenActionPaddingLand;
|
||||
@@ -218,6 +227,11 @@ public class PipMenuActivity extends Activity {
|
||||
}
|
||||
return true;
|
||||
});
|
||||
mSettingsButton = findViewById(R.id.settings);
|
||||
mSettingsButton.setAlpha(0);
|
||||
mSettingsButton.setOnClickListener((v) -> {
|
||||
showSettings();
|
||||
});
|
||||
mDismissButton = findViewById(R.id.dismiss);
|
||||
mDismissButton.setAlpha(0);
|
||||
mDismissButton.setOnClickListener((v) -> {
|
||||
@@ -352,12 +366,14 @@ public class PipMenuActivity extends Activity {
|
||||
ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
|
||||
mMenuContainer.getAlpha(), 1f);
|
||||
menuAnim.addUpdateListener(mMenuBgUpdateListener);
|
||||
ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
|
||||
mSettingsButton.getAlpha(), 1f);
|
||||
ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
|
||||
mDismissButton.getAlpha(), 1f);
|
||||
if (menuState == MENU_STATE_FULL) {
|
||||
mMenuContainerAnimator.playTogether(menuAnim, dismissAnim);
|
||||
mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim);
|
||||
} else {
|
||||
mMenuContainerAnimator.play(dismissAnim);
|
||||
mMenuContainerAnimator.playTogether(settingsAnim, dismissAnim);
|
||||
}
|
||||
mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
|
||||
mMenuContainerAnimator.setDuration(MENU_FADE_DURATION);
|
||||
@@ -394,9 +410,11 @@ public class PipMenuActivity extends Activity {
|
||||
ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
|
||||
mMenuContainer.getAlpha(), 0f);
|
||||
menuAnim.addUpdateListener(mMenuBgUpdateListener);
|
||||
ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
|
||||
mSettingsButton.getAlpha(), 0f);
|
||||
ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
|
||||
mDismissButton.getAlpha(), 0f);
|
||||
mMenuContainerAnimator.playTogether(menuAnim, dismissAnim);
|
||||
mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim);
|
||||
mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT);
|
||||
mMenuContainerAnimator.setDuration(MENU_FADE_DURATION);
|
||||
mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@@ -526,12 +544,14 @@ public class PipMenuActivity extends Activity {
|
||||
final float menuAlpha = 1 - fraction;
|
||||
if (mMenuState == MENU_STATE_FULL) {
|
||||
mMenuContainer.setAlpha(menuAlpha);
|
||||
mSettingsButton.setAlpha(menuAlpha);
|
||||
mDismissButton.setAlpha(menuAlpha);
|
||||
final float interpolatedAlpha =
|
||||
MENU_BACKGROUND_ALPHA * menuAlpha + DISMISS_BACKGROUND_ALPHA * fraction;
|
||||
alpha = (int) (interpolatedAlpha * 255);
|
||||
} else {
|
||||
if (mMenuState == MENU_STATE_CLOSE) {
|
||||
mSettingsButton.setAlpha(menuAlpha);
|
||||
mDismissButton.setAlpha(menuAlpha);
|
||||
}
|
||||
alpha = (int) (fraction * DISMISS_BACKGROUND_ALPHA * 255);
|
||||
@@ -588,6 +608,19 @@ public class PipMenuActivity extends Activity {
|
||||
sendMessage(m, "Could not notify controller to show PIP menu");
|
||||
}
|
||||
|
||||
private void showSettings() {
|
||||
final Pair<ComponentName, Integer> topPipActivityInfo =
|
||||
PipUtils.getTopPinnedActivity(this, ActivityManager.getService());
|
||||
if (topPipActivityInfo.first != null) {
|
||||
final UserHandle user = UserHandle.of(topPipActivityInfo.second);
|
||||
final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
|
||||
Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null));
|
||||
settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user);
|
||||
settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
|
||||
startActivity(settingsIntent);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyActivityCallback(Messenger callback) {
|
||||
Message m = Message.obtain();
|
||||
m.what = PipMenuActivityController.MESSAGE_UPDATE_ACTIVITY_CALLBACK;
|
||||
|
||||
@@ -1,231 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.pip.phone;
|
||||
|
||||
import static android.app.AppOpsManager.MODE_ALLOWED;
|
||||
import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
|
||||
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||
import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;
|
||||
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.AppOpsManager.OnOpChangedListener;
|
||||
import android.app.IActivityManager;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.UserHandle;
|
||||
import android.util.IconDrawableFactory;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.SystemUI;
|
||||
import com.android.systemui.util.NotificationChannels;
|
||||
|
||||
/**
|
||||
* Manages the BTW notification that shows whenever an activity enters or leaves picture-in-picture.
|
||||
*/
|
||||
public class PipNotificationController {
|
||||
private static final String TAG = PipNotificationController.class.getSimpleName();
|
||||
|
||||
private static final String NOTIFICATION_TAG = PipNotificationController.class.getName();
|
||||
private static final int NOTIFICATION_ID = 0;
|
||||
|
||||
private Context mContext;
|
||||
private IActivityManager mActivityManager;
|
||||
private AppOpsManager mAppOpsManager;
|
||||
private NotificationManager mNotificationManager;
|
||||
private IconDrawableFactory mIconDrawableFactory;
|
||||
|
||||
private PipMotionHelper mMotionHelper;
|
||||
|
||||
// Used when building a deferred notification
|
||||
private String mDeferredNotificationPackageName;
|
||||
private int mDeferredNotificationUserId;
|
||||
|
||||
private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() {
|
||||
@Override
|
||||
public void onOpChanged(String op, String packageName) {
|
||||
try {
|
||||
// Dismiss the PiP once the user disables the app ops setting for that package
|
||||
final Pair<ComponentName, Integer> topPipActivityInfo =
|
||||
PipUtils.getTopPinnedActivity(mContext, mActivityManager);
|
||||
if (topPipActivityInfo.first != null) {
|
||||
final ApplicationInfo appInfo = mContext.getPackageManager()
|
||||
.getApplicationInfoAsUser(packageName, 0, topPipActivityInfo.second);
|
||||
if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) &&
|
||||
mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid,
|
||||
packageName) != MODE_ALLOWED) {
|
||||
mMotionHelper.dismissPip();
|
||||
}
|
||||
}
|
||||
} catch (NameNotFoundException e) {
|
||||
// Unregister the listener if the package can't be found
|
||||
unregisterAppOpsListener();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public PipNotificationController(Context context, IActivityManager activityManager,
|
||||
PipMotionHelper motionHelper) {
|
||||
mContext = context;
|
||||
mActivityManager = activityManager;
|
||||
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
|
||||
mNotificationManager = NotificationManager.from(context);
|
||||
mMotionHelper = motionHelper;
|
||||
mIconDrawableFactory = IconDrawableFactory.newInstance(context);
|
||||
}
|
||||
|
||||
public void onActivityPinned(String packageName, int userId, boolean deferUntilAnimationEnds) {
|
||||
// Clear any existing notification
|
||||
mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
|
||||
|
||||
if (deferUntilAnimationEnds) {
|
||||
mDeferredNotificationPackageName = packageName;
|
||||
mDeferredNotificationUserId = userId;
|
||||
} else {
|
||||
showNotificationForApp(packageName, userId);
|
||||
}
|
||||
|
||||
// Register for changes to the app ops setting for this package while it is in PiP
|
||||
registerAppOpsListener(packageName);
|
||||
}
|
||||
|
||||
public void onPinnedStackAnimationEnded() {
|
||||
if (mDeferredNotificationPackageName != null) {
|
||||
showNotificationForApp(mDeferredNotificationPackageName, mDeferredNotificationUserId);
|
||||
mDeferredNotificationPackageName = null;
|
||||
mDeferredNotificationUserId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void onActivityUnpinned(ComponentName topPipActivity, int userId) {
|
||||
// Unregister for changes to the previously PiP'ed package
|
||||
unregisterAppOpsListener();
|
||||
|
||||
// Reset the deferred notification package
|
||||
mDeferredNotificationPackageName = null;
|
||||
mDeferredNotificationUserId = 0;
|
||||
|
||||
if (topPipActivity != null) {
|
||||
// onActivityUnpinned() is only called after the transition is complete, so we don't
|
||||
// need to defer until the animation ends to update the notification
|
||||
onActivityPinned(topPipActivity.getPackageName(), userId,
|
||||
false /* deferUntilAnimationEnds */);
|
||||
} else {
|
||||
mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and shows the notification for the given app.
|
||||
*/
|
||||
private void showNotificationForApp(String packageName, int userId) {
|
||||
// Build a new notification
|
||||
try {
|
||||
final UserHandle user = UserHandle.of(userId);
|
||||
final Context userContext = mContext.createPackageContextAsUser(
|
||||
mContext.getPackageName(), 0, user);
|
||||
final Notification.Builder builder =
|
||||
new Notification.Builder(userContext, NotificationChannels.GENERAL)
|
||||
.setLocalOnly(true)
|
||||
.setOngoing(true)
|
||||
.setSmallIcon(R.drawable.pip_notification_icon)
|
||||
.setColor(mContext.getColor(
|
||||
com.android.internal.R.color.system_notification_accent_color));
|
||||
if (updateNotificationForApp(builder, packageName, user)) {
|
||||
SystemUI.overrideNotificationAppName(mContext, builder);
|
||||
|
||||
// Show the new notification
|
||||
mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.e(TAG, "Could not show notification for application", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the notification builder with app-specific information, returning whether it was
|
||||
* successful.
|
||||
*/
|
||||
private boolean updateNotificationForApp(Notification.Builder builder, String packageName,
|
||||
UserHandle user) throws NameNotFoundException {
|
||||
final PackageManager pm = mContext.getPackageManager();
|
||||
final ApplicationInfo appInfo;
|
||||
try {
|
||||
appInfo = pm.getApplicationInfoAsUser(packageName, 0, user.getIdentifier());
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.e(TAG, "Could not update notification for application", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (appInfo != null) {
|
||||
final String appName = pm.getUserBadgedLabel(pm.getApplicationLabel(appInfo), user)
|
||||
.toString();
|
||||
final String message = mContext.getString(R.string.pip_notification_message, appName);
|
||||
final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
|
||||
Uri.fromParts("package", packageName, null));
|
||||
settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user);
|
||||
settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
|
||||
|
||||
final Drawable iconDrawable = mIconDrawableFactory.getBadgedIcon(appInfo);
|
||||
builder.setContentTitle(mContext.getString(R.string.pip_notification_title, appName))
|
||||
.setContentText(message)
|
||||
.setContentIntent(PendingIntent.getActivityAsUser(mContext, packageName.hashCode(),
|
||||
settingsIntent, FLAG_CANCEL_CURRENT, null, user))
|
||||
.setStyle(new Notification.BigTextStyle().bigText(message))
|
||||
.setLargeIcon(createBitmap(iconDrawable).createAshmemBitmap());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void registerAppOpsListener(String packageName) {
|
||||
mAppOpsManager.startWatchingMode(OP_PICTURE_IN_PICTURE, packageName,
|
||||
mAppOpsChangedListener);
|
||||
}
|
||||
|
||||
private void unregisterAppOpsListener() {
|
||||
mAppOpsManager.stopWatchingMode(mAppOpsChangedListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bakes a drawable into a bitmap.
|
||||
*/
|
||||
private Bitmap createBitmap(Drawable d) {
|
||||
Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(),
|
||||
Config.ARGB_8888);
|
||||
Canvas c = new Canvas(bitmap);
|
||||
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
|
||||
d.draw(c);
|
||||
c.setBitmap(null);
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user