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.content.res.Resources;
     24 import android.content.res.TypedArray;
     25 import android.hardware.Camera.CameraInfo;
     26 import android.hardware.Camera.Parameters;
     27 import android.hardware.Camera.Size;
     28 import android.media.CamcorderProfile;
     29 import android.util.Log;
     30 
     31 import com.android.camera.util.ApiHelper;
     32 import com.android.camera.util.CameraUtil;
     33 import com.android.camera.util.GcamHelper;
     34 import com.android.camera2.R;
     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_HDR_PLUS = "pref_camera_hdr_plus_key";
     65     public static final String KEY_CAMERA_FIRST_USE_HINT_SHOWN = "pref_camera_first_use_hint_shown_key";
     66     public static final String KEY_VIDEO_FIRST_USE_HINT_SHOWN = "pref_video_first_use_hint_shown_key";
     67     public static final String KEY_PHOTOSPHERE_PICTURESIZE = "pref_photosphere_picturesize_key";
     68     public static final String KEY_STARTUP_MODULE_INDEX = "camera.startup_module";
     69 
     70     public static final String EXPOSURE_DEFAULT_VALUE = "0";
     71 
     72     public static final int CURRENT_VERSION = 5;
     73     public static final int CURRENT_LOCAL_VERSION = 2;
     74 
     75     private static final String TAG = "CameraSettings";
     76 
     77     private final Context mContext;
     78     private final Parameters mParameters;
     79     private final CameraInfo[] mCameraInfo;
     80     private final int mCameraId;
     81 
     82     public CameraSettings(Activity activity, Parameters parameters,
     83                           int cameraId, CameraInfo[] cameraInfo) {
     84         mContext = activity;
     85         mParameters = parameters;
     86         mCameraId = cameraId;
     87         mCameraInfo = cameraInfo;
     88     }
     89 
     90     public PreferenceGroup getPreferenceGroup(int preferenceRes) {
     91         PreferenceInflater inflater = new PreferenceInflater(mContext);
     92         PreferenceGroup group =
     93                 (PreferenceGroup) inflater.inflate(preferenceRes);
     94         if (mParameters != null) initPreference(group);
     95         return group;
     96     }
     97 
     98     public static String getSupportedHighestVideoQuality(int cameraId,
     99             String defaultQuality) {
    100         // When launching the camera app first time, we will set the video quality
    101         // to the first one (i.e. highest quality) in the supported list
    102         List<String> supported = getSupportedVideoQuality(cameraId);
    103         if (supported == null) {
    104             Log.e(TAG, "No supported video quality is found");
    105             return defaultQuality;
    106         }
    107         return supported.get(0);
    108     }
    109 
    110     public static void initialCameraPictureSize(
    111             Context context, Parameters parameters) {
    112         // When launching the camera app first time, we will set the picture
    113         // size to the first one in the list defined in "arrays.xml" and is also
    114         // supported by the driver.
    115         List<Size> supported = parameters.getSupportedPictureSizes();
    116         if (supported == null) return;
    117         for (String candidate : context.getResources().getStringArray(
    118                 R.array.pref_camera_picturesize_entryvalues)) {
    119             if (setCameraPictureSize(candidate, supported, parameters)) {
    120                 SharedPreferences.Editor editor = ComboPreferences
    121                         .get(context).edit();
    122                 editor.putString(KEY_PICTURE_SIZE, candidate);
    123                 editor.apply();
    124                 return;
    125             }
    126         }
    127         Log.e(TAG, "No supported picture size found");
    128     }
    129 
    130     public static void removePreferenceFromScreen(
    131             PreferenceGroup group, String key) {
    132         removePreference(group, key);
    133     }
    134 
    135     public static boolean setCameraPictureSize(
    136             String candidate, List<Size> supported, Parameters parameters) {
    137         int index = candidate.indexOf('x');
    138         if (index == NOT_FOUND) return false;
    139         int width = Integer.parseInt(candidate.substring(0, index));
    140         int height = Integer.parseInt(candidate.substring(index + 1));
    141         for (Size size : supported) {
    142             if (size.width == width && size.height == height) {
    143                 parameters.setPictureSize(width, height);
    144                 return true;
    145             }
    146         }
    147         return false;
    148     }
    149 
    150     public static int getMaxVideoDuration(Context context) {
    151         int duration = 0;  // in milliseconds, 0 means unlimited.
    152         try {
    153             duration = context.getResources().getInteger(R.integer.max_video_recording_length);
    154         } catch (Resources.NotFoundException ex) {
    155         }
    156         return duration;
    157     }
    158 
    159     private void initPreference(PreferenceGroup group) {
    160         ListPreference videoQuality = group.findPreference(KEY_VIDEO_QUALITY);
    161         ListPreference timeLapseInterval = group.findPreference(KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL);
    162         ListPreference pictureSize = group.findPreference(KEY_PICTURE_SIZE);
    163         ListPreference whiteBalance =  group.findPreference(KEY_WHITE_BALANCE);
    164         ListPreference sceneMode = group.findPreference(KEY_SCENE_MODE);
    165         ListPreference flashMode = group.findPreference(KEY_FLASH_MODE);
    166         ListPreference focusMode = group.findPreference(KEY_FOCUS_MODE);
    167         IconListPreference exposure =
    168                 (IconListPreference) group.findPreference(KEY_EXPOSURE);
    169         IconListPreference cameraIdPref =
    170                 (IconListPreference) group.findPreference(KEY_CAMERA_ID);
    171         ListPreference videoFlashMode =
    172                 group.findPreference(KEY_VIDEOCAMERA_FLASH_MODE);
    173         ListPreference videoEffect = group.findPreference(KEY_VIDEO_EFFECT);
    174         ListPreference cameraHdr = group.findPreference(KEY_CAMERA_HDR);
    175         ListPreference cameraHdrPlus = group.findPreference(KEY_CAMERA_HDR_PLUS);
    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(mCameraId));
    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 (!CameraUtil.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             resetIfInvalid(timeLapseInterval);
    218         }
    219         if (videoEffect != null) {
    220             filterUnsupportedOptions(group, videoEffect, null);
    221         }
    222         if (cameraHdr != null && (!ApiHelper.HAS_CAMERA_HDR
    223                 || !CameraUtil.isCameraHdrSupported(mParameters))) {
    224             removePreference(group, cameraHdr.getKey());
    225         }
    226 
    227         int frontCameraId = CameraHolder.instance().getFrontCameraId();
    228         boolean isFrontCamera = (frontCameraId == mCameraId);
    229         if (cameraHdrPlus != null && (!ApiHelper.HAS_CAMERA_HDR_PLUS ||
    230                 !GcamHelper.hasGcamCapture() || isFrontCamera)) {
    231             removePreference(group, cameraHdrPlus.getKey());
    232         }
    233     }
    234 
    235     private void buildExposureCompensation(
    236             PreferenceGroup group, IconListPreference exposure) {
    237         int max = mParameters.getMaxExposureCompensation();
    238         int min = mParameters.getMinExposureCompensation();
    239         if (max == 0 && min == 0) {
    240             removePreference(group, exposure.getKey());
    241             return;
    242         }
    243         float step = mParameters.getExposureCompensationStep();
    244 
    245         // show only integer values for exposure compensation
    246         int maxValue = Math.min(3, (int) Math.floor(max * step));
    247         int minValue = Math.max(-3, (int) Math.ceil(min * step));
    248         String explabel = mContext.getResources().getString(R.string.pref_exposure_label);
    249         CharSequence entries[] = new CharSequence[maxValue - minValue + 1];
    250         CharSequence entryValues[] = new CharSequence[maxValue - minValue + 1];
    251         CharSequence labels[] = new CharSequence[maxValue - minValue + 1];
    252         int[] icons = new int[maxValue - minValue + 1];
    253         TypedArray iconIds = mContext.getResources().obtainTypedArray(
    254                 R.array.pref_camera_exposure_icons);
    255         for (int i = minValue; i <= maxValue; ++i) {
    256             entryValues[i - minValue] = Integer.toString(Math.round(i / step));
    257             StringBuilder builder = new StringBuilder();
    258             if (i > 0) builder.append('+');
    259             entries[i - minValue] = builder.append(i).toString();
    260             labels[i - minValue] = explabel + " " + builder.toString();
    261             icons[i - minValue] = iconIds.getResourceId(3 + i, 0);
    262         }
    263         exposure.setUseSingleIcon(true);
    264         exposure.setEntries(entries);
    265         exposure.setLabels(labels);
    266         exposure.setEntryValues(entryValues);
    267         exposure.setLargeIconIds(icons);
    268     }
    269 
    270     private void buildCameraId(
    271             PreferenceGroup group, IconListPreference preference) {
    272         int numOfCameras = mCameraInfo.length;
    273         if (numOfCameras < 2) {
    274             removePreference(group, preference.getKey());
    275             return;
    276         }
    277 
    278         CharSequence[] entryValues = new CharSequence[numOfCameras];
    279         for (int i = 0; i < numOfCameras; ++i) {
    280             entryValues[i] = "" + i;
    281         }
    282         preference.setEntryValues(entryValues);
    283     }
    284 
    285     private static boolean removePreference(PreferenceGroup group, String key) {
    286         for (int i = 0, n = group.size(); i < n; i++) {
    287             CameraPreference child = group.get(i);
    288             if (child instanceof PreferenceGroup) {
    289                 if (removePreference((PreferenceGroup) child, key)) {
    290                     return true;
    291                 }
    292             }
    293             if (child instanceof ListPreference &&
    294                     ((ListPreference) child).getKey().equals(key)) {
    295                 group.removePreference(i);
    296                 return true;
    297             }
    298         }
    299         return false;
    300     }
    301 
    302     private void filterUnsupportedOptions(PreferenceGroup group,
    303             ListPreference pref, List<String> supported) {
    304 
    305         // Remove the preference if the parameter is not supported or there is
    306         // only one options for the settings.
    307         if (supported == null || supported.size() <= 1) {
    308             removePreference(group, pref.getKey());
    309             return;
    310         }
    311 
    312         pref.filterUnsupported(supported);
    313         if (pref.getEntries().length <= 1) {
    314             removePreference(group, pref.getKey());
    315             return;
    316         }
    317 
    318         resetIfInvalid(pref);
    319     }
    320 
    321     private void filterSimilarPictureSize(PreferenceGroup group,
    322             ListPreference pref) {
    323         pref.filterDuplicated();
    324         if (pref.getEntries().length <= 1) {
    325             removePreference(group, pref.getKey());
    326             return;
    327         }
    328         resetIfInvalid(pref);
    329     }
    330 
    331     private void resetIfInvalid(ListPreference pref) {
    332         // Set the value to the first entry if it is invalid.
    333         String value = pref.getValue();
    334         if (pref.findIndexOfValue(value) == NOT_FOUND) {
    335             pref.setValueIndex(0);
    336         }
    337     }
    338 
    339     private static List<String> sizeListToStringList(List<Size> sizes) {
    340         ArrayList<String> list = new ArrayList<String>();
    341         for (Size size : sizes) {
    342             list.add(String.format(Locale.ENGLISH, "%dx%d", size.width, size.height));
    343         }
    344         return list;
    345     }
    346 
    347     public static void upgradeLocalPreferences(SharedPreferences pref) {
    348         int version;
    349         try {
    350             version = pref.getInt(KEY_LOCAL_VERSION, 0);
    351         } catch (Exception ex) {
    352             version = 0;
    353         }
    354         if (version == CURRENT_LOCAL_VERSION) return;
    355 
    356         SharedPreferences.Editor editor = pref.edit();
    357         if (version == 1) {
    358             // We use numbers to represent the quality now. The quality definition is identical to
    359             // that of CamcorderProfile.java.
    360             editor.remove("pref_video_quality_key");
    361         }
    362         editor.putInt(KEY_LOCAL_VERSION, CURRENT_LOCAL_VERSION);
    363         editor.apply();
    364     }
    365 
    366     public static void upgradeGlobalPreferences(SharedPreferences pref) {
    367         upgradeOldVersion(pref);
    368         upgradeCameraId(pref);
    369     }
    370 
    371     private static void upgradeOldVersion(SharedPreferences pref) {
    372         int version;
    373         try {
    374             version = pref.getInt(KEY_VERSION, 0);
    375         } catch (Exception ex) {
    376             version = 0;
    377         }
    378         if (version == CURRENT_VERSION) return;
    379 
    380         SharedPreferences.Editor editor = pref.edit();
    381         if (version == 0) {
    382             // We won't use the preference which change in version 1.
    383             // So, just upgrade to version 1 directly
    384             version = 1;
    385         }
    386         if (version == 1) {
    387             // Change jpeg quality {65,75,85} to {normal,fine,superfine}
    388             String quality = pref.getString(KEY_JPEG_QUALITY, "85");
    389             if (quality.equals("65")) {
    390                 quality = "normal";
    391             } else if (quality.equals("75")) {
    392                 quality = "fine";
    393             } else {
    394                 quality = "superfine";
    395             }
    396             editor.putString(KEY_JPEG_QUALITY, quality);
    397             version = 2;
    398         }
    399         if (version == 2) {
    400             editor.putString(KEY_RECORD_LOCATION,
    401                     pref.getBoolean(KEY_RECORD_LOCATION, false)
    402                     ? RecordLocationPreference.VALUE_ON
    403                     : RecordLocationPreference.VALUE_NONE);
    404             version = 3;
    405         }
    406         if (version == 3) {
    407             // Just use video quality to replace it and
    408             // ignore the current settings.
    409             editor.remove("pref_camera_videoquality_key");
    410             editor.remove("pref_camera_video_duration_key");
    411         }
    412 
    413         editor.putInt(KEY_VERSION, CURRENT_VERSION);
    414         editor.apply();
    415     }
    416 
    417     private static void upgradeCameraId(SharedPreferences pref) {
    418         // The id stored in the preference may be out of range if we are running
    419         // inside the emulator and a webcam is removed.
    420         // Note: This method accesses the global preferences directly, not the
    421         // combo preferences.
    422         int cameraId = readPreferredCameraId(pref);
    423         if (cameraId == 0) return;  // fast path
    424 
    425         int n = CameraHolder.instance().getNumberOfCameras();
    426         if (cameraId < 0 || cameraId >= n) {
    427             writePreferredCameraId(pref, 0);
    428         }
    429     }
    430 
    431     public static int readPreferredCameraId(SharedPreferences pref) {
    432         return Integer.parseInt(pref.getString(KEY_CAMERA_ID, "0"));
    433     }
    434 
    435     public static void writePreferredCameraId(SharedPreferences pref,
    436             int cameraId) {
    437         Editor editor = pref.edit();
    438         editor.putString(KEY_CAMERA_ID, Integer.toString(cameraId));
    439         editor.apply();
    440     }
    441 
    442     public static int readExposure(ComboPreferences preferences) {
    443         String exposure = preferences.getString(
    444                 CameraSettings.KEY_EXPOSURE,
    445                 EXPOSURE_DEFAULT_VALUE);
    446         try {
    447             return Integer.parseInt(exposure);
    448         } catch (Exception ex) {
    449             Log.e(TAG, "Invalid exposure: " + exposure);
    450         }
    451         return 0;
    452     }
    453 
    454     public static void restorePreferences(Context context,
    455             ComboPreferences preferences, Parameters parameters) {
    456         int currentCameraId = readPreferredCameraId(preferences);
    457 
    458         // Clear the preferences of both cameras.
    459         int backCameraId = CameraHolder.instance().getBackCameraId();
    460         if (backCameraId != -1) {
    461             preferences.setLocalId(context, backCameraId);
    462             Editor editor = preferences.edit();
    463             editor.clear();
    464             editor.apply();
    465         }
    466         int frontCameraId = CameraHolder.instance().getFrontCameraId();
    467         if (frontCameraId != -1) {
    468             preferences.setLocalId(context, frontCameraId);
    469             Editor editor = preferences.edit();
    470             editor.clear();
    471             editor.apply();
    472         }
    473 
    474         // Switch back to the preferences of the current camera. Otherwise,
    475         // we may write the preference to wrong camera later.
    476         preferences.setLocalId(context, currentCameraId);
    477 
    478         upgradeGlobalPreferences(preferences.getGlobal());
    479         upgradeLocalPreferences(preferences.getLocal());
    480 
    481         // Write back the current camera id because parameters are related to
    482         // the camera. Otherwise, we may switch to the front camera but the
    483         // initial picture size is that of the back camera.
    484         initialCameraPictureSize(context, parameters);
    485         writePreferredCameraId(preferences, currentCameraId);
    486     }
    487 
    488     private static ArrayList<String> getSupportedVideoQuality(int cameraId) {
    489         ArrayList<String> supported = new ArrayList<String>();
    490         // Check for supported quality
    491         if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_1080P)) {
    492             supported.add(Integer.toString(CamcorderProfile.QUALITY_1080P));
    493         }
    494         if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_720P)) {
    495             supported.add(Integer.toString(CamcorderProfile.QUALITY_720P));
    496         }
    497         if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_480P)) {
    498             supported.add(Integer.toString(CamcorderProfile.QUALITY_480P));
    499         }
    500         return supported;
    501     }
    502 }
    503