Introduction
This walkthrough will cover the creation of a service application and the following topics:
- Service creation
- Using notifications
- Context menus
- Battery status checks
- Persisting user settings
- Creating icons
The project is built using Eclipse and the Android SDK.
Background
I created this app to simulate the ring behavior of the Motorola RAZR which would switch to loud when charging and return to vibrate-only when disconnected (no manual adjustment needed). This app also allows the reverse scenario (silent when connected otherwise ring).
This tutorial assumes you already have the Eclipse environment up and running. If you are new to Eclipse and Android development, I recommend going through the temperature converter tutorial which can be found here.
Using the Code
You can create the project by going through the steps listed below. If you prefer to load the entire project, download\unzip the project file, then open Eclipse and choose File->Import..->General->Existing Projects and choose the root folder of the AutoRing
project.
Let's begin:
Start Eclipse (I'm using Eclipse Classic version 3.6.2).
Choose File -> New -> Project -> Android -> Android Project
Click Next.
Fill in the fields as shown below. You can use any version of Android 1.5 or later.
Click Finish.
Once the project is created, add these icons to the AutoRing\res\drawable folder. You can drag them directly to the folder in Eclipse or you can use Windows Explorer. If you use Explorer, right-click the folder in Eclipse and choose Refresh to see the new files. Be sure to name the files as listed below as the names will be referenced in our project. We will discuss creating your own icons at the end of this walkthrough.
circlefill.png circleopen.png downarrow.png mainicon.png
Once the icons are added, the expanded res\drawable folder should look like this:
Open AndroidManifest.xml.
Click on the last tab in the source editor to view the actual XML.
If you are using Android 1.6 or later, update the sdk version to 4. At version 3, phone and storage permissions will be required to install this application. Android 1.5 requires sdk version 3 (the application won't start otherwise). This value could also have been set in the "New Android Project" dialog but either way works.
<uses-sdk android:minSdkVersion="4" />
Update the application tag to set the application icon and title.
<application android:icon="@drawable/mainicon" android:label="AutoRing">
Add the launchMode
attribute to the activity
tag. Set it to singleInstance
. This will prevent multiple instance of our activity from running. Without this, the user can launch our app from the home screen then another instance from the notification screen (several actually).
<activity android:name=".AutoRingActivity"
android:label="@string/app_name"
android:launchMode="singleInstance">
Add the service tag for the service we will create. Setting exported to false
ensures that no other apps can talk to our service. Also add the receiver tag which is used with our RebootReceiver
class to restart our service when the phone is restarted. If you prefer to restart the service manually, you can leave this part out.
.........
</activity>
<service android:name=".AutoRingSvc" android:exported="false" />
<receiver android:name="RebootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
</application>
........
Open main.xml and click on the second tab to see the actual XML.
Remove the existing XML in main.xml.
Add the tag for our main activity.
="1.0"="utf-8"
<LinearLayout android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/main_view"
android:gravity="center_horizontal"
xmlns:android="http://schemas.android.com/apk/res/android">
Add the tags for the 2 status text boxes.
<TextView
android:id="@+id/txtStatus"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="AutoRing is stopped"
android:textAppearance="?android:attr/textAppearanceMedium"/>
<TextView
android:id="@+id/txtBattStatus"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text=""
android:textAppearance="?android:attr/textAppearanceMedium"/>
Add the tags for the option buttons. The marginTop
attribute will put some space above the button.
<Button
android:layout_marginTop="25sp"
android:id="@+id/btnConnectedOption"
android:text="When Connected:"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="fill_parent"
android:gravity="center_vertical|left"/>
<Button
android:id="@+id/btnDisconnectedOption"
android:text="When Disconnected:"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="fill_parent"
android:gravity="center_vertical|left"/>
Finally, add the tag for the service Start\Stop button.
<Button
android:layout_marginTop="40sp"
android:id="@+id/btnStartStop"
android:text="Start AutoRing"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="wrap_content"/>
</LinearLayout>
This layout should create the following GUI once our application is running (the arrows are added in code):
Next, we will add the service class.
Right Click on the AutoRing
project and choose New->Class.
Enter the Name, Package and Superclass as shown below:
Click Finish.
We need one more class. This will allow our service to restart automatically when the phone is restarted. If you prefer to restart the service manually after a phone restart, you can skip this step and the next coding step.
Coding the RebootReceiver
There are only a few lines of code we need to add to the RebootReceiver
class to restart our service. We read the StartOnReboot
preference (set in AutoRingActivity.onClick
) to determine if the service should be started. There may be several services trying to start at boot time, so we'll wait 30 seconds before starting our service. We'll use a Timer to delay the service start. We check the preferences again in (the unlikely) case the user started\stopped the service within the 30 second delay. Update the onReceive
method as follows:
@Override
public void onReceive(Context context, Intent intent) {
final Context ctxt = context.getApplicationContext();
if (!ctxt.getSharedPreferences("AutoRing", 0).getBoolean("StartOnReboot", false))
return;
(new java.util.Timer()).schedule(
new java.util.TimerTask() {
@Override
public void run()
{
if (ctxt.getSharedPreferences
("AutoRing", 0).getBoolean("StartOnReboot", false))
ctxt.startService(
new Intent(ctxt, AutoRingSvc.class));
}
}, 30000);
}
getApplicationContext()
is required to get the global context for our app. The context passed in to onReceive
is the component context which does not contain the correct preferences. - The
sleep()
methods do not work here. I think the process gets dumped while sleeping. - Adding the
BOOT_COMPLETED
action to AndroidManifest.xml will trigger the RebootReceiver
at bootup even if the app\service was never run (installed only).
Coding the AutoRingActivity Class
Open AutoRingActivity.java.
This is the class that will allow the user to set options for our service class and also to start and stop the service. Note that we'll be coding some references to the service class even though it's not done yet. This will produce some compile errors until we code the service.
Remove all the existing code from AutoRingActivity.java.
Add the package name and imports needed for our application.
package droid.ar;
import droid.ar.R;
import android.app.Activity;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.MenuItem;
import android.view.View.OnCreateContextMenuListener;
import android.widget.TextView;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Toast;
import android.content.Intent;
import android.view.View;
Create the AutoRingActivity
class. Our class will implement 2 click listener interfaces so we can use our activity to process menu and button clicks (instead of creating a separate listener class).
public class AutoRingActivity extends Activity
implements MenuItem.OnMenuItemClickListener, View.OnClickListener {
Add the class level variables for our activity pointers and button objects.
AutoRingActivity mMainActivity;
LinearLayout mMainView;
Button btnConnectedOption, btnDisconnectedOption, btnStartStop;
private int mConnectAction = 2;
private int mDisconnectAction = 1;
Begin the onCreate
handler. Do some housekeeping, then create the button objects and set their listeners. For Android 1.5, setting listeners must be done in code. After 1.5, the listeners can be declared in main.xml. Notice that we assigned our activity instance to the static
AutoRingSvc.mMainActivity
. We haven't coded the service yet, so Eclipse will give a compiler error.
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mMainView = (android.widget.LinearLayout) findViewById(R.id.main_view);
AutoRingSvc.mMainActivity = this;
mMainActivity = this;
btnConnectedOption = ((Button) findViewById(R.id.btnConnectedOption));
btnDisconnectedOption = ((Button) findViewById(R.id.btnDisconnectedOption));
btnStartStop = ((Button) findViewById(R.id.btnStartStop));
btnConnectedOption.setOnClickListener(this);
btnDisconnectedOption.setOnClickListener(this);
btnStartStop.setOnClickListener(this);
Add code to load the user preferences. The variables will remain unchanged if the preferences are not set. The 0
(Context.MODE_PRIVATE
) indicates we want to keep these preferences private
to the AutoRing
app.
mConnectAction = getSharedPreferences("AutoRing", 0)
.getInt("ConnectAction", mConnectAction);
mDisconnectAction = getSharedPreferences("AutoRing", 0)
.getInt("DisconnectAction", mDisconnectAction);
Add the call to the GUI update method.
UpdateSvcStatus();
Add the handler for the context menu. This will show the options menu when the user clicks an option button. We pass in the calling button instance as a tag to the view parameter. This will let us determine the correct heading for the menu. Each menu item is assigned the button id as the groupid so we'll know the calling button when we process the menu item click. The checked item is determined by the current action value. The service has a string static string
array (mActionList
) which lists the option choices. We use that to populate the context menu.
mMainView.setOnCreateContextMenuListener(new OnCreateContextMenuListener() {
@Override
public void onCreateContextMenu(
ContextMenu menu, View v, ContextMenuInfo menuInfo) {
Button btnCaller = (Button)v.getTag();
menu.setHeaderTitle(btnCaller == btnConnectedOption ?
"When Connected" : "When Disconnected");
int action = (v.getTag() == btnConnectedOption) ?
mConnectAction : mDisconnectAction;
for (int i=0; i<4; i++)
menu.add(
btnCaller.getId(),i,0, AutoRingSvc.mActionList[i])
.setChecked(action == i)
.setOnMenuItemClickListener(mMainActivity);
menu.setGroupCheckable(btnCaller.getId(), true, true);
}
});
}
This code should produce the following context menu when our app is running.
Add the handler for the context menu selection. Remember that we set the GroupId
of the context menu items to the id of the calling button. We use that to determine which option to update. We then set the option according to the menu item selected (item id). We then update the preferences collection and the GUI fields.
public boolean onMenuItemClick(MenuItem item) {
if (item.getGroupId() == R.id.btnConnectedOption)
mConnectAction = item.getItemId();
if (item.getGroupId() == R.id.btnDisconnectedOption)
mDisconnectAction = item.getItemId();
getSharedPreferences("AutoRing", 0).edit()
.putInt("ConnectAction", mConnectAction)
.putInt("DisconnectAction", mDisconnectAction)
.commit();
UpdateSvcStatus();
return true;
}
Add the handler for button clicks. There are 2 option buttons and a Start\Stop button on the activity screen. The start button will start the service if it's not running, else it will be stopped. For the option buttons, we attach the button instance to the main view instance (we could also use a class variable) then manually launch the context menu. we could also attach the context menu to an GUI object (registerForContextMenu
), but that would require a long press to activate the menu. We also set the StartOnReboot
preference which is read in the RebootReceiver
class. If the user manually stopped the service here, we don't want to start the service at boot time.
public void onClick(View view) {
Button btn = (Button)view;
if (btn.getId()== R.id.btnStartStop)
{
AutoRingSvc.mMainActivity = this;
getSharedPreferences("AutoRing", 0).edit()
.putBoolean("StartOnReboot", !AutoRingSvc.mSvcStarted).commit();
Intent svc = new Intent(mMainActivity, AutoRingSvc.class);
if (!AutoRingSvc.mSvcStarted)
getApplicationContext().startService(svc);
else
AutoRingSvc.mSvcStarted =
!getApplicationContext().stopService(svc);
UpdateSvcStatus();
}
if (btn.getId()== R.id.btnConnectedOption ||
btn.getId()== R.id.btnDisconnectedOption)
{
mMainView.setTag(btn);
openContextMenu(mMainView);
}
}
Add the method to update the GUI objects. There are 2 text fields to show service and connection status. If the service is running, disable the option buttons. Set the option button text to the selected option value. For Android 1.6 and above, we'll add the arrow icon to the buttons so users will know they are option buttons. It seems that Android 1.5 and earlier don't support button icons. We remove the icon when the button is disabled because the icon will not turn light gray like the button, which looks odd.
public void UpdateSvcStatus() {
((TextView) findViewById(R.id.txtBattStatus)).setText(AutoRingSvc.mBatteryStatus);
((TextView) findViewById(R.id.txtStatus)).setText
("AutoRing is "+(AutoRingSvc.mSvcStarted? "running": "stopped"));
btnStartStop.setText((AutoRingSvc.mSvcStarted? "Stop": "Start") + " AutoRing");
btnConnectedOption.setEnabled(!AutoRingSvc.mSvcStarted);
btnDisconnectedOption.setEnabled(!AutoRingSvc.mSvcStarted);
btnConnectedOption.setText("When Connected: " +
AutoRingSvc.mActionList[mConnectAction]);
btnDisconnectedOption.setText("When Disconnected: " +
AutoRingSvc.mActionList[mDisconnectAction]);
if (Integer.parseInt(android.os.Build.VERSION.SDK) > 3)
{
btnConnectedOption.setCompoundDrawablesWithIntrinsicBounds
(0,0, btnConnectedOption.isEnabled()? R.drawable.downarrow: 0, 0);
btnDisconnectedOption.setCompoundDrawablesWithIntrinsicBounds
(0,0, btnDisconnectedOption.isEnabled()? R.drawable.downarrow: 0, 0);
}
}
Add the following methods to finish off our activity class. onPause
is called when our activity is moved to the background (user started another app) so we can disconnect from the service. onResume
is called when the app is moved back to the foreground, so can reconnect to the service for updates. onDestroy
is called when our app is stopped. The Toast
call is just to prove that the GUI has ended but our service is still running. You should remove this for a production app.
@Override
public void onPause() {
super.onPause();
AutoRingSvc.mMainActivity = null;
}
@Override
public void onResume() {
super.onResume();
AutoRingSvc.mMainActivity = this;
UpdateSvcStatus();
}
@Override
public void onDestroy() {
super.onDestroy();
Toast.makeText( mMainView.getContext(), " Goodbye GUI ",
Toast.LENGTH_SHORT).show();
}
}
Coding the Service Class
Open AutoRingSvc.java and delete the existing code.
Add the package name and imports needed for our service.
package droid.ar;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.BatteryManager;
import android.os.IBinder;
import android.widget.Toast;
Add the static
variables which will be used to share data between the service and activity class. Also add the default notification volume and status indicator. The 2 action variables are set to the defaults. You can change these values to your preferred settings.
public class AutoRingSvc extends Service {
public static AutoRingActivity mMainActivity;
public static String mBatteryStatus = "";
public static boolean mSvcStarted = false;
private int mConnectAction = 2;
private int mDisconnectAction = 1;
private int mNotificationVolume = 5;
private int mLastStatus = -1;
Add the static
action list array. This will be used to populate the option menu and also used as the ringer status text. The android constants for ringer mode is 0-2, which lets us use our array index instead of the constants when setting the ringer mode.
public static String[] mActionList = {
"Silent",
"Vibrate Only",
"Ring",
"No Change"};
Add the required onBind
handler. This is another (more complicated) way to talk to a running service. We will stop our service to make setting changes, so onBind
is not needed. Just ignore this event.
@Override
public IBinder onBind(Intent arg0) { return null; }
Add the onCreate
handler. This is called each time the service starts. If the OS gets low on memory, it may stop our service. When memory is freed up, our service will be restarted automatically and onCreate
is called again.
We tell Android we want to receive battery status updates with the registerReceiver
call.
SetForeground
is used to tell Android we want to keep our service running even if memory gets low. SetForeground
only works in Android 2.0 and less. Starting in 2.1, we would need to start the service using startForeground
.
The user preferences are taken from the preferences collection (set in AutoRingActivity.onMenuItemClick
).
We also log the service start here. If you're debugging using your phone (not a virtual device), you will need to get a (free) LogCat viewer from the Android market. Log messages will not show up in the Eclipse debugger unless you use a virtual device.
@Override
public void onCreate() {
super.onCreate();
android.util.Log.d("AutoRing","Service Starting");
Toast.makeText(getApplicationContext(),
" Starting ", Toast.LENGTH_SHORT).show();
registerReceiver( batteryReceiver,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED) );
mConnectAction = getSharedPreferences("AutoRing", 0)
.getInt("ConnectAction", mConnectAction);
mDisconnectAction = getSharedPreferences("AutoRing", 0)
.getInt("DisconnectAction", mDisconnectAction);
mNotificationVolume = getSharedPreferences("AutoRing", 0)
.getInt("NotVol", mNotificationVolume);
mSvcStarted = true;
setForeground(true);
}
If you are using Android 2.0 or later, you can include the onStartCommand
handler. This will allow you to use an Intent
object to pass settings to the service at startup instead of using shared static
values and the SharedPreferences
class. This walkthrough is compatible to version 1.5, so I have this code commented.
Add the handler for the battery status changes. The main work of our service is done here. When the phone is disconnected, the battery status becomes BATTERY_STATUS_DISCHARGING
. Any other status indicates that the phone is plugged in. When the handler is triggered, we change the ringer state (on\off\vibrate) and notification (like SMS) volume (zero\non-zero) based on the user settings. We then store the notification volume in the preferences in case of service restart. We then update the GUI app if it is running. Finally, we update the notification icon and text in the phone's status bar.
public BroadcastReceiver batteryReceiver = new BroadcastReceiver() {
@Override
public void onReceive( Context context, Intent intent )
{
int newStatus = intent.getIntExtra( "status", 0 );
if (newStatus != mLastStatus)
{
AudioManager am = (AudioManager)context.getSystemService
(Context.AUDIO_SERVICE);
int maxRingVol = am.getStreamMaxVolume(AudioManager.STREAM_RING);
int notVol = am.getStreamVolume(AudioManager.STREAM_NOTIFICATION);
int curAction =
(newStatus == BatteryManager.BATTERY_STATUS_DISCHARGING ||
newStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING) ?
mDisconnectAction : mConnectAction;
mBatteryStatus =
(newStatus == BatteryManager.BATTERY_STATUS_DISCHARGING ||
newStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING ?
"Not ": "") +"Connected ("+mActionList[curAction]+")";
if (curAction < 3)
{
am.setRingerMode(curAction);
if (notVol > 0 && curAction < 2)
{
mNotificationVolume = notVol;
am.setStreamVolume(AudioManager.STREAM_RING, 0, 0);
am.setStreamVolume(
AudioManager.STREAM_NOTIFICATION, 0, 0);
getSharedPreferences("AutoRing", 0)
.edit()
.putInt("NotVol", mNotificationVolume).commit();
}
else
{
if (curAction==2 && notVol==0)
am.setStreamVolume(AudioManager.STREAM_NOTIFICATION,
mNotificationVolume, 0);
am.setStreamVolume(
AudioManager.STREAM_RING, maxRingVol, 0);
}
}
if (mMainActivity!=null) mMainActivity.UpdateSvcStatus();
mLastStatus = newStatus;
DoNotify(!(newStatus == BatteryManager.BATTERY_STATUS_DISCHARGING ||
newStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING));
}
}};
Add the method to update the notification bar on the phone. We change the icon based on the connection status. We set the notification text based on the user settings and phone status. Use the FLAG_ONGOING_EVENT
flag so our notification will be in the Ongoing list and the Clear button will not delete our message. The text is set in the status bar next to our icon and also the text which appears in the notification drop down.
private void DoNotify(boolean connected)
{
Notification notification = new Notification (
connected? R.drawable.circlefill: R.drawable.circleopen,
mBatteryStatus, System.currentTimeMillis());
Intent notificationIntent = new Intent(this, AutoRingActivity.class);
notification.setLatestEventInfo(
getApplicationContext(), "AutoRing", mBatteryStatus,
PendingIntent.getActivity(this, 0, notificationIntent, 0));
notification.flags |= Notification.FLAG_ONGOING_EVENT;
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE))
.notify(1, notification);
}
Once the app is up and running, the AutoRing
notifications will look like this. We're using the mBatteryStatus
variable for the text in the notification list. This is set in the BroadcastReceiver
method. Note that the system icons are hidden while our status is showing the status bar so our text can fill the entire bar. If the message exceeds that length of the bar, the message will scroll line by line. You can also use "\n
" in the message to explicitly cause scrolling.
Finish the service class by adding the onDestroy
handler. We log the event and show a popup to confirm it happened. We then unregister the battery event listener then cancel the notification so the icon and text are removed from the status bar and notification list. There doesn't seem to be a built-in way to determine if the service is started\stopped, so we explicitly set the mSvcStarted
variable. We set this variable to true
in the onCreate
method.
onDestroy
is called when we use the stopService()
call in AutoRingActivity
and when the user uses the "Running services" app (Android 2.0 and later) in the Android Settings. onDestroy
is not called when the user uses Force stop in the "Manage applications" app in the Android settings or when using adp kill (discussed later).
@Override
public void onDestroy() {
super.onDestroy();
android.util.Log.d("AutoRing","Service Stopping");
Toast.makeText(getApplicationContext(),
" Stopping ", Toast.LENGTH_SHORT).show();
unregisterReceiver(batteryReceiver);
((NotificationManager) getSystemService
(Context.NOTIFICATION_SERVICE)).cancelAll();
mSvcStarted = false;
}
}
And that's it. We're done coding our application.
Build the project (Project->Build All). If you have Build Automatically set, the project will rebuild each time you save a source file.
Running the App
To see the connect\disconnect functionality actually work, you will need to connect your phone to the computer (virtual devices are always connected):
- Connect your phone using a USB cable (you may need to install the USB drivers for your phone)
- On your phone, in Settings->Application->Development, enable USB debugging
- USB Storage should be disabled on your phone
In Eclipse, press F11 to start debugging.
After a few seconds (if everything goes right), the application should start on your phone.
If the Eclipse starts a virtual device instead:
- Close the virtual device
- Go to Run->Debug Configurations
- In the configuration settings under Target, choose Manual. This will allow you to choose which device to debug on.
Click Debug then select Choose a running Android device in the next screen and click OK to start debugging.
If you don't see any running devices, confirm that you phone is attached to your PC and you have a working USB cable. You can also download USBDeview (freeware) which lists the attached USB devices on your PC.
To exit the app, use the back button on your phone or choose Run->Terminate in Eclipse.
To install the application to your phone, using an APK file
On your phone, in Settings->Applications, enable Unknown sources to allow non-market apps on your phone.
In Eclipse, choose File->Export..->Android-> Export Android Application.
Click Next.
Enter AutoRing
as the project name.
Click Next.
If you already have a keystore, choose Use existing keystore. If not, here are the steps to create one.
Choose Create new keystore. Enter a file name (no extension is needed) and a password.
Click Next.
For Alias and Password, you can use the same values you entered into the previous screen. Set validity to 100 years. Enter any name in the Name field. If you plan to publish any apps using this keystore, you should probably use your real information.
Click Next.
Enter the file name for your apk file.
Click Finish.
To install the apk file onto your phone, use the adb tool in the android-sdk\platform-tools folder. If you don't know the folder, just search your computer for adb.exe.
To install the apk file, use this command line:
adb install C:\AutoRing.apk
You can also use one of the (free) installer apps from the Android market which lets you install apk files from the phone's SD card.
Once the install is complete, AutoRing
should be available in your phones application list.
Testing the Service Restart Logic
As mentioned previously, onDestroy
is called when we use the stopService()
call and when the user uses the "Running services" app (Android 2.0 and later) in the Android Settings. onDestroy
is not called when the user uses Force stop in the "Manage applications" app in the Android settings.
If you want to simulate a memory-low process kill\restart, you can use the adb.exe from your computer:
- Ensure you only have one emulator running and your phone is not attached
- Start the
AutoRing
service on the emulator - From a
cmd
prompt, go to the android-sdk\platform-tools folder and run adb shell - Enter ps. This will give you a list of processes running on your device (or emulator). Search for the process where name is droid.ar. This is the
AutoRing
service. Note the PID (second column) for the process. - Enter kill pid where the pid is for the droid.ar process. This will cause the service to be killed immediately (
onDestroy
is not called). It will also trigger a service restart, which confirms the restart logic is working. - Enter exit to quit the shell.
adb would not let me kill a process on my actual phone (Operation not permitted) so I tested only on the emulator.
Creating your Own Icons
All the icons used for Android apps are transparent PNG files. Microsoft Paint (the default Windows image editor) does not support transparent PNGs, so you will need to use another application if you want transparent pixels. Here are instructions for Photoshop and Paint.Net (a free image editor).
Some basic rules for Android icons:
- Icon names must be lower case or numbers. Underscores ( _ ) are also allowed. Periods are allowed, but only the first part of the file name will be used as the resource name (myicon.abc.png is the same as myicon.xyz.png).
- The notification icon (in the status bar) is 25x25 pixels and should be grayscale (though color is supported as in the blue icons above).
- The main application icon is 72x72 pixels and can be full color. I saved mine as 8 bit color to reduce file size.
- The arrow icon is just a copy of the Android arrow used for preferences. I used the gray version, but color icons will also work (though may look odd on a gray button). I tested color using this green arrow shown above.
PhotoShop (I'm using version 7, which is somewhat old):
- Select File->New
- In the "New" dialog, choose the Transparent option under Contents to allow for transparency. Set the size you want then click OK. The checkered pattern is the transparent part of the image.
- Draw what you want, then save as a PNG file
- If you have an existing image and want to set the background to transparent
- Open the existing image
- Choose Select->All, then choose Edit->Copy
- Open a new image with Transparent selected
- Paste the old image (Edit->Paste)
- Use the Magic Wand Tool to select the pixels you want to set transparent, then click delete
- You can also use the Background Eraser Tool to clear specific pixels
Paint.Net (This is freeware which has some impressive features). You can download it here.
- Select File->New
- Set the size you want then click OK
- Choose Edit->Select All then press delete. This will set all the pixels to transparent.
- Draw the image you want then save as a PNG file. I used 8 bit color to reduce the file size.
- If you have an existing image and you want to set some pixels to transparent
- Open the existing image
- Use the Magic Wand tool to select the pixels you want to remove then press delete
- You can also use the Eraser tool to remove specific pixels
The full Android icon design guidelines can be found here.
Additional Thoughts
- The service class is not needed if you just want to test a listener or notifications. The listener will still run when the activity is moved to the background. The issues with this are that the OS will remove background GUI apps before removing services when memory gets low and that GUI apps are not restarted automatically.
- Dealing with the API changes between 1.5 and 2.0 can be tedious. Be prepared for this if trying to write backward compatible apps. Cryptic crashes may indicate a version problem.
- According to the version distribution chart, 97% of Android users use Android 2.1 and above. You can probably ignore version 1.5\1.6 when creating Android apps.
- The
AutoRing
app may be better without the "No Change" option. If the user chooses this, the status text will show No Change instead of the actual ring state. Of course, it we set the actual ring state, it may confuse the user. Hmmm. Fortunately, this option is impractical and will rarely be used. - I only tested the reboot logic on my Ideos 2.2. The virtual machines for version 1.5\1.6\2.2 didn't let me power down the device.
- In this app, we used a context menu to set app options. If you have many options to set, consider using the
PreferenceActivity
class where a separate activity is used for setting user preferences. - I couldn't get the main app icon to appear in the 1.5 emulator. I tried several icon versions. I think this is an emulator issue. The 2.2 emulator showed the icon correctly.
- The virtual machine is a good way to get screen captures from your app. Just capture\copy the emulator screen (Alt+PrtScr) then crop using an image editor.
- The main icon is a quickie creation using 3D Studio (very not free) but there are many free 3D modeling tools available if you want to give 3D stuff a try.
- If you want a full app that can detect battery conditions (among many others), check out the Llama app in the Android market. It's also free.
- I recently found out that there is a similarly named app in the Android market. That app is not related to this walkthrough. The naming is just coincidence.
Resources
"Copy from one, it's plagiarism; copy from two, it's research." - Wilson Mizner
And I think we're done. If you're still reading this, you have a high tolerance for boredom. I hope you found this walkthrough useful. If you found any part confusing or if you think I missed something, please let me know so I can update this page.