Android’s New Launcher Paradigm

Dave Smith
Dave Smith
Android’s New Launcher Paradigm

The ability to create a new Home/Launcher application for your Android device is not a new concept. There’s even a sample application in the SDK that shows you exactly what needs to be implemented in a 3rd party application that wishes to act as the Home/Launcher of the device.

The basic recipe is as follows:

  1. Provide an exported Activity in the AndroidManifest.xml that filters for an Intent with android.intent.action.MAIN and android.intent.category.HOME
  2. Use a launch mode like singleInstance or singleTask, so multiple Home button presses get delivered to your existing Activity, rather than making new ones each time.

Until now, we could expect that the Home/Launcher application on the Android images for the Nexus devices would be the open sourced Launcher package in AOSP ( Launcher, Launcher2, or Launcher3 depending on Android version). But, when the Nexus 5 device shipped recently (and subsequently factory images for the rest of the Nexus line to get Android 4.4), all of that changed.

Suddenly, people realized that the Home application they were seeing on the Nexus 5 wasn’t the same on other Nexus devices...and it certainly wasn’t the app from AOSP. This led those of us interested in finding out more on a bit of a goose chase to track down what Google was doing in the latest release.

Note: The discussions in this section do not require reverse engineering or decompiling Google’s closed source APKs. Everything can be obtained from the publicly available definitions in these application’s manifest files, which can easily be dumped out from any APK using the aapt tool that is included with the SDK. More specifically, aapt dump xmltree <APK PATH> AndroidManifest.xml

The New Launcher App

The first trick is finding which application on the device is now acting as the Home application.

As platform developers started pouring through /system/app on the Nexus 5, (the go-to location for system applications on the device) they discovered "GoogleHome.apk", which seemed promising given its name. However, a simple inspection of this application’s manifest indicates that it only has one Activity inside, and it does not have the HOME Intent filter we described above, that is necessary for Android to trigger an application as the Launcher. In fact, the Activity looks to be a stub with little functionality included.

Someone with enough time on their hands to go through and dump the manifest files of each APK on the device would find that a file name "Velvet.apk" located in /system/priv-app (a new directory in Android 4.4 for system applications) does in fact include an Activity in its manifest, named com.google.android.launcher.GEL, that filters for HOME. A closer inspection of the other published Activity elements in this application indicate fairly quickly that it is the official Google Search application, which is also available in Google Play.

In Android 4.4, if you have more than one Launcher application installed (e.g. a 3rd party application downloaded from Google Play in addition to the bundled Home application on the device), you can control which application is the default/preferred Home application (it’s not visible unless you have more than one installed Launcher application, so don’t go looking for it unless you do).

On a Nexus 5, the "Launcher" application represented by GoogleHome is visible in this list, NOT the "Google Search" application represented by Velvet, which has the HOME filter. In addition, devices like the Nexus 4 or Nexus 7, that have BOTH Launcher2 and Velvet, never provide the option to set the new Google Search application as Home (why these devices have Launcher2 instead of Launcher3 on Android 4.4 is beyond me).

So what gives? How is Google bypassing this filter for their own apps and hiding it in certain situations?

If we look again at the manifest for Velvet, the GEL Activity is actually disabled by default (the android:enabled attribute is set to a boolean resource that is false). That explains half of the issue; a disabled Activity will not show up in filter queries, so even with Velvet installed, and Home Activity component is not active. Then, the question becomes "how do they turn it on?"

A Hidden Trigger

A combination of the manifest entries for Velvet and some logging provides the answer, and it reveals an interesting pattern that platform developers could leverage as well. When GoogleHome.apk is installed onto a device (e.g. a Nexus 7) that has the updated Search application, the following will spit out logcat (emphasis added):

V/GelStubAppWatcher﹕ onReceive: android.intent.action.PACKAGE_ADDED
V/GelStubAppWatcher﹕ stub is enabled
V/GelStubAppWatcher﹕ setting GEL component (ComponentInfo{com.google.android.googlequicksearchbox/com.android.launcher3.LauncherProvider}) enabled
V/GelStubAppWatcher﹕ setting GEL component (ComponentInfo{com.google.android.googlequicksearchbox/com.android.launcher3.PreloadReceiver}) enabled
V/GelStubAppWatcher﹕ setting GEL component (ComponentInfo{com.google.android.googlequicksearchbox/com.android.launcher3.InstallShortcutReceiver}) enabled
V/GelStubAppWatcher﹕ setting GEL component (ComponentInfo{com.google.android.googlequicksearchbox/com.android.launcher3.UninstallShortcutReceiver}) enabled
V/GelStubAppWatcher﹕ setting GEL component (ComponentInfo{com.google.android.googlequicksearchbox/com.android.launcher3.UserInitializeReceiver}) enabled
V/GelStubAppWatcher﹕ setting GEL component (ComponentInfo{com.google.android.googlequicksearchbox/com.android.launcher3.PackageChangedReceiver}) enabled
V/GelStubAppWatcher﹕ setting GEL component (ComponentInfo{com.google.android.googlequicksearchbox/com.android.launcher3.WallpaperCropActivity}) enabled
V/GelStubAppWatcher﹕ setting GEL component (ComponentInfo{com.google.android.googlequicksearchbox/com.google.android.launcher.GelWallpaperPickerActivity}) enabled
V/GelStubAppWatcher﹕ setting GEL component (ComponentInfo{com.google.android.googlequicksearchbox/com.google.android.launcher.MarketUpdateReceiver}) enabled
V/GelStubAppWatcher﹕ setting GEL component (ComponentInfo{com.google.android.googlequicksearchbox/com.google.android.velvet.tg.SetupWizardOptInIntroActivity}) enabled
V/GelStubAppWatcher﹕ setting GEL component (ComponentInfo{com.google.android.googlequicksearchbox/com.google.android.launcher.GEL}) enabled

Back in the Velvet manifest, we can find a receiver element named com.google.android.googlequicksearchbox.GelStubAppWatcher that is registered for events when packages are added/removed/changed. Therefore, we have a search application that lies in wait until GoogleHome.apk is found or installed. When that event occurs, the GEL Activity is enabled and becomes a viable target as the Home application of the device. If GoogleHome is then later removed, the search application disables this element again.

Okay, but then how did they get the Google Home Launcher to show up in the new Default Home Settings? Shouldn’t it be Google Search?

Home App Proxies

To answer this question, we take a quick detour to some of the framework changes made in Android 4.4, specifically a new manifest meta-data constant called META_HOME_ALTERNATE, added for API Level 19. As the documentation states:

name for a ‘home’ Activity that declares a package that is to be uninstalled in lieu of the declaring one.

Adding this metadata to an Activity allows that application to proxy what application should be displayed as the Home application to display in Home Settings as the option to select or uninstall. The Velvet Home Activity has this metadata element defined... and it points to the package of Google Home.

A quick search through the AOSP sources indicates two places in the system Settings application where this metadata is observed. In packages/apps/Settings/src/com/android/settings/applications/InstalledAppDetails.java (which is the screen where the Force Stop, Uninstall, and Disable buttons are for an application), the value is used to determine whether the current application is a Home app or the proxy of a Home app. Apps in this category can’t be disabled, so the user can’t accidentally kill the only Launcher.

Additionally, packages/apps/Settings/src/com/android/settings/HomeSettings.java (which is the new Default Home Application option) uses this element to determine if a Home application declares a proxy, and overrides the values it uses for these settings with that package. This is how the Google Search application shows Google Home instead as a target in Settings.

Proxy Example

The following is an example of an application manifest that would use this same technique to define a Launcher application but hide it until the time is right.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yetanotherlauncher.app"
    android:versionCode="1"
    android:versionName="1.0">

    <uses-sdk android:minSdkVersion="19"
        android:targetSdkVersion="19" />

    <application>

        <activity
            android:name=".LauncherActivity"
            android:enabled="false"
            android:launchMode="singleInstance">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.HOME" />
            </intent-filter>
            <meta-data android:name="android.app.home.alternate"
                android:value="ANOTHER_APP_PACKAGE" />
        </activity>

        <receiver android:name=".StubReceiver">
            <intent-filter>
                <action android:name="android.intent.action.PACKAGE_ADDED" />
                <action android:name="android.intent.action.PACKAGE_CHANGED" />
                <action android:name="android.intent.action.PACKAGE_REPLACED" />
                <action android:name="android.intent.action.PACKAGE_REMOVED" />
                <data android:scheme="package" />
            </intent-filter>
        </receiver>
    </application>

</manifest>

Here we have defined an Activity that will be our new Home/Launcher interface (defined by adding the appropriate Intent filter), but we also set that component to be disabled via android:enabled="false" which will not allow it to engage with the system. The Activity also includes a meta-data filter for a package name that we will proxy to, and this could be any other app you’d like to install later. This addition indicates that the proxy application will be what the user may uninstall to remove the Home application.

We also include a BroadcastReceiver that listens for package changes, defined below:

public class StubReceiver extends BroadcastReceiver {
    //Expected to be the same package name as defined in the manifest
    private static final String LAUNCHER_PACKAGE = "ANOTHER_APP_PACKAGE";

    public void onReceive(Context context, Intent intent) {
        if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) {
            Uri data = intent.getData();
            String pkgName = data.getEncodedSchemeSpecificPart();
            if (LAUNCHER_PACKAGE.equals(pkgName)) {
                Log.i("MyStubReceiver", "Enabling our Launcher");
                //Enable our launcher component
                ComponentName componentName = new ComponentName(context, LauncherActivity.class);
                context.getPackageManager().setComponentEnabledSetting(componentName,
                        PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
            }
        }

        if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
            Uri data = intent.getData();
            String pkgName = data.getEncodedSchemeSpecificPart();
            if (LAUNCHER_PACKAGE.equals(pkgName)) {
                Log.i("MyStubReceiver", "Disabling our Launcher");
                //Disable our launcher component
                ComponentName componentName = new ComponentName(context, LauncherActivity.class);
                context.getPackageManager().setComponentEnabledSetting(componentName,
                        PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 0);
            }
        }
    }
}

This receiver will monitor for the event when our trigger application is installed, and internally override the enabled setting in the manifest to enable the Activity (Note this doesn’t rewrite the manifest; it overrides it, so the enabled value in activityInfo would still read false). This would allow our Home application to now be selected as the default. If the trigger application were ever uninstalled, we revert the enabled setting back to what it was in the manifest (in this case, false), effectively hiding it again.

Shipping the Launcher

On the surface, these changes may seem a bit odd. Why go through all this trouble and indirection? I certainly have no conclusive idea.

However, my expectation is that, while Google Search is a bundled system application as part of the Google Apps package, Google Home is an application we may likely see on Google Play soon. This mechanism allows Google to distribute the capabilities necessary to run the Launcher as a system application but only activate it on the devices of users who explicitly download the stub application. This might be because it needs system privileges to do certain operations.

More likely, though, since OEMs must include Google Search as part of the package devices needed to run Google Play, this would allow users of any Android device from any major manufacturer to replace the OEM interface with the "Google Experience", using a simple download from the Play store.

In either case, those of use who do platform-level development can look at this as an interesting pattern for bundling core functionality that can be activated using additional installed components.