Create a shortcut to open bubbles
Allow launching bubbles via a shortcut that is pinned to the home
screen.
When there are no bubbles open, open the bubbles overflow view.
When there are bubbles, open and select the first bubble.
Create an activity in wmshell to handle creating the shortcut and
handling clicking on the shortcut.
Flag: com.android.wm.shell.enable_retrievable_bubbles
Bug: 340337839
Test: manual
Change-Id: If5e2941f265945f78c73fa163f306f1d9bc4d274
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index 7a98683..bcb1d29 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -30,5 +30,29 @@
android:excludeFromRecents="true"
android:launchMode="singleInstance"
android:theme="@style/DesktopWallpaperTheme" />
+
+ <activity
+ android:name=".bubbles.shortcut.CreateBubbleShortcutActivity"
+ android:exported="true"
+ android:excludeFromRecents="true"
+ android:theme="@android:style/Theme.NoDisplay"
+ android:label="Bubbles"
+ android:icon="@drawable/ic_bubbles_shortcut_widget">
+ <intent-filter>
+ <action android:name="android.intent.action.CREATE_SHORTCUT" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <activity
+ android:name=".bubbles.shortcut.ShowBubblesActivity"
+ android:exported="true"
+ android:excludeFromRecents="true"
+ android:theme="@android:style/Theme.NoDisplay" >
+ <intent-filter>
+ <action android:name="com.android.wm.shell.bubbles.action.SHOW_BUBBLES"/>
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/libs/WindowManager/Shell/res/drawable/ic_bubbles_shortcut_widget.xml b/libs/WindowManager/Shell/res/drawable/ic_bubbles_shortcut_widget.xml
new file mode 100644
index 0000000..b208f2f
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/ic_bubbles_shortcut_widget.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 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.
+ -->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_bubbles_shortcut_widget_background" />
+ <foreground android:drawable="@drawable/ic_bubbles_shortcut_widget_foreground" />
+</adaptive-icon>
diff --git a/libs/WindowManager/Shell/res/drawable/ic_bubbles_shortcut_widget_background.xml b/libs/WindowManager/Shell/res/drawable/ic_bubbles_shortcut_widget_background.xml
new file mode 100644
index 0000000..510221f
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/ic_bubbles_shortcut_widget_background.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 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="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path
+ android:pathData="M0,0h108v108h-108z"
+ android:fillColor="#FFC20C"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/ic_bubbles_shortcut_widget_foreground.xml b/libs/WindowManager/Shell/res/drawable/ic_bubbles_shortcut_widget_foreground.xml
new file mode 100644
index 0000000..a41b6a9
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/ic_bubbles_shortcut_widget_foreground.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 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="108dp"
+ android:height="108dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="@android:color/white">
+ <group android:scaleX="0.58"
+ android:scaleY="0.58"
+ android:translateX="5.04"
+ android:translateY="5.04">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M7.2,14.4m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M14.8,18m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M15.2,8.8m-4.8,0a4.8,4.8 0,1 1,9.6 0a4.8,4.8 0,1 1,-9.6 0"/>
+ </group>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index bf654d9..4784674 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -182,6 +182,12 @@
<!-- Content description to tell the user a bubble has been dismissed. -->
<string name="accessibility_bubble_dismissed">Bubble dismissed.</string>
+ <!-- Label used to for bubbles shortcut [CHAR_LIMIT=10] -->
+ <string name="bubble_shortcut_label">Bubbles</string>
+
+ <!-- Longer label used to for bubbles shortcut, shown if there is enough space [CHAR_LIMIT=25] -->
+ <string name="bubble_shortcut_long_label">Show Bubbles</string>
+
<!-- Description of the restart button in the hint of size compatibility mode. [CHAR LIMIT=NONE] -->
<string name="restart_button_description">Tap to restart this app for a better view</string>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index d9055fb..a86164a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -86,6 +86,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.util.CollectionUtils;
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.Flags;
import com.android.wm.shell.R;
@@ -93,6 +94,7 @@
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
import com.android.wm.shell.bubbles.properties.BubbleProperties;
+import com.android.wm.shell.bubbles.shortcut.BubbleShortcutHelper;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.FloatingContentCoordinator;
@@ -511,6 +513,10 @@
}
mCurrentProfiles = userProfiles;
+ if (Flags.enableRetrievableBubbles()) {
+ registerShortcutBroadcastReceiver();
+ }
+
mShellController.addConfigurationChangeListener(this);
mShellController.addExternalInterface(KEY_EXTRA_SHELL_BUBBLES,
this::createExternalInterface, this);
@@ -986,6 +992,25 @@
}
};
+ private void registerShortcutBroadcastReceiver() {
+ IntentFilter shortcutFilter = new IntentFilter();
+ shortcutFilter.addAction(BubbleShortcutHelper.ACTION_SHOW_BUBBLES);
+ ProtoLog.d(WM_SHELL_BUBBLES, "register broadcast receive for bubbles shortcut");
+ mContext.registerReceiver(mShortcutBroadcastReceiver, shortcutFilter,
+ Context.RECEIVER_NOT_EXPORTED);
+ }
+
+ private final BroadcastReceiver mShortcutBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ ProtoLog.v(WM_SHELL_BUBBLES, "receive broadcast to show bubbles %s",
+ intent.getAction());
+ if (BubbleShortcutHelper.ACTION_SHOW_BUBBLES.equals(intent.getAction())) {
+ mMainExecutor.execute(() -> showBubblesFromShortcut());
+ }
+ }
+ };
+
/**
* Called by the BubbleStackView and whenever all bubbles have animated out, and none have been
* added in the meantime.
@@ -2221,6 +2246,34 @@
}
/**
+ * Show bubbles UI when triggered via shortcut.
+ *
+ * <p>When there are bubbles visible, expands the top-most bubble. When there are no bubbles
+ * visible, opens the bubbles overflow UI.
+ */
+ public void showBubblesFromShortcut() {
+ if (isStackExpanded()) {
+ ProtoLog.v(WM_SHELL_BUBBLES, "showBubblesFromShortcut: stack visible, skip");
+ return;
+ }
+ if (mBubbleData.getSelectedBubble() != null) {
+ ProtoLog.v(WM_SHELL_BUBBLES, "showBubblesFromShortcut: open selected bubble");
+ expandStackWithSelectedBubble();
+ return;
+ }
+ BubbleViewProvider bubbleToSelect = CollectionUtils.firstOrNull(mBubbleData.getBubbles());
+ if (bubbleToSelect == null) {
+ ProtoLog.v(WM_SHELL_BUBBLES, "showBubblesFromShortcut: no bubbles");
+ // make sure overflow bubbles are loaded
+ loadOverflowBubblesFromDisk();
+ bubbleToSelect = mBubbleData.getOverflow();
+ }
+ ProtoLog.v(WM_SHELL_BUBBLES, "showBubblesFromShortcut: select and open %s",
+ bubbleToSelect.getKey());
+ mBubbleData.setSelectedBubbleAndExpandStack(bubbleToSelect);
+ }
+
+ /**
* Description of current bubble state.
*/
private void dump(PrintWriter pw, String prefix) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 874102c..5f913f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -912,6 +912,9 @@
((Bubble) bubble).markAsAccessedAt(mTimeSource.currentTimeMillis());
}
mSelectedBubble = bubble;
+ if (isOverflow) {
+ mShowingOverflow = true;
+ }
mStateChange.selectedBubble = bubble;
mStateChange.selectionChanged = true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/BubbleShortcutHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/BubbleShortcutHelper.kt
new file mode 100644
index 0000000..efa1238
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/BubbleShortcutHelper.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 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.wm.shell.bubbles.shortcut
+
+import android.content.Context
+import android.content.pm.ShortcutInfo
+import android.graphics.drawable.Icon
+import com.android.wm.shell.R
+
+/** Helper class for creating a shortcut to open bubbles */
+object BubbleShortcutHelper {
+ const val SHORTCUT_ID = "bubbles_shortcut_id"
+ const val ACTION_SHOW_BUBBLES = "com.android.wm.shell.bubbles.action.SHOW_BUBBLES"
+
+ /** Create a shortcut that launches [ShowBubblesActivity] */
+ fun createShortcut(context: Context, icon: Icon): ShortcutInfo {
+ return ShortcutInfo.Builder(context, SHORTCUT_ID)
+ .setIntent(ShowBubblesActivity.createIntent(context))
+ .setActivity(ShowBubblesActivity.createComponent(context))
+ .setShortLabel(context.getString(R.string.bubble_shortcut_label))
+ .setLongLabel(context.getString(R.string.bubble_shortcut_long_label))
+ .setLongLived(true)
+ .setIcon(icon)
+ .build()
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/CreateBubbleShortcutActivity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/CreateBubbleShortcutActivity.kt
new file mode 100644
index 0000000..a124f95
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/CreateBubbleShortcutActivity.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 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.wm.shell.bubbles.shortcut
+
+import android.app.Activity
+import android.content.pm.ShortcutManager
+import android.graphics.drawable.Icon
+import android.os.Bundle
+import com.android.wm.shell.Flags
+import com.android.wm.shell.R
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES
+import com.android.wm.shell.util.KtProtoLog
+
+/** Activity to create a shortcut to open bubbles */
+class CreateBubbleShortcutActivity : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if (Flags.enableRetrievableBubbles()) {
+ KtProtoLog.d(WM_SHELL_BUBBLES, "Creating a shortcut for bubbles")
+ createShortcut()
+ }
+ finish()
+ }
+
+ private fun createShortcut() {
+ val icon = Icon.createWithResource(this, R.drawable.ic_bubbles_shortcut_widget)
+ // TODO(b/340337839): shortcut shows the sysui icon
+ val shortcutInfo = BubbleShortcutHelper.createShortcut(this, icon)
+ val shortcutManager = getSystemService(ShortcutManager::class.java)
+ val shortcutIntent = shortcutManager?.createShortcutResultIntent(shortcutInfo)
+ if (shortcutIntent != null) {
+ setResult(RESULT_OK, shortcutIntent)
+ } else {
+ setResult(RESULT_CANCELED)
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/ShowBubblesActivity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/ShowBubblesActivity.kt
new file mode 100644
index 0000000..ae7940c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/ShowBubblesActivity.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 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.wm.shell.bubbles.shortcut
+
+import android.app.Activity
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import com.android.wm.shell.Flags
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES
+import com.android.wm.shell.util.KtProtoLog
+
+/** Activity that sends a broadcast to open bubbles */
+class ShowBubblesActivity : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if (Flags.enableRetrievableBubbles()) {
+ val intent =
+ Intent().apply {
+ action = BubbleShortcutHelper.ACTION_SHOW_BUBBLES
+ // Set the package as the receiver is not exported
+ `package` = packageName
+ }
+ KtProtoLog.v(WM_SHELL_BUBBLES, "Sending broadcast to show bubbles")
+ sendBroadcast(intent)
+ }
+ finish()
+ }
+
+ companion object {
+ /** Create intent to launch this activity */
+ fun createIntent(context: Context): Intent {
+ return Intent(context, ShowBubblesActivity::class.java).apply {
+ action = BubbleShortcutHelper.ACTION_SHOW_BUBBLES
+ }
+ }
+
+ /** Create component for this activity */
+ fun createComponent(context: Context): ComponentName {
+ return ComponentName(context, ShowBubblesActivity::class.java)
+ }
+ }
+}