summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/ITaskStackListener.aidl5
-rw-r--r--core/java/android/app/TaskStackListener.java6
-rw-r--r--packages/SystemUI/res/drawable/pip_notification_icon.xml25
-rw-r--r--packages/SystemUI/res/values/strings.xml15
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java142
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java25
-rw-r--r--services/core/java/com/android/server/am/ActivityStack.java5
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java2
-rw-r--r--services/core/java/com/android/server/am/TaskChangeNotificationController.java23
11 files changed, 248 insertions, 17 deletions
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 5768d1a0393f..47817a72e962 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -25,7 +25,10 @@ oneway interface ITaskStackListener {
void onTaskStackChanged();
/** Called whenever an Activity is moved to the pinned stack from another stack. */
- void onActivityPinned();
+ void onActivityPinned(String packageName);
+
+ /** Called whenever an Activity is moved from the pinned stack to another stack. */
+ void onActivityUnpinned();
/**
* Called whenever IActivityManager.startActivity is called on an activity that is already
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index a07e11e2b8af..57fc874517b7 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -31,7 +31,11 @@ public abstract class TaskStackListener extends ITaskStackListener.Stub {
}
@Override
- public void onActivityPinned() throws RemoteException {
+ public void onActivityPinned(String packageName) throws RemoteException {
+ }
+
+ @Override
+ public void onActivityUnpinned() throws RemoteException {
}
@Override
diff --git a/packages/SystemUI/res/drawable/pip_notification_icon.xml b/packages/SystemUI/res/drawable/pip_notification_icon.xml
new file mode 100644
index 000000000000..592bc60d553e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/pip_notification_icon.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11.99,18.54l-7.37,-5.73L3,14.07l9,7 9,-7 -1.63,-1.27 -7.38,5.74zM12,16l7.36,-5.73L21,9l-9,-7 -9,7 1.63,1.27L12,16z"/>
+</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c9e7e57ecc5a..368c5484f902 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1750,6 +1750,18 @@
<!-- Label for PIP the drag to close zone [CHAR LIMIT=NONE]-->
<string name="pip_phone_close">Close</string>
+ <!-- Title of menu shown over picture-in-picture. Used for accessibility. -->
+ <string name="pip_menu_title">Picture in picture menu</string>
+
+ <!-- User visible notification channel name for the PiP BTW notification. [CHAR LIMIT=NONE] -->
+ <string name="pip_notification_channel_name">Picture-in-picture</string>
+
+ <!-- PiP BTW notification title. [CHAR LIMIT=50] -->
+ <string name="pip_notification_title"><xliff:g id="name" example="Google Maps">%s</xliff:g> is in picture-in-picture</string>
+
+ <!-- PiP BTW notification description. [CHAR LIMIT=NONE] -->
+ <string name="pip_notification_message">If you don’t want <xliff:g id="name" example="Google Maps">%s</xliff:g> to use this feature, tap to open settings and turn it off.</string>
+
<!-- Tuner string -->
<string name="change_theme_reboot" translatable="false">Changing the theme requires a restart.</string>
<!-- Tuner string -->
@@ -1818,9 +1830,6 @@
<!-- App label of the instant apps notification [CHAR LIMIT=60] -->
<string name="instant_apps">Instant Apps</string>
- <!-- Title of menu shown over picture-in-picture. Used for accessibility. -->
- <string name="pip_menu_title">Picture in picture menu</string>
-
<!-- Message of the instant apps notification indicating they don't need install [CHAR LIMIT=NONE] -->
<string name="instant_apps_message">Instant apps don\'t require installation.</string>
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 ecc2fadf5ae2..6cda0766663d 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -56,6 +56,7 @@ public class PipManager implements BasePipManager {
private InputConsumerController mInputConsumerController;
private PipMenuActivityController mMenuController;
private PipMediaController mMediaController;
+ private PipNotificationController mNotificationController;
private PipTouchHandler mTouchHandler;
/**
@@ -63,13 +64,24 @@ public class PipManager implements BasePipManager {
*/
TaskStackListener mTaskStackListener = new TaskStackListener() {
@Override
- public void onActivityPinned() {
+ public void onActivityPinned(String packageName) {
if (!checkCurrentUserId(false /* debug */)) {
return;
}
+
mTouchHandler.onActivityPinned();
mMediaController.onActivityPinned();
mMenuController.onActivityPinned();
+ mNotificationController.onActivityPinned(packageName);
+ }
+
+ @Override
+ public void onActivityUnpinned() {
+ if (!checkCurrentUserId(false /* debug */)) {
+ return;
+ }
+
+ mNotificationController.onActivityUnpinned();
}
@Override
@@ -160,6 +172,7 @@ public class PipManager implements BasePipManager {
mInputConsumerController);
mTouchHandler = new PipTouchHandler(context, mActivityManager, mMenuController,
mInputConsumerController);
+ mNotificationController = new PipNotificationController(context, mActivityManager);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java
new file mode 100644
index 000000000000..bdd6b65026f0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java
@@ -0,0 +1,142 @@
+/*
+ * 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.NotificationManager.IMPORTANCE_MIN;
+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.IActivityManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+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.content.res.Resources;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.util.Log;
+
+import com.android.systemui.R;
+import com.android.systemui.SystemUI;
+
+/**
+ * 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 CHANNEL_ID = PipNotificationController.class.getName();
+ private static final int BTW_NOTIFICATION_ID = 0;
+
+ private Context mContext;
+ private IActivityManager mActivityManager;
+ private NotificationManager mNotificationManager;
+
+ public PipNotificationController(Context context, IActivityManager activityManager) {
+ mContext = context;
+ mActivityManager = activityManager;
+ mNotificationManager = NotificationManager.from(context);
+ createNotificationChannel();
+ }
+
+ public void onActivityPinned(String packageName) {
+ // Clear any existing notification
+ mNotificationManager.cancel(CHANNEL_ID, BTW_NOTIFICATION_ID);
+
+ // Build a new notification
+ final Notification.Builder builder = new Notification.Builder(mContext, CHANNEL_ID)
+ .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)) {
+ SystemUI.overrideNotificationAppName(mContext, builder);
+
+ // Show the new notification
+ mNotificationManager.notify(CHANNEL_ID, BTW_NOTIFICATION_ID, builder.build());
+ }
+ }
+
+ public void onActivityUnpinned() {
+ ComponentName topPipActivity = PipUtils.getTopPinnedActivity(mContext, mActivityManager);
+ if (topPipActivity != null) {
+ onActivityPinned(topPipActivity.getPackageName());
+ } else {
+ mNotificationManager.cancel(CHANNEL_ID, BTW_NOTIFICATION_ID);
+ }
+ }
+
+ /**
+ * Create the notification channel for the PiP BTW notifications if necessary.
+ */
+ private NotificationChannel createNotificationChannel() {
+ NotificationChannel channel = mNotificationManager.getNotificationChannel(CHANNEL_ID);
+ if (channel == null) {
+ channel = new NotificationChannel(CHANNEL_ID,
+ mContext.getString(R.string.pip_notification_channel_name), IMPORTANCE_MIN);
+ channel.enableLights(false);
+ channel.enableVibration(false);
+ mNotificationManager.createNotificationChannel(channel);
+ }
+ return channel;
+ }
+
+ /**
+ * Updates the notification builder with app-specific information, returning whether it was
+ * successful.
+ */
+ private boolean updateNotificationForApp(Notification.Builder builder, String packageName) {
+ final PackageManager pm = mContext.getPackageManager();
+ final ApplicationInfo appInfo;
+ try {
+ appInfo = pm.getApplicationInfo(packageName, 0);
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Could not update notification for application", e);
+ return false;
+ }
+
+ if (appInfo != null) {
+ final String appName = pm.getApplicationLabel(appInfo).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.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+ final Icon appIcon = appInfo.icon != 0
+ ? Icon.createWithResource(packageName, appInfo.icon)
+ : Icon.createWithResource(Resources.getSystem(),
+ com.android.internal.R.drawable.sym_def_app_icon);
+
+ builder.setContentTitle(mContext.getString(R.string.pip_notification_title, appName))
+ .setContentText(message)
+ .setContentIntent(PendingIntent.getActivity(mContext, packageName.hashCode(),
+ settingsIntent, FLAG_CANCEL_CURRENT))
+ .setStyle(new Notification.BigTextStyle().bigText(message))
+ .setLargeIcon(appIcon);
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index b71c87d4cc23..b96b0ae9ddd9 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -627,7 +627,7 @@ public class PipManager implements BasePipManager {
}
@Override
- public void onActivityPinned() {
+ public void onActivityPinned(String packageName) {
if (DEBUG) Log.d(TAG, "onActivityPinned()");
if (!checkCurrentUserId(DEBUG)) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 47468aeaeb97..ac8c0f2fe1f4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -153,7 +153,8 @@ public class SystemServicesProxy {
public abstract static class TaskStackListener {
public void onTaskStackChanged() { }
public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { }
- public void onActivityPinned() { }
+ public void onActivityPinned(String packageName) { }
+ public void onActivityUnpinned() { }
public void onPinnedActivityRestartAttempt() { }
public void onPinnedStackAnimationStarted() { }
public void onPinnedStackAnimationEnded() { }
@@ -194,17 +195,22 @@ public class SystemServicesProxy {
}
@Override
- public void onActivityPinned() throws RemoteException {
+ public void onActivityPinned(String packageName) throws RemoteException {
mHandler.removeMessages(H.ON_ACTIVITY_PINNED);
- mHandler.sendEmptyMessage(H.ON_ACTIVITY_PINNED);
+ mHandler.obtainMessage(H.ON_ACTIVITY_PINNED, packageName).sendToTarget();
+ }
+
+ @Override
+ public void onActivityUnpinned() throws RemoteException {
+ mHandler.removeMessages(H.ON_ACTIVITY_UNPINNED);
+ mHandler.sendEmptyMessage(H.ON_ACTIVITY_UNPINNED);
}
@Override
public void onPinnedActivityRestartAttempt()
throws RemoteException{
mHandler.removeMessages(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT);
- mHandler.obtainMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT)
- .sendToTarget();
+ mHandler.sendEmptyMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT);
}
@Override
@@ -1231,6 +1237,7 @@ public class SystemServicesProxy {
private static final int ON_ACTIVITY_DISMISSING_DOCKED_STACK = 7;
private static final int ON_TASK_PROFILE_LOCKED = 8;
private static final int ON_PINNED_STACK_ANIMATION_STARTED = 9;
+ private static final int ON_ACTIVITY_UNPINNED = 10;
@Override
public void handleMessage(Message msg) {
@@ -1250,7 +1257,13 @@ public class SystemServicesProxy {
}
case ON_ACTIVITY_PINNED: {
for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onActivityPinned();
+ mTaskStackListeners.get(i).onActivityPinned((String) msg.obj);
+ }
+ break;
+ }
+ case ON_ACTIVITY_UNPINNED: {
+ for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+ mTaskStackListeners.get(i).onActivityUnpinned();
}
break;
}
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 2885e663af3f..e64b4b325642 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -4983,6 +4983,11 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
}
task.setStack(null);
+
+ // Notify if a task from the pinned stack is being removed (or moved depending on the mode)
+ if (mStackId == PINNED_STACK_ID) {
+ mService.mTaskChangeNotificationController.notifyActivityUnpinned();
+ }
}
TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 42efe0b5d8e0..bce8c404a9e8 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2852,7 +2852,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
resumeFocusedStackTopActivityLocked();
stack.animateResizePinnedStack(bounds, -1 /* animationDuration */);
- mService.mTaskChangeNotificationController.notifyActivityPinned();
+ mService.mTaskChangeNotificationController.notifyActivityPinned(r.packageName);
}
/** Move activity with its stack to front and make the stack focused. */
diff --git a/services/core/java/com/android/server/am/TaskChangeNotificationController.java b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
index 3cec7e478046..94cf092baed3 100644
--- a/services/core/java/com/android/server/am/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
@@ -47,6 +47,7 @@ class TaskChangeNotificationController {
static final int NOTIFY_TASK_PROFILE_LOCKED_LISTENERS_MSG = 14;
static final int NOTIFY_TASK_SNAPSHOT_CHANGED_LISTENERS_MSG = 15;
static final int NOTIFY_PINNED_STACK_ANIMATION_STARTED_LISTENERS_MSG = 16;
+ static final int NOTIFY_ACTIVITY_UNPINNED_LISTENERS_MSG = 17;
// Delay in notifying task stack change listeners (in millis)
static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -94,7 +95,11 @@ class TaskChangeNotificationController {
};
private final TaskStackConsumer mNotifyActivityPinned = (l, m) -> {
- l.onActivityPinned();
+ l.onActivityPinned((String) m.obj);
+ };
+
+ private final TaskStackConsumer mNotifyActivityUnpinned = (l, m) -> {
+ l.onActivityUnpinned();
};
private final TaskStackConsumer mNotifyPinnedActivityRestartAttempt = (l, m) -> {
@@ -168,6 +173,9 @@ class TaskChangeNotificationController {
case NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG:
forAllRemoteListeners(mNotifyActivityPinned, msg);
break;
+ case NOTIFY_ACTIVITY_UNPINNED_LISTENERS_MSG:
+ forAllRemoteListeners(mNotifyActivityUnpinned, msg);
+ break;
case NOTIFY_PINNED_ACTIVITY_RESTART_ATTEMPT_LISTENERS_MSG:
forAllRemoteListeners(mNotifyPinnedActivityRestartAttempt, msg);
break;
@@ -263,13 +271,22 @@ class TaskChangeNotificationController {
}
/** Notifies all listeners when an Activity is pinned. */
- void notifyActivityPinned() {
+ void notifyActivityPinned(String packageName) {
mHandler.removeMessages(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG);
- final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG);
+ final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG,
+ packageName);
forAllLocalListeners(mNotifyActivityPinned, msg);
msg.sendToTarget();
}
+ /** Notifies all listeners when an Activity is unpinned. */
+ void notifyActivityUnpinned() {
+ mHandler.removeMessages(NOTIFY_ACTIVITY_UNPINNED_LISTENERS_MSG);
+ final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_UNPINNED_LISTENERS_MSG);
+ forAllLocalListeners(mNotifyActivityUnpinned, msg);
+ msg.sendToTarget();
+ }
+
/**
* Notifies all listeners when an attempt was made to start an an activity that is already
* running in the pinned stack and the activity was not actually started, but the task is