Handling Application Logic inside an AppWidgetProvider

After the pending intent has started your AppWidgetProvider, you need to perform some logic on behalf of the calling application (in this case, the Home screen application). In the following sections, I show you how to perform time-sensitive work on behalf of the caller.

Before I hop into the code, it's best to understand how the work will be done in the AppWidgetProvider. Due to the nature of remote processes and how resource-intensive they can be, it's best to do all work inside a background service. I will be performing the changing of the brightness mode via a background service.

Understanding the IntentService

Why should you use a background service for such a trivial task as changing the device brightness mode? Well, it has to do with avoiding Application Not Responding (ANR) errors.

Any code that executes for too long without responding to the Android system is subject to the ANR error. App widgets are especially vulnerable to ANR errors because they are executing code in a remote process — they execute across process boundaries that can take time to set up, execute, and tear down — the entire process is very CPU-, memory-, and battery-intensive. The Android system watches app widgets to ensure that they do not take too long to execute. If they do take an extended period of time to execute, the calling application (the Home screen) locks up and the device is unusable. Therefore, the Android platform wants to make sure that you're never capable of making the device unresponsive for more than a couple of seconds.

Because app widgets are very expensive in regard to CPU and memory, it's really hard to judge whether an app widget will cause an ANR error. If the device is not doing any other expensive tasks, the app widget would probably work just fine. However, if the device is in the middle of one or many expensive CPU operations, the app widget could take too long to respond — causing an ANR error. This unknown CPU state is a dangerous variation to introduce to your app widget. Therefore, to get around it, move the work of the app widget into a space where it can take as long as it needs to complete, a space that in turn will not affect the Home screen application — in other words, a background service. (You're using a background service called an IntentService for this app.) Unlike most background services, which are long-running, an IntentService uses the work queue processor pattern that handles each intent in turn using a worker thread, and it stops when it runs out of work. In laymen's terms, the IntentService simply takes the work given to it, runs it as a background service, and then stops the background service when no more work needs to be done.

Implementing the AppWidgetProvider and IntentService

In your AppWidgetProvider class, type the code in Listing 8-3 into your code editor.

Listing 8-3: The Full AppWidget Implementation

public class AppWidget extends AppWidgetProvider {

     @Override
     public void onReceive(Context context, Intent intent) {

         if (intent.getAction()==null) {
             context.startService(new Intent(context, ToggleService.class));  →7
             Intent i = new Intent(context, ToggleActivity.class);            →8
             i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);                     →9
             i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);                       →10

             context.startActivity(i);                                        →12
            } else {
                super.onReceive(context, intent);                             →14
            }
        }

       public static class ToggleService extends IntentService {              →18

           public ToggleService() {
               super(“AppWidget$ToggleService”);                              →21
           }

           @Override
        protected void onHandleIntent(Intent intent) {                        →25
             ComponentName me=new ComponentName(this, AppWidget.class);       →26
             AppWidgetManager mgr=AppWidgetManager.getInstance(this);         →27
             mgr.updateAppWidget(me, buildUpdate(this));                      →28

        }

        private RemoteViews buildUpdate(Context context) {                    →32
            RemoteViews updateViews=new
                RemoteViews(context.getPackageName(),R.layout.widget);        →34

            int currentBrightness =
               BrightnessHelper.getCurrentBrightness(ToggleService.this);     →37
            if(currentBrightness > 51) {                                      →38
                // Dim it to 20 %. 20% of 255 is 51
              updateViews.setImageViewResource(R.id.brightnessState,
                  R.drawable.night);                                          →41
            } else {
                // Push it back to full brightness
                  updateViews.setImageViewResource(R.id.brightnessState,
                  R.drawable.sun);                                            →45
            }

            Intent i=new Intent(this, AppWidget.class);                       →48
            PendingIntent pi = PendingIntent.getBroadcast(context, 0, i,0);   →49
            updateViews.setOnClickPendingIntent(R.id.brightnessState,pi);     →50

            return updateViews;
        }
    }
}

The following list briefly explains what each major section of code does:

→7 This line of code starts a new instance of the ToggleService. The context object in this line of code refers to the Android Context object, which is an interface to global information about the application. The context is passed into the onReceive() and onUpdate() method calls. A new intent is created to let the Android system know what should happen. This method is initiated by the user when the user taps the app widget on the Home screen.
→8 This line builds a new intent that you will use to start an activity.
→9 This line sets one of the two flags that will be set on this intent. The first flag is the FLAG_ACTIVITY_CLEAR_TASK. This flag informs the Android system to clear any existing task associated with the activity prior to starting a new one.
→10 This line sets the second of the two flags that will be set on this intent. The second flag is the FLAG_ACTIVITY_NEW_TASK. This flag informs the Android system to start a new task on the Activity history stack.
→12 This line starts the activity. You may be wondering why I'm starting an activity from the widget intent service. Remember back when you built the MainActivity class (in Chapter 7) when you changed the current window brightness by setting the layout parameters with the brightness level? Without that call, the brightness setting in the system would change, but the current screen brightness would not. Therefore, you have to start the ToggleActivity for about one second to update the brightness of the device while the activity is running. This is needed because this is the only way that you can get a handle on the actual running window of the device. This activity has not been created yet. I will cover this new activity below.
→14 Because the intent action was not null, a default course of action was taken to allow the superclass to handle the onReceive event.
→18 This is an implementation of an IntentService. This IntentService handles the same logic as your MainActivity for handling the brightness-mode switching but in regard to the app widget infrastructure. This is an implementation of a background service in the Android platform, as described previously. This class is a nested static class within the app widget.
→21 This method calls the superclass with the name AppWidget$ToggleService. This method call takes place to help with debugging purposes for the thread name. If you leave this line of code out, you receive a compiler error informing you that you must explicitly invoke the super's constructor. If your app widget is named something else other than AppWidget, you should change this to the class name of your class.
→25 The onHandleIntent() method is responsible for handling the intent that was passed to the service. In this case, it would be the intent that was created on line 7. Because the intent that you created was an explicit intent (you specified a class name to execute), no extra data was provided, and therefore by the time you get to line 24, you don't need to use the intent anymore. However, you could have provided extra information to the Intent object that could have been extracted from this method parameter. In this case, the Intent object was merely a courier to instruct the ToggleService to start its processing.
→26 A ComponentName object is created. This object is used with the AppWidgetManager (explained next) as the provider of the new content that will be sent to the app widget via the RemoteViews instance.
→27 An instance of AppWidgetManager is obtained from the static AppWidgetManager.getInstance() call. The AppWidgetManager class is responsible for updating the state of the app widget and provides other information about the installed app widget. You will be using it to update the app widget state.
→28 The app widget gets updated with a call to updateAppWidget() on this line. This call needs two things: the Android ComponentName that is doing the update and the RemoteView object that is used to update the app widget. The ComponentName is created on line 24. The RemoteView object that will be used to update the state of the app widget on the Home screen is a little more complicated and is explained next.
→32 The method definition for the buildUpdate() method. This method returns a new RemoteView object that will be used on line 28. The logic for what should happen and the actions to proceed with are included in this method.
→34 Here I am building a RemoteView object with the current package name as well as the layout that will be returned from this method. The layout, R.layout.widget, is shown in Listing 8-2.
→37 The code from line 37 to line 45 is very similar to the code that you wrote for the MainActivity. The code between these lines checks the current brightness level of the device and then decides which code path to execute. This line gets the current brightness level of the device using the BrightnessHelper you built previously.
→38 If the brightness level is greater than 51, then the device is assumed to be in bright mode.
→41 This line of code updates the RemoteView's ImageView whose ID is brightnessState. The new image will be the evening image (night.png).
→45 If the logic determines that the brightness level is not greater than 51, then this code will execute. This code updates the RemoteView's ImageView whose ID is brightnessState. The new image will be the daytime image (sun.png).
→48 This line creates an Intent object that will start the AppWidget class when initiated.
→49 Unfortunately, app widgets cannot communicate through basic intents; they require the use of a PendingIntent. Remember, app widgets use cross-process communication; therefore, PendingIntent objects are needed for communication. On this line, I build the PendingIntent that instructs the app widget of its next action via the child intent built on line 48.
→50 Because you're working with a RemoteView, you have to rebuild the entire event hierarchy in the view. The reason for this is the app widget framework will be replacing the entire RemoteView with a brand-new one that you supply via this method. Therefore, you have one thing left to do: tell the RemoteView what to do when it's tapped/clicked from the Home screen. The PendingIntent that you built on line 49 instructs the app widget what to do when someone clicks or taps the view. The setOnClickPendingIntent() sets this up. This method accepts two parameters: the ID of the view that was clicked (in this case an image) and the pi argument, which is the PendingIntent that you created on line 49. In other words, you're setting the click listener for the ImageView in the app widget.

Adding the ToggleActivity to Help Adjust Current Window Brightness

In Listing 8-3 you were introduced to the ToggleActivity. The ToggleActivity has one function — to act as an invisible courier that does work, kind of like a ninja on a midnight mission! The ToggleActivity checks the current screen brightness and then, depending upon the current state of that brightness, updates the current window parameters with the new screen brightness in order to get the brightness to change. You want this activity to start, do its work so fast that no one notices, and then close. You do not want the user to view the Activity — they should not notice a new activity starting. Therefore, you will hide the title bar of the activity. When you register this activity later in the chapter in the AndroidManifest.xml file, you will inform the Android system to make the background of the activity transparent, effectively hiding any known trace that the activity is actually running.

Why does this step need to be done in an activity? Why can't it be done in a service? Because you need access to the currently running window in order to get access to the current window/screen brightness. A background service runs in the background, so it has no window. Because the widget is running in a remote process, Linux security won't let you have access to that window. Therefore, you have to start a new activity (one hidden from the user), so you can gain access to the window in order to change the current brightness.

To set up your activity, add a new class with the name of ToggleActivity.java to the src/ folder. Replace the contents of this file with what is shown in Listing 8-4.

Listing 8-4: The ToggleActivity.java

public class ToggleActivity extends Activity {

     @Override
     public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            requestWindowFeature(Window.FEATURE_NO_TITLE);            →6

            int currentBrightness =
                         BrightnessHelper.getCurrentBrightness(this); →9

            if(currentBrightness > 51) {                              →11
                // Dim it to 20 %. 20% of 255 is 51
                BrightnessHelper.setCurrentBrightness(this, 51);
            } else {
                // Push it back to full brightness
                BrightnessHelper.setCurrentBrightness(this, 255);
            }

            // Wait for 1000 milliseconds (1 second) for the brightness
            // change to start, then quit.
            final Timer t = new Timer();                              →21
            t.schedule(new TimerTask() {                              →22

                @Override
                public void run() {                                   →25
                    finish();                                         →26
                    t.cancel();                                       →27
                }
           }, 1000);                                                  →29
    }
}

In the following list, I explain each major area of the code:

→6 This line informs the Android system to not show the title bar at the top of this Activity when it starts.
→9 Gets the current screen brightness so that your code can decide what action to pursue.
→11 This line checks the current brightness and lets execution fall into setting the brightness to evening (dim) if it is greater than 51. Otherwise the code block will fall through to update the brightness to daytime (full brightness) code block.
→21 This line creates a new timer for you to use in the application. You need to use a timer because in order for the screen brightness update to be applied, your application — not ToggleActivity — must be in the forefront of the activity stack. If it is not, the screen brightness change will not happen. By creating a timer to schedule when your activity should finish, you ensure that ToggleActivity quits in a timely manner. It should only take about 100–300 milliseconds to update the screen brightness, but just to be safe, I've set the value to 1000 (on line 29) of the time schedule call that takes place on line 22. This increase gives the timer 1000 milliseconds (1 full second) to update the screen brightness. After 1000 milliseconds, the activity will be finished (the ToggleActivity will quit and will return to the Home screen where the widget is located).
→22 This line calls the schedule method of the Timer class. This method allows you to schedule code to be run after a specified time has elapsed. After the time has elapsed, the run method will be called. Any code you put in the run method will be called at that time.
→25 This line defines the run method for the timer's schedule method call.
→26 At this point, the timer's 1000 milliseconds would have elapsed and now your code would be running again. This line calls the activity's finish method. The finish method finishes (quits) the activity.
→27 Because you are no longer in need of the timer (you just quit the activity by calling its finish method), you can now cancel the timer so that it no longer fires.
→29 This line contains the time in which you'd like the specified timer to execute. In this instance, it's going to run every 1000 milliseconds (every second).

You've written the code to implement the widget as well as to update the screen brightness in the settings as well as on the screen. Now you need to work with the metadata of the widget and add the new components to the AndroidManifest.xml.

Working with the app widget's metadata

Now that the code is written to handle the updating of the app widget, you may be wondering how to get the app widget to show up on the Widgets menu so that you can install it. This fairly simple process requires that you add a single XML file to your project. This XML file describes some basic metadata about the app widget so that the Android platform can determine how to lay out the app widget onto the Home screen. Here's how you do this:

  1. In your project, right-click the res directory and choose NewimageNew Folder.
  2. For the folder name, type xml and click Finish.
  3. Right-click the new res/xml folder, choose New, and then choose Android XML File.
  4. In the New Android XML File Wizard, type widget_provider.xml for the filename.
  5. The file type will be of the type AppWidgetProvider. Select that radio button and then click Finish.
  6. After the file opens, open the XML editor and type the following into the widget_provider.xml file:
    <?xml version=“1.0” encoding=“utf-8”?>
    <appwidget-provider xmlns:android=“http://schemas.android.com/apk/res/android”
               android:minWidth=“79px”
               android:minHeight=“79px”
               android:updatePeriodMillis=“0”
               android:initialLayout=“@layout/widget”
    />

The minWidth and minHeight properties are used for setting the very minimum space that the view will take on the Home screen. These values could be larger if you want.

The updatePeriodMillis property defines how often the app widget should attempt to update itself. In the case of the Screen Brightness Toggle application, you do not want this to happen — the only time the brightness should change is if the user initiates it. Therefore, this value is set to 0 seconds. If you were to set this to 180000 milliseconds — 30 minutes then every 30 minutes, the app would update itself through sending an intent that executes the onUpdate() method call in the AppWidgetProvider (if you have implemented that method).

The initialLayout property identifies what the app widget will look like when the app widget is first added to the Home screen before any work takes place. Note that it may take a couple of seconds (or longer) for the app widget to initialize and update your app widget's RemoteView object by calling the onReceive() method.

An example of a longer delay is if you had an app widget that checked Twitter for status updates. If the network is slow, the initialLayout would be shown until updates were received from Twitter. Therefore, if you foresee this becoming an issue, inform the user in the initialLayout that information is loading. Therefore, the user is kept aware of what is happening when the app widget is initially loaded to the Home screen. You can do this by providing a TextView with the contents of ”Loading …” while the AppWidgetProvider does its work.

At this point, you can install the Screen Brightness Toggle application, long-press the Home screen, and choose the Widgets category; now you should see the Screen Brightness Toggle present. The metadata that you just defined is what made this happen. The icon defaults to the application icon. However, the app widget would throw an exception if you attempted to add it to the Home screen. This mistake is fairly common: I forgot to let the AndroidManifest.xml file know about my new IntentService and BroadcastReceiver. If the AndroidManifest.xml does not know about these new items, exceptions will be thrown because the application context has no idea where to find them. You solve this problem next.

Registering your new components with the manifest

Anytime you add an Activity, Service, or BroadcastReceiver (as well as other items) to your application, you need to register them with the application manifest file. The application manifest presents vital information to the Android platform, namely, the components of the application. The Activity, Service, and BroadcastReceiver objects that are not registered in the application manifest will not be recognized by the system and will not be able to be run. Therefore, if you added the app widget to your Home screen, you would have it crash because your AppWidgetProvider is a BroadcastReceiver, and the code in the receiver is using a service that is also not registered in the manifest.

To add your AppWidgetProvider and IntentService to your application manifest file, open the AndroidManifest.xml file and type the code shown in Listing 8-5 into the already-existing file. Bolded lines are the newly added lines for the new components.

Listing 8-5: An Updated AndroidManifest.xml File with New Components Registered

<?xml version=“1.0” encoding=“utf-8”?>
<manifest xmlns:android=“http://schemas.android.com/apk/res/android”
    package=“com.dummies.android.screeenbrightnesstoggle”
    android:versionCode=“1” android:versionName=“1.0”>
    <uses-permission android:name=“android.permission.WRITE_SETTINGS”></uses-permission>
    <application android:icon=“@drawable/icon” android:label=“@string/app_name”>
        <activity android:name=“.MainActivity” android:label=“@string/app_name”>
            <intent-filter>
                <action android:name=“android.intent.action.MAIN” />
                <category android:name=“android.intent.category.LAUNCHER” />
            </intent-filter>
        </activity>

        <activity android:name=
           “com.dummies.android.screeenbrightnesstoggle.ToggleActivity”                     →14
            android:theme=“@android:style/Theme.Translucent” />                             →15
     <receiver android:name=
        “com.dummies.android.screeenbrightnesstoggle.AppWidget”                             →17
        android:label=“@string/app_name” android:icon=“@drawable/icon”
        android:previewImage=“@drawable/night”>                                             →19
<intent-filter>
<action android:name=“android.appwidget.action.APPWIDGET_UPDATE” />
                                                                                            →22
</intent-filter>
<meta-data android:name=“android.appwidget.provider”
android:resource=“@xml/widget_provider” />
                                                                                            →26
</receiver>
<service     android:name=
                                                                                            →28
“com.dummies.android.screeenbrightnesstoggle.AppWidget$ToggleService” />

    </application>
    <uses-sdk android:minSdkVersion=“11” />
</manifest>

The following is a brief explanation of what each section does:

→14 This line registers the ToggleActivity so that your application can call it.
→15 This line instructs the Android system to use a different theme for this activity only. The theme that this activity uses is the Translucent theme. Therefore, the user will not even see an activity start.
→17 This line is the opening element that registers a BroadcastReceiver as part of this application. The name property identifies what the name of the receiver is. In this case, it is .AppWidget, which correlates to the AppWidget.java file in the application. The name and the label are there to help identify the receiver.
→19 This line defines the previewImage attribute for the widget. When the widget is added to the screen, this is going to be the preview image that is shown while the application is loading. A lot of popular apps have a preview image that shows a loading icon to illustrate that the application widget is indeed loading (perhaps the widget must perform some action when it's first added to the screen, such as determine the user's location through GPS).
→22 Identifies what kind of intent (based on the action of the intent in the intent filter) the app widget will automatically respond to when the particular intent is broadcast. This is known as an IntentFilter, and it helps the Android system understand what kind of events your app should get notified of. In this case, your application is concerned about the APPWIDGET_UPDATE action of the broadcast intent. This event fires after the update- PeriodMillis property has elapsed, which is defined in the widget_provider.xml file. Other actions include enabled, deleted, disabled, and more. Currently you have this set to zero, instructing the app widget framework to not send an update to the provider. However, if you omit this intent filter, you will not be able to add the widget to the home screen.
→26 This line identifies the location of the metadata that you recently built into your application. Android uses the metadata to help determine defaults and lay out parameters for your app widget.
→28 This line identifies the Intent Service that you created to handle offloading of tasks from the main user interface thread.

At this point, your application is ready to be installed and tested. To install the application, choose RunimageRun or press Ctrl+F11. Your application should now show up on the emulator. Return to the Home screen by pressing the Home key. You can now add the app widget you recently created to your Home screen.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.251.169