diff --git a/packages/SystemUI/res/layout/pip_menu_activity.xml b/packages/SystemUI/res/layout/pip_menu_activity.xml index 8b7f6926c9f7c..03d587bd447cc 100644 --- a/packages/SystemUI/res/layout/pip_menu_activity.xml +++ b/packages/SystemUI/res/layout/pip_menu_activity.xml @@ -60,14 +60,24 @@ - + + + diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 78e621e4b559c..4643d4146fded 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1887,6 +1887,9 @@ Close + + Settings + Drag down to dismiss diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java index 2963506865efa..f975f033ff4e1 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java @@ -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); } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java index 90f7b8db1c599..bfe07a980ce7c 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java @@ -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 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; diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java deleted file mode 100644 index 6d083e9d601dd..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java +++ /dev/null @@ -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 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; - } -}