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
17package com.android.gallery3d.ui;
18
19import android.app.AlertDialog;
20import android.app.Dialog;
21import android.content.Context;
22import android.content.DialogInterface;
23import android.content.DialogInterface.OnDismissListener;
24import android.text.format.Formatter;
25import android.view.LayoutInflater;
26import android.view.View;
27import android.view.ViewGroup;
28import android.widget.BaseAdapter;
29import android.widget.ListView;
30import android.widget.TextView;
31
32import com.android.gallery3d.R;
33import com.android.gallery3d.app.AbstractGalleryActivity;
34import com.android.gallery3d.common.Utils;
35import com.android.gallery3d.data.MediaDetails;
36import com.android.gallery3d.ui.DetailsAddressResolver.AddressResolvingListener;
37import com.android.gallery3d.ui.DetailsHelper.CloseListener;
38import com.android.gallery3d.ui.DetailsHelper.DetailsSource;
39import com.android.gallery3d.ui.DetailsHelper.DetailsViewContainer;
40import com.android.gallery3d.ui.DetailsHelper.ResolutionResolvingListener;
41
42import java.text.DecimalFormat;
43import java.util.ArrayList;
44import java.util.Locale;
45import java.util.Map.Entry;
46
47public 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
59    public DialogDetailsView(AbstractGalleryActivity activity, DetailsSource source) {
60        mActivity = activity;
61        mSource = source;
62    }
63
64    @Override
65    public void show() {
66        reloadDetails();
67        mDialog.show();
68    }
69
70    @Override
71    public void hide() {
72        mDialog.hide();
73    }
74
75    @Override
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
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
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
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
250        public boolean areAllItemsEnabled() {
251            return false;
252        }
253
254        @Override
255        public boolean isEnabled(int position) {
256            return false;
257        }
258
259        @Override
260        public int getCount() {
261            return mItems.size();
262        }
263
264        @Override
265        public Object getItem(int position) {
266            return mDetails.getDetail(position);
267        }
268
269        @Override
270        public long getItemId(int position) {
271            return position;
272        }
273
274        @Override
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
288        public void onAddressAvailable(String address) {
289            mItems.set(mLocationIndex, address);
290            notifyDataSetChanged();
291        }
292
293        @Override
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         */
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. */
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. */
334        private String toLocalNumber(double n) {
335            return mDecimalFormat.format(n);
336        }
337    }
338
339    @Override
340    public void setCloseListener(CloseListener listener) {
341        mListener = listener;
342    }
343}
344