Còn nhớ khoảng năm 2015 2016 gì đó tôi hay nghe thấy những request về làm một cái “quả bóng tròn” giống Facebook Messenger. Vâng, đúng rồi đấy ạ, chắc hẳn những ai dùng Facebook Messenger trên Android hẳn sẽ rất quen thuộc với 1 icon tròn nổi trên màn hình khi nhận được tin nhắn như hình dưới
Rất nhiều ý kiến đã được đưa ra để làm sao làm được như vậy, trong đó ý kiến phổ biến nhất là tạo ra một floating window nằm đè lên các app khác thông qua việc request SYSTEM_ALERT_WINDOW.
Tuy nhiên đây lại là một ý tưởng không được khuyến khích, theo như document của Android
Very few apps should use this permission; these windows are intended for system-level interaction with the user.
thì permission này vốn được dùng ở tầng hệ thống để tương tác với user.
Bẵng đi một thời gian, cho đến gần đây, thì cuối cùng Google cũng đã đưa tính năng hay ho này vào Android với tên gọi Bubble. Bắt đầu từ Android 10, developer đã có thể vọc thử tính năng này bằng cách enable Settings ▸ Developer Options ▸ Bubbles. Và trên Android 11 thì nó đã chính thức được sử dụng.
Giờ chúng ta hãy cùng thử triển khai xem sao
Trước hết, bạn có thể đọc thêm về Bubble ở đây
https://developer.android.com/guide/topics/ui/bubbles
Đầu tiên bạn cần set targetSdkVersion thành 30 (Android 11) trong build.gradle
Tiếp đó, bạn cần xây dựng 1 activity để khi user bấm vào Bubble, nó sẽ expand ra.
1 2 3 4 5 6 7 8 | <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>activity</span> <span class="token attr-name"><span class="token namespace">android:</span>name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>.bubbles.BubbleActivity<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">android:</span>theme</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@style/AppTheme.NoActionBar<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">android:</span>label</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@string/title_activity_bubble<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">android:</span>allowEmbedded</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">android:</span>resizeableActivity</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> |
Chú ý 2 dòng cuối, như tài liệu đã nói The activity must be resizeable and embedded
Bước tiếp theo, hãy chú ý đến dòng sau
Bubbles are built into the Notification system. They float on top of other app content and follow the user wherever they go. Bubbles can be expanded to reveal app functionality and information, and can be collapsed when not being used.
Như vậy, Bubble được xây dựng với hệ thống Notification, nghĩa là để Bubble “nhảy” ra thì cần phải có một notification được bắn ra. Vì vậy ở bước này, ta sẽ build 1 notification, bắt đầu từ tạo channel
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">createNotificationChannel</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Create the NotificationChannel, but only on API 26+ because</span> <span class="token comment">// the NotificationChannel class is new and not in the support library</span> <span class="token class-name">CharSequence</span> name <span class="token operator">=</span> <span class="token string">"notification name"</span><span class="token punctuation">;</span> <span class="token class-name">String</span> description <span class="token operator">=</span> <span class="token string">"notification description"</span><span class="token punctuation">;</span> <span class="token comment">// The importance must be IMPORTANCE_HIGH to show Bubbles.</span> <span class="token keyword">int</span> importance <span class="token operator">=</span> <span class="token class-name">NotificationManager</span><span class="token punctuation">.</span>IMPORTANCE_HIGH<span class="token punctuation">;</span> <span class="token class-name">NotificationChannel</span> channel <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">NotificationChannel</span><span class="token punctuation">(</span><span class="token string">"1000"</span><span class="token punctuation">,</span> name<span class="token punctuation">,</span> importance<span class="token punctuation">)</span><span class="token punctuation">;</span> channel<span class="token punctuation">.</span><span class="token function">setDescription</span><span class="token punctuation">(</span>description<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Register the channel with the system; you can't change the importance</span> <span class="token comment">// or other notification behaviors after this</span> <span class="token class-name">NotificationManager</span> notificationManager <span class="token operator">=</span> <span class="token function">getContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getSystemService</span><span class="token punctuation">(</span><span class="token class-name">NotificationManager</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span> notificationManager<span class="token punctuation">.</span><span class="token function">createNotificationChannel</span><span class="token punctuation">(</span>channel<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token function">updateShortcuts</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Tuy nhiên, không phải notification nào cũng có thể show Bubble, mà cần phải là notification loại conversation, bạn có thể đọc thêm ở đây https://developer.android.com/guide/topics/ui/conversations#api-notifications
Giờ ta sẽ build shortcut cho notification của chúng ta (lưu ý, đây chỉ là code demo nên id, tên tuổi, icon… chỉ mang tính ước lệ và dùng hard-code, bạn không nên dùng vào code thật)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">updateShortcuts</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">IOException</span> <span class="token punctuation">{</span> <span class="token comment">// Create a dynamic shortcut for each of the contacts.</span> <span class="token comment">// The same shortcut ID will be used when we show a bubble notification.</span> <span class="token class-name">HashSet</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">String</span><span class="token punctuation">></span></span> set <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashSet</span><span class="token generics"><span class="token punctuation"><</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> set<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"com.example.android.bubbles.category.TEXT_SHARE_TARGET"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">ShortcutInfo</span> shortcutInfo <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ShortcutInfo</span><span class="token punctuation">.</span><span class="token class-name">Builder</span><span class="token punctuation">(</span><span class="token function">getContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"1000"</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setLocusId</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">LocusId</span><span class="token punctuation">(</span><span class="token string">"1000"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setActivity</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ComponentName</span><span class="token punctuation">(</span><span class="token function">getContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">MainActivity</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setShortLabel</span><span class="token punctuation">(</span><span class="token string">"contact name"</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setIcon</span><span class="token punctuation">(</span><span class="token class-name">Icon</span><span class="token punctuation">.</span><span class="token function">createWithAdaptiveBitmap</span><span class="token punctuation">(</span><span class="token class-name">BitmapFactory</span><span class="token punctuation">.</span><span class="token function">decodeStream</span><span class="token punctuation">(</span><span class="token function">getContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getResources</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getAssets</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token string">"cat.jpg"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setLongLived</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setCategories</span><span class="token punctuation">(</span>set<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setIntent</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token class-name">Intent</span><span class="token punctuation">(</span><span class="token function">getContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">MainActivity</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setAction</span><span class="token punctuation">(</span><span class="token class-name">Intent</span><span class="token punctuation">.</span>ACTION_VIEW<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setData</span><span class="token punctuation">(</span> <span class="token class-name">Uri</span><span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span> <span class="token string">"https://android.example.com/chat/${contact.id}"</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setPerson</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token class-name">Person</span><span class="token punctuation">.</span><span class="token class-name">Builder</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setName</span><span class="token punctuation">(</span><span class="token string">"contact name"</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setIcon</span><span class="token punctuation">(</span><span class="token class-name">Icon</span><span class="token punctuation">.</span><span class="token function">createWithAdaptiveBitmap</span><span class="token punctuation">(</span><span class="token class-name">BitmapFactory</span><span class="token punctuation">.</span><span class="token function">decodeStream</span><span class="token punctuation">(</span><span class="token function">getContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getResources</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getAssets</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token string">"cat.jpg"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">List</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">ShortcutInfo</span><span class="token punctuation">></span></span> shortcuts <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token generics"><span class="token punctuation"><</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> shortcuts<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>shortcutInfo<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token class-name">ShortcutManager</span><span class="token punctuation">)</span> <span class="token function">getContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getSystemService</span><span class="token punctuation">(</span><span class="token class-name">Context</span><span class="token punctuation">.</span>SHORTCUT_SERVICE<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">addDynamicShortcuts</span><span class="token punctuation">(</span>shortcuts<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> |
Tiếp đến, ta tạo Bubble Intent và metadata để add vào notification
1 2 3 4 5 6 7 8 9 10 11 12 | <span class="token comment">// Create bubble intent</span> <span class="token class-name">Intent</span> target <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Intent</span><span class="token punctuation">(</span><span class="token function">getContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">BubbleActivity</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">PendingIntent</span> bubbleIntent <span class="token operator">=</span> <span class="token class-name">PendingIntent</span><span class="token punctuation">.</span><span class="token function">getActivity</span><span class="token punctuation">(</span><span class="token function">getContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> target<span class="token punctuation">,</span> <span class="token class-name">PendingIntent</span><span class="token punctuation">.</span>FLAG_UPDATE_CURRENT <span class="token comment">/* flags */</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Create bubble metadata</span> <span class="token class-name">Notification</span><span class="token punctuation">.</span><span class="token class-name">BubbleMetadata</span> bubbleData <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Notification</span><span class="token punctuation">.</span><span class="token class-name">BubbleMetadata</span><span class="token punctuation">.</span><span class="token class-name">Builder</span><span class="token punctuation">(</span>bubbleIntent<span class="token punctuation">,</span> <span class="token class-name">Icon</span><span class="token punctuation">.</span><span class="token function">createWithAdaptiveBitmap</span><span class="token punctuation">(</span><span class="token class-name">BitmapFactory</span><span class="token punctuation">.</span><span class="token function">decodeStream</span><span class="token punctuation">(</span><span class="token function">getContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getResources</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getAssets</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token string">"cat.jpg"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setDesiredHeight</span><span class="token punctuation">(</span><span class="token number">600</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
Cuối cùng là tạo notification:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <span class="token comment">// Create notification</span> <span class="token class-name">Person</span> chatPartner <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Person</span><span class="token punctuation">.</span><span class="token class-name">Builder</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setName</span><span class="token punctuation">(</span><span class="token string">"Chat partner"</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setImportant</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">Notification</span><span class="token punctuation">.</span><span class="token class-name">Builder</span> builder <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Notification</span><span class="token punctuation">.</span><span class="token class-name">Builder</span><span class="token punctuation">(</span>mContext<span class="token punctuation">,</span> CHANNEL_ID<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setContentIntent</span><span class="token punctuation">(</span>contentIntent<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setSmallIcon</span><span class="token punctuation">(</span>smallIcon<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setBubbleMetadata</span><span class="token punctuation">(</span>bubbleData<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">addPerson</span><span class="token punctuation">(</span>chatPartner<span class="token punctuation">)</span><span class="token punctuation">;</span> |
Đến đây, bạn có thể bắn thử notifcation
1 2 3 4 5 | <span class="token class-name">NotificationManager</span> notificationManager <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">NotificationManager</span><span class="token punctuation">)</span> <span class="token function">getContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getSystemService</span><span class="token punctuation">(</span><span class="token class-name">Context</span><span class="token punctuation">.</span>NOTIFICATION_SERVICE<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// notificationId is a unique int for each notification that you must define</span> notificationManager<span class="token punctuation">.</span><span class="token function">notify</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">,</span> builder<span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
Tuy nhiên bạn sẽ không thấy Bubble hiện ra? Why? Tôi đã làm đúng theo document rồi mà?
Đó là do code trong document chưa thỏa mãn điều kiện
mà cần phải là notification loại conversation
Ở phần build notification ở trên, hãy thêm vào vài dòng code để thành như sau
1 2 3 4 5 6 7 8 9 10 11 12 | <span class="token class-name">Notification</span><span class="token punctuation">.</span><span class="token class-name">Builder</span> builder <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Notification</span><span class="token punctuation">.</span><span class="token class-name">Builder</span><span class="token punctuation">(</span>mContext<span class="token punctuation">,</span> CHANNEL_ID<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setContentIntent</span><span class="token punctuation">(</span>contentIntent<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setSmallIcon</span><span class="token punctuation">(</span>smallIcon<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setBubbleMetadata</span><span class="token punctuation">(</span>bubbleData<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">addPerson</span><span class="token punctuation">(</span>chatPartner<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setCategory</span><span class="token punctuation">(</span><span class="token class-name">Notification</span><span class="token punctuation">.</span>CATEGORY_MESSAGE<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setShortcutId</span><span class="token punctuation">(</span><span class="token string">"1000"</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setLocusId</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">LocusId</span><span class="token punctuation">(</span><span class="token string">"1000"</span><span class="token punctuation">)</span>https<span class="token operator">:</span><span class="token operator">/</span><span class="token operator">/</span>images<span class="token punctuation">.</span>viblo<span class="token punctuation">.</span>asia<span class="token operator">/</span><span class="token number">64</span>x<span class="token operator">-</span><span class="token operator">/</span><span class="token number">9</span>cd6ae80<span class="token operator">-</span><span class="token number">2</span>ec5<span class="token operator">-</span><span class="token number">4382</span><span class="token operator">-</span><span class="token number">99</span>b4<span class="token operator">-</span><span class="token number">87e8</span>b7ca4b1e<span class="token punctuation">.</span>png<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setStyle</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Notification</span><span class="token punctuation">.</span><span class="token class-name">MessagingStyle</span><span class="token punctuation">(</span>chatPartner<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">setGroupConversation</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
Vậy là xong rồi đấy, giờ hãy thử tính năng mới so với của Facebook xem như nào. Happy coding!!!