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