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