Android Tutorial Getting Started With a Homescreen Launcher

Hey guys!

This forum and Basic4Android has helped my career prospects greatly, so I thought I'd try and do something useful...

As some of you know, I've been working on an Android Launcher or two and I've learned a lot in the process. Launchers are a great type of app to release in the play store - customisation fanatics love them and they can sell for some pretty high prices! However they're also rather complicated and there's not much information on them. I think most of the launchers on the Play Store are just modified examples of the sample launcher Google gives away with the Android SDK.

So I've had to learn a lot of stuff to get mine working and I thought I'd share all that here in one place. Feel free to add useful code snippets and tips so that future launcher-coders have a good starting point!


Manifest Changes

First of all you're going to need to make some changes to your manifest file. The most important is this:

B4X:
AddActivityText(main, <intent-filter >
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.HOME" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>)

Which is what tells Android that your app is a homescreen giving the users the option to set it as the page that loads when they hit the home key. Any app can become a launcher this way - you could make a game and set it as the launcher for instance, but it would be pretty impractical! That does mean you can get a lot more creative with your launcher though than most people seem to realise (all launchers look the same!).

Next you'll probably want to add these permissions:

B4X:
AddPermission("android.permission.SET_WALLPAPER")
AddPermission("android.permission.SET_WALLPAPER_HINTS")

This will allow your launcher to change the wallpaper (set_wallpaper) and also to communicate with the wallpaper regarding the number of homescreens etc. If you're only going to use the intent to let your user choose their wallpaper though (rather than setting it on the first load and tricks like that) then you won't need 'SET_WALLPAPER' as it won't actually be your app that's setting the wallpaper.

Another addition you want to make is this one:

B4X:
SetActivityAttribute(main, android:theme, @android:style/Theme.Wallpaper)

This sets the phone's wallpaper as the background for your activity. You don't want to just find the image and set that because it won't support Live Wallpapers and it won't be consistent with other apps necessarily.


The Basic Framework

Now your app behaves basically like a launcher, but it doesn't look much like one and doesn't do anything. To start with then you'll probably want to make some different homescreens that can be swiped through.

For that I personally used the AHViewPager library (http://www.b4x.com/android/forum/threads/ahviewpager-library-sliding-panels-now-perfect.14165/) from Corwin42. This library and the example make it very easy to swipe through screens with different layouts, and even to add/remove pages. If you save when people add or remove screens in a file in your internal folder, then you can allow recreate that number of screens when the app loads again. I got rid of the tabs along the top, but that is of course up to you!

AHViewPager is but one way to accomplish this though. Alternatively you could always just use sliding panels and add in the rest manually (http://www.b4x.com/android/forum/th...e-way-to-create-sliding-layouts.8233/#content).


Wallpaper Extras

B4X:
SetActivityAttribute(main, android:theme, @android:style/Theme.Wallpaper)

With the line above in your manifest (covered earlier) you now have an application that has your phone's wallpaper as the background and that probably looks pretty nice and will even respond to touch. However when you slide panels you'll find that your wallpapers don't scroll, which is an issue if you want to use things like Multi-Picture Live Wallpaper.

What you need to do then is to communicate with the wallpaper how large your homescreen is, what page your users are on and etc.

To this end you'll need to use the reflection library and do a bit of Java. So do this when you create your activity:

B4X:
Sub Activity_Create(FirstTime AsBoolean)

Dim r AsReflector
 r.Target = r.RunStaticMethod("android.app.WallpaperManager", "getInstance", _ArrayAsObject(r.GetContext), ArrayAsString("android.content.Context"))
 
  r.RunMethod4("suggestDesiredDimensions", ArrayAsObject(Activity.width * 2, Activity.height), _ArrayAsString("java.lang.int", "java.lang.int"))
Dim pagesx As Float

Dim pagesy As Float
pagesx = 1.00 / 5
pagesy = 1.00
r.RunMethod4("setWallpaperOffsetSteps", ArrayAsObject(pagesx, pagesy), _ArrayAsString("java.lang.float", "java.lang.float"))
End Sub

This tells the wallpaper two things: the first is the width and height you want for the wallpaper. I've chosen activity.width * 2 as my width because that means we can scroll along the image each time the homescreen changes when a user has set a single picture as the background. Going too high (I tried activity.width * 5) will cause the image to look horrible or even just fail to load. Plus you might get motion sickness...

The next part 'setWallpaperOffsetSteps' tells the wallpaper how many screens there are. The format of this number is a little weird though as you don't state how many screens you want, but rather how many times you want the screen to move as a fraction (and must be a float). For instance '0.50' would mean three screens, those being '0', '0.5' and '1.0'). I found that the easiest way to do this was simply to divide 1.00 by the number of screens. I've used '5' here because my launcher currently doesn't allow the user to add or remove screens. However if your screens are likely to change then you'll want to replace that with a INT that you save internally somehow.

Now when you want your screen to change you'll add this:

B4X:
Dim r AsReflector
 r.Target = r.RunStaticMethod("android.app.WallpaperManager", "getInstance", _ArrayAsObject(r.GetContext), ArrayAsString("android.content.Context"))

Dim o AsReflector
 o.Target = pager
 r.RunMethod4("setWallpaperOffsets", ArrayAsObject(o.RunMethod("getWindowToken"), x, y), _ArrayAsString("android.os.IBinder", "java.lang.float", "java.lang.float"))

This tells the wallpaper to move by 'x', which should be the same value as your number of offsets. x = x + 1.00 / 5 (or minus) in my case. This creates a sudden 'jump' to the next frame though, so to add a smooth transition you might want to trigger smaller changes and put it in your timer_tick sub. That way the page will be registered as having changed, and the wallpaper will then smoothly scroll to the next bit.


Hosting Widgets

Something else that users expect with homescreen launchers is that they should be able to host widgets. This is nice and easy now thanks to XVerhelstX's wonderful RSSmartWidgets library found here: http://www.b4x.com/android/forum/threads/rssmartwidgets-host-widgets-in-app.32807/

To use it, all you need is to use this line in your click event:

B4X:
CreateSmartWidget (1)

And then add these two subs:

B4X:
Sub CreateSmartWidget(id As Int)
    smartwidgets.Initialize("SmartWidgets", id)
    smartwidgets.SelectWidget
End Sub

Sub SmartWidgets_WidgetCreated (HostId As Int, HostView As Object, WidgetId As Int, widgetinfo As Object)

If widginform.IsInitialized Then 
widginform.RemoveView
smartwidgets.RemoveWidget(widginform)
End If

widginform.Initialize("widginform")
widginform = smartwidgets.HostView

smartwidgets.AddView(widg1, widginform, 0, 0)
widginform.BringToFront
smartwidgets.StartListening

End Sub

This automatically shows a nicely designed widget picker then adds the view. You might need to do a little more reading, but this will show you the basics.

What I found a little trickier here was getting it to remember when a widget has been set and then load it next time. In the end I used this code in my Activity_Create:

B4X:
If File.Exists(File.DirInternal, "theappid.txt") Then
  smartwidgets.Initialize("SmartWidgets", 1)
  smartwidgets.AppToStart = File.ReadString(File.DirInternal, "theappid.txt")
  smartwidgets.getAppWidgetInfo(File.ReadString(File.DirInternal, "theappid.txt")))
  smartwidgets.HostId = File.ReadString(File.DirInternal, "theid.txt")
  widginform.Initialize("widginform")
  smartwidgets.CreateSmartWidget(File.ReadString(File.DirInternal, "theappid.txt"))
  widginform = smartwidgets.HostView

smartwidgets.AddView(widg1, widginform, 0, 0)
widginform.BringToFront
smartwidgets.StartListening
widg2.Visible = False
widg2.Enabled = False
End If

And added this code to my 'WidgetCreated' sub:

B4X:
File.WriteString(File.DirInternal, "thehost.txt", HostView)
File.writestring(File.DirInternal, "theid.txt", HostId)
File.WriteString(File.DirInternal, "theappid.txt", smartwidgets.AppWidgetId)

This also remembers settings - so if you chose the 'primary inbox' to show on your Gmail app for instance, it won't ask again each time you load the app.


Applications

Most important of all you'll want to let your users navigate to other apps and probably to manage them a little. You can do all this with the package manager and intents.

This simple bit of code will get you a list of all apps.

B4X:
Dim PM As PackageManager
Dim applist As List
applist.initialize
applist = packman1.GetInstalledPackages

That will give you the package names, which you can then launch with an intent like so:

B4X:
Try
Dim pm As PackageManager
Dim il As Intent
il = pm.GetApplicationIntent (browsericon.tag)
StartActivity (il)
Catch
End Try

Here I set the package name as the tag on a button, but you can do whatever you like really!

Additionally you can get the application label (the name) and the icon like so:

B4X:
packman1.GetApplicationLabel(thisoneapp)
packman1.GetApplicationIcon(browsericon.tag)

You'll probably want to sort your list alphabetically by the application label though and additionally you'll want to get rid of system apps and widgets that can't be launched that way and that will clutter your app drawer. To do that you can use this:

B4X:
If packman1.GetApplicationIntent("com.android.vending").IsInitialized Then...

This will test whether the app is a 'regular' app that can be launched and if so you can then show it in your app drawer. If the app has been uninstalled, or if it isn't for normal use, then it will return false.

Creating a drawer with icons that takes all this into account is SLOW. So the best thing to do is to put it in a timer1 and have it build the drawer in the background each time ACTIVITY_CREATE is called. This looks nice and professional too as all the apps pop into existence satisfyingly. Quick loading is crucial for your app - you don't want your users to be greeted with a blank screen for two minutes whenever they rotate their phone/hit home.

You may also want to let users uninstall apps right from your launcher. You can do that like so:

B4X:
If check2.checked = True Then 
Dim pm As PackageManager

Dim il As Intent
il = pm.GetApplicationIntent (packagename)

StartActivity(il)
End If

    If check1.checked = True Then 
Dim im As Intent

im.Initialize("android.intent.action.DELETE", "package:" & packagename)

StartActivity(im)
End If 

'you should probably put a try and catch in here...


If they do that, then you'll need to spot it and of course refresh your apps list. That's why you use PhoneEvents and then add:

B4X:
Sub PE_PackageAdded(Package As String, Intent As Intent)
ToastMessageShow(Package & " added!", False)
refreshlistsub
End Sub

Sub PE_PackageRemoved(Package As String, Intent As Intent)
ToastMessageShow(Package & " removed!", False)
refreshlistsub
End Sub



And Finally!

So with all that you should be able to create a pretty functional launcher! Amazing what Basic4Android can do :)

When creating your launcher though, make sure that you remember this is going to replace the *default* look and interface of the users' phones. In other words, this thing needs to be practical to use, it needs to work flawlessly and it needs to look great. It's a very ambitious undertaking and you really can't cut corners on this one. If it crashes then you'll have a lot of angry customers, and if it's slow for users to find their contacts/phone then it just won't be practical.

I hope this proves helpful for someone, thanks again to everyone who helped me learn all this! Feel free to ask any questions and I'll do my best to help, or to add more tips of your own of course. Happy coding!
 

JohnC

Expert
Licensed User
Longtime User
Thank you for this very helpful info!
 

rbsoft

Active Member
Licensed User
Longtime User
Nice tutorial - thank you.

Rolf
 

NeoTechni

Well-Known Member
Licensed User
Longtime User
widg1, widginform and widg2 aren't declared anywhere.
And I second the request for an example, please.
 

NeoTechni

Well-Known Member
Licensed User
Longtime User
I managed to get widget creation working, but it can't handle activity pause/resume, or loading it from the file.
 
Top