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
.
13.2 Test-Driving the Enhanced Slideshow App
13.4 Building the GUI and Resource Files
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.5 PictureTaker
Subclass of Activity
Self-Review Exercises | Answers to Self-Review Exercises | Exercises
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.]
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.
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.]
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.]
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.
This section presents the new technologies that we use in the Enhanced Slideshow app.
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>
.
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
.
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.
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.
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.
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"
.
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>
SlideshowEditor ListActivity
’s Modified LayoutFigure 13.5 diagrams the modified layout for the SlideshowEditor ListActivity
, which now contains two rows of Button
s in a TableLayout
at the top of the GUI.
PictureTaker Activity
’s LayoutFigure 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>
SlideshowPlayer Activity
’s Modified LayoutFigure 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 View
s 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>
This app consists of classes MediaItem
(Fig. 13.8), SlideshowInfo
(Fig. 13.9), Slideshow
(Figs. 13.10–13.15), SlideshowEditor
(Figs. 13.16–13.18), PictureTaker
(Figs. 13.19–13.24) and SlideshowPlayer
(Figs. 13.25–13.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
MediaItem
ClassIn 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 MediaItem
s 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
.
SlideshowInfo
ClassThe 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 MediaItem
s rather than String
s. 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
Slideshow
ClassIn 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.10–13.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
package
and import
Statements, and FieldsThe 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.
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
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
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
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
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
SlideshowEditor
ClassClass SlideshowEditor
(Figs. 13.16–13.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
Activity
Method onActivityResult
Class SlideshowEditor
contains two more Button
s 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.
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
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
PictureTaker
Subclass of Activity
Class PictureTaker
(Figs. 13.19–13.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
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 effect
s (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.
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 Callback
s—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
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
SurfaceHolder
’s Callback
sWhen 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
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.
Camera
’s PictureCallback
sFigure 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
SurfaceView
’s Touch EventsThe 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
SlideshowPlayer
ClassThe SlideshowPlayer Activity
(Figs. 13.25–13.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
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.
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
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
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.
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.
a. VideoView
.
b. tagging.
c. MediaPlayer.OnCompletionListener
.
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.
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 Intent
s) 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
.