I'm John Baligod, an Android app engineer at LY Corporation.
This article presents the unique way of implementing the minimized browser in the Android version of the LINE app.
Minimized browser
The LINE app has its own internal browsers powered by Google Chrome WebView. Browsers in the LINE app allow users to multitask by shrinking the currently displayed webpage into a small floating view. The minimized view is displayed with a favicon, an icon or logo of the company or entity managing the webpage, to easily identify which webpage is currently minimized. Users can move the screen around while it's kept above other screens in the LINE app. By minimizing the browser, users can continue using other LINE features without losing progress done on the minimized webpage. If the user wishes to reopen the minimized webpage, all they need to do is to tap the minimized view.
Differences with Android Custom Tabs (ACT)
Recently, ACT also introduced its own minimized feature. LINE's minimized browser and that of ACT have basically the same functions.
The main difference is that we only display the minimized view when the LINE app is on the foreground while minimized ACT is displayed on-screen all the time.
Available APIs for displaying minimized view
There were a few APIs we tried for displaying minimized view on Android:
- Android Chat Bubbles
- Android picture-in-picture (PiP)
- Android WindowManager
Android Chat Bubbles
We briefly considered using this API because it can display a minimized view. However, this API is mainly used to keep chats active. It's tightly integrated with Notification API and requires a target Person to activate the minimized function. Also, minimized view has an accompanying dot icon that cannot be removed.
Android PiP
Android PiP is a special multi-window mode that displays the target Activity in a small, separate floating window. This API provides almost all of the functions we need for displaying and managing the minimized view.
It automatically minimizes the target Activity into a minimized view and also restores it back to fullscreen.
This API recommends the target Activity to be in a singleTask
launch mode so an underlying process can still send actions to it. This is fine with the LINE app internal browsers as they are already using singleTask
launch mode.
However, we met some major issues regarding Activity stack management. The LINE app uses a multi-Activity architecture; each Activity is managed by other teams. An Activity managed by other teams can launch an Activity managed by another team. This means each Activity must play nice with other Activities. When we initially implemented minimized browser with Android PiP we found out that if an Activity that launched the minimized browser launches another Activity, the new Activity is opened in the minimized view!
Other issues such as limitation on the shape and size of the minimize view and not being able to set initial location of the minimize view made us decide that this API is not viable for our use case.
Android WindowManager
This API allows you to display a floating view on top of the screen... That's it. Management of the floating view has to be implemented separately. This means we need to implement dragging or flinging the minimized view across the screen by ourselves. Also, unlike Android PiP, we need to implement minimizing the Activity and restoring it back to fullscreen. By using this API, we needed more resources to develop and maintain the minimized browser feature but the upsides far outweighed the downsides:
- We will have more control on the displayed minimized view.
- We will have less concerns about breaking changes in case there are changes to the API.
Implementing the minimized browser on Android
A minimized view has 3 components:
- Floating favicon or logo
This is the main minimized view that displays the favicon or logo of the webpage. - Floating remove icon
This is a remove icon located at the bottom center of the screen. It's only displayed when a user starts dragging the minimized view. If the user drops the minimized view on top of the remove icon, the minimized view is removed and cannot be restored anymore. - Invisible screen observer
This keeps track of the screen changes. It's mainly used to hide the minimized view when another Activity shifts to fullscreen mode. This allows the user to not get distracted by the minimized view while on fullscreen mode (for example, while watching a video in fullscreen). When fullscreen mode is toggled off, the screen observer will notify the app to show the minimized view again.
Minimizing or restoring an Activity
Android doesn't provide APIs to hide and restore an Activity. There is an API to move a whole Activity stack to the background and then back to the foreground but unfortunately, the LINE app's browsers are in the same stack as the main LINE app screen. We may move the LINE app's browsers to a separate Activity stack but then we will have the same issues as with using Android PiP.
Novel solution: destroying the Activity when the browser is minimized and then creating a new Activity when it's restored. The new Activity is launched by the same Intent data used to launch the previous Activity.
This seems risky; we may be able to save and restore non-WebView related states but how about WebView states? Android WebView provides an API to save and restore its state but not all of its states can be restored. Data such as user input on forms and session storage aren't restorable.
App-scoped WebView
To consistently keep all WebView states across Activity instances, we created an app-scoped WebView. This WebView is instantiated with an Application context and has the same lifecycle as that of the application instead of Activity or Fragment. However, WebView with Application context has one issue: it cannot know its host Activity theme, so the styles of the Activity is not passed to the WebView. Fortunately, we can use MutableContextWrapper to switch between Application context and Activity context even after the WebView object is created.
When we bind the app-scoped WebView to an Activity, we set the Activity context to the MutableContextWrapper. On the other hand, when we unbind the app-scoped WebView from an Activity, we set the Application context to the MutableContextWrapper.
Adopting app-scoped WebView means we need to refactor current implementation of our browsers. We need to consolidate access to WebView instances so we could have a single source of truth. In this way, it will be very easy to replace the layout-embedded WebView with app-scoped WebView.
Summary
Implementing the minimized browser feature on Android wasn't straightforward. We encountered several issues along the way, but in the end were able to create a novel solution that fits all the unique requirements of the minimized browser for the LINE app on Android.
References (external sites)
- LINE Front-end Framework (LIFF) | LINE Developers
- Overview of Android Custom Tabs | Web on Android | Chrome for Developers
- Use bubbles to let users participate in conversations | Views | Android Developers
- Add videos using picture-in-picture (PiP) | Views | Android Developers
- WindowManager | Android Developers