title: Android error saving pictures to sd card
body: <p>I have an Android app which is crashing on some phones, and not on others. It has about 5k downloads, and so far has received about 50 crash reports. However, based on the amount of ratings/comments I get from users saying that it's force crashing, I think the percentage of affected users is actually much higher. </p>
  
  <p>As the issue doesn't affect my phone, I've been unable to reproduce the error and debug it as I would like to. </p>
  
  <p>The app takes a picture from the camera, overlays a bitmap on it, and saves the resulting image to the SD card. </p>
  
  <p>Below is my code for the <code>onPictureTaken</code> PictureCallback method.</p>
  
  <pre><code>    private Camera.PictureCallback mPicture = new Camera.PictureCallback() {
  
      public void onPictureTaken(byte[] data, Camera camera) {
  
          captureBtn.setVisibility(ImageButton.INVISIBLE); 
  
          File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE);
          if (pictureFile == null){
              return;
          }
  
          BitmapFactory.Options options = new BitmapFactory.Options();
          Bitmap mutableBitmap = null;
          Bitmap finalBitmap = null; 
          byte[] byteArray = null; 
          try {   
              mutableBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options).copy(Bitmap.Config.RGB_565, true);
              Matrix matrix = new Matrix();
              int width = mutableBitmap.getWidth();
              int height = mutableBitmap.getHeight();
              int newWidth = overlayView.getDrawable().getBounds().width();
              int newHeight = overlayView.getDrawable().getBounds().height();
              float scaleWidth = ((float) newWidth) / width;
              float scaleHeight = ((float) newHeight) / height;
              matrix.postScale(scaleWidth, scaleHeight);
              matrix.postRotate(90);
  
              Bitmap resizedBitmap = Bitmap.createBitmap(mutableBitmap, 0, 0, mutableBitmap.getWidth(), mutableBitmap.getHeight(), matrix, true);
              finalBitmap = resizedBitmap.copy(Bitmap.Config.RGB_565, true);
              Canvas canvas = new Canvas(finalBitmap);
  
              Bitmap overlayBitmap = null;
              if (mWeaponType == wep1) {
                  overlayBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.wep1);
              } else if (mWeaponType == wep2) {
                  overlayBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.wep2); 
              }
  
              if (overlayBitmap != null) {
  
                  matrix = new Matrix();
                  matrix.postRotate(90);
                  Bitmap resizedOverlay = Bitmap.createBitmap(overlayBitmap, 0, 0, overlayBitmap.getWidth(), overlayBitmap.getHeight(), matrix, true);
                  canvas.drawBitmap(resizedOverlay, 0, 0, new Paint());
                  canvas.scale(50, 0);
                  canvas.save();
                  //finalBitmap is the image with the overlay on it
  
                  // rotate image to save in landscape mode
                  matrix = new Matrix(); 
                  matrix.postRotate(270); 
                  finalBitmap = Bitmap.createBitmap(finalBitmap, 0, 0, finalBitmap.getWidth(), finalBitmap.getHeight(), matrix,
                          true);
  
                  // convert final bitmap to byte array
                  ByteArrayOutputStream stream = new ByteArrayOutputStream();
                  finalBitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
                  byteArray = stream.toByteArray();
              }
          }
  
          catch(OutOfMemoryError e) {
              //fail
          }
  
          try {
                  FileOutputStream fos = new FileOutputStream(pictureFile);
                  fos.write(byteArray); 
                  fos.close();
  
                  // Notify system that SD card has been updated
                  sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://"+ Environment.getExternalStorageDirectory())));
                  Log.i(TAG, "Picture saved, intent broadcast that SD has been updated");
          } catch (FileNotFoundException e) {
              Log.d(TAG, "File not found: " + e.getMessage());
          } catch (IOException e) {
              Log.d(TAG, "Error accessing file: " + e.getMessage());
          } catch (NullPointerException e) {
  
          }
  
          finally {
              mCamera.startPreview();
              captureBtn.setVisibility(ImageButton.VISIBLE);
          }
      }
  };
  </code></pre>
  
  <p>I get a NullPointerException as follows:</p>
  
  <pre><code>java.lang.NullPointerException
  at java.io.FileOutputStream.write(FileOutputStream.java:256)
  at com.mypackage.MyActivity.onPictureTaken(MyActivity.java:215)
  </code></pre>
  
  <p>which is the </p>
  
  <pre><code> fos.write(byteArray); 
  </code></pre>
  
  <p>line.</p>
  
  <p>The <code>getOutputMediaFile</code> method listed above is as follows: </p>
  
  <pre><code>/** Create a File for saving an image or video */
  private static File getOutputMediaFile(int type){
      // To be safe, you should check that the SDCard is mounted
      // using Environment.getExternalStorageState() before doing this.
  
      File mediaStorageDir = Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES);
  
      // Create the storage directory if it does not exist
      if (! mediaStorageDir.exists()){
          if (! mediaStorageDir.mkdirs()){
              Log.d("HalfLife2 Booth", "failed to create directory");
              return null;
          }
      }
  
      // Create a media file name
      String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
      File mediaFile;
      if (type == MEDIA_TYPE_IMAGE){
          mediaFile = new File(mediaStorageDir.getPath() + File.separator +
          "IMG_"+ timeStamp + ".jpg");
      } else if(type == MEDIA_TYPE_VIDEO) {
          mediaFile = new File(mediaStorageDir.getPath() + File.separator +
          "VID_"+ timeStamp + ".mp4");
      } else {
          return null;
      }
  
      return mediaFile;
  }
  </code></pre>
  
  <p>I think that the problem may be in here somewhere, in trying to get an output media file. I've tried updating the app to use:</p>
  
  <pre><code>    File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES), "MyApp");
  </code></pre>
  
  <p>instead, and one user has reported the issue to be fixed, but several have reported that it persists.</p>
  
  <p>Any ideas, anyone? I'm finding this a tricky one to debug/fix when I can't reproduce or get anywhere near it. </p>