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:
Winson Chung
2017-12-19 14:46:23 -08:00
parent 107f7cc4c4
commit a556fe6a35
5 changed files with 58 additions and 250 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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;
}
}