OSDN Git Service

4e9a5ddfe8cf0cb1f67c8a54a95ac7c417ad39e2
[android-x86/packages-apps-Gallery2.git] / src / com / android / camera / CameraSettings.java
1 /*
2  * Copyright (C) 2009 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.camera;
18
19 import android.annotation.TargetApi;
20 import android.app.Activity;
21 import android.content.Context;
22 import android.content.SharedPreferences;
23 import android.content.SharedPreferences.Editor;
24 import android.content.res.Resources;
25 import android.content.res.TypedArray;
26 import android.hardware.Camera.CameraInfo;
27 import android.hardware.Camera.Parameters;
28 import android.hardware.Camera.Size;
29 import android.media.CamcorderProfile;
30 import android.util.FloatMath;
31 import android.util.Log;
32
33 import com.android.gallery3d.R;
34 import com.android.gallery3d.common.ApiHelper;
35
36 import java.util.ArrayList;
37 import java.util.List;
38 import java.util.Locale;
39
40 /**
41  *  Provides utilities and keys for Camera settings.
42  */
43 public class CameraSettings {
44     private static final int NOT_FOUND = -1;
45
46     public static final String KEY_VERSION = "pref_version_key";
47     public static final String KEY_LOCAL_VERSION = "pref_local_version_key";
48     public static final String KEY_RECORD_LOCATION = "pref_camera_recordlocation_key";
49     public static final String KEY_VIDEO_QUALITY = "pref_video_quality_key";
50     public static final String KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL = "pref_video_time_lapse_frame_interval_key";
51     public static final String KEY_PICTURE_SIZE = "pref_camera_picturesize_key";
52     public static final String KEY_JPEG_QUALITY = "pref_camera_jpegquality_key";
53     public static final String KEY_FOCUS_MODE = "pref_camera_focusmode_key";
54     public static final String KEY_FLASH_MODE = "pref_camera_flashmode_key";
55     public static final String KEY_VIDEOCAMERA_FLASH_MODE = "pref_camera_video_flashmode_key";
56     public static final String KEY_WHITE_BALANCE = "pref_camera_whitebalance_key";
57     public static final String KEY_SCENE_MODE = "pref_camera_scenemode_key";
58     public static final String KEY_EXPOSURE = "pref_camera_exposure_key";
59     public static final String KEY_TIMER = "pref_camera_timer_key";
60     public static final String KEY_TIMER_SOUND_EFFECTS = "pref_camera_timer_sound_key";
61     public static final String KEY_VIDEO_EFFECT = "pref_video_effect_key";
62     public static final String KEY_CAMERA_ID = "pref_camera_id_key";
63     public static final String KEY_CAMERA_HDR = "pref_camera_hdr_key";
64     public static final String KEY_CAMERA_FIRST_USE_HINT_SHOWN = "pref_camera_first_use_hint_shown_key";
65     public static final String KEY_VIDEO_FIRST_USE_HINT_SHOWN = "pref_video_first_use_hint_shown_key";
66     public static final String KEY_PHOTOSPHERE_PICTURESIZE = "pref_photosphere_picturesize_key";
67
68     public static final String EXPOSURE_DEFAULT_VALUE = "0";
69
70     public static final int CURRENT_VERSION = 5;
71     public static final int CURRENT_LOCAL_VERSION = 2;
72
73     private static final String TAG = "CameraSettings";
74
75     private final Context mContext;
76     private final Parameters mParameters;
77     private final CameraInfo[] mCameraInfo;
78     private final int mCameraId;
79
80     public CameraSettings(Activity activity, Parameters parameters,
81                           int cameraId, CameraInfo[] cameraInfo) {
82         mContext = activity;
83         mParameters = parameters;
84         mCameraId = cameraId;
85         mCameraInfo = cameraInfo;
86     }
87
88     public PreferenceGroup getPreferenceGroup(int preferenceRes) {
89         PreferenceInflater inflater = new PreferenceInflater(mContext);
90         PreferenceGroup group =
91                 (PreferenceGroup) inflater.inflate(preferenceRes);
92         if (mParameters != null) initPreference(group);
93         return group;
94     }
95
96     @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
97     public static String getDefaultVideoQuality(int cameraId,
98             String defaultQuality) {
99         if (ApiHelper.HAS_FINE_RESOLUTION_QUALITY_LEVELS) {
100             if (CamcorderProfile.hasProfile(
101                     cameraId, Integer.valueOf(defaultQuality))) {
102                 return defaultQuality;
103             }
104         }
105         return Integer.toString(CamcorderProfile.QUALITY_HIGH);
106     }
107
108     public static void initialCameraPictureSize(
109             Context context, Parameters parameters) {
110         // When launching the camera app first time, we will set the picture
111         // size to the first one in the list defined in "arrays.xml" and is also
112         // supported by the driver.
113         List<Size> supported = parameters.getSupportedPictureSizes();
114         if (supported == null) return;
115         for (String candidate : context.getResources().getStringArray(
116                 R.array.pref_camera_picturesize_entryvalues)) {
117             if (setCameraPictureSize(candidate, supported, parameters)) {
118                 SharedPreferences.Editor editor = ComboPreferences
119                         .get(context).edit();
120                 editor.putString(KEY_PICTURE_SIZE, candidate);
121                 editor.apply();
122                 return;
123             }
124         }
125         Log.e(TAG, "No supported picture size found");
126     }
127
128     public static void removePreferenceFromScreen(
129             PreferenceGroup group, String key) {
130         removePreference(group, key);
131     }
132
133     public static boolean setCameraPictureSize(
134             String candidate, List<Size> supported, Parameters parameters) {
135         int index = candidate.indexOf('x');
136         if (index == NOT_FOUND) return false;
137         int width = Integer.parseInt(candidate.substring(0, index));
138         int height = Integer.parseInt(candidate.substring(index + 1));
139         for (Size size : supported) {
140             if (size.width == width && size.height == height) {
141                 parameters.setPictureSize(width, height);
142                 return true;
143             }
144         }
145         return false;
146     }
147
148     public static int getMaxVideoDuration(Context context) {
149         int duration = 0;  // in milliseconds, 0 means unlimited.
150         try {
151             duration = context.getResources().getInteger(R.integer.max_video_recording_length);
152         } catch (Resources.NotFoundException ex) {
153         }
154         return duration;
155     }
156
157     private void initPreference(PreferenceGroup group) {
158         ListPreference videoQuality = group.findPreference(KEY_VIDEO_QUALITY);
159         ListPreference timeLapseInterval = group.findPreference(KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL);
160         ListPreference pictureSize = group.findPreference(KEY_PICTURE_SIZE);
161         ListPreference whiteBalance =  group.findPreference(KEY_WHITE_BALANCE);
162         ListPreference sceneMode = group.findPreference(KEY_SCENE_MODE);
163         ListPreference flashMode = group.findPreference(KEY_FLASH_MODE);
164         ListPreference focusMode = group.findPreference(KEY_FOCUS_MODE);
165         IconListPreference exposure =
166                 (IconListPreference) group.findPreference(KEY_EXPOSURE);
167         CountDownTimerPreference timer =
168                 (CountDownTimerPreference) group.findPreference(KEY_TIMER);
169         ListPreference countDownSoundEffects = group.findPreference(KEY_TIMER_SOUND_EFFECTS);
170         IconListPreference cameraIdPref =
171                 (IconListPreference) group.findPreference(KEY_CAMERA_ID);
172         ListPreference videoFlashMode =
173                 group.findPreference(KEY_VIDEOCAMERA_FLASH_MODE);
174         ListPreference videoEffect = group.findPreference(KEY_VIDEO_EFFECT);
175         ListPreference cameraHdr = group.findPreference(KEY_CAMERA_HDR);
176
177         // Since the screen could be loaded from different resources, we need
178         // to check if the preference is available here
179         if (videoQuality != null) {
180             filterUnsupportedOptions(group, videoQuality, getSupportedVideoQuality());
181         }
182
183         if (pictureSize != null) {
184             filterUnsupportedOptions(group, pictureSize, sizeListToStringList(
185                     mParameters.getSupportedPictureSizes()));
186             filterSimilarPictureSize(group, pictureSize);
187         }
188         if (whiteBalance != null) {
189             filterUnsupportedOptions(group,
190                     whiteBalance, mParameters.getSupportedWhiteBalance());
191         }
192         if (sceneMode != null) {
193             filterUnsupportedOptions(group,
194                     sceneMode, mParameters.getSupportedSceneModes());
195         }
196         if (flashMode != null) {
197             filterUnsupportedOptions(group,
198                     flashMode, mParameters.getSupportedFlashModes());
199         }
200         if (focusMode != null) {
201             if (!Util.isFocusAreaSupported(mParameters)) {
202                 filterUnsupportedOptions(group,
203                         focusMode, mParameters.getSupportedFocusModes());
204             } else {
205                 // Remove the focus mode if we can use tap-to-focus.
206                 removePreference(group, focusMode.getKey());
207             }
208         }
209         if (videoFlashMode != null) {
210             filterUnsupportedOptions(group,
211                     videoFlashMode, mParameters.getSupportedFlashModes());
212         }
213         if (exposure != null) buildExposureCompensation(group, exposure);
214         if (cameraIdPref != null) buildCameraId(group, cameraIdPref);
215
216         if (timeLapseInterval != null) {
217             if (ApiHelper.HAS_TIME_LAPSE_RECORDING) {
218                 resetIfInvalid(timeLapseInterval);
219             } else {
220                 removePreference(group, timeLapseInterval.getKey());
221             }
222         }
223         if (videoEffect != null) {
224             if (ApiHelper.HAS_EFFECTS_RECORDING) {
225                 initVideoEffect(group, videoEffect);
226                 resetIfInvalid(videoEffect);
227             } else {
228                 filterUnsupportedOptions(group, videoEffect, null);
229             }
230         }
231         if (cameraHdr != null && (!ApiHelper.HAS_CAMERA_HDR
232                     || !Util.isCameraHdrSupported(mParameters))) {
233             removePreference(group, cameraHdr.getKey());
234         }
235     }
236
237     private void buildExposureCompensation(
238             PreferenceGroup group, IconListPreference exposure) {
239         int max = mParameters.getMaxExposureCompensation();
240         int min = mParameters.getMinExposureCompensation();
241         if (max == 0 && min == 0) {
242             removePreference(group, exposure.getKey());
243             return;
244         }
245         float step = mParameters.getExposureCompensationStep();
246
247         // show only integer values for exposure compensation
248         int maxValue = Math.min(3, (int) FloatMath.floor(max * step));
249         int minValue = Math.max(-3, (int) FloatMath.ceil(min * step));
250         String explabel = mContext.getResources().getString(R.string.pref_exposure_label);
251         CharSequence entries[] = new CharSequence[maxValue - minValue + 1];
252         CharSequence entryValues[] = new CharSequence[maxValue - minValue + 1];
253         CharSequence labels[] = new CharSequence[maxValue - minValue + 1];
254         int[] icons = new int[maxValue - minValue + 1];
255         TypedArray iconIds = mContext.getResources().obtainTypedArray(
256                 R.array.pref_camera_exposure_icons);
257         for (int i = minValue; i <= maxValue; ++i) {
258             entryValues[i - minValue] = Integer.toString(Math.round(i / step));
259             StringBuilder builder = new StringBuilder();
260             if (i > 0) builder.append('+');
261             entries[i - minValue] = builder.append(i).toString();
262             labels[i - minValue] = explabel + " " + builder.toString();
263             icons[i - minValue] = iconIds.getResourceId(3 + i, 0);
264         }
265         exposure.setUseSingleIcon(true);
266         exposure.setEntries(entries);
267         exposure.setLabels(labels);
268         exposure.setEntryValues(entryValues);
269         exposure.setLargeIconIds(icons);
270     }
271
272     private void buildCameraId(
273             PreferenceGroup group, IconListPreference preference) {
274         int numOfCameras = mCameraInfo.length;
275         if (numOfCameras < 2) {
276             removePreference(group, preference.getKey());
277             return;
278         }
279
280         CharSequence[] entryValues = new CharSequence[numOfCameras];
281         for (int i = 0; i < numOfCameras; ++i) {
282             entryValues[i] = "" + i;
283         }
284         preference.setEntryValues(entryValues);
285     }
286
287     private static boolean removePreference(PreferenceGroup group, String key) {
288         for (int i = 0, n = group.size(); i < n; i++) {
289             CameraPreference child = group.get(i);
290             if (child instanceof PreferenceGroup) {
291                 if (removePreference((PreferenceGroup) child, key)) {
292                     return true;
293                 }
294             }
295             if (child instanceof ListPreference &&
296                     ((ListPreference) child).getKey().equals(key)) {
297                 group.removePreference(i);
298                 return true;
299             }
300         }
301         return false;
302     }
303
304     private void filterUnsupportedOptions(PreferenceGroup group,
305             ListPreference pref, List<String> supported) {
306
307         // Remove the preference if the parameter is not supported or there is
308         // only one options for the settings.
309         if (supported == null || supported.size() <= 1) {
310             removePreference(group, pref.getKey());
311             return;
312         }
313
314         pref.filterUnsupported(supported);
315         if (pref.getEntries().length <= 1) {
316             removePreference(group, pref.getKey());
317             return;
318         }
319
320         resetIfInvalid(pref);
321     }
322
323     private void filterSimilarPictureSize(PreferenceGroup group,
324             ListPreference pref) {
325         pref.filterDuplicated();
326         if (pref.getEntries().length <= 1) {
327             removePreference(group, pref.getKey());
328             return;
329         }
330         resetIfInvalid(pref);
331     }
332
333     private void resetIfInvalid(ListPreference pref) {
334         // Set the value to the first entry if it is invalid.
335         String value = pref.getValue();
336         if (pref.findIndexOfValue(value) == NOT_FOUND) {
337             pref.setValueIndex(0);
338         }
339     }
340
341     private static List<String> sizeListToStringList(List<Size> sizes) {
342         ArrayList<String> list = new ArrayList<String>();
343         for (Size size : sizes) {
344             list.add(String.format(Locale.ENGLISH, "%dx%d", size.width, size.height));
345         }
346         return list;
347     }
348
349     public static void upgradeLocalPreferences(SharedPreferences pref) {
350         int version;
351         try {
352             version = pref.getInt(KEY_LOCAL_VERSION, 0);
353         } catch (Exception ex) {
354             version = 0;
355         }
356         if (version == CURRENT_LOCAL_VERSION) return;
357
358         SharedPreferences.Editor editor = pref.edit();
359         if (version == 1) {
360             // We use numbers to represent the quality now. The quality definition is identical to
361             // that of CamcorderProfile.java.
362             editor.remove("pref_video_quality_key");
363         }
364         editor.putInt(KEY_LOCAL_VERSION, CURRENT_LOCAL_VERSION);
365         editor.apply();
366     }
367
368     public static void upgradeGlobalPreferences(SharedPreferences pref) {
369         upgradeOldVersion(pref);
370         upgradeCameraId(pref);
371     }
372
373     private static void upgradeOldVersion(SharedPreferences pref) {
374         int version;
375         try {
376             version = pref.getInt(KEY_VERSION, 0);
377         } catch (Exception ex) {
378             version = 0;
379         }
380         if (version == CURRENT_VERSION) return;
381
382         SharedPreferences.Editor editor = pref.edit();
383         if (version == 0) {
384             // We won't use the preference which change in version 1.
385             // So, just upgrade to version 1 directly
386             version = 1;
387         }
388         if (version == 1) {
389             // Change jpeg quality {65,75,85} to {normal,fine,superfine}
390             String quality = pref.getString(KEY_JPEG_QUALITY, "85");
391             if (quality.equals("65")) {
392                 quality = "normal";
393             } else if (quality.equals("75")) {
394                 quality = "fine";
395             } else {
396                 quality = "superfine";
397             }
398             editor.putString(KEY_JPEG_QUALITY, quality);
399             version = 2;
400         }
401         if (version == 2) {
402             editor.putString(KEY_RECORD_LOCATION,
403                     pref.getBoolean(KEY_RECORD_LOCATION, false)
404                     ? RecordLocationPreference.VALUE_ON
405                     : RecordLocationPreference.VALUE_NONE);
406             version = 3;
407         }
408         if (version == 3) {
409             // Just use video quality to replace it and
410             // ignore the current settings.
411             editor.remove("pref_camera_videoquality_key");
412             editor.remove("pref_camera_video_duration_key");
413         }
414
415         editor.putInt(KEY_VERSION, CURRENT_VERSION);
416         editor.apply();
417     }
418
419     private static void upgradeCameraId(SharedPreferences pref) {
420         // The id stored in the preference may be out of range if we are running
421         // inside the emulator and a webcam is removed.
422         // Note: This method accesses the global preferences directly, not the
423         // combo preferences.
424         int cameraId = readPreferredCameraId(pref);
425         if (cameraId == 0) return;  // fast path
426
427         int n = CameraHolder.instance().getNumberOfCameras();
428         if (cameraId < 0 || cameraId >= n) {
429             writePreferredCameraId(pref, 0);
430         }
431     }
432
433     public static int readPreferredCameraId(SharedPreferences pref) {
434         return Integer.parseInt(pref.getString(KEY_CAMERA_ID, "0"));
435     }
436
437     public static void writePreferredCameraId(SharedPreferences pref,
438             int cameraId) {
439         Editor editor = pref.edit();
440         editor.putString(KEY_CAMERA_ID, Integer.toString(cameraId));
441         editor.apply();
442     }
443
444     public static int readExposure(ComboPreferences preferences) {
445         String exposure = preferences.getString(
446                 CameraSettings.KEY_EXPOSURE,
447                 EXPOSURE_DEFAULT_VALUE);
448         try {
449             return Integer.parseInt(exposure);
450         } catch (Exception ex) {
451             Log.e(TAG, "Invalid exposure: " + exposure);
452         }
453         return 0;
454     }
455
456     public static int readEffectType(SharedPreferences pref) {
457         String effectSelection = pref.getString(KEY_VIDEO_EFFECT, "none");
458         if (effectSelection.equals("none")) {
459             return EffectsRecorder.EFFECT_NONE;
460         } else if (effectSelection.startsWith("goofy_face")) {
461             return EffectsRecorder.EFFECT_GOOFY_FACE;
462         } else if (effectSelection.startsWith("backdropper")) {
463             return EffectsRecorder.EFFECT_BACKDROPPER;
464         }
465         Log.e(TAG, "Invalid effect selection: " + effectSelection);
466         return EffectsRecorder.EFFECT_NONE;
467     }
468
469     public static Object readEffectParameter(SharedPreferences pref) {
470         String effectSelection = pref.getString(KEY_VIDEO_EFFECT, "none");
471         if (effectSelection.equals("none")) {
472             return null;
473         }
474         int separatorIndex = effectSelection.indexOf('/');
475         String effectParameter =
476                 effectSelection.substring(separatorIndex + 1);
477         if (effectSelection.startsWith("goofy_face")) {
478             if (effectParameter.equals("squeeze")) {
479                 return EffectsRecorder.EFFECT_GF_SQUEEZE;
480             } else if (effectParameter.equals("big_eyes")) {
481                 return EffectsRecorder.EFFECT_GF_BIG_EYES;
482             } else if (effectParameter.equals("big_mouth")) {
483                 return EffectsRecorder.EFFECT_GF_BIG_MOUTH;
484             } else if (effectParameter.equals("small_mouth")) {
485                 return EffectsRecorder.EFFECT_GF_SMALL_MOUTH;
486             } else if (effectParameter.equals("big_nose")) {
487                 return EffectsRecorder.EFFECT_GF_BIG_NOSE;
488             } else if (effectParameter.equals("small_eyes")) {
489                 return EffectsRecorder.EFFECT_GF_SMALL_EYES;
490             }
491         } else if (effectSelection.startsWith("backdropper")) {
492             // Parameter is a string that either encodes the URI to use,
493             // or specifies 'gallery'.
494             return effectParameter;
495         }
496
497         Log.e(TAG, "Invalid effect selection: " + effectSelection);
498         return null;
499     }
500
501     public static void restorePreferences(Context context,
502             ComboPreferences preferences, Parameters parameters) {
503         int currentCameraId = readPreferredCameraId(preferences);
504
505         // Clear the preferences of both cameras.
506         int backCameraId = CameraHolder.instance().getBackCameraId();
507         if (backCameraId != -1) {
508             preferences.setLocalId(context, backCameraId);
509             Editor editor = preferences.edit();
510             editor.clear();
511             editor.apply();
512         }
513         int frontCameraId = CameraHolder.instance().getFrontCameraId();
514         if (frontCameraId != -1) {
515             preferences.setLocalId(context, frontCameraId);
516             Editor editor = preferences.edit();
517             editor.clear();
518             editor.apply();
519         }
520
521         // Switch back to the preferences of the current camera. Otherwise,
522         // we may write the preference to wrong camera later.
523         preferences.setLocalId(context, currentCameraId);
524
525         upgradeGlobalPreferences(preferences.getGlobal());
526         upgradeLocalPreferences(preferences.getLocal());
527
528         // Write back the current camera id because parameters are related to
529         // the camera. Otherwise, we may switch to the front camera but the
530         // initial picture size is that of the back camera.
531         initialCameraPictureSize(context, parameters);
532         writePreferredCameraId(preferences, currentCameraId);
533     }
534
535     private ArrayList<String> getSupportedVideoQuality() {
536         ArrayList<String> supported = new ArrayList<String>();
537         // Check for supported quality
538         if (ApiHelper.HAS_FINE_RESOLUTION_QUALITY_LEVELS) {
539             getFineResolutionQuality(supported);
540         } else {
541             supported.add(Integer.toString(CamcorderProfile.QUALITY_HIGH));
542             CamcorderProfile high = CamcorderProfile.get(
543                     mCameraId, CamcorderProfile.QUALITY_HIGH);
544             CamcorderProfile low = CamcorderProfile.get(
545                     mCameraId, CamcorderProfile.QUALITY_LOW);
546             if (high.videoFrameHeight * high.videoFrameWidth >
547                     low.videoFrameHeight * low.videoFrameWidth) {
548                 supported.add(Integer.toString(CamcorderProfile.QUALITY_LOW));
549             }
550         }
551
552         return supported;
553     }
554
555     @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
556     private void getFineResolutionQuality(ArrayList<String> supported) {
557         if (CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_1080P)) {
558             supported.add(Integer.toString(CamcorderProfile.QUALITY_1080P));
559         }
560         if (CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_720P)) {
561             supported.add(Integer.toString(CamcorderProfile.QUALITY_720P));
562         }
563         if (CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_480P)) {
564             supported.add(Integer.toString(CamcorderProfile.QUALITY_480P));
565         }
566     }
567
568     private void initVideoEffect(PreferenceGroup group, ListPreference videoEffect) {
569         CharSequence[] values = videoEffect.getEntryValues();
570
571         boolean goofyFaceSupported =
572                 EffectsRecorder.isEffectSupported(EffectsRecorder.EFFECT_GOOFY_FACE);
573         boolean backdropperSupported =
574                 EffectsRecorder.isEffectSupported(EffectsRecorder.EFFECT_BACKDROPPER) &&
575                 Util.isAutoExposureLockSupported(mParameters) &&
576                 Util.isAutoWhiteBalanceLockSupported(mParameters);
577
578         ArrayList<String> supported = new ArrayList<String>();
579         for (CharSequence value : values) {
580             String effectSelection = value.toString();
581             if (!goofyFaceSupported && effectSelection.startsWith("goofy_face")) continue;
582             if (!backdropperSupported && effectSelection.startsWith("backdropper")) continue;
583             supported.add(effectSelection);
584         }
585
586         filterUnsupportedOptions(group, videoEffect, supported);
587     }
588 }