1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.gallery3d.ui; 18 19 import android.app.AlertDialog; 20 import android.app.Dialog; 21 import android.content.Context; 22 import android.content.DialogInterface; 23 import android.content.DialogInterface.OnDismissListener; 24 import android.text.format.Formatter; 25 import android.view.LayoutInflater; 26 import android.view.View; 27 import android.view.ViewGroup; 28 import android.widget.BaseAdapter; 29 import android.widget.ListView; 30 import android.widget.TextView; 31 32 import com.android.gallery3d.R; 33 import com.android.gallery3d.app.AbstractGalleryActivity; 34 import com.android.gallery3d.common.Utils; 35 import com.android.gallery3d.data.MediaDetails; 36 import com.android.gallery3d.ui.DetailsAddressResolver.AddressResolvingListener; 37 import com.android.gallery3d.ui.DetailsHelper.CloseListener; 38 import com.android.gallery3d.ui.DetailsHelper.DetailsSource; 39 import com.android.gallery3d.ui.DetailsHelper.DetailsViewContainer; 40 import com.android.gallery3d.ui.DetailsHelper.ResolutionResolvingListener; 41 42 import java.text.DecimalFormat; 43 import java.util.ArrayList; 44 import java.util.Locale; 45 import java.util.Map.Entry; 46 47 public class DialogDetailsView implements DetailsViewContainer { 48 @SuppressWarnings("unused") 49 private static final String TAG = "DialogDetailsView"; 50 51 private final AbstractGalleryActivity mActivity; 52 private DetailsAdapter mAdapter; 53 private MediaDetails mDetails; 54 private final DetailsSource mSource; 55 private int mIndex; 56 private Dialog mDialog; 57 private CloseListener mListener; 58 DialogDetailsView(AbstractGalleryActivity activity, DetailsSource source)59 public DialogDetailsView(AbstractGalleryActivity activity, DetailsSource source) { 60 mActivity = activity; 61 mSource = source; 62 } 63 64 @Override show()65 public void show() { 66 reloadDetails(); 67 mDialog.show(); 68 } 69 70 @Override hide()71 public void hide() { 72 mDialog.hide(); 73 } 74 75 @Override reloadDetails()76 public void reloadDetails() { 77 int index = mSource.setIndex(); 78 if (index == -1) return; 79 MediaDetails details = mSource.getDetails(); 80 if (details != null) { 81 if (mIndex == index && mDetails == details) return; 82 mIndex = index; 83 mDetails = details; 84 setDetails(details); 85 } 86 } 87 setDetails(MediaDetails details)88 private void setDetails(MediaDetails details) { 89 mAdapter = new DetailsAdapter(details); 90 String title = String.format( 91 mActivity.getAndroidContext().getString(R.string.details_title), 92 mIndex + 1, mSource.size()); 93 ListView detailsList = (ListView) LayoutInflater.from(mActivity.getAndroidContext()).inflate( 94 R.layout.details_list, null, false); 95 detailsList.setAdapter(mAdapter); 96 mDialog = new AlertDialog.Builder(mActivity) 97 .setView(detailsList) 98 .setTitle(title) 99 .setPositiveButton(R.string.close, new DialogInterface.OnClickListener() { 100 @Override 101 public void onClick(DialogInterface dialog, int whichButton) { 102 mDialog.dismiss(); 103 } 104 }) 105 .create(); 106 107 mDialog.setOnDismissListener(new OnDismissListener() { 108 @Override 109 public void onDismiss(DialogInterface dialog) { 110 if (mListener != null) { 111 mListener.onClose(); 112 } 113 } 114 }); 115 } 116 117 118 private class DetailsAdapter extends BaseAdapter 119 implements AddressResolvingListener, ResolutionResolvingListener { 120 private final ArrayList<String> mItems; 121 private int mLocationIndex; 122 private final Locale mDefaultLocale = Locale.getDefault(); 123 private final DecimalFormat mDecimalFormat = new DecimalFormat(".####"); 124 private int mWidthIndex = -1; 125 private int mHeightIndex = -1; 126 DetailsAdapter(MediaDetails details)127 public DetailsAdapter(MediaDetails details) { 128 Context context = mActivity.getAndroidContext(); 129 mItems = new ArrayList<String>(details.size()); 130 mLocationIndex = -1; 131 setDetails(context, details); 132 } 133 setDetails(Context context, MediaDetails details)134 private void setDetails(Context context, MediaDetails details) { 135 boolean resolutionIsValid = true; 136 String path = null; 137 for (Entry<Integer, Object> detail : details) { 138 String value; 139 switch (detail.getKey()) { 140 case MediaDetails.INDEX_LOCATION: { 141 double[] latlng = (double[]) detail.getValue(); 142 mLocationIndex = mItems.size(); 143 value = DetailsHelper.resolveAddress(mActivity, latlng, this); 144 break; 145 } 146 case MediaDetails.INDEX_SIZE: { 147 value = Formatter.formatFileSize( 148 context, (Long) detail.getValue()); 149 break; 150 } 151 case MediaDetails.INDEX_WHITE_BALANCE: { 152 value = "1".equals(detail.getValue()) 153 ? context.getString(R.string.manual) 154 : context.getString(R.string.auto); 155 break; 156 } 157 case MediaDetails.INDEX_FLASH: { 158 MediaDetails.FlashState flash = 159 (MediaDetails.FlashState) detail.getValue(); 160 // TODO: camera doesn't fill in the complete values, show more information 161 // when it is fixed. 162 if (flash.isFlashFired()) { 163 value = context.getString(R.string.flash_on); 164 } else { 165 value = context.getString(R.string.flash_off); 166 } 167 break; 168 } 169 case MediaDetails.INDEX_EXPOSURE_TIME: { 170 value = (String) detail.getValue(); 171 double time = Double.valueOf(value); 172 if (time < 1.0f) { 173 value = String.format(mDefaultLocale, "%d/%d", 1, 174 (int) (0.5f + 1 / time)); 175 } else { 176 int integer = (int) time; 177 time -= integer; 178 value = String.valueOf(integer) + "''"; 179 if (time > 0.0001) { 180 value += String.format(mDefaultLocale, " %d/%d", 1, 181 (int) (0.5f + 1 / time)); 182 } 183 } 184 break; 185 } 186 case MediaDetails.INDEX_WIDTH: 187 mWidthIndex = mItems.size(); 188 if (detail.getValue().toString().equalsIgnoreCase("0")) { 189 value = context.getString(R.string.unknown); 190 resolutionIsValid = false; 191 } else { 192 value = toLocalInteger(detail.getValue()); 193 } 194 break; 195 case MediaDetails.INDEX_HEIGHT: { 196 mHeightIndex = mItems.size(); 197 if (detail.getValue().toString().equalsIgnoreCase("0")) { 198 value = context.getString(R.string.unknown); 199 resolutionIsValid = false; 200 } else { 201 value = toLocalInteger(detail.getValue()); 202 } 203 break; 204 } 205 case MediaDetails.INDEX_PATH: 206 // Prepend the new-line as a) paths are usually long, so 207 // the formatting is better and b) an RTL UI will see it 208 // as a separate section and interpret it for what it 209 // is, rather than trying to make it RTL (which messes 210 // up the path). 211 value = "\n" + detail.getValue().toString(); 212 path = detail.getValue().toString(); 213 break; 214 case MediaDetails.INDEX_ISO: 215 value = toLocalNumber(Integer.parseInt((String) detail.getValue())); 216 break; 217 case MediaDetails.INDEX_FOCAL_LENGTH: 218 double focalLength = Double.parseDouble(detail.getValue().toString()); 219 value = toLocalNumber(focalLength); 220 break; 221 case MediaDetails.INDEX_ORIENTATION: 222 value = toLocalInteger(detail.getValue()); 223 break; 224 default: { 225 Object valueObj = detail.getValue(); 226 // This shouldn't happen, log its key to help us diagnose the problem. 227 if (valueObj == null) { 228 Utils.fail("%s's value is Null", 229 DetailsHelper.getDetailsName(context, detail.getKey())); 230 } 231 value = valueObj.toString(); 232 } 233 } 234 int key = detail.getKey(); 235 if (details.hasUnit(key)) { 236 value = String.format("%s: %s %s", DetailsHelper.getDetailsName( 237 context, key), value, context.getString(details.getUnit(key))); 238 } else { 239 value = String.format("%s: %s", DetailsHelper.getDetailsName( 240 context, key), value); 241 } 242 mItems.add(value); 243 } 244 if (!resolutionIsValid) { 245 DetailsHelper.resolveResolution(path, this); 246 } 247 } 248 249 @Override areAllItemsEnabled()250 public boolean areAllItemsEnabled() { 251 return false; 252 } 253 254 @Override isEnabled(int position)255 public boolean isEnabled(int position) { 256 return false; 257 } 258 259 @Override getCount()260 public int getCount() { 261 return mItems.size(); 262 } 263 264 @Override getItem(int position)265 public Object getItem(int position) { 266 return mDetails.getDetail(position); 267 } 268 269 @Override getItemId(int position)270 public long getItemId(int position) { 271 return position; 272 } 273 274 @Override getView(int position, View convertView, ViewGroup parent)275 public View getView(int position, View convertView, ViewGroup parent) { 276 TextView tv; 277 if (convertView == null) { 278 tv = (TextView) LayoutInflater.from(mActivity.getAndroidContext()).inflate( 279 R.layout.details, parent, false); 280 } else { 281 tv = (TextView) convertView; 282 } 283 tv.setText(mItems.get(position)); 284 return tv; 285 } 286 287 @Override onAddressAvailable(String address)288 public void onAddressAvailable(String address) { 289 mItems.set(mLocationIndex, address); 290 notifyDataSetChanged(); 291 } 292 293 @Override onResolutionAvailable(int width, int height)294 public void onResolutionAvailable(int width, int height) { 295 if (width == 0 || height == 0) return; 296 // Update the resolution with the new width and height 297 Context context = mActivity.getAndroidContext(); 298 String widthString = String.format(mDefaultLocale, "%s: %d", 299 DetailsHelper.getDetailsName( 300 context, MediaDetails.INDEX_WIDTH), width); 301 String heightString = String.format(mDefaultLocale, "%s: %d", 302 DetailsHelper.getDetailsName( 303 context, MediaDetails.INDEX_HEIGHT), height); 304 mItems.set(mWidthIndex, String.valueOf(widthString)); 305 mItems.set(mHeightIndex, String.valueOf(heightString)); 306 notifyDataSetChanged(); 307 } 308 309 /** 310 * Converts the given integer (given as String or Integer object) to a 311 * localized String version. 312 */ toLocalInteger(Object valueObj)313 private String toLocalInteger(Object valueObj) { 314 if (valueObj instanceof Integer) { 315 return toLocalNumber((Integer) valueObj); 316 } else { 317 String value = valueObj.toString(); 318 try { 319 value = toLocalNumber(Integer.parseInt(value)); 320 } catch (NumberFormatException ex) { 321 // Just keep the current "value" if we cannot 322 // parse it as a fallback. 323 } 324 return value; 325 } 326 } 327 328 /** Converts the given integer to a localized String version. */ toLocalNumber(int n)329 private String toLocalNumber(int n) { 330 return String.format(mDefaultLocale, "%d", n); 331 } 332 333 /** Converts the given double to a localized String version. */ toLocalNumber(double n)334 private String toLocalNumber(double n) { 335 return mDecimalFormat.format(n); 336 } 337 } 338 339 @Override setCloseListener(CloseListener listener)340 public void setCloseListener(CloseListener listener) { 341 mListener = listener; 342 } 343 } 344