An Activity Without UI

I suppose the proper way would be to change the activity into a Service then use the startService() method, but this came up in when I Googled. Oh’so much less coding and I was back to work in seconds.

Change this:
<activity  android:name=".RingBellActivity"></activity>
To
<activity  android:name=".RingBellActivity"  android:theme="@android:style/Theme.NoDisplay"></activity>

Schedule a Job for the Time You Choose

There are a bunch of pieces on this one.
You need:
1. an activity(RingBellActivity) to do something
2. a broadcast receiver (AlarmReceiver) that ties to the alarm manager to receive the alarm request
3. a setup activity(MainActivity) to schedule the task(tied to the MAIN, LAUNCHER)
4. an activity to schedule it next time the android reboots
Special bonus for…
5. A way to turn it off
6. a behind the scenes activity with no user interaction(RingBellService)

Step 0 – xml files

res/layout/main_activity.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <TimePicker
        android:id="@+id/timePicker1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_marginTop="17dp" />

    <Button
        android:id="@+id/btnGo"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@+id/timePicker1"
        android:layout_marginTop="44dp"
        android:text="Wakeme" />

    <Button
        android:id="@+id/btnStop"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@+id/btnGo"
        android:text="Cancel" />

    <Button
        android:id="@+id/btnTest"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@+id/btnStop"
        android:text="Test" />

</RelativeLayout>
manifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.steelmarble.ontime"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="7"
        android:targetSdkVersion="15" />
	<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/title_activity_main" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".RingBellActivity"
            android:theme="@android:style/Theme.NoDisplay">
        </activity>
        <receiver android:name=".AlarmReceiver" android:process=":remote"></receiver>
        <service android:name="RingBellService"></service>
        <receiver android:name="Autostart">
            <intent-filter>
                <action android:name="android.intent.action.QUICKBOOT_POWERON" />
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="com.steelmarble.ontime.AUTOSTART" />
            </intent-filter>
        </receiver>
    </application>

</manifest>
Step 1
public class RingBellActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        NotificationManager nm = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
        Notification notification = new Notification(R.drawable.ic_launcher, "DingALing", System.currentTimeMillis());
        // This is intent, we want to launch when user clicks on the notification.
        Intent intentTL = new Intent(this, MainActivity.class);
        notification.setLatestEventInfo(this, "TIME", "To Do Something!",
        PendingIntent.getActivity(this, 0, intentTL, PendingIntent.FLAG_CANCEL_CURRENT));
        notification.flags = Notification.DEFAULT_LIGHTS | Notification.FLAG_AUTO_CANCEL;
        nm.notify(1, notification);
        finish();
    }
}
2. a broadcast receiver (AlarmReceiver) that ties to the alarm manager to receive the alarm request
public class AlarmReceiver extends BroadcastReceiver {
 	@Override
	public void onReceive(Context context, Intent intent) {
 		 Intent scheduledIntent = new Intent(context, RingBellActivity.class);
 		 scheduledIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 		 context.startActivity(scheduledIntent);
 	}
}
3. a setup activity(MainActivity) to schedule the task(tied to the MAIN, LAUNCHER)
public class MainActivity extends Activity {
	public static final String PREFERENCE_FILENAME = "AppGamePrefs";

	TimePicker timePicker;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // setup UI time scheuler
        timePicker=(TimePicker) findViewById(R.id.timePicker1);
        
    	SharedPreferences prefs = getSharedPreferences(PREFERENCE_FILENAME,MODE_PRIVATE);
    	timePicker.setCurrentHour(prefs.getInt("hour",10));
    	timePicker.setCurrentMinute(prefs.getInt("min",0));
        
        // Schedule it...
        Button b = (Button) findViewById(R.id.btnGo);
        b.setOnClickListener(new View.OnClickListener() {
              public void onClick(View v) { 
              Calendar cal = Calendar.getInstance();
              cal.setTimeInMillis(System.currentTimeMillis());
              Date stamp =  cal.getTime();
              stamp.setHours(timePicker.getCurrentHour());
              stamp.setMinutes(timePicker.getCurrentMinute());

              PendingIntent pendingIntent = getAlarmPendingIntent(MainActivity.this);
                	    
              AlarmManager manager=(AlarmManager)getSystemService(Context.ALARM_SERVICE);
              manager.cancel(pendingIntent); // kill any other outstanding requests first
              manager.setRepeating(AlarmManager.RTC_WAKEUP, stamp.getTime(), 10*1000, pendingIntent);
              // or as a one-shot
              //manager.set(AlarmManager.RTC_WAKEUP, stamp.getTime(), pendingIntent);

               	finish();
         }
        });        		
          
        // unschedule
        Button b2 = (Button) findViewById(R.id.btnStop);
        b2.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {            	
                	AlarmManager manager=(AlarmManager)getSystemService(Context.ALARM_SERVICE);
                	manager.cancel(getAlarmPendingIntent(MainActivity.this));
                	finish();
                }
        });        
        // special bonus bonus - test the reboot code without rebooting
        // click wakeme(to set), cancel(to turn it off), test(fake the reboot by calling custom intent)
        Button b3 = (Button) findViewById(R.id.btnTest);
        b3.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                	Intent intent = new Intent();
                	intent.setAction(Autostart.CUSTOM_INTENT);
                	sendBroadcast(intent); 
                }
        });        		
    }
    @Override
    public void onDestroy(){
       // save the preferences
    	super.onDestroy();
    	SharedPreferences.Editor editor = getSharedPreferences(PREFERENCE_FILENAME,MODE_PRIVATE).edit();
    	 editor.putInt("hour", timePicker.getCurrentHour());
    	 editor.putInt("min", timePicker.getCurrentMinute());    	 
    	 editor.commit();
    }

    // one stop shop so we can set/cancel without recoding all over the place
    public static PendingIntent getAlarmPendingIntent(Context context){
    	Intent intent= new Intent(context,AlarmReceiver.class);
    	return PendingIntent.getBroadcast(context,0, intent,0);
    }
4. an activity to schedule it next time the android reboots
public class Autostart extends BroadcastReceiver {
	public static final String CUSTOM_INTENT = "com.steelmarble.ontime.AUTOSTART";

	@Override
	public void onReceive(Context context, Intent arg1) {
		String text="Coming right up";
    	Calendar cal = Calendar.getInstance();
        cal.setTimeInMillis(System.currentTimeMillis());
    	SharedPreferences prefs = context.getSharedPreferences(MainActivity.PREFERENCE_FILENAME,context.MODE_PRIVATE);
    	Date dStamp= cal.getTime();
    	Date dNow  = cal.getTime();
    	
    	dStamp.setHours(prefs.getInt("hour",1));
    	dStamp.setMinutes(prefs.getInt("minute",23));
    	
        Long stamp =  dStamp.getTime();       
        Long now   =  dNow.getTime();
        if(now>stamp){
        	 // to late for today, avoid immediate trigger and schedule for tomorrow
        	stamp+= AlarmManager.INTERVAL_DAY; // add one day
        	text="Scheduled for tomorrow";
        }

        NotificationManager nm = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
        Notification notification = new Notification(R.drawable.ic_launcher, "DingALing", System.currentTimeMillis());
        // This is intent, we want to launch when user clicks on the notification.
        Intent intentTL = new Intent(context, MainActivity.class);
        notification.setLatestEventInfo(context, "TIME", text,
        PendingIntent.getActivity(context, 0, intentTL, PendingIntent.FLAG_CANCEL_CURRENT));
        notification.flags = Notification.DEFAULT_LIGHTS | Notification.FLAG_AUTO_CANCEL;
        nm.notify(1, notification);

        
        PendingIntent pendingIntent=MainActivity.getAlarmPendingIntent(context);
    	    
    	AlarmManager manager=(AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
    	manager.cancel(pendingIntent);
    	manager.setRepeating(AlarmManager.RTC_WAKEUP, stamp, 10*1000, pendingIntent);
	}
}

Special bonus for…

5. A way to turn it off
i'd add a checkbox in the main_activity and tie it to preferences, then use that on autostart
6. a behind the scenes activity with no user interaction (sounds like a service to me or a fast alternative)

6a. Create a service

public class RingBellService extends Service {

	@Override
	public IBinder onBind(Intent arg0) {
		// TODO Auto-generated method stub
		return null;
	}
	@Override
	public void onStart(Intent intent, int startid) {
		Toast.makeText(this, "RingBellService", Toast.LENGTH_LONG).show();
	}
}

6b. Change the AlarmReceiver to issue a

public class AlarmReceiver extends BroadcastReceiver {
	/* Start some run once service */
 	@Override
	public void onReceive(Context context, Intent intent) {
 		 Intent scheduledIntent = new Intent(context, RingBellService.class);
 		 scheduledIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 		 context.startService(scheduledIntent);
 	}
}

Bugs and More

Did I see the preferences get reset to default?

Quick and dirty Custom Preferences

in the activity onCreate…
SharedPreferences prefs = getPreferences(MODE_PRIVATE);
timePicker.setCurrentHour(prefs.getInt("hour",10));
timePicker.setCurrentMinute(prefs.getInt("min",0));
in the activity onDestroy…
SharedPreferences.Editor editor = getPreferences(MODE_PRIVATE).edit();
 editor.putInt("hour", timePicker.getCurrentHour());
 editor.putInt("min", timePicker.getCurrentMinute());    	 
 editor.commit();

QUICKIES

Screen starts/stays in PORTRAIT mode:

manifest.xml…

<activity android:name="CalFlipActivity" android:screenOrientation="portrait">

No title bar on screen

manifest.xml…

<activity android:name="CalFlipActivity" android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">

Activity Looks Like Dialog

manifest.xml…

<activity android:name="CalFlipActivity" android:theme="@android:style/Theme.Dialog">

 

Menus – Option Menu

Android has a couple menu types.  The “option” menu is one that is access by using the unit’s menu button.  Since it is built into the view, you only need to

The setup – some constants:

	final public int MENU_GROUP=0;
	final public int MENU_ADD=0;
	final public int MENU_EDIT=1;
	final public int MENU_DELETE=2;

The setup – build the list

@Override 
public boolean onCreateOptionsMenu(Menu menu) {
 	menu.add(MENU_GROUP, MENU_ADD, 0, "add");
    	menu.add(MENU_GROUP, MENU_EDIT, 0, "edit");
    	menu.add(MENU_GROUP, MENU_DELETE, 0, "delete");
    	return super.onCreateOptionsMenu(menu); 
}

The handler – respond to the options

@Override 
public boolean onOptionsItemSelected(MenuItem item) {	
    	switch(item.getItemId()) { 
    	case MENU_ADD:
    		Intent intent = new Intent(getBaseContext(), editActivity.class);
    		intent.putExtra("ID", 0);
    		startActivity(intent);
    		return true;
    	case MENU_EDIT: 
    		return true;
    	case MENU_DELETE: 
    		return true;
    	} 
    	return super.onOptionsItemSelected(item);
}

Text Input Dialog with Validation

What I want:

  • Displays like normal input field, act like a button.
  • When user taps, a dialog pops up
  • User can only leave when when valid data is entered or cancel button is pressed

Accomplished

  1. create the activity XML including the input field in question
  2. create another layout file including an edit box, ok and cancel buttons
  3. edit the activity code to lockout edits and fire the pop_dialog when touched
  4. in the same activity code, create a pop_dialog

1 – build an activity main.xml with an input field with id=strText
Note the hack that keeps the field from getting the cursor | when the screen opens. Also the first LinearLayout’s android:orientation=”vertical” to keep the screen in one nice looking direction.

<?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/linearLayout1"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
         android:orientation="vertical"        
        >
 <!-- hack so autofocus goes here and later edittext elements dont have cursor flash -->
<LinearLayout
    android:focusable="true" android:focusableInTouchMode="true"
    android:layout_width="0px" android:layout_height="0px"/>
<!--  end hack -->

            <LinearLayout
                android:id="@+id/linearLayout3"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                android:orientation="vertical" >

                <EditText
                    android:id="@+id/strText"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content" >
                </EditText>

            </LinearLayout>

    </LinearLayout>

2  – build a dialog activity screen with an input field and ok/cancel buttons

Right click  layout/new/android/xml file.  Resource type: layout, file: dialog_text.xml

<?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/linearLayout1"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
         android:orientation="vertical" 
        >
        <EditText
            android:id="@+id/strEditText"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" android:inputType="textShortMessage">

            <requestFocus />
        </EditText>
        <LinearLayout
            android:id="@+id/linearLayout2"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" >

            <Button
                android:id="@+id/btnOk"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="OK" android:layout_weight="1"/>

            <Button
                android:id="@+id/btnCancel"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Cancel" android:layout_weight="1"/>

        </LinearLayout>
    </LinearLayout>

edit the activity code to lockout edits and fire the pop_dialog when touched

public class SpinnerActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        /* turn our input box into a display-only button that fires the dialog popup */
        EditText t1=(EditText)findViewById(R.id.strText);
        t1.setOnTouchListener(new OnTouchListener()
        {
			public boolean onTouch(View arg0, MotionEvent arg1) {
				// TODO Auto-generated method stub
				 if (arg1.getAction() == MotionEvent.ACTION_DOWN) {
					 editText_dialog(R.id.strText);
		         }
				return false;
			}
        });
    }
}

in the same activity code, create a pop_dialog

    public void editText(final int id){
    	final Dialog dialog = new Dialog(this);
        dialog.setContentView(R.layout.dialog_text);
        dialog.setTitle("You got that right:");
        dialog.setCancelable(false);
        final EditText strEditText=(EditText)dialog.findViewById(R.id.strEditText);
        Button xbutton = (Button) dialog.findViewById(R.id.btnOk);
        xbutton.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
        		EditText sText=strEditText;
        		String x=sText.getText().toString();
        		// validation 
                if(x.equals("")){ 
                	/* bad - hilight in red */ 
                  //showToast
                	sText.setBackgroundColor(Color.argb(220, 0xff, 0xa0, 0xa0));
                	sText.requestFocus();
                }else{
                   /* good - copy the field back to the display and close dialog */
                  TextView target=(TextView)findViewById(id);
                  target.setText(x);
                  dialog.dismiss();
                  //other stuff to do
                }        	
            }
        }); 
        Button xbutton2 = (Button) dialog.findViewById(R.id.btnCancel);
        xbutton2.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
                  dialog.dismiss();
            }
        }); 
        WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
        lp.copyFrom(dialog.getWindow().getAttributes());
        lp.width = WindowManager.LayoutParams.FILL_PARENT;
        dialog.show();
        dialog.getWindow().setAttributes(lp);
    }

View in portrait mode

In layout – not so useful

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/linearLayout1"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
         android:orientation="vertical"        
        >

In the manifest – very good

<activity 
    android:name=".MyActivity" 
    android:screenOrientation="portrait"
    android:configChanges="orientation|keyboardHidden|keyboard"/>

In Code

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

Special bonus – full screen

        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 
                                WindowManager.LayoutParams.FLAG_FULLSCREEN);