Home | History | Annotate | Download | only in camera
      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 
     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 
     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 
     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 
     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 
    115     public static void removePreferenceFromScreen(
    116             PreferenceGroup group, String key) {
    117         removePreference(group, key);
    118     }
    119 
    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 
    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 
    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 
    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[2];
    229         for (int i = 0; i < mCameraInfo.length; ++i) {
    230             int index =
    231                     (mCameraInfo[i].facing == CameraInfo.CAMERA_FACING_FRONT)
    232                     ? CameraInfo.CAMERA_FACING_FRONT
    233                     : CameraInfo.CAMERA_FACING_BACK;
    234             if (entryValues[index] == null) {
    235                 entryValues[index] = "" + i;
    236                 if (entryValues[((index == 1) ? 0 : 1)] != null) break;
    237             }
    238         }
    239         preference.setEntryValues(entryValues);
    240     }
    241 
    242     private static boolean removePreference(PreferenceGroup group, String key) {
    243         for (int i = 0, n = group.size(); i < n; i++) {
    244             CameraPreference child = group.get(i);
    245             if (child instanceof PreferenceGroup) {
    246                 if (removePreference((PreferenceGroup) child, key)) {
    247                     return true;
    248                 }
    249             }
    250             if (child instanceof ListPreference &&
    251                     ((ListPreference) child).getKey().equals(key)) {
    252                 group.removePreference(i);
    253                 return true;
    254             }
    255         }
    256         return false;
    257     }
    258 
    259     private void filterUnsupportedOptions(PreferenceGroup group,
    260             ListPreference pref, List<String> supported) {
    261 
    262         // Remove the preference if the parameter is not supported or there is
    263         // only one options for the settings.
    264         if (supported == null || supported.size() <= 1) {
    265             removePreference(group, pref.getKey());
    266             return;
    267         }
    268 
    269         pref.filterUnsupported(supported);
    270         if (pref.getEntries().length <= 1) {
    271             removePreference(group, pref.getKey());
    272             return;
    273         }
    274 
    275         resetIfInvalid(pref);
    276     }
    277 
    278     private void resetIfInvalid(ListPreference pref) {
    279         // Set the value to the first entry if it is invalid.
    280         String value = pref.getValue();
    281         if (pref.findIndexOfValue(value) == NOT_FOUND) {
    282             pref.setValueIndex(0);
    283         }
    284     }
    285 
    286     private static List<String> sizeListToStringList(List<Size> sizes) {
    287         ArrayList<String> list = new ArrayList<String>();
    288         for (Size size : sizes) {
    289             list.add(String.format("%dx%d", size.width, size.height));
    290         }
    291         return list;
    292     }
    293 
    294     public static void upgradeLocalPreferences(SharedPreferences pref) {
    295         int version;
    296         try {
    297             version = pref.getInt(KEY_LOCAL_VERSION, 0);
    298         } catch (Exception ex) {
    299             version = 0;
    300         }
    301         if (version == CURRENT_LOCAL_VERSION) return;
    302 
    303         SharedPreferences.Editor editor = pref.edit();
    304         if (version == 1) {
    305             // We use numbers to represent the quality now. The quality definition is identical to
    306             // that of CamcorderProfile.java.
    307             editor.remove("pref_video_quality_key");
    308         }
    309         editor.putInt(KEY_LOCAL_VERSION, CURRENT_LOCAL_VERSION);
    310         editor.apply();
    311     }
    312 
    313     public static void upgradeGlobalPreferences(SharedPreferences pref) {
    314         int version;
    315         try {
    316             version = pref.getInt(KEY_VERSION, 0);
    317         } catch (Exception ex) {
    318             version = 0;
    319         }
    320         if (version == CURRENT_VERSION) return;
    321 
    322         SharedPreferences.Editor editor = pref.edit();
    323         if (version == 0) {
    324             // We won't use the preference which change in version 1.
    325             // So, just upgrade to version 1 directly
    326             version = 1;
    327         }
    328         if (version == 1) {
    329             // Change jpeg quality {65,75,85} to {normal,fine,superfine}
    330             String quality = pref.getString(KEY_JPEG_QUALITY, "85");
    331             if (quality.equals("65")) {
    332                 quality = "normal";
    333             } else if (quality.equals("75")) {
    334                 quality = "fine";
    335             } else {
    336                 quality = "superfine";
    337             }
    338             editor.putString(KEY_JPEG_QUALITY, quality);
    339             version = 2;
    340         }
    341         if (version == 2) {
    342             editor.putString(KEY_RECORD_LOCATION,
    343                     pref.getBoolean(KEY_RECORD_LOCATION, false)
    344                     ? RecordLocationPreference.VALUE_ON
    345                     : RecordLocationPreference.VALUE_NONE);
    346             version = 3;
    347         }
    348         if (version == 3) {
    349             // Just use video quality to replace it and
    350             // ignore the current settings.
    351             editor.remove("pref_camera_videoquality_key");
    352             editor.remove("pref_camera_video_duration_key");
    353         }
    354 
    355         editor.putInt(KEY_VERSION, CURRENT_VERSION);
    356         editor.apply();
    357     }
    358 
    359     public static int readPreferredCameraId(SharedPreferences pref) {
    360         return Integer.parseInt(pref.getString(KEY_CAMERA_ID, "0"));
    361     }
    362 
    363     public static void writePreferredCameraId(SharedPreferences pref,
    364             int cameraId) {
    365         Editor editor = pref.edit();
    366         editor.putString(KEY_CAMERA_ID, Integer.toString(cameraId));
    367         editor.apply();
    368     }
    369 
    370     public static int readExposure(ComboPreferences preferences) {
    371         String exposure = preferences.getString(
    372                 CameraSettings.KEY_EXPOSURE,
    373                 EXPOSURE_DEFAULT_VALUE);
    374         try {
    375             return Integer.parseInt(exposure);
    376         } catch (Exception ex) {
    377             Log.e(TAG, "Invalid exposure: " + exposure);
    378         }
    379         return 0;
    380     }
    381 
    382     public static int readEffectType(SharedPreferences pref) {
    383         String effectSelection = pref.getString(KEY_VIDEO_EFFECT, "none");
    384         if (effectSelection.equals("none")) {
    385             return EffectsRecorder.EFFECT_NONE;
    386         } else if (effectSelection.startsWith("goofy_face")) {
    387             return EffectsRecorder.EFFECT_GOOFY_FACE;
    388         } else if (effectSelection.startsWith("backdropper")) {
    389             return EffectsRecorder.EFFECT_BACKDROPPER;
    390         }
    391         Log.e(TAG, "Invalid effect selection: " + effectSelection);
    392         return EffectsRecorder.EFFECT_NONE;
    393     }
    394 
    395     public static Object readEffectParameter(SharedPreferences pref) {
    396         String effectSelection = pref.getString(KEY_VIDEO_EFFECT, "none");
    397         if (effectSelection.equals("none")) {
    398             return null;
    399         }
    400         int separatorIndex = effectSelection.indexOf('/');
    401         String effectParameter =
    402                 effectSelection.substring(separatorIndex + 1);
    403         if (effectSelection.startsWith("goofy_face")) {
    404             if (effectParameter.equals("squeeze")) {
    405                 return EffectsRecorder.EFFECT_GF_SQUEEZE;
    406             } else if (effectParameter.equals("big_eyes")) {
    407                 return EffectsRecorder.EFFECT_GF_BIG_EYES;
    408             } else if (effectParameter.equals("big_mouth")) {
    409                 return EffectsRecorder.EFFECT_GF_BIG_MOUTH;
    410             } else if (effectParameter.equals("small_mouth")) {
    411                 return EffectsRecorder.EFFECT_GF_SMALL_MOUTH;
    412             } else if (effectParameter.equals("big_nose")) {
    413                 return EffectsRecorder.EFFECT_GF_BIG_NOSE;
    414             } else if (effectParameter.equals("small_eyes")) {
    415                 return EffectsRecorder.EFFECT_GF_SMALL_EYES;
    416             }
    417         } else if (effectSelection.startsWith("backdropper")) {
    418             // Parameter is a string that either encodes the URI to use,
    419             // or specifies 'gallery'.
    420             return effectParameter;
    421         }
    422 
    423         Log.e(TAG, "Invalid effect selection: " + effectSelection);
    424         return null;
    425     }
    426 
    427 
    428     public static void restorePreferences(Context context,
    429             ComboPreferences preferences, Parameters parameters) {
    430         int currentCameraId = readPreferredCameraId(preferences);
    431 
    432         // Clear the preferences of both cameras.
    433         int backCameraId = CameraHolder.instance().getBackCameraId();
    434         if (backCameraId != -1) {
    435             preferences.setLocalId(context, backCameraId);
    436             Editor editor = preferences.edit();
    437             editor.clear();
    438             editor.apply();
    439         }
    440         int frontCameraId = CameraHolder.instance().getFrontCameraId();
    441         if (frontCameraId != -1) {
    442             preferences.setLocalId(context, frontCameraId);
    443             Editor editor = preferences.edit();
    444             editor.clear();
    445             editor.apply();
    446         }
    447 
    448         // Switch back to the preferences of the current camera. Otherwise,
    449         // we may write the preference to wrong camera later.
    450         preferences.setLocalId(context, currentCameraId);
    451 
    452         upgradeGlobalPreferences(preferences.getGlobal());
    453         upgradeLocalPreferences(preferences.getLocal());
    454 
    455         // Write back the current camera id because parameters are related to
    456         // the camera. Otherwise, we may switch to the front camera but the
    457         // initial picture size is that of the back camera.
    458         initialCameraPictureSize(context, parameters);
    459         writePreferredCameraId(preferences, currentCameraId);
    460     }
    461 
    462     private ArrayList<String> getSupportedVideoQuality() {
    463         ArrayList<String> supported = new ArrayList<String>();
    464         // Check for supported quality
    465         if (CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_1080P)) {
    466             supported.add(Integer.toString(CamcorderProfile.QUALITY_1080P));
    467         }
    468         if (CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_720P)) {
    469             supported.add(Integer.toString(CamcorderProfile.QUALITY_720P));
    470         }
    471         if (CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_480P)) {
    472             supported.add(Integer.toString(CamcorderProfile.QUALITY_480P));
    473         }
    474 
    475         return supported;
    476     }
    477 
    478     private void initVideoEffect(PreferenceGroup group, ListPreference videoEffect) {
    479         CharSequence[] values = videoEffect.getEntryValues();
    480 
    481         boolean goofyFaceSupported =
    482                 EffectsRecorder.isEffectSupported(EffectsRecorder.EFFECT_GOOFY_FACE);
    483         boolean backdropperSupported =
    484                 EffectsRecorder.isEffectSupported(EffectsRecorder.EFFECT_BACKDROPPER) &&
    485                 mParameters.isAutoExposureLockSupported() &&
    486                 mParameters.isAutoWhiteBalanceLockSupported();
    487 
    488         ArrayList<String> supported = new ArrayList<String>();
    489         for (CharSequence value : values) {
    490             String effectSelection = value.toString();
    491             if (!goofyFaceSupported && effectSelection.startsWith("goofy_face")) continue;
    492             if (!backdropperSupported && effectSelection.startsWith("backdropper")) continue;
    493             supported.add(effectSelection);
    494         }
    495 
    496         filterUnsupportedOptions(group, videoEffect, supported);
    497     }
    498 }
    499