INTERMEDIATE
ANDROID
Michael Galpin
                 Bump Technologies
ABOUT: ME
 ‣       Android engineer, Bump Technologies

 ‣       ex-eBay: eBay Mobile for Android

 ‣       Android in Practice

 ‣       Social info

     ‣    @michaelg
     ‣    +Michael Galpin
Bump
Technologies
     • Creators of Bump app
       • Android + iOS
       • 65M+ Downloads
     • Creators of BumpCube
     • Hiring!
You down with AIP?
• Chapter 11: “Appeal to
  the senses using
  multimedia.”

• Slideshow app
  MediaMogul.apk

• http://
  code.google.com/p/
  android-in-practice/
MULTIMEDIA
“Of all of our inventions for mass
communication, pictures still speak the
most universally understood language.”

                          -- Walt Disney
DETECTING
CAPABILITIES
AndroidManifest.xml
<uses-feature android:name="android.hardware.camera"
    android:required="true" />
<uses-feature android:name="android.hardware.camera.autofocus"
    android:required="true"/>
<uses-feature android:name="android.hardware.camera.flash"
    android:required="false" />
<uses-feature android:name="android.hardware.camera.front"
    android:required="false" />
<uses-feature android:name="android.hardware.microphone"
    android:required="true"/>
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
Check the front
private boolean hasFrontFacingCamera(){
    PackageManager mgr = this.getPackageManager();
    for (FeatureInfo fi : mgr.getSystemAvailableFeatures()){
        if (fi.name.equals(PackageManager.FEATURE_CAMERA_FRONT)){
            return true;
        }
    }
    return false;
}

private Camera getFrontFacingCamera(){
    for (int i=0;i<Camera.getNumberOfCameras();i++){
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(i, info);
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT){
            return Camera.open(i);
        }
    }
    return null;
}
Check the front
private boolean hasFrontFacingCamera(){
    PackageManager mgr = this.getPackageManager();
    for (FeatureInfo fi : mgr.getSystemAvailableFeatures()){
        if (fi.name.equals(PackageManager.FEATURE_CAMERA_FRONT)){
            return true;
        }
    }
    return false;
}

private Camera getFrontFacingCamera(){
    for (int i=0;i<Camera.getNumberOfCameras();i++){
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(i, info);
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT){
            return Camera.open(i);
        }
    }
    return null;
}
But I have to
  support
Android 2.1
Reflection?
LOLZ

// Can't use Build.VERSION_CODES.GINGERBREAD
if (Build.VERSION.SDK_INT >= 9){
    PostFroyoClass wontThisBlowUp = new PostFroyoClass();
}
These are my customers!
These are my customers!
Proprietary APIs FTW!

import com.sprint.hardware.twinCamDevice.FrontFacingCamera;

Camera evoCam = FrontFacingCamera.getFrontFacingCamera();
RESOURCES
MediaPlayer.framework?
MediaPlayer.framework?

[MPMusicPlayerController iPodMusicPlayer] ??
MediaPlayer.framework?

[MPMusicPlayerController iPodMusicPlayer] ??

   [MusicLibrary sharedMusicLibrary] ???
MediaPlayer.framework?

[MPMusicPlayerController iPodMusicPlayer] ??

   [MusicLibrary sharedMusicLibrary] ???
        UIImagePickerController ?
MediaPlayer.framework?

[MPMusicPlayerController iPodMusicPlayer] ??

   [MusicLibrary sharedMusicLibrary] ???
        UIImagePickerController ?
MediaPlayer.framework?

[MPMusicPlayerController iPodMusicPlayer] ??

   [MusicLibrary sharedMusicLibrary] ???
        UIImagePickerController ?




            Files !!!!
Inside your APK




                  External
Just give it to me /res/raw

Resources res = this.getResources();


InputStream stream = res.openRawResource(R.raw.sunflower);


MediaPlayer.create(this, R.raw.constancy).start();
I like big /
     assets
and I cannot lie
MediaPlayer player = new MediaPlayer();
AssetManager mgr = getResources().getAssets();
String audioDir = "audio";
final LinkedList<FileDescriptor> queue =
    new LinkedList<FileDescriptor>();
for (String song : mgr.list("audio")){
    queue.add(
            mgr.openFd(audioDir + "/" + song).getFileDescriptor());
}
if (!queue.isEmpty()){
    FileDescriptor song = queue.poll();
    player.setDataSource(song);
    player.prepare();
    player.start();
}
player.setOnCompletionListener(new OnCompletionListener(){
    @Override
    public void onCompletion(MediaPlayer mp) {
        if (!queue.isEmpty()){
            FileDescriptor song = queue.poll();
            player.setDataSource(song);
            player.prepare();
            player.start();
        }
    }
});
File picturesDir =
    Environment.getExternalStoragePublicDirectory(
        Environment.DIRECTORY_PICTURES);
File picturesDir =
    Environment.getExternalStoragePublicDirectory(
        Environment.DIRECTORY_PICTURES);
IMAGES
Loading local images
InputStream stream = new FileInputStream(imgFile);
Bitmap bm = BitmapFactory.decodeStream(stream);
Loading local images
InputStream stream = new FileInputStream(imgFile);
Bitmap bm = BitmapFactory.decodeStream(stream);



// calculate desired width and height



Bitmap thumb = ThumbnailUtils.extractThumbnail(bm, width, height);

Bitmap thumb = Bitmap.createScaledBitmap(bm, width, height, false);
Loading without
                         exploding
public static Bitmap decodeDownsizedBitmapStream(File file, int target, Context context) throws
IOException {
    FileInputStream stream = new FileInputStream(file);
    Pair<Integer, Integer> source = getDimensionsForStream(stream);
    stream.close();
    FileInputStream in = new FileInputStream(file);
    Options options = new Options();
    options.inSampleSize = 1 + getDownSampleSize(max(source.first, source.second), target);
    return BitmapFactory.decodeStream(in, null, options);
}
public static Pair<Integer, Integer> getDimensionsForStream(InputStream in){
    Options options = new Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(in, null, options);
    return new Pair<Integer, Integer>(options.outWidth, options.outHeight);
}
public static int getDownSampleSize(int source, int target){
    int size = 1;
    if (source <= 2*target){
        int power = (int) ((log (source / target)) / log(2));
        size = (int) pow(2, power);
    }
    return size;
}
Loading web images
URL url = new URL(urlString);



//   NOTE, be careful about just doing "url.openStream()"
//   it's a shortcut for openConnection().getInputStream() and doesn't set timeouts
//   the defaults are "infinite" so it will wait forever if endpoint server is down
//   do it properly with a few more lines of code . . .



URLConnection conn = url.openConnection();
conn.setConnectTimeout(3000);
conn.setReadTimeout(5000);

Bitmap bitmap = BitmapFactory.decodeStream(conn.getInputStream());
AsyncTask FTW!
private class RetrieveImageTask extends AsyncTask<String, Void, Bitmap> {
   private ImageView imageView;

    public RetrieveImageTask(ImageView imageView) {
       this.imageView = imageView;
    }

    @Override
    protected Bitmap doInBackground(String... args) {
       try {
          URL url = new URL(args[0]);
          URLConnection conn = url.openConnection();
          conn.setConnectTimeout(3000);
          conn.setReadTimeout(5000);
          return BitmapFactory.decodeStream(conn.getInputStream());
       } catch (Exception e) {
       }
       return null;
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
       if (bitmap != null) {
          imageView.setImageBitmap(bitmap);
       }
    }
}
Loading a dozen images
       isn’t cool.
 Y’know what’s cool?
Loading a billion images.
Hot or Not?
private class DealsAdapter extends ArrayAdapter<Item> {

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
       if (convertView == null) {
          LayoutInflater inflater = (LayoutInflater)
              getSystemService(Context.LAYOUT_INFLATER_SERVICE);
          convertView = inflater.inflate(R.layout.list_item, parent, false);
       }
       TextView text = (TextView) convertView.findViewById(R.id.deal_title);
       ImageView image = (ImageView) convertView.findViewById(R.id.deal_img);
       Item item = getItem(position);
       if (item != null) {
          text.setText(item.getTitle());
          new RetrieveImageTask(image).execute(item.getSmallPicUrl());
       }
       return convertView;
    }
}
Watch out for leaks
public class LeakProofActivity extends Activity {
   LeakProofAsyncTask task;
   private class LeakProofAsyncTask extends AsyncTask<String, Void, Bitmap>{
      Context potentialLeak = LeakProofActivity.this;

      @Override
      protected Bitmap doInBackground(String... arg0) {
         // Do something that takes a long time
         // Note if you need a Context here, use Application
         return null;
      }

      @Override
      protected void onPostExecute(Bitmap result){
         // update UI, etc.
      }
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);

      task = (LeakProofAsyncTask) getLastNonConfigurationInstance();
      if (task != null){
         task.potentialLeak = this;
      }
    }
    @Override
    protected void onDestroy() {
       super.onDestroy();
       if (task != null){
          task.potentialLeak = null;
       }
    }
    @Override
    public Object onRetainNonConfigurationInstance() {
       return task;
    }
}
How to build a cache?

• WeakReferences?
• SoftReferences?
• WeakHashMap?
• MapMaker?
LinkedHashMap!?
private static class ImageCache extends LinkedHashMap<String, Bitmap>{

    private final int capacity;

    public ImageCache(int capacity){
        super(capacity/2, 0.75f, true);
        this.capacity = capacity;
    }

    @Override
    protected boolean removeEldestEntry(Entry<String, Bitmap> eldest) {
        return this.size() > capacity;
    }
}
Disk Cache

• Context.getCacheDir()
• SQLite
• Environment.getExternalStorage()
Sidebar: Bitmaps & Heap
Reported by andreas....@googlemail.com, May 22, 2010
Note: This is NOT a requst for assistance!

In my applications, I keep getting the following exception:

E/AndroidRuntime( 1420): java.lang.OutOfMemoryError: bitmap size exceeds
VM budget
E/AndroidRuntime( 1420): 
 at
android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
E/AndroidRuntime( 1420): 
 at
android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:459)
E/AndroidRuntime( 1420): 
 at
android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:515)
E/AndroidRuntime( 1420): 
 at
de.schildbach.bitmapbug.MyActivity.bitmap(MyActivity.java:38)

Comment 1 by romaingu...@gtempaccount.com, May 23, 2010
Your app needs to use less memory.

                       http://code.google.com/p/android/issues/detail?id=8488
Use Placeholders

      ImageView image =
          (ImageView) convertView.findViewById(R.id.deal_img);

      image.setImageBitmap(
          BitmapFactory.decodeResource(
              getResources(), R.drawable.temp));

      Item item = getItem(position);

      new RetrieveImageTask(image).
          execute(item.getSmallPicUrl());
@Override
                       View Holders
public View getView(final int position, View cell, ViewGroup parent) {
	 ViewHolder holder = (ViewHolder) cell.getTag();
	 if (holder == null){
	 	 holder = new ViewHolder(cell);
	 	 cell.setTag(holder);
	 }
	 Bitmap thumb = (Bitmap) getItem(position);
   holder.img.setImageBitmap(thumb);
	 File file = getImageFile(position);
	 if (selectedFiles.contains(file)){
	 	 holder.cbox.setChecked(true);
	 } else {
	 	 holder.cbox.setChecked(false);
	 }
	 return cell;
}

static class ViewHolder {
    final ImageView img;
    final CheckBox cbox;
    ViewHolder(View cell){
        img = (ImageView) cell.findViewById(R.id.thumb);
    	 cbox = (CheckBox) cell.findViewById(R.id.cbox);
    }
}
Sidebar: Strict Mode
CONTENT
PROVIDERS
SQL?


       NoSQL?


           SortOfSQL!
Queries
Cursor cursor = getContentResolver().query(table,
                                           columns,
                                           whereClause,
                                           paramValues,
                                           sortOrder);
while (cursor.moveToNext()){
    // process results
}
cursor.close();
Music
import   static   android.provider.BaseColumns._ID;
import   static   android.provider.MediaStore.Audio.AudioColumns.ARTIST;
import   static   android.provider.MediaStore.Audio.AudioColumns.IS_MUSIC;
import   static   android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
import   static   android.provider.MediaStore.MediaColumns.DATA;
import   static   android.provider.MediaStore.MediaColumns.TITLE;

String[] columns = {TITLE,ARTIST,_ID, DATA};
// where clause so we don't get ringtones, podcasts, etc.
String whereClause = IS_MUSIC + " = ?";
String[] whereValues = {"1"};
cursor = managedQuery(EXTERNAL_CONTENT_URI,
	 columns,
	 whereClause,
	 whereValues,
	 null
);
Contacts
String[] projection = {Phone.CONTACT_ID, Phone.NUMBER};
String selection = Data.IN_VISIBLE_GROUP + "=1 AND " +
    Phone.NUMBER + " LIKE ?";
String[] selectionArgs = {"%" + phoneSubStr + "%"};
Cursor phoneCursor = resolver.query(Phone.CONTENT_URI,
                                 projection,
                                 selection,
                                 selectionArgs,
                                 null);


String[] projection = new String[] {StructuredName.GIVEN_NAME,
                                    StructuredName.FAMILY_NAME,
                                    StructuredName.RAW_CONTACT_ID,
                                    StructuredName.CONTACT_ID};
String selection = StructuredName.CONTACT_ID+ " = ? AND " +
    Data.MIMETYPE + " = '" + StructuredName.CONTENT_ITEM_TYPE +"'";
String[] selectionArgs = new String[] {contact.id};
Cursor nameCursor = resolver.query(Data.CONTENT_URI,
                                projection,
                                selection,
                                selectionArgs,
                                null);
Scanning media
Scanning 101
MediaScannerConnection conn = new MediaScannerConnection(this,
  new MediaScannerConnectionClient(){
    public void onMediaScannerConnected(){
        scanFile("/some/path/SomeSong.mp3", "audio/mpeg3");
        scanFile("/some/other/path/IMG1999.jpg", "image/jpeg");
    }

      public void onScanCompleted(String path, Uri uri){
          Log.d("LogTag", "Media scanned uir=" + uri.toString());
      }
});

conn.connect();
Cursor   Adapter
CursorAdapter
Avoid
Use Other Apps
private static final int SELECT_VIDEO = 1;
private Uri videoUri;
@Override
protected void onCreate(Bundle savedInstanceState) {
    Button vidBtn = (Button) findViewById(R.id.vidBtn);
    vidBtn.setOnClickListener(new OnClickListener(){
        @Override
        public void onClick(View button) {
            Intent videoChooser = new Intent(Intent.ACTION_GET_CONTENT);
            videoChooser.setType("video/*");
            startActivityForResult(videoChooser, SELECT_VIDEO);
        }
    });
}
@Override
protected void onActivityResult(int requestCode, int resultCode,
        Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == SELECT_VIDEO && resultCode == RESULT_OK){
        videoUri = data.getData();
    }
}
ANIMATION
Dissolve Animation
private void nextSlide() {
    AlphaAnimation animation = new AlphaAnimation(0.0f, 1.0f);
    if ((count % 2) == 0) {
        animation = new AlphaAnimation(1.0f, 0.0f);
    }
    animation.setStartOffset(TIME_PER_SLIDE);
    animation.setDuration(TIME_PER_SLIDE);
    animation.setFillAfter(true);
    animation.setAnimationListener(new Animation.AnimationListener() {
        @Override
        public void onAnimationStart(Animation animation) {}
        @Override
        public void onAnimationRepeat(Animation animation) {}
        @Override
        public void onAnimationEnd(Animation animation) {
            if (playingSlides){
                nextImage = getNextImage();
                ImageView backgroundImage =
                    (count % 2 == 0) ? rightSlide : leftSlide;
                backgroundImage.setImageBitmap(nextImage);
                count++;
                nextSlide();
            }
        }
    });
    rightSlide.startAnimation(animation);
    currentImage = nextImage;
}
Honeycomb+
Animators
ImageView backgroundImage = (count % 2 == 0) ? rightSlide : leftSlide;
ObjectAnimator anim =
    ObjectAnimator.ofFloat(backgroundImage, "alpha", 0.0f, 1.0f);
anim.addListener(new AnimatorListenerAdapter(){
    public void onAnimationEnd(Animator animator){
        nextSlide();
    }
});
A/V
PLAYBACK
playBtn.setOnClickListener(new OnClickListener(){
    private Handler handler = new Handler();
    MediaPlayer player = null;
    long maxTime = 15L*1000; // 15 seconds
    long timeLeft = maxTime;
                                                      Audio Preview
    Runnable autoStop;
    @Override
    public void onClick(View view) {
        if (player == null){
            player = MediaPlayer.create(activity, song.uri);
        }
        if (!playingSongs.contains(song.id)){           Start/Resume
            player.start();
            playingSongs.add(song.id);
            autoStop = new Runnable(){
                                                     Timer
                 @Override
                 public void run() {
                     player.pause();
                     player.seekTo(0);
                     playingSongs.remove(song.id);
                     playBtn.setText(R.string.play);
                     timeLeft = maxTime;
                }
            };
            handler.postDelayed(autoStop, timeLeft);
            playBtn.setText(R.string.pause);
        } else {
            player.pause();
                                                                   Pause
            playingSongs.remove(song.id);
            timeLeft = maxTime - player.getCurrentPosition();
            playBtn.setText(R.string.play);
                                                                Calc time left
            handler.removeCallbacks(autoStop);
        }
    }
});
Handler?
Handler?
Pipeline Thread
Looper.prepare();
handler = new Handler();

// handle tasks in queue



Looper.loop();
Handler?
      Thread                 Pipeline Thread
                             Looper.prepare();
                             handler = new Handler();

                             // handle tasks in queue



                             Looper.loop();

// long running task
Message msg =
  handler.obtainMessage();
handler.sendMessage(msg);
Handler?
      Thread                 Pipeline Thread                 Thread
                             Looper.prepare();
                             handler = new Handler();

                             // handle tasks in queue
                                                        // long running task
                                                        handler.post(
                             Looper.loop();                 new Runnable() {...
                                                        });
// long running task
Message msg =
  handler.obtainMessage();
handler.sendMessage(msg);
Theme Muzak
private MediaPlayer player;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setupThemeMusic();
}

@Override
public void onResume() {
    super.onResume();
    if (player != null){
        player.start();
    }
                                               @Override
}
                                               public void onPause(){
                                                   super.onPause();
                                                   if (player != null && player.isPlaying()){
                                                       player.pause();
                                                   }
                                               }

                                               @Override
                                               protected void onDestroy() {
                                                   super.onDestroy();
                                                   if (player != null && player.isPlaying()){
                                                       player.stop();
                                                   }
                                                   player.release();
                                               }
Activity Lifecycle
Video Playback
Uri videoUri = ...;

VideoView video = (VideoView) findViewById(R.id.video);
video.setVideoURI(videoUri);

MediaController controller = new MediaController(this);
controller.setMediaPlayer(video);
video.setMediaController(controller);
video.requestFocus();
video.start();
TAKE A
PICTURE
Use the Camera (app)
import static android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI;

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
photoUri = getContentResolver().insert(
        EXTERNAL_CONTENT_URI, new ContentValues());
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
startActivityForResult(intent,TAKE_PHOTO);




@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (resultCode == Activity.RESULT_OK && requestCode == TAKE_PHOTO){
        ImageView img = (ImageView) findViewById(R.id.photoThumb);
        InputStream stream =
            getContentResolver().openInputStream(photoUri);
        Bitmap bmp = BitmapFactory.decodeStream(stream);
        img.setImageBitmap(bmp);
    }
}
EXIF!
Popular on Facebook
Correctly Oriented
Options photoImageOptions = new BitmapFactory.Options();
Matrix matrix = new Matrix();
ExifInterface ei = new ExifInterface(somePath);
int orientation = ei.getAttributeInt(TAG_ORIENTATION, ORIENTATION_UNDEFINED);
int angle = 0;
switch (orientation) {
    case ORIENTATION_ROTATE_90 : angle=90; break;
    case ORIENTATION_ROTATE_180 : angle=180; break;
    case ORIENTATION_ROTATE_270 : angle=270; break;
    default: break;
}
Bitmap bm = BitmapFactory.decodeFile(path, photoImageOptions);
if (angle > 0){
    matrix.setRotate(angle);
    Bitmap bm = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(),
                                    matrix, true);
}
Geo Tagging


ExifInterface exif = new ExifInterface(filename);
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, latitude);
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, longitude);
exif.saveAttributes();




                                                    “dd/1,mm/1,ss/1”
Where?
Getting a (geo) fix

LocationManager mgr = getSystemService(LOCATION_SERVICE);

for (String provider : mgr.getAllProviders()){
    Location last = mgr.getLastKnownLocation(last);

    mgr.requestLocationUpdates(provider, 60000, 500, new LocationListener(){
        public void onLocationChanged(Location loc){
            // do stuff
        }
        // other methods
    });
}
To the cloud!
Uploading a photo

String url = "http://my/server";
HttpClient client = new DefaultHttpClient();
HttpContext context = new BasicHttpContext();
HttpPost post = new HttpPost(url);

File imgFile = new File("/path/to/my/image");
MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE);
entity.addPart("image", new FileBody(imgFile));
post.setEntity(entity);

HttpResponse response = client.execute(httpPost, localContext);
Upload Service
UploadService extends IntentService {

    protected void onHandleIntent(Intent i){
        String imgPath = i.getStringExtra("file");
        // upload code goes here
    }
}




Intent i = new Intent(this, UploadService.class);
i.putExtra("file", "/path/to/my/image");
startService(i);
Process Priority
       Foreground

         Visible


         Service

       Background
         Empty
A/V
RECORDING
Video Preview
private   SurfaceHolder holder;
private   Camera camera;
private   MediaRecorder mediaRecorder;
private   File tempFile;
private   SurfaceView preview;
private   boolean isRecording = false;
private   final int maxDurationInMs = 20000;
private   final long maxFileSizeInBytes = 500000;
private   final int videoFramesPerSecond = 20;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    preview = new SurfaceView(this);
    holder = preview.getHolder();
    holder.addCallback(cameraman);
    holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    setContentView(preview);
    tempFile = new File(getCacheDir(), "temp.mov");
    if (tempFile.length() > 0){
        tempFile.delete();
    }
}
Video Preview
private Callback cameraman = new Callback(){
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        camera = Camera.open();
        camera.setPreviewDisplay(holder);
    }
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format,
            int width,int height) {
        Parameters params = camera.getParameters();
        List<Size> sizes = params.getSupportedPreviewSizes();
        Size optimalSize = getOptimalPreviewSize(sizes, width, height);
        params.setPreviewSize(optimalSize.width, optimalSize.height);
        camera.setParameters(params);
        camera.startPreview();
    }
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        camera.stopPreview();
        camera.release();
    }
};
Video Preview
private void startRecording(){
    if (isRecording){
        return;
    }
    isRecording = true;
    camera.unlock();
    mediaRecorder = new MediaRecorder();
    mediaRecorder.setCamera(camera);
    mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
    mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
    mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
    mediaRecorder.setMaxDuration(maxDurationInMs);

    mediaRecorder.setOutputFile(tempFile.getPath());
    mediaRecorder.setVideoFrameRate(videoFramesPerSecond);
    mediaRecorder.setVideoSize(preview.getWidth(), preview.getHeight());
    mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
    mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
    mediaRecorder.setPreviewDisplay(holder.getSurface());
    mediaRecorder.setMaxFileSize(maxFileSizeInBytes);

    mediaRecorder.prepare();
    mediaRecorder.start();
}

Android workshop

  • 1.
  • 2.
    ABOUT: ME ‣ Android engineer, Bump Technologies ‣ ex-eBay: eBay Mobile for Android ‣ Android in Practice ‣ Social info ‣ @michaelg ‣ +Michael Galpin
  • 3.
    Bump Technologies • Creators of Bump app • Android + iOS • 65M+ Downloads • Creators of BumpCube • Hiring!
  • 4.
    You down withAIP? • Chapter 11: “Appeal to the senses using multimedia.” • Slideshow app MediaMogul.apk • http:// code.google.com/p/ android-in-practice/
  • 6.
  • 7.
    “Of all ofour inventions for mass communication, pictures still speak the most universally understood language.” -- Walt Disney
  • 12.
  • 14.
    AndroidManifest.xml <uses-feature android:name="android.hardware.camera" android:required="true" /> <uses-feature android:name="android.hardware.camera.autofocus" android:required="true"/> <uses-feature android:name="android.hardware.camera.flash" android:required="false" /> <uses-feature android:name="android.hardware.camera.front" android:required="false" /> <uses-feature android:name="android.hardware.microphone" android:required="true"/> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.RECORD_AUDIO" />
  • 15.
    Check the front privateboolean hasFrontFacingCamera(){ PackageManager mgr = this.getPackageManager(); for (FeatureInfo fi : mgr.getSystemAvailableFeatures()){ if (fi.name.equals(PackageManager.FEATURE_CAMERA_FRONT)){ return true; } } return false; } private Camera getFrontFacingCamera(){ for (int i=0;i<Camera.getNumberOfCameras();i++){ Camera.CameraInfo info = new Camera.CameraInfo(); Camera.getCameraInfo(i, info); if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT){ return Camera.open(i); } } return null; }
  • 16.
    Check the front privateboolean hasFrontFacingCamera(){ PackageManager mgr = this.getPackageManager(); for (FeatureInfo fi : mgr.getSystemAvailableFeatures()){ if (fi.name.equals(PackageManager.FEATURE_CAMERA_FRONT)){ return true; } } return false; } private Camera getFrontFacingCamera(){ for (int i=0;i<Camera.getNumberOfCameras();i++){ Camera.CameraInfo info = new Camera.CameraInfo(); Camera.getCameraInfo(i, info); if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT){ return Camera.open(i); } } return null; }
  • 17.
    But I haveto support Android 2.1
  • 20.
  • 21.
    LOLZ // Can't useBuild.VERSION_CODES.GINGERBREAD if (Build.VERSION.SDK_INT >= 9){ PostFroyoClass wontThisBlowUp = new PostFroyoClass(); }
  • 22.
    These are mycustomers!
  • 23.
    These are mycustomers!
  • 24.
    Proprietary APIs FTW! importcom.sprint.hardware.twinCamDevice.FrontFacingCamera; Camera evoCam = FrontFacingCamera.getFrontFacingCamera();
  • 25.
  • 27.
  • 28.
  • 29.
  • 30.
    MediaPlayer.framework? [MPMusicPlayerController iPodMusicPlayer] ?? [MusicLibrary sharedMusicLibrary] ??? UIImagePickerController ?
  • 31.
    MediaPlayer.framework? [MPMusicPlayerController iPodMusicPlayer] ?? [MusicLibrary sharedMusicLibrary] ??? UIImagePickerController ?
  • 32.
    MediaPlayer.framework? [MPMusicPlayerController iPodMusicPlayer] ?? [MusicLibrary sharedMusicLibrary] ??? UIImagePickerController ? Files !!!!
  • 33.
  • 34.
    Just give itto me /res/raw Resources res = this.getResources(); InputStream stream = res.openRawResource(R.raw.sunflower); MediaPlayer.create(this, R.raw.constancy).start();
  • 35.
    I like big/ assets and I cannot lie
  • 36.
    MediaPlayer player =new MediaPlayer(); AssetManager mgr = getResources().getAssets(); String audioDir = "audio"; final LinkedList<FileDescriptor> queue = new LinkedList<FileDescriptor>(); for (String song : mgr.list("audio")){ queue.add( mgr.openFd(audioDir + "/" + song).getFileDescriptor()); } if (!queue.isEmpty()){ FileDescriptor song = queue.poll(); player.setDataSource(song); player.prepare(); player.start(); } player.setOnCompletionListener(new OnCompletionListener(){ @Override public void onCompletion(MediaPlayer mp) { if (!queue.isEmpty()){ FileDescriptor song = queue.poll(); player.setDataSource(song); player.prepare(); player.start(); } } });
  • 38.
    File picturesDir = Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES);
  • 39.
    File picturesDir = Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES);
  • 40.
  • 42.
    Loading local images InputStreamstream = new FileInputStream(imgFile); Bitmap bm = BitmapFactory.decodeStream(stream);
  • 43.
    Loading local images InputStreamstream = new FileInputStream(imgFile); Bitmap bm = BitmapFactory.decodeStream(stream); // calculate desired width and height Bitmap thumb = ThumbnailUtils.extractThumbnail(bm, width, height); Bitmap thumb = Bitmap.createScaledBitmap(bm, width, height, false);
  • 45.
    Loading without exploding public static Bitmap decodeDownsizedBitmapStream(File file, int target, Context context) throws IOException { FileInputStream stream = new FileInputStream(file); Pair<Integer, Integer> source = getDimensionsForStream(stream); stream.close(); FileInputStream in = new FileInputStream(file); Options options = new Options(); options.inSampleSize = 1 + getDownSampleSize(max(source.first, source.second), target); return BitmapFactory.decodeStream(in, null, options); } public static Pair<Integer, Integer> getDimensionsForStream(InputStream in){ Options options = new Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeStream(in, null, options); return new Pair<Integer, Integer>(options.outWidth, options.outHeight); } public static int getDownSampleSize(int source, int target){ int size = 1; if (source <= 2*target){ int power = (int) ((log (source / target)) / log(2)); size = (int) pow(2, power); } return size; }
  • 46.
    Loading web images URLurl = new URL(urlString); // NOTE, be careful about just doing "url.openStream()" // it's a shortcut for openConnection().getInputStream() and doesn't set timeouts // the defaults are "infinite" so it will wait forever if endpoint server is down // do it properly with a few more lines of code . . . URLConnection conn = url.openConnection(); conn.setConnectTimeout(3000); conn.setReadTimeout(5000); Bitmap bitmap = BitmapFactory.decodeStream(conn.getInputStream());
  • 48.
    AsyncTask FTW! private classRetrieveImageTask extends AsyncTask<String, Void, Bitmap> { private ImageView imageView; public RetrieveImageTask(ImageView imageView) { this.imageView = imageView; } @Override protected Bitmap doInBackground(String... args) { try { URL url = new URL(args[0]); URLConnection conn = url.openConnection(); conn.setConnectTimeout(3000); conn.setReadTimeout(5000); return BitmapFactory.decodeStream(conn.getInputStream()); } catch (Exception e) { } return null; } @Override protected void onPostExecute(Bitmap bitmap) { if (bitmap != null) { imageView.setImageBitmap(bitmap); } } }
  • 49.
    Loading a dozenimages isn’t cool. Y’know what’s cool? Loading a billion images.
  • 50.
    Hot or Not? privateclass DealsAdapter extends ArrayAdapter<Item> { @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = inflater.inflate(R.layout.list_item, parent, false); } TextView text = (TextView) convertView.findViewById(R.id.deal_title); ImageView image = (ImageView) convertView.findViewById(R.id.deal_img); Item item = getItem(position); if (item != null) { text.setText(item.getTitle()); new RetrieveImageTask(image).execute(item.getSmallPicUrl()); } return convertView; } }
  • 51.
    Watch out forleaks public class LeakProofActivity extends Activity { LeakProofAsyncTask task; private class LeakProofAsyncTask extends AsyncTask<String, Void, Bitmap>{ Context potentialLeak = LeakProofActivity.this; @Override protected Bitmap doInBackground(String... arg0) { // Do something that takes a long time // Note if you need a Context here, use Application return null; } @Override protected void onPostExecute(Bitmap result){ // update UI, etc. } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); task = (LeakProofAsyncTask) getLastNonConfigurationInstance(); if (task != null){ task.potentialLeak = this; } } @Override protected void onDestroy() { super.onDestroy(); if (task != null){ task.potentialLeak = null; } } @Override public Object onRetainNonConfigurationInstance() { return task; } }
  • 53.
    How to builda cache? • WeakReferences? • SoftReferences? • WeakHashMap? • MapMaker?
  • 54.
    LinkedHashMap!? private static classImageCache extends LinkedHashMap<String, Bitmap>{ private final int capacity; public ImageCache(int capacity){ super(capacity/2, 0.75f, true); this.capacity = capacity; } @Override protected boolean removeEldestEntry(Entry<String, Bitmap> eldest) { return this.size() > capacity; } }
  • 55.
    Disk Cache • Context.getCacheDir() •SQLite • Environment.getExternalStorage()
  • 56.
    Sidebar: Bitmaps &Heap Reported by andreas....@googlemail.com, May 22, 2010 Note: This is NOT a requst for assistance! In my applications, I keep getting the following exception: E/AndroidRuntime( 1420): java.lang.OutOfMemoryError: bitmap size exceeds VM budget E/AndroidRuntime( 1420): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method) E/AndroidRuntime( 1420): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:459) E/AndroidRuntime( 1420): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:515) E/AndroidRuntime( 1420): at de.schildbach.bitmapbug.MyActivity.bitmap(MyActivity.java:38) Comment 1 by romaingu...@gtempaccount.com, May 23, 2010 Your app needs to use less memory. http://code.google.com/p/android/issues/detail?id=8488
  • 58.
    Use Placeholders ImageView image = (ImageView) convertView.findViewById(R.id.deal_img); image.setImageBitmap( BitmapFactory.decodeResource( getResources(), R.drawable.temp)); Item item = getItem(position); new RetrieveImageTask(image). execute(item.getSmallPicUrl());
  • 59.
    @Override View Holders public View getView(final int position, View cell, ViewGroup parent) { ViewHolder holder = (ViewHolder) cell.getTag(); if (holder == null){ holder = new ViewHolder(cell); cell.setTag(holder); } Bitmap thumb = (Bitmap) getItem(position); holder.img.setImageBitmap(thumb); File file = getImageFile(position); if (selectedFiles.contains(file)){ holder.cbox.setChecked(true); } else { holder.cbox.setChecked(false); } return cell; } static class ViewHolder { final ImageView img; final CheckBox cbox; ViewHolder(View cell){ img = (ImageView) cell.findViewById(R.id.thumb); cbox = (CheckBox) cell.findViewById(R.id.cbox); } }
  • 60.
  • 61.
  • 62.
    SQL? NoSQL? SortOfSQL!
  • 63.
    Queries Cursor cursor =getContentResolver().query(table, columns, whereClause, paramValues, sortOrder); while (cursor.moveToNext()){ // process results } cursor.close();
  • 64.
    Music import static android.provider.BaseColumns._ID; import static android.provider.MediaStore.Audio.AudioColumns.ARTIST; import static android.provider.MediaStore.Audio.AudioColumns.IS_MUSIC; import static android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; import static android.provider.MediaStore.MediaColumns.DATA; import static android.provider.MediaStore.MediaColumns.TITLE; String[] columns = {TITLE,ARTIST,_ID, DATA}; // where clause so we don't get ringtones, podcasts, etc. String whereClause = IS_MUSIC + " = ?"; String[] whereValues = {"1"}; cursor = managedQuery(EXTERNAL_CONTENT_URI, columns, whereClause, whereValues, null );
  • 65.
    Contacts String[] projection ={Phone.CONTACT_ID, Phone.NUMBER}; String selection = Data.IN_VISIBLE_GROUP + "=1 AND " + Phone.NUMBER + " LIKE ?"; String[] selectionArgs = {"%" + phoneSubStr + "%"}; Cursor phoneCursor = resolver.query(Phone.CONTENT_URI, projection, selection, selectionArgs, null); String[] projection = new String[] {StructuredName.GIVEN_NAME, StructuredName.FAMILY_NAME, StructuredName.RAW_CONTACT_ID, StructuredName.CONTACT_ID}; String selection = StructuredName.CONTACT_ID+ " = ? AND " + Data.MIMETYPE + " = '" + StructuredName.CONTENT_ITEM_TYPE +"'"; String[] selectionArgs = new String[] {contact.id}; Cursor nameCursor = resolver.query(Data.CONTENT_URI, projection, selection, selectionArgs, null);
  • 67.
  • 68.
    Scanning 101 MediaScannerConnection conn= new MediaScannerConnection(this, new MediaScannerConnectionClient(){ public void onMediaScannerConnected(){ scanFile("/some/path/SomeSong.mp3", "audio/mpeg3"); scanFile("/some/other/path/IMG1999.jpg", "image/jpeg"); } public void onScanCompleted(String path, Uri uri){ Log.d("LogTag", "Media scanned uir=" + uri.toString()); } }); conn.connect();
  • 69.
    Cursor Adapter
  • 70.
  • 71.
  • 72.
    Use Other Apps privatestatic final int SELECT_VIDEO = 1; private Uri videoUri; @Override protected void onCreate(Bundle savedInstanceState) { Button vidBtn = (Button) findViewById(R.id.vidBtn); vidBtn.setOnClickListener(new OnClickListener(){ @Override public void onClick(View button) { Intent videoChooser = new Intent(Intent.ACTION_GET_CONTENT); videoChooser.setType("video/*"); startActivityForResult(videoChooser, SELECT_VIDEO); } }); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == SELECT_VIDEO && resultCode == RESULT_OK){ videoUri = data.getData(); } }
  • 74.
  • 75.
    Dissolve Animation private voidnextSlide() { AlphaAnimation animation = new AlphaAnimation(0.0f, 1.0f); if ((count % 2) == 0) { animation = new AlphaAnimation(1.0f, 0.0f); } animation.setStartOffset(TIME_PER_SLIDE); animation.setDuration(TIME_PER_SLIDE); animation.setFillAfter(true); animation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) {} @Override public void onAnimationRepeat(Animation animation) {} @Override public void onAnimationEnd(Animation animation) { if (playingSlides){ nextImage = getNextImage(); ImageView backgroundImage = (count % 2 == 0) ? rightSlide : leftSlide; backgroundImage.setImageBitmap(nextImage); count++; nextSlide(); } } }); rightSlide.startAnimation(animation); currentImage = nextImage; }
  • 76.
  • 77.
    Animators ImageView backgroundImage =(count % 2 == 0) ? rightSlide : leftSlide; ObjectAnimator anim = ObjectAnimator.ofFloat(backgroundImage, "alpha", 0.0f, 1.0f); anim.addListener(new AnimatorListenerAdapter(){ public void onAnimationEnd(Animator animator){ nextSlide(); } });
  • 80.
  • 81.
    playBtn.setOnClickListener(new OnClickListener(){ private Handler handler = new Handler(); MediaPlayer player = null; long maxTime = 15L*1000; // 15 seconds long timeLeft = maxTime; Audio Preview Runnable autoStop; @Override public void onClick(View view) { if (player == null){ player = MediaPlayer.create(activity, song.uri); } if (!playingSongs.contains(song.id)){ Start/Resume player.start(); playingSongs.add(song.id); autoStop = new Runnable(){ Timer @Override public void run() { player.pause(); player.seekTo(0); playingSongs.remove(song.id); playBtn.setText(R.string.play); timeLeft = maxTime; } }; handler.postDelayed(autoStop, timeLeft); playBtn.setText(R.string.pause); } else { player.pause(); Pause playingSongs.remove(song.id); timeLeft = maxTime - player.getCurrentPosition(); playBtn.setText(R.string.play); Calc time left handler.removeCallbacks(autoStop); } } });
  • 82.
  • 83.
    Handler? Pipeline Thread Looper.prepare(); handler =new Handler(); // handle tasks in queue Looper.loop();
  • 84.
    Handler? Thread Pipeline Thread Looper.prepare(); handler = new Handler(); // handle tasks in queue Looper.loop(); // long running task Message msg = handler.obtainMessage(); handler.sendMessage(msg);
  • 85.
    Handler? Thread Pipeline Thread Thread Looper.prepare(); handler = new Handler(); // handle tasks in queue // long running task handler.post( Looper.loop(); new Runnable() {... }); // long running task Message msg = handler.obtainMessage(); handler.sendMessage(msg);
  • 86.
    Theme Muzak private MediaPlayerplayer; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setupThemeMusic(); } @Override public void onResume() { super.onResume(); if (player != null){ player.start(); } @Override } public void onPause(){ super.onPause(); if (player != null && player.isPlaying()){ player.pause(); } } @Override protected void onDestroy() { super.onDestroy(); if (player != null && player.isPlaying()){ player.stop(); } player.release(); }
  • 87.
  • 89.
    Video Playback Uri videoUri= ...; VideoView video = (VideoView) findViewById(R.id.video); video.setVideoURI(videoUri); MediaController controller = new MediaController(this); controller.setMediaPlayer(video); video.setMediaController(controller); video.requestFocus(); video.start();
  • 91.
  • 92.
    Use the Camera(app) import static android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI; Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); photoUri = getContentResolver().insert( EXTERNAL_CONTENT_URI, new ContentValues()); intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri); startActivityForResult(intent,TAKE_PHOTO); @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == Activity.RESULT_OK && requestCode == TAKE_PHOTO){ ImageView img = (ImageView) findViewById(R.id.photoThumb); InputStream stream = getContentResolver().openInputStream(photoUri); Bitmap bmp = BitmapFactory.decodeStream(stream); img.setImageBitmap(bmp); } }
  • 94.
  • 95.
  • 96.
    Correctly Oriented Options photoImageOptions= new BitmapFactory.Options(); Matrix matrix = new Matrix(); ExifInterface ei = new ExifInterface(somePath); int orientation = ei.getAttributeInt(TAG_ORIENTATION, ORIENTATION_UNDEFINED); int angle = 0; switch (orientation) { case ORIENTATION_ROTATE_90 : angle=90; break; case ORIENTATION_ROTATE_180 : angle=180; break; case ORIENTATION_ROTATE_270 : angle=270; break; default: break; } Bitmap bm = BitmapFactory.decodeFile(path, photoImageOptions); if (angle > 0){ matrix.setRotate(angle); Bitmap bm = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true); }
  • 97.
    Geo Tagging ExifInterface exif= new ExifInterface(filename); exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, latitude); exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, longitude); exif.saveAttributes(); “dd/1,mm/1,ss/1”
  • 98.
  • 99.
    Getting a (geo)fix LocationManager mgr = getSystemService(LOCATION_SERVICE); for (String provider : mgr.getAllProviders()){ Location last = mgr.getLastKnownLocation(last); mgr.requestLocationUpdates(provider, 60000, 500, new LocationListener(){ public void onLocationChanged(Location loc){ // do stuff } // other methods }); }
  • 100.
  • 101.
    Uploading a photo Stringurl = "http://my/server"; HttpClient client = new DefaultHttpClient(); HttpContext context = new BasicHttpContext(); HttpPost post = new HttpPost(url); File imgFile = new File("/path/to/my/image"); MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE); entity.addPart("image", new FileBody(imgFile)); post.setEntity(entity); HttpResponse response = client.execute(httpPost, localContext);
  • 103.
    Upload Service UploadService extendsIntentService { protected void onHandleIntent(Intent i){ String imgPath = i.getStringExtra("file"); // upload code goes here } } Intent i = new Intent(this, UploadService.class); i.putExtra("file", "/path/to/my/image"); startService(i);
  • 105.
    Process Priority Foreground Visible Service Background Empty
  • 106.
  • 108.
    Video Preview private SurfaceHolder holder; private Camera camera; private MediaRecorder mediaRecorder; private File tempFile; private SurfaceView preview; private boolean isRecording = false; private final int maxDurationInMs = 20000; private final long maxFileSizeInBytes = 500000; private final int videoFramesPerSecond = 20; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); preview = new SurfaceView(this); holder = preview.getHolder(); holder.addCallback(cameraman); holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); setContentView(preview); tempFile = new File(getCacheDir(), "temp.mov"); if (tempFile.length() > 0){ tempFile.delete(); } }
  • 109.
    Video Preview private Callbackcameraman = new Callback(){ @Override public void surfaceCreated(SurfaceHolder holder) { camera = Camera.open(); camera.setPreviewDisplay(holder); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) { Parameters params = camera.getParameters(); List<Size> sizes = params.getSupportedPreviewSizes(); Size optimalSize = getOptimalPreviewSize(sizes, width, height); params.setPreviewSize(optimalSize.width, optimalSize.height); camera.setParameters(params); camera.startPreview(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { camera.stopPreview(); camera.release(); } };
  • 110.
    Video Preview private voidstartRecording(){ if (isRecording){ return; } isRecording = true; camera.unlock(); mediaRecorder = new MediaRecorder(); mediaRecorder.setCamera(camera); mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); mediaRecorder.setMaxDuration(maxDurationInMs); mediaRecorder.setOutputFile(tempFile.getPath()); mediaRecorder.setVideoFrameRate(videoFramesPerSecond); mediaRecorder.setVideoSize(preview.getWidth(), preview.getHeight()); mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT); mediaRecorder.setPreviewDisplay(holder.getSurface()); mediaRecorder.setMaxFileSize(maxFileSizeInBytes); mediaRecorder.prepare(); mediaRecorder.start(); }