13. Enhanced Slideshow App

Objectives

In this chapter you’ll:

• Use an Intent and content resolvers to allow the user to select videos from the device’s media library.

• Use the device’s rear-facing camera to take new pictures to add to the slideshow.

• Use SurfaceView, SurfaceHolder and Camera objects to display a photo preview with various color effects.

• Use an VideoView to play videos.

• Use Serializable objects to save and load slideshows.

• Use ObjectOutputStream and FileOutputStream to save slideshows to a device.

• Load slideshows from the device with ObjectInputStream and FileInputStream.

Outline

13.1 Introduction

13.2 Test-Driving the Enhanced Slideshow App

13.3 Technologies Overview

13.4 Building the GUI and Resource Files

13.4.1 Creating the Project

13.4.2 AndroidManifest.xml

13.4.3 SlideshowEditor ListActivity's Modified Layout

13.4.4 PictureTaker Activity’s Layout

13.4.5 SlideshowPlayer Activity’s Modified Layout

13.5 Building the App

13.5.1 MediaItem Class

13.5.2 SlideshowInfo Class

13.5.3 Slideshow Class

13.5.4 SlideshowEditor Class

13.5.5 PictureTaker Subclass of Activity

13.5.6 SlideshowPlayer Class

13.6 Wrap-Up

Self-Review Exercises | Answers to Self-Review Exercises | Exercises

13.1. Introduction

The Enhanced Slideshow app adds several capabilities to Chapter 12’s Slideshow app. With this version, the user can save the slideshows’ contents on the device using file processing and object serialization, so the slideshows are available for playback when the app executes in the future. In addition, when editing a slideshow, the user can take a new picture using the device’s camera (rear facing, by default; Fig. 13.1) and select videos from the device to include in the slideshow (Fig. 13.2(a)). As with images, after the user selects a video, a thumbnail is displayed (Fig. 13.2(b)) in the list of items included in the slideshow. When the SlideshowPlayer Activity encounters a video (Fig. 13.2), it plays the video in a VideoView while the slideshow’s music continues to play in the background. [Note: This app’s picture taking and video features require an actual Android device for testing purposes. At the time of this writing, the Android emulator does not support camera functionality and its video playback capabilities are buggy.]

Image

Fig. 13.1. Previewing a new picture with the camera.

Image

Fig. 13.2. Selecting a video and displaying the video’s thumbnail after selection.

Image

Fig. 13.3. Video playing in a VideoView with the device in landscape mode.

13.2. Test-Driving the Enhanced Slideshow App

Opening and Running the App

Open Eclipse and import the Enhanced Slideshow app project. To import the project:

1. Select File > Import... to display the Import dialog.

2. Expand the General node and select Existing Projects into Workspace, then click Next >.

3. To the right of the Select root directory: textfield, click Browse... then locate and select the EnhancedSlideshow folder.

4. Click Finish to import the project.

Right click the app’s project in the Package Explorer window, then select Run As > Android Application from the menu that appears.

Adding Video to Your AVD

Follow the steps in Section 12.2 for adding images and audio to your AVD to add the sample video that we provide in the video folder with the book’s examples. [Note: Again, the emulator does not support video well, so it’s best to test this app on a device if possible.]

Adding and Editing a New Slideshow

As in Chapter 12, touch the device’s menu button then the New Slideshow menu item to display the Set Slideshow Name dialog. Name the slideshow, then touch Set Name to create the new slideshow and display the Slideshow Editor.

Edit the slideshow as you did in Chapter 12. In this version of the app, be sure to test adding a video and taking a new picture. When you finish editing the slideshow and touch the Done button, the app returns to the main Slideshow Activity, which saves the slideshow to the device. [Note: This app saves the slideshow when the user returns to the app’s main screen after editing the slideshow. The app could certainly be configured to save as changes are made to a slideshow in the Slideshow Editor.]

Playing a Sideshow

During playback, when the SlideshowPlayer Activity encounters a video, it plays the video in a VideoView while the slideshow’s music continues to play in the background.

13.3. Technologies Overview

This section presents the new technologies that we use in the Enhanced Slideshow app.

File Processing and Object Serialization

The app stores slideshows on the device for viewing later. Earlier apps showed techniques for saving text data in key–value pairs. This app stores entire SlideshowInfo objects using object serialization (Section 13.5.3). A serialized object is represented as a sequence of bytes that includes the object’s data and information about the object’s type.

The serialization capabilities are located in package java.io. To use an object with the serialization mechanism, the object’s class must implement the Serializable interface, which is a tagging interface. Such an interface does not contain methods. Objects of a class that implements Serializable are tagged as being Serializable objects—that is, any object of a class that implements Serializable is a Serializable object. An ObjectOutputStream serializes Serializable objects to a specified OutputSteam—in this app, a FileOutputStream. Tagging objects as Serializable is important, because an ObjectOutputStream output only Serializable objects.

This app serializes the entire List of SlideshowInfo objects by passing the List to ObjectOutputStream’s writeObject method. This method creates an object graph that contains the List object, all of the SlideshowInfo objects referenced by the List, all of the objects that those SlideshowInfo objects reference, and so on. If any object in the graph is not Serializable, a NotSerializableException occurs, so it’s important to check the class descriptions for library classes in the online documentation to determine whether the objects you’re trying to serialize implement Serializable directly or by inheriting that relationship from a superclass in the hierarchy.

A serialized object can be read from a file and deserialized—that is, the type information and bytes that represent the object and its data can be used to recreate the object graph in memory. This is accomplished with an ObjectInputStream that reads the bytes from a specified InputStream—a FileInputStream in this app. ObjectOutputStream’s readObject method returns the deserialized object as type Object. To use it in the app, you must cast the object to the appropriate type—in this app, List<SlideshowInfo>.

Using a Rear Facing Camera to Take Pictures and Store Them in the Device’s Gallery

The Enhanced Slideshow app allows the user to take a new picture using the device’s rear facing camera, store that picture in the device’s Gallery and add the new picture to the slideshow. In Section 13.5.5, we use class Camera (package android.hardware)and a SurfaceView (package android.view) to display a preview of the picture the user is about to take. When the user touches the screen, our PictureTaker Activity tells the Camera to take the picture, then a Camera.PictureCallback object is notified that the picture was taken. We capture the image data, store it in the Gallery and return its Uri to the SlideshowEditor Activity, which adds the new image to the slideshow. The PictureTaker Activity also provides a menu in which the user can select from the Camera’s list of supported color effects. The default is to take a color picture, but the cameras in today’s devices support color effects, such as black and white, sepia and photo negative. You obtain the list of supported effects from a Camera.Parameters object associated with the Camera. Note that we could have launched the built-in camera Activity to allow the user to take pictures, but we wanted to demonstrate how to use camera features directly. You can use the built-in camera Activity, as follows:

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intput.putExtra(MediaStore.EXTRA_OUTPUT, storageURI);
startActivityForResult(intent, requestCode);

in which storageURI indicates where to save the photo. Then, you can override onActivityResult to check for requestCode and process the results returned from the built-in camera Activity.

Selecting Videos to Play in the Slideshow

The Slideshow app used an Intent to launch an Activity for choosing an image from the Gallery. We use the same technique in this app (Section 13.5.4) to allow the user to select videos, but specify a different MIME type for the data so that only videos are displayed.

Playing Videos with a VideoView

The Enhanced Slideshow app’s SlideshowPlayer Activity (Section 13.5.6) uses a VideoView to play a slideshow’s videos. We’ll specify the VideoView’s video URI to indicate the location of the video to play and MediaController (package android.widget) to provide video playback controls. The VideoView maintains its own MediaPlayer to play the video. We’ll use a MediaPlayer.OnCompletionListener to determine when the video finishes playing so we can continue playing the slideshow with the next image or video.

13.4. Building the GUI and Resource Files

In this section, we discuss the Enhanced Slideshow app’s changes to the resources and GUI layouts from Chapter 12’s Slideshow app. Once again, you can view the complete contents of the resource files by opening them in Eclipse.

13.4.1. Creating the Project

Rather than creating this app from scratch, you can copy Chapter 12’s Slideshow app and rename it as follows:

1. Copy the Slideshow folder and name the new folder EnhancedSlideshow.

2. Import the project from the EnhancedSlideshow folder into Eclipse.

3. Expand the project’s src node

4. Right click the package com.deitel.slideshow and select Refactor > Rename....

5. In the Rename Package dialog, enter com.deitel.enhancedslideshow, then click Preview >.

6. Click OK to change the package name throughout the project.

7. In the strings.xml resource file, change the value of the app_name String resource "Enhanced Slideshow".

13.4.2. AndroidManifest.xml

Figure 13.4 shows this app’s AndroidManifest.xml file. We’ve added an activity element for the new PictureTaker Activity (lines 26–29) and indicated that this app requires the WRITE_EXTERNAL_STORAGE and CAMERA permissions.


 1   <?xml version="1.0" encoding="utf-8"?>
 2   <manifest xmlns:android="http://schemas.android.com/apk/res/android"
 3      package="com.deitel.enhancedslideshow" android:versionCode="1"
 4      android:versionName="1.0">
 5      <application android:icon="@drawable/icon"
 6         android:label="@string/app_name"
 7         android:debuggable="true">
 8         <activity android:name=".Slideshow"
 9            android:label="@string/app_name"
10            android:screenOrientation="portrait"
11            android:theme="@android:style/Theme.Light">
12            <intent-filter>
13               <action android:name="android.intent.action.MAIN" />
14               <category android:name="android.intent.category.LAUNCHER" />
15            </intent-filter>
16         </activity>
17
18         <activity android:name=".SlideshowEditor"
19            android:label="@string/slideshow_editor"
20            android:screenOrientation="portrait"></activity>
21
22         <activity android:name=".SlideshowPlayer"
23            android:label="@string/app_name"
24            android:theme="@android:style/Theme.NoTitleBar"></activity>
25
26         <activity android:name=".PictureTaker"                       
27            android:label="@string/app_name"                          
28            android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
29            android:screenOrientation="landscape"></activity>         
30      </application>
31      <uses-sdk android:minSdkVersion="8" />
32      <uses-permission                                            
33         android:name="android.permission.WRITE_EXTERNAL_STORAGE">
34      </uses-permission>                                          
35      <uses-permission android:name="android.permission.CAMERA">  
36      </uses-permission>                                          
37   </manifest>


Fig. 13.4. AndroidManifest.xml.

13.4.3. SlideshowEditor ListActivity’s Modified Layout

Figure 13.5 diagrams the modified layout for the SlideshowEditor ListActivity, which now contains two rows of Buttons in a TableLayout at the top of the GUI.

Image

Fig. 13.5. Modified layout for the SlideshowEditor ListActivity—defined in slideshow_editor.xml.

13.4.4. PictureTaker Activity’s Layout

Figure 13.5 shows the PictureTaker Activity’s XML layout (camera_preview.xml), which consists of a SurfaceView that fills the screen. The SurfaceView will display the camera’s preview image when the user is preparing to take a picture.


 1   <?xml version="1.0" encoding="utf-8"?>
 2   <SurfaceView xmlns:android="http://schemas.android.com/apk/res/android"
 3      android:id="@+id/cameraSurfaceView" android:layout_width="match_parent"
 4      android:layout_height="match_parent">
 5   </SurfaceView>


Fig. 13.6. PictureTaker Activity’s layout—camera_preview.xml.

13.4.5. SlideshowPlayer Activity’s Modified Layout

Figure 13.7 shows slideshow_player.xml—the SlideshowPlayer Activity’s modified XML layout. In this app, we display an ImageView or a VideoView, depending on whether the current item in the slideshow is an image or a video, respectively. For this reason, we chose a FrameLayout with both the ImageView and the VideoView occupying the entire screen. We programmatically show and hide these Views based on what needs to be displayed at a given time.


 1   <?xml version="1.0" encoding="utf-8"?>
 2   <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3      android:layout_width="match_parent"
 4      android:layout_height="match_parent">
 5      <ImageView android:id="@+id/imageView" android:scaleType="centerInside"
 6         android:layout_width="match_parent"
 7         android:layout_height="match_parent"
 8         android:layout_gravity="center"></ImageView>
 9      <VideoView android:id="@+id/videoView" android:layout_gravity="center"
10         android:layout_width="match_parent"
11         android:layout_height="match_parent"></VideoView>
12   </FrameLayout>


Fig. 13.7. Modified layout for the SlideshowPlayer Activity—defined in slideshow_editor.xml.

13.5. Building the App

This app consists of classes MediaItem (Fig. 13.8), SlideshowInfo (Fig. 13.9), Slideshow (Figs. 13.1013.15), SlideshowEditor (Figs. 13.1613.18), PictureTaker (Figs. 13.1913.24) and SlideshowPlayer (Figs. 13.2513.27). For the classes that are modified from Chapter 12, we show only what has changed.


 1   // MediaItem.java
 2   // Represents an image or video in a slideshow.
 3   package com.deitel.enhancedslideshow;
 4
 5   import java.io.Serializable;
 6
 7   public class MediaItem implements Serializable
 8   {
 9      private static final long serialVersionUID = 1L; // class's version #
10
11      // constants for media types
12      public static enum MediaType { IMAGE, VIDEO }
13
14      private final MediaType type; // this MediaItem is an IMAGE or VIDEO
15      private final String path; // location of this MediaItem            
16
17      // constructor
18      public MediaItem(MediaType mediaType, String location)
19      {
20         type = mediaType;
21         path = location;
22      } // end constructor
23
24      // get the MediaType of this image or video
25      public MediaType getType()
26      {
27         return type;
28      } // end method MediaType
29
30      // return the description of this image or video
31      public String getPath()
32      {
33         return path;
34      } // end method getDescription
35   } // end class MediaItem


Fig. 13.8. MediaItem class used to represent images and videos in a slideshow.

13.5.1. MediaItem Class

In the Slideshow app, we stored each image’s location in a List<String> that was maintained by the Slideshow ListActivity. This app allows the user to include images and video in the app, so we created class MediaItem (Fig. 13.8), which stores a MediaType and a String. The enum MediaType (line 12) contains constants for specifying whether the MediaItem represents an image or a video. Class SlideshowInfo (Section 13.5.2) maintains a List of MediaItems representing all the images and video in a slideshow. Because the Enhanced Slideshow app serializes SlideshowInfo objects so the user can play them in the future, class MediaItem implements interface Serializable.

13.5.2. SlideshowInfo Class

The SlideshowInfo class in this app (Fig. 13.9) has been modified to store a List<MediaItem> (line 13) representing image and video locations and the type of each item, rather than a List<String> representing just image locations. In addition, methods getImageList, addImage and getImageAt have been renamed as getMediaItemList (line 31), addMediaItem (line 37) and getMediaItemAt (line 43), respectively. Each method now manipulates MediaItems rather than Strings. To support serialization, class SlideshowInfo implements Serializable (line 9).


 1   // SlideshowInfo.java
 2   // Stores the data for a single slideshow.
 3   package com.deitel.enhancedslideshow;
 4
 5   import java.io.Serializable;
 6   import java.util.ArrayList;
 7   import java.util.List;
 8
 9   public class SlideshowInfo implements Serializable
10   {
11      private static final long serialVersionUID = 1L; // class's version #
12      private String name; // name of this slideshow
13      private List<MediaItem> mediaItemList; // this slideshow's images
14      private String musicPath; // location of music to play
15
16      // constructor
17      public SlideshowInfo(String slideshowName)
18      {
19         name = slideshowName; // set the slideshow name
20         mediaItemList = new ArrayList<MediaItem>();
21         musicPath = null; // currently there is no music for the slideshow
22      } // end SlideshowInfo constructor
23
24      // return this slideshow's name
25      public String getName()
26      {
27         return name;
28      } // end method getName
29
30      // return List of MediaItems pointing to the slideshow's images
31      public List<MediaItem> getMediaItemList()
32      {
33         return mediaItemList;
34      } // end method getMediaItemList
35
36      // add a new MediaItem
37      public void addMediaItem(MediaItem.MediaType type, String path)
38      {
39         mediaItemList.add(new MediaItem(type, path));
40      } // end method addMediaItem
41
42      // return MediaItem at position index
43      public MediaItem getMediaItemAt(int index)
44      {
45         if (index >= 0 && index < mediaItemList.size())
46            return mediaItemList.get(index);
47         else
48            return null;
49      } // end method getMediaItemAt
50
51      // return this slideshow's music
52      public String getMusicPath()
53      {
54         return musicPath;
55      } // end method getMusicPath
56
57      // set this slideshow's music
58      public void setMusicPath(String path)
59      {
60         musicPath = path;
61      } // end method setMusicPath
62
63      // return number of images/videos in the slideshow
64      public int size()
65      {
66         return mediaItemList.size();
67      } // end method size
68   } // end class SlideshowInfo


Fig. 13.9. Modified SlideshowInfo class stores a List of MediaItems.

13.5.3. Slideshow Class

In this app, we save the slideshows to the device for future playback. As discussed in Section 13.3, we use object serialization to save the slideshow information. Class Slideshow (Figs. 13.1013.15)—the app’s main Activity—has been modified to support saving and loading the List<SlideshowInfo> object. This section presents only the changes to class Slideshow.


 1   // Slideshow.java
 2   // Main Activity for the Slideshow class.
 3   package com.deitel.enhancedslideshow;
 4
 5   import java.io.File;              
 6   import java.io.FileInputStream;   
 7   import java.io.FileOutputStream;  
 8   import java.io.ObjectInputStream; 
 9   import java.io.ObjectOutputStream;
10   import java.util.ArrayList;
11   import java.util.List;
12
13   import android.app.AlertDialog;
14   import android.app.ListActivity;
15   import android.content.ContentResolver;
16   import android.content.Context;
17   import android.content.DialogInterface;
18   import android.content.Intent;
19   import android.graphics.Bitmap;
20   import android.graphics.BitmapFactory;
21   import android.net.Uri;
22   import android.os.AsyncTask;
23   import android.os.Bundle;
24   import android.provider.MediaStore;
25   import android.util.Log;
26   import android.view.Gravity;
27   import android.view.LayoutInflater;
28   import android.view.Menu;
29   import android.view.MenuInflater;
30   import android.view.MenuItem;
31   import android.view.View;
32   import android.view.View.OnClickListener;
33   import android.view.ViewGroup;
34   import android.widget.ArrayAdapter;
35   import android.widget.Button;
36   import android.widget.EditText;
37   import android.widget.ImageView;
38   import android.widget.ListView;
39   import android.widget.TextView;
40   import android.widget.Toast;
41
42
43   public class Slideshow extends ListActivity
44   {
45      private static final String TAG = "SLIDESHOW"; // error logging tag
46
47      // used when adding slideshow name as an extra to an Intent
48      public static final String NAME_EXTRA = "NAME";
49
50      static List<SlideshowInfo> slideshowList; // List of slideshows
51      private ListView slideshowListView; // this ListActivity's ListView
52      private SlideshowAdapter slideshowAdapter; // adapter for the ListView
53      private File slideshowFile; // File representing location of slideshows
54


Fig. 13.10. package and import statements, and instance variables for class Slideshow.

package and import Statements, and Fields

The Slideshow subclass of ListActivity (Fig. 13.10) has several new import statements and a new instance variable. The new features are highlighted. Lines 5–9 and 24 import classes that are used for the file processing and serialization in this app. The instance variable slideshowFile (line 53) represents the location of the app’s file on the device.

Overriding Activity Method onCreate

Slideshow’s onCreate method (Fig. 13.11) creates a File object (lines 63–65) representing the location where this app stores slideshows in the Android file system. The Context class provides methods for accessing the file system. Its method getExternalFilesDir returns a File representing an application-specific external storage directory on the device—typically an SD card, but it could be on the device itself if it does not support SD cards. Files you create in this location are automatically managed by the system—if you delete your app, its files are deleted as well. We call getAbsolutePath on the File object, then append /EnhancedSlideshowData.ser to create a path to the file in which this app will store the slideshows. (Keep in mind that a device’s external directory may not be available for many reasons that are outside of the control of your app—for example, the user could have removed the SD card.) Line 66 creates an object of our AsyncTask subclass LoadSlideshowsTask (Fig. 13.12) and calls its execute method to load previously saved slideshows (if any). The task does not require any arguments, so we pass null to execute.


55      // called when the activity is first created
56      @Override
57      public void onCreate(Bundle savedInstanceState)
58      {
59         super.onCreate(savedInstanceState);
60         slideshowListView = getListView(); // get the built-in ListView
61
62         // get File location and start task to load slideshows
63         slideshowFile = new File(                             
64            getExternalFilesDir(null).getAbsolutePath() +      
65               "/EnhancedSlideshowData.ser");                  
66         new LoadSlideshowsTask().execute((Object[]) null);    
67
68         // create a new AlertDialog Builder
69         AlertDialog.Builder builder = new AlertDialog.Builder(this);
70         builder.setTitle(R.string.welcome_message_title);
71         builder.setMessage(R.string.welcome_message);
72         builder.setPositiveButton(R.string.button_ok, null);
73         builder.show();
74      } // end method onCreate
75


Fig. 13.11. Overriding Activity method onCreate in class Slideshow.

LoadSlideshowsTask Subclass of AsyncTask

The doInBackground method of class LoadSlideshowsTask (Fig. 13.12) checks whether the EnhancedSlideshowData.ser file exists (line 84) and, if so, creates an ObjectInputStream (lines 88–89). Line 90 calls ObjectInputStream method readObject to read the List<SlideshowInfo> object from the slideshowFile. If the file does not exist, or there is an exception when reading from the file, line 115 creates a new List<SlideshowInfo> object. If an exception occurs, lines 94–110 use Activity method runOnUiThread to display a Toast from the UI thread indicating the problem. When the background task completes, method onPostExecute (lines 121–130) is called on the UI thread to set up the Slideshow’s ListView adapter.


76      // Class to load the List<SlideshowInfo> object from the device
77      private class LoadSlideshowsTask extends AsyncTask<Object,Object,Object>
78      {
79         // load from non-GUI thread
80         @Override
81         protected Object doInBackground(Object... arg0)
82         {
83            // if the file exists, read the file; otherwise, create it
84            if (slideshowFile.exists() )
85            {
86               try
87               {
88                  ObjectInputStream input = new ObjectInputStream(         
89                     new FileInputStream(slideshowFile));                  
90                  slideshowList = (List<SlideshowInfo>) input.readObject();
91               } // end try
92               catch (final Exception e)
93               {
94                  runOnUiThread(
95                     new Runnable()
96                     {
97                        public void run()
98                        {
99                           // display error reading message
100                          Toast message = Toast.makeText(Slideshow.this,
101                             R.string.message_error_reading,
102                             Toast.LENGTH_LONG);
103                          message.setGravity(Gravity.CENTER,
104                             message.getXOffset() / 2,
105                             message.getYOffset() / 2);
106                          message.show(); // display the Toast
107                          Log.v(TAG, e.toString());
108                       } // end method run
109                    } // end Runnable
110                 ); // end call to runOnUiThread
111              } // end catch
112           } // end if
113
114           if (slideshowList == null) // if null, create it
115              slideshowList = new ArrayList<SlideshowInfo>();
116
117           return (Object) null; // method must satisfy the return type
118        } // end method doInBackground
119
120        // create the ListView's adapter on the GUI thread
121        @Override
122        protected void onPostExecute(Object result)
123        {
124           super.onPostExecute(result);
125
126           // create and set the ListView's adapter
127           slideshowAdapter =
128              new SlideshowAdapter(Slideshow.this, slideshowList);
129           slideshowListView.setAdapter(slideshowAdapter);
130        } // end method onPostEecute
131     } // end class LoadSlideshowsTask
132


Fig. 13.12. Class LoadSlideshowsTask deserializes the List<SlideshowInfo> object from a file or creates the object if the file does not exist.

SaveSlideshowsTask Subclass of AsyncTask

The doInBackground method of class SaveSlideshowsTask (Fig. 13.13) checks whether the EnhancedSlideshowData.ser file exists (line 143) and, if not, creates the file. Next, lines 147–148 create an ObjectOutputStream. Line 149 calls ObjectOutputStream method writeObject to write the List<SlideshowInfo> object into slideshowFile. If an exception occurs, lines 154–169 use Activity method runOnUiThread to display a Toast from the UI thread indicating the problem.


133     // Class to save the List<SlideshowInfo> object to the device
134     private class SaveSlideshowsTask extends
AsyncTask<Object,Object,Object>
135     {
136        // save from non-GUI thread
137        @Override
138        protected Object doInBackground(Object... arg0)
139        {
140           try
141           {
142              // if the file doesn't exist, create it
143              if (!slideshowFile.exists())     
144                 slideshowFile.createNewFile();
145
146              // create ObjectOutputStream, then write slideshowList to it
147              ObjectOutputStream output = new ObjectOutputStream(         
148                 new FileOutputStream(slideshowFile));                    
149              output.writeObject(slideshowList);                          
150              output.close();                                             
151           } // end try
152           catch (final Exception e)
153           {
154              runOnUiThread(
155                 new Runnable()
156                 {
157                    public void run()
158                    {
159                       // display error reading message
160                       Toast message = Toast.makeText(Slideshow.this,
161                          R.string.message_error_writing, Toast.LENGTH_LONG);
162                       message.setGravity(Gravity.CENTER,
163                          message.getXOffset() / 2,
164                          message.getYOffset() / 2);
165                       message.show(); // display the Toast
166                       Log.v(TAG, e.toString());
167                    } // end method run
168                 } // end Runnable
169              ); // end call to runOnUiThread
170           } // end catch
171
172           return (Object) null; // method must satisfy the return type
173        } // end method doInBackground
174     } // end class SaveSlideshowsTask
175


Fig. 13.13. Class SaveSlideshowsTask serializes the List<SlideshowInfo> object to a file.

Overriding Activity Method onActivityResult

Method onActivityResult (Fig. 13.14) has been changed to save the List<SlideshowInfo> object once the user returns from editing a slideshow. To do so, line 251 creates an object of the AsyncTask subclass SaveSlideshowsTask (Fig. 13.13) and invokes its execute method.


176     // refresh ListView after slideshow editing is complete
177     @Override
178     protected void onActivityResult(int requestCode, int resultCode,
179        Intent data)
180     {
181        super.onActivityResult(requestCode, resultCode, data);
182        new SaveSlideshowsTask().execute((Object[]) null); // save slideshows
183        slideshowAdapter.notifyDataSetChanged(); // refresh the adapter
184     } // end method onActivityResult
185


Fig. 13.14. Overriding Activity methods onCreateOptionsMenu, onOptionsItemSelected and onActivityResult.

Method getThumbnail

Method getThumbnail (Fig. 13.15) has been updated to support loading thumbnails for both images and videos (lines 439–454).


186     // utility method to get a thumbnail image Bitmap
187     public static Bitmap getThumbnail(MediaItem.MediaType type, Uri uri,
188        ContentResolver cr, BitmapFactory.Options options)
189     {
190        Bitmap bitmap = null;
191        int id = Integer.parseInt(uri.getLastPathSegment());
192
193        if (type == MediaItem.MediaType.IMAGE) // if it is an image
194           bitmap = MediaStore.Images.Thumbnails.getThumbnail(cr, id,
195              MediaStore.Images.Thumbnails.MICRO_KIND, options);
196        else if (type == MediaItem.MediaType.VIDEO) // if it is a video
197           bitmap = MediaStore.Video.Thumbnails.getThumbnail(cr, id,
198              MediaStore.Video.Thumbnails.MICRO_KIND, options);     
199
200        return bitmap;
201     } // end method getThumbnail


Fig. 13.15. Method getThumbnail updated to return an image thumbnail or a video thumbnail.

13.5.4. SlideshowEditor Class

Class SlideshowEditor (Figs. 13.1613.18) now supports taking a picture and selecting videos to include in a slideshow. This section shows the changes required to support these new features.


202     // set IDs for each type of media result
203     private static final int PICTURE_ID = 1;
204     private static final int MUSIC_ID = 2;
205     private static final int VIDEO_ID = 3;       
206     private static final int TAKE_PICTURE_ID = 4;
207
208     // called when an Activity launched from this Activity returns
209     @Override
210     protected final void onActivityResult(int requestCode, int resultCode,
211        Intent data)
212     {
213        if (resultCode == RESULT_OK) // if there was no error
214        {
215           Uri selectedUri = data.getData();
216
217           // if the Activity returns an image
218           if (requestCode == PICTURE_ID ||
219              requestCode == TAKE_PICTURE_ID || requestCode == VIDEO_ID )
220           {
221              // determine media type
222              MediaItem.MediaType type = (requestCode == VIDEO_ID ?     
223                 MediaItem.MediaType.VIDEO : MediaItem.MediaType.IMAGE);
224
225              // add new MediaItem to the slideshow
226              slideshow.addMediaItem(type, selectedUri.toString());
227
228              // refresh the ListView
229              slideshowEditorAdapter.notifyDataSetChanged();
230           } // end if
231           else if (requestCode == MUSIC_ID) // Activity returns music
232              slideshow.setMusicPath(selectedUri.toString());
233        } // end if
234     } // end method onActivityResult


Fig. 13.16. Updated constants and method onActivityResult.

Overriding Activity Method onActivityResult

Class SlideshowEditor contains two more Buttons that initiate selecting a video and taking a picture, respectively. For this reason, we’ve added the constants at lines 71–72 (Fig. 13.16) which are passed to Activity method startActivityForResult then returned to method onActivityResult to identify which Activity returned the result. Method onActivityResult has been modified to use these constants to process the Uri that’s returned for the picture or video.

Event Listeners for the takePictureButton and addVideoButton

Figure 13.17 presents the event handlers for the takePictureButton (lines 128–141) and the addVideoButton (lines 144–155). To select a video, the addVideoButtonListener uses the same techniques shown in Fig. 12.29, but sets the MIME type to "video/*" so that the user can select from the videos stored on the device.


235     // called when the user touches the "Take Picture" Button
236     private OnClickListener takePictureButtonListener =
237        new OnClickListener()
238        {
239           // launch image choosing activity
240           @Override
241           public void onClick(View v)
242           {
243              // create new Intent to launch the Slideshowplayer Activity
244              Intent takePicture =
245                 new Intent(SlideshowEditor.this, PictureTaker.class);
246
247              startActivityForResult(takePicture, TAKE_PICTURE_ID);
248           } // end method onClick
249        }; // end OnClickListener takePictureButtonListener
250
251     // called when the user touches the "Add Picture" Button
252     private OnClickListener addVideoButtonListener = new OnClickListener()
253     {
254        // launch image choosing activity
255        @Override
256        public void onClick(View v)
257        {
258           Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
259           intent.setType("video/*");
260           startActivityForResult(Intent.createChooser(intent,
261              getResources().getText(R.string.chooser_video)), VIDEO_ID);
262        } // end method onClick
263     }; // end OnClickListener addVideoButtonListener


Fig. 13.17. Event Listeners for the takePictureButton and addVideoButton.

Updated LoadThumbnailTask Sublcass of AsyncTask

Class LoadThumbnailTask (Fig. 13.18) has been updated to pass the MediaItem’s type to Slideshow method getThumbnail, which returns a thumbnail Bitmap for the specified image or video.


264     // task to load thumbnails in a separate thread
265     private class LoadThumbnailTask extends AsyncTask<Object,Object,Bitmap>
266     {
267        ImageView imageView; // displays the thumbnail
268
269        // load thumbnail: ImageView, MediaType and Uri as args
270        @Override
271        protected Bitmap doInBackground(Object... params)
272        {
273           imageView = (ImageView) params[0];
274
275           return Slideshow.getThumbnail((MediaItem.MediaType)params[1],
276              (Uri) params[2], getContentResolver(),                    
277              new BitmapFactory.Options());                             
278        } // end method doInBackground
279
280        // set thumbnail on ListView
281        @Override
282        protected void onPostExecute(Bitmap result)
283        {
284           super.onPostExecute(result);
285           imageView.setImageBitmap(result);
286        } // end method onPostExecute
287     } // end class LoadThumbnailTask


Fig. 13.18. Class LoadThumbnailTask loads image or video thumbnails in a separate thread.

13.5.5. PictureTaker Subclass of Activity

Class PictureTaker (Figs. 13.1913.24) allows the user to take a picture that will be added to the slideshow. While previewing the picture, the user can touch the screen to take the picture.


 1   // PictureTaker.java
 2   // Activity for taking a picture with the device's camera
 3   package com.deitel.enhancedslideshow;
 4
 5   import java.io.IOException;
 6   import java.io.OutputStream;
 7   import java.util.List;
 8
 9   import android.app.Activity;
10   import android.content.ContentValues;
11   import android.content.Intent;
12   import android.hardware.Camera;
13   import android.net.Uri;
14   import android.os.Bundle;
15   import android.provider.MediaStore.Images;
16   import android.util.Log;
17   import android.view.Gravity;
18   import android.view.Menu;
19   import android.view.MenuItem;
20   import android.view.MotionEvent;
21   import android.view.SurfaceHolder;
22   import android.view.SurfaceView;  
23   import android.view.View;
24   import android.view.View.OnTouchListener;
25   import android.widget.Toast;
26
27   public class PictureTaker extends Activity
28   {
29      private static final String TAG = "PICTURE_TAKER"; // for logging errors
30
31      private SurfaceView surfaceView; // used to display camera preview     
32      private SurfaceHolder surfaceHolder; // manages the SurfaceView changes
33      private boolean isPreviewing; // is the preview running?
34
35      private Camera camera; // used to capture image data
36      private List<String> effects; // supported color effects for camera
37      private List<Camera.Size> sizes; // supported preview sizes for camera
38      private String effect = Camera.Parameters.EFFECT_NONE; // default effec
39


Fig. 13.19. PictureTaker package statement, import statements and fields.

package and import Statements, and Instance Variables of Class SlideshowEditor

Figure 13.19 begins the definition of class PictureTaker. We’ve highlighted the import statements for the new classes and interfaces discussed in Section 13.3 and used in this sction. Lines 31–32 declare the SurfaceView that displays the live camera-preview image and the SurfaceHolder that manages the SurfaceView. Line 35 declares a Camera, which provides access to the device’s camera hardware. The List<String> named effects (line 36) stores the camera’s supported color effects—we’ll use this to populate a menu from which the user can select the effect to apply to the picture (such as black and white, sepia, etc.). The List<Camera.Size> named sizes (line 37) stores the camera’s supported image-preview sizes—we’ll use the first supported size for the image preview in this app. The String effect is initialized to Camera.Parameter’s EFFECT_NONE constant to indicate that no color effect is selected.

Overriding Activity Method onCreate

Method onCreate (Fig. 13.20) prepares the view to display a photo preview, much like Android’s actual Camera app. First we create the SurfaceView and register a listener for its touch events—when the user touches the screen, the PictureTaker Activity will capture the picture and store it in the device’s gallery. Next, we create the SurfaceHolder and register an object to handle its Callbacks—these occur when the SurfaceView being managed is created, changed or destroyed. Finally, prior to Android 3.0 line 56 was required. SurfaceHolder method setType and its constant argument are now both deprecated and will simply be ignored in Android 3.0 and higher.


40      // called when the activity is first created
41      @Override
42      public void onCreate(Bundle bundle)
43      {
44         super.onCreate(bundle);
45         setContentView(R.layout.camera_preview); // set the layout
46
47         // initialize the surfaceView and set its touch listener
48         surfaceView = (SurfaceView) findViewById(R.id.cameraSurfaceView);
49         surfaceView.setOnTouchListener(touchListener);                   
50
51         // initialize surfaceHolder and set object to handles its callbacks
52         surfaceHolder = surfaceView.getHolder();   
53         surfaceHolder.addCallback(surfaceCallback);
54
55         // required before Android 3.0 for camera preview
56         surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
57      } // end method onCreate
58


Fig. 13.20. Overriding Activity method onCreate in class PictureTaker.

Overriding Activity Methods onCreateOptionsMenu and onOptionsItemSelected

Method onCreateOptionsMenu (Fig. 13.21, lines 60–70) displays the list of the camera’s supported color effects in a menu. When the user selects one of these options, method onOptionsItemSelected gets the camera’s Camera.Parameter object (line 76) then uses its setColorEffect method to set the effect. Line 78 uses the camera’s setParameters method to reconfigure the camera. At this point, the selected color effect is applied to the camera preview image on the device’s screen.


59      // create the Activity's menu from list of supported color effects
60      @Override
61      public boolean onCreateOptionsMenu(Menu menu)
62      {
63         super.onCreateOptionsMenu(menu);
64
65         // create menu items for each supported effect
66         for (String effect : effects)
67            menu.add(effect);
68
69         return true;
70      } // end method onCreateOptionsMenu
71
72      // handle choice from options menu
73      @Override
74      public boolean onOptionsItemSelected(MenuItem item)
75      {
76         Camera.Parameters p = camera.getParameters(); // get parameters  
77         p.setColorEffect(item.getTitle().toString()); // set color effect
78         camera.setParameters(p); // apply the new parameters             
79         return true;
80      } // end method onOptionsItemSelected
81


Fig. 13.21. Overriding Activity methods onCreateOptionsMenu and onOptionsItemSelected.

Handling the SurfaceHolder’s Callbacks

When the SurfaceView is created, changed or destroyed, its SurfaceHolder’s Callback methods are called. Figure 13.22 presents the anonymous inner class that implements SurfaceHolder.Callback.


82      // handles SurfaceHolder.Callback events
83      private SurfaceHolder.Callback surfaceCallback =
84         new SurfaceHolder.Callback()
85         {
86            // release resources after the SurfaceView is destroyed
87            @Override
88            public void surfaceDestroyed(SurfaceHolder arg0)
89            {
90               camera.stopPreview(); // stop the Camera preview
91               isPreviewing = false;
92               camera.release(); // release the Camera's Object resources
93            } // end method surfaceDestroyed
94
95            // initialize the camera when the SurfaceView is created
96            @Override
97            public void surfaceCreated(SurfaceHolder arg0)
98            {
99               // get camera and its supported color effects/preview sizes
100              camera = Camera.open(); // defaults to back facing camera   
101              effects = camera.getParameters().getSupportedColorEffects();
102              sizes = camera.getParameters().getSupportedPreviewSizes();  
103           } // end method surfaceCreated
104
105           @Override
106           public void surfaceChanged(SurfaceHolder holder, int format,
107              int width, int height)                                   
108           {
109              if (isPreviewing) // if there's already a preview running
110                 camera.stopPreview(); // stop the preview
111
112              // configure and set the camera parameters
113              Camera.Parameters p = camera.getParameters();               
114              p.setPreviewSize(sizes.get(0).width, sizes.get(0).height);  
115              p.setColorEffect(effect); // use the current selected effect
116              camera.setParameters(p); // apply the new parameters        
117
118              try
119              {
120                 camera.setPreviewDisplay(holder); // display using holder
121              } // end try
122              catch (IOException e)
123              {
124                 Log.v(TAG, e.toString());
125              } // end catch
126
127              camera.startPreview(); // begin the preview
128              isPreviewing = true;
129           } // end method surfaceChanged
130        }; // end SurfaceHolder.Callback
131


Fig. 13.22. PictureTaker package statement, import statements and fields.

SurfaceHolder.Callback’s surfaceDestroyed method (lines 88–93) stops the photo preview and releases the Camera’s resources. We use SurfaceHolder.Callback’s surfaceCreated method (lines 96–103) to get a Camera and its supported features. Camera’s static open method gets a Camera object that allows the app to use the device’s rear facing camera. Next, we use the Camera’s Parameters object to get the List<String> representing the camera’s supported effects and the List<Camera.Size> representing the supported preview image sizes. [Note: We did not catch the open method’s possible RuntimeException that occurs if the camera is not available.]

The SurfaceHolder.Callback interface’s surfaceChanged method (lines 105–129) is called each time the size or format of the SurfaceView changes—typically when the device is rotated and when the SurfaceView is first created and displayed. (In the manifest, we’ve disabled rotation for this Activity.) Line 109 checks if the camera preview is running and if so stops it using Camera’s stopPreview method. Next, we get the Camera’s Parameters then call the setPreviewSize method to set the camera’s preview size using the width and height of the first object in sizes (the List<Camera.Size> containing the supported preview sizes). We call setColorEffect to apply the current color effect to the preview (and any photos to be taken). We then reconfigure the Camera by calling its setParameters method to apply the changes. Line 120 passes the SurfaceHolder to Camera’s setPreviewDisplay method—this indicates that the preview will be displayed on our SurfaceView. Line 127 then starts the preview using Camera’s startPreview method.

Handling the Camera’s PictureCallbacks

Figure 13.23 defines the Camera.PictureCallback anonymous class that receives the image data after the user takes a picture. Method onPictureTaken takes a byte array containing the picture data and the Camera that was used to take the picture. In this example, the imageData byte array stores the JPEG format version of the picture, so we can simply save the imageData array to the device (lines 154–158). Lines 161–163 create a new Intent and use its setData method to specify the Uri of the saved image as the data to return from this Activity. Activity method setResult (line 163) is used to indicate that there was no error and set the returnIntent as the result. The SlideshowEditor Activity will use this Intent’s data to store the image in the slideshow and load the corresponding thumbnail image.


132     // handles Camera callbacks
133     Camera.PictureCallback pictureCallback = new Camera.PictureCallback()
134     {
135        // called when the user takes a picture
136        public void onPictureTaken(byte[] imageData, Camera c)
137        {
138           // use "Slideshow_" + current time in ms as new image file name
139           String fileName = "Slideshow_" + System.currentTimeMillis();
140
141           // create a ContentValues and configure new image's data
142           ContentValues values = new ContentValues();
143           values.put(Images.Media.TITLE, fileName);
144           values.put(Images.Media.DATE_ADDED, System.currentTimeMillis());
145           values.put(Images.Media.MIME_TYPE, "image/jpg");
146
147           // get a Uri for the location to save the file
148           Uri uri = getContentResolver().insert(
149              Images.Media.EXTERNAL_CONTENT_URI, values);
150
151           try
152           {
153              // get an OutputStream to uri
154              OutputStream outStream =
155                 getContentResolver().openOutputStream(uri);
156              outStream.write(imageData); // output the image
157              outStream.flush(); // empty the buffer
158              outStream.close(); // close the stream
159
160              // Intent for returning data to SlideshowEditor
161              Intent returnIntent = new Intent();
162              returnIntent.setData(uri); // return Uri to SlideshowEditor 
163              setResult(RESULT_OK, returnIntent); // took pic successfully
164
165              // display a message indicating that the image was saved
166              Toast message = Toast.makeText(PictureTaker.this,
167                 R.string.message_saved, Toast.LENGTH_SHORT);
168              message.setGravity(Gravity.CENTER, message.getXOffset() / 2,
169                 message.getYOffset() / 2);
170              message.show(); // display the Toast
171
172              finish(); // finish and return to SlideshowEditor
173           } // end try
174           catch (IOException ex)
175           {
176              setResult(RESULT_CANCELED); // error taking picture
177
178              // display a message indicating that the image was saved
179              Toast message = Toast.makeText(PictureTaker.this,
180                 R.string.message_error_saving, Toast.LENGTH_SHORT);
181              message.setGravity(Gravity.CENTER, message.getXOffset() / 2,
182                 message.getYOffset() / 2);
183              message.show(); // display the Toast
184           } // end catch
185        } // end method onPictureTaken
186     }; // end pictureCallback
187


Fig. 13.23. Implementing Camera.PictureCallback to save a picture.

Handling the SurfaceView’s Touch Events

The onTouch method (Fig. 13.24) takes a picture when the user touches the screen. Camera’s takePicture method (line 195) asynchronously takes a picture with the device’s camera. This method receives several listeners as arguments. The first is an instance of Camera.ShutterCallback that’s notified just after the image is captured. This is the ideal place to provide visual or audio feedback that the picture was taken. We don’t use this callback in the app, so we pass null as the first argument. The last two listeners are instances of Camera.PictureCallback that enable the app to receive and process the RAW image data (i.e., uncompressed image data) and JPEG image data, respectively. We don’t use the RAW data in this app, so takePicture’s second argument is also null. The third call back uses our pictureCallback (Fig. 13.23) to process the JPEG image.


188     // takes picture when user touches the screen
189     private OnTouchListener touchListener = new OnTouchListener()
190     {
191        @Override
192        public boolean onTouch(View v, MotionEvent event)
193        {
194           // take a picture
195           camera.takePicture(null, null, pictureCallback);
196           return false;
197        } // end method onTouch
198     }; // end touchListener
199  } // end class PictureTaker


Fig. 13.24. Implementing OnTouchListener to handle touch events.

13.5.6. SlideshowPlayer Class

The SlideshowPlayer Activity (Figs. 13.2513.27) plays a slideshow with accompanying background music. We’ve updated SlideshowPlayer to play any videos that are included in the slideshow. This section shows only the parts of the class that have changed.


 1   // SlideshowPlayer.java
 2   // Plays the selected slideshow that's passed as an Intent extra
 3   package com.deitel.enhancedslideshow;
 4
 5   import java.io.FileNotFoundException;
 6   import java.io.InputStream;
 7
 8   import android.app.Activity;
 9   import android.content.ContentResolver;
10   import android.graphics.Bitmap;
11   import android.graphics.BitmapFactory;
12   import android.graphics.drawable.BitmapDrawable;
13   import android.graphics.drawable.Drawable;
14   import android.graphics.drawable.TransitionDrawable;
15   import android.media.MediaPlayer;                     
16   import android.media.MediaPlayer.OnCompletionListener;
17   import android.net.Uri;
18   import android.os.AsyncTask;
19   import android.os.Bundle;
20   import android.os.Handler;
21   import android.util.Log;
22   import android.view.View;
23   import android.widget.ImageView;
24   import android.widget.MediaController;
25   import android.widget.VideoView;
26
27   public class SlideshowPlayer extends Activity
28   {
29      private static final String TAG = "SLIDESHOW"; // error logging tag
30
31      // constants for saving slideshow state when config changes
32      private static final String MEDIA_TIME = "MEDIA_TIME";
33      private static final String IMAGE_INDEX = "IMAGE_INDEX";
34      private static final String SLIDESHOW_NAME = "SLIDESHOW_NAME";
35
36      private static final int DURATION = 5000; // 5 seconds per slide
37      private ImageView imageView; // displays the current image
38      private VideoView videoView; // displays the current video
39      private String slideshowName; // name of current slideshow
40      private SlideshowInfo slideshow; // slideshow being played
41      private BitmapFactory.Options options; // options for loading images
42      private Handler handler; // used to update the slideshow
43      private int nextItemIndex; // index of the next image to display
44      private int mediaTime; // time in ms from which media should play
45      private MediaPlayer mediaPlayer; // plays the background music, if any
46


Fig. 13.25. SlideshowPlayer package statement, import statements and fields.

package and import Statements, and Instance Variables of Class SlideshowEditor

Figure 13.25 begins class SlideshowPlayer. We’ve highlighted the import statements for the new classes and interfaces discussed in Section 13.3 and used in this section. Variable videoView is used to manipulate the VideoView on which videos are played.

Overriding Activity Method onCreate

Lines 55–65 are the only changes in method onCreate (Fig. 13.26). Line 55 gets the layout’s VideoView, then lines 56–65 register its OnCompletionListener, which is notified when a video in the VideoView completes playing. Method onCompletion calls the Handler’s postUpdate method and passes the updateSlideshow Runnable as an argument to process the next image or video in the slideshow.


47      // initializes the SlideshowPlayer Activity
48      @Override
49      public void onCreate(Bundle savedInstanceState)
50      {
51         super.onCreate(savedInstanceState);
52         setContentView(R.layout.slideshow_player);
53
54         imageView = (ImageView) findViewById(R.id.imageView);
55         videoView = (VideoView) findViewById(R.id.videoView);             
56         videoView.setOnCompletionListener( // set video completion handler
57            new OnCompletionListener()                                     
58            {                                                              
59              @Override                                                    
60              public void onCompletion(MediaPlayer mp)                     
61              {                                                            
62                 handler.post(updateSlideshow); // update the slideshow    
63              } // end method onCompletion                                 
64           } // end anonymous inner class                                  
65         ); // end OnCompletionListener                                    
66
67         if (savedInstanceState == null) // Activity starting
68         {
69            // get slideshow name from Intent's extras
70            slideshowName = getIntent().getStringExtra(Slideshow.NAME_EXTRA);
71            mediaTime = 0; // position in media clip
72            nextItemIndex = 0; // start from first image
73         } // end if
74         else // Activity resuming
75         {
76            // get the play position that was saved when config changed
77            mediaTime = savedInstanceState.getInt(MEDIA_TIME);
78
79            // get index of image that was displayed when config changed
80            nextItemIndex = savedInstanceState.getInt(IMAGE_INDEX);
81
82            // get name of slideshow that was playing when config changed
83            slideshowName = savedInstanceState.getString(SLIDESHOW_NAME);
84         } // end else
85
86         // get SlideshowInfo for slideshow to play
87         slideshow = Slideshow.getSlideshowInfo(slideshowName);
88
89         // configure BitmapFactory.Options for loading images
90         options = new BitmapFactory.Options();
91         options.inSampleSize = 4; // sample at 1/4 original width/height
92
93         // if there is music to play
94         if (slideshow.getMusicPath() != null)
95         {
96            // try to create a MediaPlayer to play the music
97            try
98            {
99               mediaPlayer = new MediaPlayer();
100              mediaPlayer.setDataSource(
101                 this, Uri.parse(slideshow.getMusicPath()));
102              mediaPlayer.prepare(); // prepare the MediaPlayer to play
103              mediaPlayer.setLooping(true); // loop the music
104              mediaPlayer.seekTo(mediaTime); // seek to mediaTime
105           } // end try
106           catch (Exception e)
107           {
108              Log.v(TAG, e.toString());
109           } // end catch
110        } // end if
111
112        handler = new Handler(); // create handler to control slideshow
113     } // end method onCreate


Fig. 13.26. Overriding Activity method onCreate in class SlideshowPlayer.

Changes to the updateSlideshow Runnable

The updateSlideshow Runnable (Fig. 13.27) now processes images and videos. In method run, if the slideshow hasn’t completed, lines 193–208 determine whether the next item in the slideshow is an image or a video. If it’s an image, lines 197–198 show the imageView and hide the videoView, then line 199 creates a LoadImageTask AsyncTask (defined in lines 213–249) to load and display the image. Otherwise, lines 203–204 hide the imageView and show the videoView, then line 205 calls playVideo (defined in lines 272–279). The playVideo method plays a video file located at the given Uri. Line 275 calls VideoView’s setVideoUri method to specify the location of the video file to play. Lines 276–277 set the MediaController for the VideoView, which displays video playback controls. Line 278 begins the video playback using VideoView’s start method.


114     // anonymous inner class that implements Runnable to control slideshow
115     private Runnable updateSlideshow = new Runnable()
116     {
117        @Override
118        public void run()
119        {
120           if (nextItemIndex >= slideshow.size())
121           {
122              // if there is music playing
123              if (mediaPlayer != null && mediaPlayer.isPlaying())
124                 mediaPlayer.reset(); // slideshow done, reset mediaPlayer
125              finish(); // return to launching Activity
126           } // end if
127           else
128           {
129              MediaItem item = slideshow.getMediaItemAt(nextItemIndex);
130
131              if (item.getType() == MediaItem.MediaType.IMAGE)
132              {
133                 imageView.setVisibility(View.VISIBLE); // show imageView  
134                 videoView.setVisibility(View.INVISIBLE); // hide videoView
135                 new LoadImageTask().execute(Uri.parse(item.getPath()));   
136              } // end if
137              else
138              {
139                 imageView.setVisibility(View.INVISIBLE); // hide imageView
140                 videoView.setVisibility(View.VISIBLE); // show videoView  
141                 playVideo(Uri.parse(item.getPath())); // plays the video  
142              } // end else
143
144              ++nextItemIndex;
145           } // end else
146        } // end method run
147
148        // task to load thumbnails in a separate thread
149        class LoadImageTask extends AsyncTask<Uri, Object, Bitmap>
150        {
151           // load iamges
152           @Override
153           protected Bitmap doInBackground(Uri... params)
154           {
155              return getBitmap(params[0], getContentResolver(), options);
156           } // end method doInBackground
157
158           // set thumbnail on ListView
159           @Override
160           protected void onPostExecute(Bitmap result)
161           {
162              super.onPostExecute(result);
163              BitmapDrawable next = new BitmapDrawable(result);
164              next.setGravity(android.view.Gravity.CENTER);
165              Drawable previous = imageView.getDrawable();
166
167              // if previous is a TransitionDrawable,
168              // get its second Drawable item
169              if (previous instanceof TransitionDrawable)
170                 previous = ((TransitionDrawable) previous).getDrawable(1);
171
172              if (previous == null)
173                 imageView.setImageDrawable(next);
174              else
175              {
176                 Drawable[] drawables = { previous, next };
177                 TransitionDrawable transition =
178                    new TransitionDrawable(drawables);
179                 imageView.setImageDrawable(transition);
180                 transition.startTransition(1000);
181              } // end else
182
183              handler.postDelayed(updateSlideshow, DURATION);
184           } // end method onPostExecute
185        } // end class LoadImageTask
186
187        // utility method to get a Bitmap from a Uri
188        public Bitmap getBitmap(Uri uri, ContentResolver cr,
189           BitmapFactory.Options options)
190        {
191           Bitmap bitmap = v;
192
193           // get the image
194           try
195           {
196              InputStream input = cr.openInputStream(uri);
197              bitmap = BitmapFactory.decodeStream(input, null, options);
198           } // end try
199           catch (FileNotFoundException e)
200           {
201              Log.v(TAG, e.toString());
202           } // end catch
203
204           return bitmap;
205        } // end method getBitmap
206
207        // play a video
208        private void playVideo(Uri videoUri)             
209        {                                                
210           // configure the video view and play video    
211           videoView.setVideoURI(videoUri);              
212           videoView.setMediaController(                 
213              new MediaController(SlideshowPlayer.this));
214           videoView.start(); // start the video         
215        } // end method playVideo                        
216     }; // end Runnable updateSlideshow
217  } // end class SlideshowPlayer


Fig. 13.27. Runnable that handles the display of an image or playing of a video.

13.6. Wrap-Up

In this app, you used the java.io package’s object serialization capabilities to store slideshows on the device for viewing later. To use an object with the serialization mechanism, you implemented the tagging interface Serializable. You used an ObjectOutputStream’s writeObject method to create an object graph and serialize objects. You read and deserialized objects with an ObjectInputStream’s readObject method.

You allowed users to take new pictures using a device’s rear facing camera, stored that picture in the device’s Gallery and added the new picture to the slideshow. To do so, you used class Camera and a SurfaceView to display a preview of the picture. When the user touched the screen, you told the Camera to take the picture, then a Camera.PictureCallback object was notified that the picture was taken and processed the image data. You also used the Camera’s supported color effects.

The Slideshow app used an Intent to launch an Activity for choosing an image from the Gallery. You used the same technique here to allow the user to select videos, but specified a different MIME type for the data so that only videos were displayed.

You used a VideoView to play videos in a slideshow. To do so, you specified the VideoView video URI and MediaController. A MediaPlayer.OnCompletionListener determined when the video finished playing.

The next chapter covers several key features of developing tablet apps with Android 3.x. In addition, we’ll use WeatherBug’s web services to create the Weather Viewer app.

Self-Review Exercises

13.1. Fill in the blanks in each of the following statements:

a. Use a(n) __________ to play videos.

b. To use an object with the serialization mechanism, implement the __________ interface Serializable.

c. A(n) __________ can determine when a video finishes playing.

13.2. State whether each of the following is true or false. If false, explain why.

a. A serialized object is represented as a sequence of bytes that includes the object’s data and information about the object’s type.

b. Objects of a class that implements Serializable are tagged as being Serializable objects—that is, any object of a class that implements Serializable can be serialized.

c. When using a VideoView you must create a MediaPlayer to play the video.

d. The Context class provides methods for accessing the file system.

e. When a SurfaceView is created, changed or destroyed, its Callback methods are called.

Answers to Self-Review Exercises

13.1.

a. VideoView.

b. tagging.

c. MediaPlayer.OnCompletionListener.

13.2.

a. True.

b. True.

c. False. A VideoView maintains its own MediaPlayer to play the video.

d. True.

e. False. When the SurfaceView is created, changed or destroyed, its SurfaceHolder’s Callback methods are called.

Exercises

13.3. Fill in the blanks in each of the following statements:

a. To use an object with the serialization mechanism, the object’s class must implement the __________ interface, which is a tagging interface.

b. A serialized object can be read from a file and __________—that is, the type information and bytes that represent the object and its data can be used to recreate the object graph in memory. This is accomplished with an ObjectInputStream that reads the bytes from a specified InputStream.

c. ObjectOutputStream’s __________ method returns the deserialized object as type Object. To use it in an app, you must cast the object to the appropriate type.

d. The SurfaceHolder.Callback interface’s __________ method is called each time the size or format of the SurfaceView changes—typically when the device is rotated and when the SurfaceView is first created and displayed.

e. Read and deserialize objects with an ObjectInputStream’s __________ method.

13.4. State whether each of the following is true or false. If false, explain why.

a. You can use a MediaPlayer.OnFinishListener to determine when a video finishes playing so we can continue playing.

b. Camera’s static open method can get a Camera object that allows the app to use the device’s rear facing camera.

13.5. (Enhanced Slideshow App Enhancement) Modify the Enhanced Slideshow app to use the built in camera app (via Intents) rather than manipulating the camera directly. Also, investigate Android’s ViewSwitcher class (package android.widget) and use it to switch between images.

13.6. (Enhanced Movie Collection App) Modify the Movie Collection app in Exercise 10.6 to save a picture of the DVD cover for each movie.

13.7. (Enhanced Recipe App) Modify the Recipe app in Exercise 10.7 to save up to three pictures with each recipe.

13.8. (Quiz App) Modify the Flag Quiz app in Chapter 6 to create your own quiz app that show videos rather than images. Possible quizzes could include U.S. presidents, world landmarks, movie stars, recording artists, and more. Once you learn to use web services in the next chapter, consider using YouTube web services to obtain videos for display in the app.

13.9. (Expense Tracker App Enhancement) Add camera functionality to the app you created in Exercise 10.10 so the user can take pictures of receipts to track spending and to reconcile credit card bills at a later time. Allow the user to browse through the receipt photos.

13.10. (FlipBook Animation App) Allow the user to take pictures of individual sketches that represent frames of an animation, then use those images in an flip-book style app that switches quickly between the images to create an animation effect. If you’re feeling ambitious, investigate capabilities for creating a page curl effect in Android and apply it to the images for the inter-image transitions.

13.11. (One-Armed Bandit App) Read about how these work online, then create your own. Develop a multimedia simulation of a “one-armed bandit.” Have three randomly changing images. Use symbols and images of various fruits for each image. Search the web to find examples. If you’re feeling ambitious, place the images on spinning wheels. To enhance the app:

a. Allow the user to shake the phone to spin the wheels.

b. Play sounds as the wheels spin.

c. Play different sounds if the users wins or loses.

d. Add betting and payouts.

13.12. (Color Swiper App) Create an app that displays a color photo in black and white. As the user drags one or more fingers across the screen, reveal the color pixels from the original image. Allow the user to select an image from the device’s gallery. Once you learn web services in Chapter 14, all the user to select images by searching with keywords on Flickr.com.

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

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