summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSascha Haeberling <haeberling@google.com>2013-08-15 21:38:07 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2013-08-15 21:38:07 +0000
commit29a27c76f2f581cdf5ab5c7e196312f635669420 (patch)
tree719bbd59f9871003a4c42c339bb8c9db055686b3
parentfca1c5ece5450914ed49115443e1d45737f2ee40 (diff)
parent6f64b50db6565bc6454304fd20c0c4020b297bb7 (diff)
downloadandroid_packages_apps_Snap-29a27c76f2f581cdf5ab5c7e196312f635669420.tar.gz
android_packages_apps_Snap-29a27c76f2f581cdf5ab5c7e196312f635669420.tar.bz2
android_packages_apps_Snap-29a27c76f2f581cdf5ab5c7e196312f635669420.zip
Merge "Brings back the details menu with most EXIF data in it. Some are still missing and will come back later." into gb-ub-photos-carlsbad
-rw-r--r--res/layout/details.xml23
-rw-r--r--res/layout/details_list.xml22
-rw-r--r--src/com/android/camera/CameraActivity.java23
-rw-r--r--src/com/android/camera/data/DataUtils.java49
-rw-r--r--src/com/android/camera/data/LocalData.java6
-rw-r--r--src/com/android/camera/data/LocalMediaData.java58
-rw-r--r--src/com/android/camera/data/MediaDetails.java171
-rw-r--r--src/com/android/camera/data/SimpleViewData.java5
-rw-r--r--src/com/android/camera/ui/DetailsDialog.java297
9 files changed, 636 insertions, 18 deletions
diff --git a/res/layout/details.xml b/res/layout/details.xml
new file mode 100644
index 000000000..dfda0ee22
--- /dev/null
+++ b/res/layout/details.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2006 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/text1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:gravity="left"
+/>
diff --git a/res/layout/details_list.xml b/res/layout/details_list.xml
new file mode 100644
index 000000000..b80ab6ca8
--- /dev/null
+++ b/res/layout/details_list.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<ListView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="16dp"
+ android:dividerHeight="8dp"
+/>
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java
index 099b5035f..98ea363eb 100644
--- a/src/com/android/camera/CameraActivity.java
+++ b/src/com/android/camera/CameraActivity.java
@@ -29,6 +29,7 @@ import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -54,10 +55,12 @@ import com.android.camera.data.FixedFirstDataAdapter;
import com.android.camera.data.FixedLastDataAdapter;
import com.android.camera.data.LocalData;
import com.android.camera.data.LocalDataAdapter;
+import com.android.camera.data.MediaDetails;
import com.android.camera.data.SimpleViewData;
import com.android.camera.util.ApiHelper;
import com.android.camera.ui.CameraSwitcher;
import com.android.camera.ui.CameraSwitcher.CameraSwitchListener;
+import com.android.camera.ui.DetailsDialog;
import com.android.camera.ui.FilmStripView;
import com.android.camera.util.CameraUtil;
import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
@@ -93,7 +96,7 @@ public class CameraActivity extends Activity
private static final int SUPPORT_SHOW_ON_MAP = 1 << 8;
private static final int SUPPORT_ALL = 0xffffffff;
- /** This data adapter is used by FilmStirpView. */
+ /** This data adapter is used by FilmStripView. */
private LocalDataAdapter mDataAdapter;
/** This data adapter represents the real local camera data. */
private LocalDataAdapter mWrappedDataAdapter;
@@ -412,6 +415,12 @@ public class CameraActivity extends Activity
@Override
public boolean onOptionsItemSelected(MenuItem item) {
+ int currentDataId = mFilmStripView.getCurrentId();
+ if (currentDataId < 0) {
+ return false;
+ }
+ final LocalData localData = mDataAdapter.getLocalData(currentDataId);
+
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.action_delete:
@@ -439,7 +448,17 @@ public class CameraActivity extends Activity
// TODO: add the functionality.
return true;
case R.id.action_details:
- // TODO: add the functionality.
+ (new AsyncTask<Void, Void, MediaDetails>() {
+ @Override
+ protected MediaDetails doInBackground(Void... params) {
+ return localData.getMediaDetails(CameraActivity.this);
+ }
+
+ @Override
+ protected void onPostExecute(MediaDetails mediaDetails) {
+ DetailsDialog.create(CameraActivity.this, mediaDetails).show();
+ }
+ }).execute();
return true;
case R.id.action_show_on_map:
// TODO: add the functionality.
diff --git a/src/com/android/camera/data/DataUtils.java b/src/com/android/camera/data/DataUtils.java
new file mode 100644
index 000000000..0b074d824
--- /dev/null
+++ b/src/com/android/camera/data/DataUtils.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.data;
+
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore;
+
+public class DataUtils {
+
+ /**
+ * Get the file path from a Media storage URI.
+ */
+ public static String getPathFromURI(ContentResolver contentResolver, Uri contentUri) {
+ String[] proj = {
+ MediaStore.Images.Media.DATA
+ };
+ Cursor cursor = contentResolver.query(contentUri, proj, null, null, null);
+ if (cursor == null) {
+ return null;
+ }
+ try {
+ int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
+ if (!cursor.moveToFirst()) {
+ return null;
+ } else {
+ return cursor.getString(columnIndex);
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+}
diff --git a/src/com/android/camera/data/LocalData.java b/src/com/android/camera/data/LocalData.java
index 7d6dfefc6..61714e243 100644
--- a/src/com/android/camera/data/LocalData.java
+++ b/src/com/android/camera/data/LocalData.java
@@ -23,7 +23,6 @@ import android.net.Uri;
import android.view.View;
import com.android.camera.ui.FilmStripView;
-import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
import java.util.Comparator;
@@ -103,6 +102,11 @@ public interface LocalData extends FilmStripView.ImageData {
Uri getContentUri();
/**
+ * Return media data (such as EXIF) for the item.
+ */
+ MediaDetails getMediaDetails(Context context);
+
+ /**
* Returns the type of the local data defined by {@link LocalData}.
*
* @param dataID The ID of the data.
diff --git a/src/com/android/camera/data/LocalMediaData.java b/src/com/android/camera/data/LocalMediaData.java
index d5a929d36..e55274f06 100644
--- a/src/com/android/camera/data/LocalMediaData.java
+++ b/src/com/android/camera/data/LocalMediaData.java
@@ -35,12 +35,13 @@ import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
-import com.android.camera.util.CameraUtil;
import com.android.camera.ui.FilmStripView;
+import com.android.camera.util.CameraUtil;
import com.android.camera.util.PhotoSphereHelper;
import com.android.camera2.R;
import java.io.File;
+import java.text.DateFormat;
import java.util.Date;
/**
@@ -53,12 +54,13 @@ public abstract class LocalMediaData implements LocalData {
protected long id;
protected String title;
protected String mimeType;
- protected long dateTaken;
- protected long dateModified;
+ protected long dateTakenInSeconds;
+ protected long dateModifiedInSeconds;
protected String path;
// width and height should be adjusted according to orientation.
protected int width;
protected int height;
+ protected long sizeInBytes;
/** The panorama metadata information of this media data. */
protected PhotoSphereHelper.PanoramaMetadata mPanoramaMetadata;
@@ -74,12 +76,12 @@ public abstract class LocalMediaData implements LocalData {
@Override
public long getDateTaken() {
- return dateTaken;
+ return dateTakenInSeconds;
}
@Override
public long getDateModified() {
- return dateModified;
+ return dateModifiedInSeconds;
}
@Override
@@ -214,6 +216,7 @@ public abstract class LocalMediaData implements LocalData {
public static final int COL_ORIENTATION = 6;
public static final int COL_WIDTH = 7;
public static final int COL_HEIGHT = 8;
+ public static final int COL_SIZE = 9;
static final Uri CONTENT_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
@@ -232,6 +235,7 @@ public abstract class LocalMediaData implements LocalData {
MediaStore.Images.ImageColumns.ORIENTATION, // 6, int, 0, 90, 180, 270
MediaStore.Images.ImageColumns.WIDTH, // 7, int
MediaStore.Images.ImageColumns.HEIGHT, // 8, int
+ MediaStore.Images.ImageColumns.SIZE, // 9, long
};
private static final int mSupportedUIActions =
@@ -251,8 +255,8 @@ public abstract class LocalMediaData implements LocalData {
d.id = c.getLong(COL_ID);
d.title = c.getString(COL_TITLE);
d.mimeType = c.getString(COL_MIME_TYPE);
- d.dateTaken = c.getLong(COL_DATE_TAKEN);
- d.dateModified = c.getLong(COL_DATE_MODIFIED);
+ d.dateTakenInSeconds = c.getLong(COL_DATE_TAKEN);
+ d.dateModifiedInSeconds = c.getLong(COL_DATE_MODIFIED);
d.path = c.getString(COL_DATA);
d.orientation = c.getInt(COL_ORIENTATION);
d.width = c.getInt(COL_WIDTH);
@@ -281,6 +285,7 @@ public abstract class LocalMediaData implements LocalData {
d.width = d.height;
d.height = b;
}
+ d.sizeInBytes = c.getLong(COL_SIZE);
return d;
}
@@ -288,7 +293,7 @@ public abstract class LocalMediaData implements LocalData {
public String toString() {
return "Photo:" + ",data=" + path + ",mimeType=" + mimeType
+ "," + width + "x" + height + ",orientation=" + orientation
- + ",date=" + new Date(dateTaken);
+ + ",date=" + new Date(dateTakenInSeconds);
}
@Override
@@ -320,6 +325,23 @@ public abstract class LocalMediaData implements LocalData {
}
@Override
+ public MediaDetails getMediaDetails(Context context) {
+ DateFormat formater = DateFormat.getDateTimeInstance();
+ MediaDetails mediaDetails = new MediaDetails();
+ mediaDetails.addDetail(MediaDetails.INDEX_TITLE, title);
+ mediaDetails.addDetail(MediaDetails.INDEX_WIDTH, width);
+ mediaDetails.addDetail(MediaDetails.INDEX_HEIGHT, height);
+ mediaDetails.addDetail(MediaDetails.INDEX_PATH, path);
+ mediaDetails.addDetail(MediaDetails.INDEX_DATETIME,
+ formater.format(new Date(dateModifiedInSeconds * 1000)));
+ if (sizeInBytes > 0)
+ mediaDetails.addDetail(MediaDetails.INDEX_SIZE, sizeInBytes);
+
+ MediaDetails.extractExifInfo(mediaDetails, path);
+ return mediaDetails;
+ }
+
+ @Override
public int getLocalDataType(int dataID) {
if (mPanoramaMetadata != null && mPanoramaMetadata.mUsePanoramaViewer) {
return LOCAL_PHOTO_SPHERE;
@@ -338,8 +360,8 @@ public abstract class LocalMediaData implements LocalData {
id = newData.id;
title = newData.title;
mimeType = newData.mimeType;
- dateTaken = newData.dateTaken;
- dateModified = newData.dateModified;
+ dateTakenInSeconds = newData.dateTakenInSeconds;
+ dateModifiedInSeconds = newData.dateModifiedInSeconds;
path = newData.path;
orientation = newData.orientation;
width = newData.width;
@@ -436,8 +458,8 @@ public abstract class LocalMediaData implements LocalData {
d.id = c.getLong(COL_ID);
d.title = c.getString(COL_TITLE);
d.mimeType = c.getString(COL_MIME_TYPE);
- d.dateTaken = c.getLong(COL_DATE_TAKEN);
- d.dateModified = c.getLong(COL_DATE_MODIFIED);
+ d.dateTakenInSeconds = c.getLong(COL_DATE_TAKEN);
+ d.dateModifiedInSeconds = c.getLong(COL_DATE_MODIFIED);
d.path = c.getString(COL_DATA);
d.width = c.getInt(COL_WIDTH);
d.height = c.getInt(COL_HEIGHT);
@@ -475,7 +497,7 @@ public abstract class LocalMediaData implements LocalData {
@Override
public String toString() {
return "Video:" + ",data=" + path + ",mimeType=" + mimeType
- + "," + width + "x" + height + ",date=" + new Date(dateTaken);
+ + "," + width + "x" + height + ",date=" + new Date(dateTakenInSeconds);
}
@Override
@@ -507,6 +529,12 @@ public abstract class LocalMediaData implements LocalData {
}
@Override
+ public MediaDetails getMediaDetails(Context context) {
+ // TODO: Return valid MediaDetails for videos.
+ return new MediaDetails();
+ }
+
+ @Override
public int getLocalDataType(int dataID) {
return LOCAL_VIDEO;
}
@@ -525,8 +553,8 @@ public abstract class LocalMediaData implements LocalData {
id = newData.id;
title = newData.title;
mimeType = newData.mimeType;
- dateTaken = newData.dateTaken;
- dateModified = newData.dateModified;
+ dateTakenInSeconds = newData.dateTakenInSeconds;
+ dateModifiedInSeconds = newData.dateModifiedInSeconds;
path = newData.path;
width = newData.width;
height = newData.height;
diff --git a/src/com/android/camera/data/MediaDetails.java b/src/com/android/camera/data/MediaDetails.java
new file mode 100644
index 000000000..cb752733a
--- /dev/null
+++ b/src/com/android/camera/data/MediaDetails.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.data;
+
+import android.util.Log;
+
+import com.android.camera2.R;
+import com.android.gallery3d.exif.ExifInterface;
+import com.android.gallery3d.exif.ExifTag;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+public class MediaDetails implements Iterable<Entry<Integer, Object>> {
+ @SuppressWarnings("unused")
+ private static final String TAG = "MediaDetails";
+
+ private TreeMap<Integer, Object> mDetails = new TreeMap<Integer, Object>();
+ private HashMap<Integer, Integer> mUnits = new HashMap<Integer, Integer>();
+
+ public static final int INDEX_TITLE = 1;
+ public static final int INDEX_DESCRIPTION = 2;
+ public static final int INDEX_DATETIME = 3;
+ public static final int INDEX_LOCATION = 4;
+ public static final int INDEX_WIDTH = 5;
+ public static final int INDEX_HEIGHT = 6;
+ public static final int INDEX_ORIENTATION = 7;
+ public static final int INDEX_DURATION = 8;
+ public static final int INDEX_MIMETYPE = 9;
+ public static final int INDEX_SIZE = 10;
+
+ // for EXIF
+ public static final int INDEX_MAKE = 100;
+ public static final int INDEX_MODEL = 101;
+ public static final int INDEX_FLASH = 102;
+ public static final int INDEX_FOCAL_LENGTH = 103;
+ public static final int INDEX_WHITE_BALANCE = 104;
+ public static final int INDEX_APERTURE = 105;
+ public static final int INDEX_SHUTTER_SPEED = 106;
+ public static final int INDEX_EXPOSURE_TIME = 107;
+ public static final int INDEX_ISO = 108;
+
+ // Put this last because it may be long.
+ public static final int INDEX_PATH = 200;
+
+ public static class FlashState {
+ private static int FLASH_FIRED_MASK = 1;
+ private static int FLASH_RETURN_MASK = 2 | 4;
+ private static int FLASH_MODE_MASK = 8 | 16;
+ private static int FLASH_FUNCTION_MASK = 32;
+ private static int FLASH_RED_EYE_MASK = 64;
+ private int mState;
+
+ public FlashState(int state) {
+ mState = state;
+ }
+
+ public boolean isFlashFired() {
+ return (mState & FLASH_FIRED_MASK) != 0;
+ }
+ }
+
+ public void addDetail(int index, Object value) {
+ mDetails.put(index, value);
+ }
+
+ public Object getDetail(int index) {
+ return mDetails.get(index);
+ }
+
+ public int size() {
+ return mDetails.size();
+ }
+
+ @Override
+ public Iterator<Entry<Integer, Object>> iterator() {
+ return mDetails.entrySet().iterator();
+ }
+
+ public void setUnit(int index, int unit) {
+ mUnits.put(index, unit);
+ }
+
+ public boolean hasUnit(int index) {
+ return mUnits.containsKey(index);
+ }
+
+ public int getUnit(int index) {
+ return mUnits.get(index);
+ }
+
+ private static void setExifData(MediaDetails details, ExifTag tag,
+ int key) {
+ if (tag != null) {
+ String value = null;
+ int type = tag.getDataType();
+ if (type == ExifTag.TYPE_UNSIGNED_RATIONAL || type == ExifTag.TYPE_RATIONAL) {
+ value = String.valueOf(tag.getValueAsRational(0).toDouble());
+ } else if (type == ExifTag.TYPE_ASCII) {
+ value = tag.getValueAsString();
+ } else {
+ value = String.valueOf(tag.forceGetValueAsLong(0));
+ }
+ if (key == MediaDetails.INDEX_FLASH) {
+ MediaDetails.FlashState state = new MediaDetails.FlashState(
+ Integer.valueOf(value.toString()));
+ details.addDetail(key, state);
+ } else {
+ details.addDetail(key, value);
+ }
+ }
+ }
+
+ /**
+ * Extracts data from the EXIF of the given file and stores it in the
+ * MediaDetails instance.
+ */
+ public static void extractExifInfo(MediaDetails details, String filePath) {
+ ExifInterface exif = new ExifInterface();
+ try {
+ exif.readExif(filePath);
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, "Could not find file to read exif: " + filePath, e);
+ } catch (IOException e) {
+ Log.w(TAG, "Could not read exif from file: " + filePath, e);
+ }
+
+ setExifData(details, exif.getTag(ExifInterface.TAG_FLASH),
+ MediaDetails.INDEX_FLASH);
+ setExifData(details, exif.getTag(ExifInterface.TAG_IMAGE_WIDTH),
+ MediaDetails.INDEX_WIDTH);
+ setExifData(details, exif.getTag(ExifInterface.TAG_IMAGE_LENGTH),
+ MediaDetails.INDEX_HEIGHT);
+ setExifData(details, exif.getTag(ExifInterface.TAG_MAKE),
+ MediaDetails.INDEX_MAKE);
+ setExifData(details, exif.getTag(ExifInterface.TAG_MODEL),
+ MediaDetails.INDEX_MODEL);
+ setExifData(details, exif.getTag(ExifInterface.TAG_APERTURE_VALUE),
+ MediaDetails.INDEX_APERTURE);
+ setExifData(details, exif.getTag(ExifInterface.TAG_ISO_SPEED_RATINGS),
+ MediaDetails.INDEX_ISO);
+ setExifData(details, exif.getTag(ExifInterface.TAG_WHITE_BALANCE),
+ MediaDetails.INDEX_WHITE_BALANCE);
+ setExifData(details, exif.getTag(ExifInterface.TAG_EXPOSURE_TIME),
+ MediaDetails.INDEX_EXPOSURE_TIME);
+ ExifTag focalTag = exif.getTag(ExifInterface.TAG_FOCAL_LENGTH);
+ if (focalTag != null) {
+ details.addDetail(MediaDetails.INDEX_FOCAL_LENGTH,
+ focalTag.getValueAsRational(0).toDouble());
+ details.setUnit(MediaDetails.INDEX_FOCAL_LENGTH, R.string.unit_mm);
+ }
+ }
+}
diff --git a/src/com/android/camera/data/SimpleViewData.java b/src/com/android/camera/data/SimpleViewData.java
index d6fc1c416..8801599bb 100644
--- a/src/com/android/camera/data/SimpleViewData.java
+++ b/src/com/android/camera/data/SimpleViewData.java
@@ -145,4 +145,9 @@ public class SimpleViewData implements LocalData {
public boolean canSwipeInFullScreen() {
return true;
}
+
+ @Override
+ public MediaDetails getMediaDetails(Context context) {
+ return null;
+ }
}
diff --git a/src/com/android/camera/ui/DetailsDialog.java b/src/com/android/camera/ui/DetailsDialog.java
new file mode 100644
index 000000000..cc0130d22
--- /dev/null
+++ b/src/com/android/camera/ui/DetailsDialog.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.ui;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.text.format.Formatter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.camera.data.MediaDetails;
+import com.android.camera2.R;
+
+import java.util.ArrayList;
+import java.util.Map.Entry;
+
+/**
+ * Displays details (such as Exif) of a local media item.
+ */
+public class DetailsDialog {
+
+ /**
+ * Creates a dialog for showing media data.
+ *
+ * @param context the Android context.
+ * @param mediaDetails the media details to display.
+ * @return A dialog that can be made visible to show the media details.
+ */
+ public static Dialog create(Context context, MediaDetails mediaDetails) {
+ ListView detailsList = (ListView) LayoutInflater.from(context).inflate(
+ R.layout.details_list, null, false);
+ detailsList.setAdapter(new DetailsAdapter(context, mediaDetails));
+
+ final AlertDialog.Builder builder =
+ new AlertDialog.Builder(context);
+ return builder.setTitle(R.string.details).setView(detailsList)
+ .setPositiveButton(R.string.close, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton) {
+ dialog.dismiss();
+ }
+ }).create();
+ }
+
+ /**
+ * An adapter for feeding a details list view with the contents of a
+ * {@link MediaDetails} instance.
+ */
+ private static class DetailsAdapter extends BaseAdapter {
+ private final Context mContext;
+ private final MediaDetails mMediaDetails;
+ private final ArrayList<String> mItems;
+ private int mLocationIndex;
+ private int mWidthIndex = -1;
+ private int mHeightIndex = -1;
+
+ public DetailsAdapter(Context context, MediaDetails details) {
+ mContext = context;
+ mMediaDetails = details;
+ mItems = new ArrayList<String>(details.size());
+ mLocationIndex = -1;
+ setDetails(context, details);
+ }
+
+ private void setDetails(Context context, MediaDetails details) {
+ boolean resolutionIsValid = true;
+ String path = null;
+ for (Entry<Integer, Object> detail : details) {
+ String value;
+ switch (detail.getKey()) {
+ // TODO: Resolve address asynchronously.
+ case MediaDetails.INDEX_SIZE: {
+ value = Formatter.formatFileSize(
+ context, (Long) detail.getValue());
+ break;
+ }
+ case MediaDetails.INDEX_WHITE_BALANCE: {
+ value = "1".equals(detail.getValue())
+ ? context.getString(R.string.manual)
+ : context.getString(R.string.auto);
+ break;
+ }
+ case MediaDetails.INDEX_FLASH: {
+ MediaDetails.FlashState flash =
+ (MediaDetails.FlashState) detail.getValue();
+ // TODO: camera doesn't fill in the complete values,
+ // show more information when it is fixed.
+ if (flash.isFlashFired()) {
+ value = context.getString(R.string.flash_on);
+ } else {
+ value = context.getString(R.string.flash_off);
+ }
+ break;
+ }
+ case MediaDetails.INDEX_EXPOSURE_TIME: {
+ value = (String) detail.getValue();
+ double time = Double.valueOf(value);
+ if (time < 1.0f) {
+ value = String.format("1/%d", (int) (0.5f + 1 / time));
+ } else {
+ int integer = (int) time;
+ time -= integer;
+ value = String.valueOf(integer) + "''";
+ if (time > 0.0001) {
+ value += String.format(" 1/%d", (int) (0.5f + 1 / time));
+ }
+ }
+ break;
+ }
+ case MediaDetails.INDEX_WIDTH:
+ mWidthIndex = mItems.size();
+ value = detail.getValue().toString();
+ if (value.equalsIgnoreCase("0")) {
+ value = context.getString(R.string.unknown);
+ resolutionIsValid = false;
+ }
+ break;
+ case MediaDetails.INDEX_HEIGHT: {
+ mHeightIndex = mItems.size();
+ value = detail.getValue().toString();
+ if (value.equalsIgnoreCase("0")) {
+ value = context.getString(R.string.unknown);
+ resolutionIsValid = false;
+ }
+ break;
+ }
+ case MediaDetails.INDEX_PATH:
+ // Get the path and then fall through to the default
+ // case
+ path = detail.getValue().toString();
+ default: {
+ Object valueObj = detail.getValue();
+ // This shouldn't happen, log its key to help us
+ // diagnose the problem.
+ if (valueObj == null) {
+ fail("%s's value is Null",
+ getDetailsName(context,
+ detail.getKey()));
+ }
+ value = valueObj.toString();
+ }
+ }
+ int key = detail.getKey();
+ if (details.hasUnit(key)) {
+ value = String.format("%s: %s %s", getDetailsName(
+ context, key), value, context.getString(details.getUnit(key)));
+ } else {
+ value = String.format("%s: %s", getDetailsName(
+ context, key), value);
+ }
+ mItems.add(value);
+ if (!resolutionIsValid) {
+ resolveResolution(path);
+ }
+ }
+ }
+
+ public void resolveResolution(String path) {
+ Bitmap bitmap = BitmapFactory.decodeFile(path);
+ if (bitmap == null)
+ return;
+ onResolutionAvailable(bitmap.getWidth(), bitmap.getHeight());
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return false;
+ }
+
+ @Override
+ public int getCount() {
+ return mItems.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mMediaDetails.getDetail(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TextView tv;
+ if (convertView == null) {
+ tv = (TextView) LayoutInflater.from(mContext).inflate(
+ R.layout.details, parent, false);
+ } else {
+ tv = (TextView) convertView;
+ }
+ tv.setText(mItems.get(position));
+ return tv;
+ }
+
+ public void onResolutionAvailable(int width, int height) {
+ if (width == 0 || height == 0)
+ return;
+ // Update the resolution with the new width and height
+ String widthString = String.format("%s: %d",
+ getDetailsName(
+ mContext, MediaDetails.INDEX_WIDTH), width);
+ String heightString = String.format("%s: %d",
+ getDetailsName(
+ mContext, MediaDetails.INDEX_HEIGHT), height);
+ mItems.set(mWidthIndex, String.valueOf(widthString));
+ mItems.set(mHeightIndex, String.valueOf(heightString));
+ notifyDataSetChanged();
+ }
+ }
+
+ public static String getDetailsName(Context context, int key) {
+ switch (key) {
+ case MediaDetails.INDEX_TITLE:
+ return context.getString(R.string.title);
+ case MediaDetails.INDEX_DESCRIPTION:
+ return context.getString(R.string.description);
+ case MediaDetails.INDEX_DATETIME:
+ return context.getString(R.string.time);
+ case MediaDetails.INDEX_LOCATION:
+ return context.getString(R.string.location);
+ case MediaDetails.INDEX_PATH:
+ return context.getString(R.string.path);
+ case MediaDetails.INDEX_WIDTH:
+ return context.getString(R.string.width);
+ case MediaDetails.INDEX_HEIGHT:
+ return context.getString(R.string.height);
+ case MediaDetails.INDEX_ORIENTATION:
+ return context.getString(R.string.orientation);
+ case MediaDetails.INDEX_DURATION:
+ return context.getString(R.string.duration);
+ case MediaDetails.INDEX_MIMETYPE:
+ return context.getString(R.string.mimetype);
+ case MediaDetails.INDEX_SIZE:
+ return context.getString(R.string.file_size);
+ case MediaDetails.INDEX_MAKE:
+ return context.getString(R.string.maker);
+ case MediaDetails.INDEX_MODEL:
+ return context.getString(R.string.model);
+ case MediaDetails.INDEX_FLASH:
+ return context.getString(R.string.flash);
+ case MediaDetails.INDEX_APERTURE:
+ return context.getString(R.string.aperture);
+ case MediaDetails.INDEX_FOCAL_LENGTH:
+ return context.getString(R.string.focal_length);
+ case MediaDetails.INDEX_WHITE_BALANCE:
+ return context.getString(R.string.white_balance);
+ case MediaDetails.INDEX_EXPOSURE_TIME:
+ return context.getString(R.string.exposure_time);
+ case MediaDetails.INDEX_ISO:
+ return context.getString(R.string.iso);
+ default:
+ return "Unknown key" + key;
+ }
+ }
+
+ /**
+ * Throw an assertion error wit the given message.
+ *
+ * @param message the message, can contain placeholders.
+ * @param args if he message contains placeholders, these values will be
+ * used to fill them.
+ */
+ private static void fail(String message, Object... args) {
+ throw new AssertionError(
+ args.length == 0 ? message : String.format(message, args));
+ }
+}