Home | History | Annotate | Download | only in settings
      1 /*
      2  * Copyright (C) 2014 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.settings;
     18 
     19 import android.app.ActionBar;
     20 import android.app.Activity;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.SharedPreferences;
     24 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
     25 import android.os.Bundle;
     26 import android.preference.ListPreference;
     27 import android.preference.Preference;
     28 import android.preference.Preference.OnPreferenceClickListener;
     29 import android.preference.PreferenceFragment;
     30 import android.preference.PreferenceGroup;
     31 import android.preference.PreferenceScreen;
     32 import android.support.v4.app.FragmentActivity;
     33 import android.view.MenuItem;
     34 
     35 import com.android.camera.FatalErrorHandler;
     36 import com.android.camera.FatalErrorHandlerImpl;
     37 import com.android.camera.debug.Log;
     38 import com.android.camera.device.CameraId;
     39 import com.android.camera.one.OneCamera.Facing;
     40 import com.android.camera.one.OneCameraAccessException;
     41 import com.android.camera.one.OneCameraCharacteristics;
     42 import com.android.camera.one.OneCameraException;
     43 import com.android.camera.one.OneCameraManager;
     44 import com.android.camera.one.OneCameraModule;
     45 import com.android.camera.settings.PictureSizeLoader.PictureSizes;
     46 import com.android.camera.settings.SettingsUtil.SelectedVideoQualities;
     47 import com.android.camera.util.CameraSettingsActivityHelper;
     48 import com.android.camera.util.GoogleHelpHelper;
     49 import com.android.camera.util.Size;
     50 import com.android.camera2.R;
     51 import com.android.ex.camera2.portability.CameraAgentFactory;
     52 import com.android.ex.camera2.portability.CameraDeviceInfo;
     53 
     54 import java.text.DecimalFormat;
     55 import java.util.ArrayList;
     56 import java.util.List;
     57 
     58 /**
     59  * Provides the settings UI for the Camera app.
     60  */
     61 public class CameraSettingsActivity extends FragmentActivity {
     62 
     63     /**
     64      * Used to denote a subsection of the preference tree to display in the
     65      * Fragment. For instance, if 'Advanced' key is provided, the advanced
     66      * preference section will be treated as the root for display. This is used
     67      * to enable activity transitions between preference sections, and allows
     68      * back/up stack to operate correctly.
     69      */
     70     public static final String PREF_SCREEN_EXTRA = "pref_screen_extra";
     71     public static final String HIDE_ADVANCED_SCREEN = "hide_advanced";
     72     private OneCameraManager mOneCameraManager;
     73 
     74     @Override
     75     public void onCreate(Bundle savedInstanceState) {
     76         super.onCreate(savedInstanceState);
     77 
     78         FatalErrorHandler fatalErrorHandler = new FatalErrorHandlerImpl(this);
     79         boolean hideAdvancedScreen = false;
     80 
     81         try {
     82             mOneCameraManager = OneCameraModule.provideOneCameraManager();
     83         } catch (OneCameraException e) {
     84             // Log error and continue. Modules requiring OneCamera should check
     85             // and handle if null by showing error dialog or other treatment.
     86             fatalErrorHandler.onGenericCameraAccessFailure();
     87         }
     88 
     89         // Check if manual exposure is available, so we can decide whether to
     90         // display Advanced screen.
     91         try {
     92             CameraId frontCameraId = mOneCameraManager.findFirstCameraFacing(Facing.FRONT);
     93             CameraId backCameraId = mOneCameraManager.findFirstCameraFacing(Facing.BACK);
     94 
     95             // The exposure compensation is supported when both of the following conditions meet
     96             //   - we have the valid camera, and
     97             //   - the valid camera supports the exposure compensation
     98             boolean isExposureCompensationSupportedByFrontCamera = (frontCameraId != null) &&
     99                     (mOneCameraManager.getOneCameraCharacteristics(frontCameraId)
    100                             .isExposureCompensationSupported());
    101             boolean isExposureCompensationSupportedByBackCamera = (backCameraId != null) &&
    102                     (mOneCameraManager.getOneCameraCharacteristics(backCameraId)
    103                             .isExposureCompensationSupported());
    104 
    105             // Hides the option if neither front and back camera support exposure compensation.
    106             if (!isExposureCompensationSupportedByFrontCamera &&
    107                     !isExposureCompensationSupportedByBackCamera) {
    108                 hideAdvancedScreen = true;
    109             }
    110         } catch (OneCameraAccessException e) {
    111             fatalErrorHandler.onGenericCameraAccessFailure();
    112         }
    113 
    114         ActionBar actionBar = getActionBar();
    115         actionBar.setDisplayHomeAsUpEnabled(true);
    116         actionBar.setTitle(R.string.mode_settings);
    117 
    118         String prefKey = getIntent().getStringExtra(PREF_SCREEN_EXTRA);
    119         CameraSettingsFragment dialog = new CameraSettingsFragment();
    120         Bundle bundle = new Bundle(1);
    121         bundle.putString(PREF_SCREEN_EXTRA, prefKey);
    122         bundle.putBoolean(HIDE_ADVANCED_SCREEN, hideAdvancedScreen);
    123         dialog.setArguments(bundle);
    124         getFragmentManager().beginTransaction().replace(android.R.id.content, dialog).commit();
    125     }
    126 
    127     @Override
    128     public boolean onMenuItemSelected(int featureId, MenuItem item) {
    129         int itemId = item.getItemId();
    130         if (itemId == android.R.id.home) {
    131             finish();
    132             return true;
    133         }
    134         return true;
    135     }
    136 
    137     public static class CameraSettingsFragment extends PreferenceFragment implements
    138             OnSharedPreferenceChangeListener {
    139 
    140         public static final String PREF_CATEGORY_RESOLUTION = "pref_category_resolution";
    141         public static final String PREF_CATEGORY_ADVANCED = "pref_category_advanced";
    142         public static final String PREF_LAUNCH_HELP = "pref_launch_help";
    143         private static final Log.Tag TAG = new Log.Tag("SettingsFragment");
    144         private static DecimalFormat sMegaPixelFormat = new DecimalFormat("##0.0");
    145         private String[] mCamcorderProfileNames;
    146         private CameraDeviceInfo mInfos;
    147         private String mPrefKey;
    148         private boolean mHideAdvancedScreen;
    149         private boolean mGetSubPrefAsRoot = true;
    150 
    151         // Selected resolutions for the different cameras and sizes.
    152         private PictureSizes mPictureSizes;
    153 
    154         @Override
    155         public void onCreate(Bundle savedInstanceState) {
    156             super.onCreate(savedInstanceState);
    157             Bundle arguments = getArguments();
    158             if (arguments != null) {
    159                 mPrefKey = arguments.getString(PREF_SCREEN_EXTRA);
    160                 mHideAdvancedScreen = arguments.getBoolean(HIDE_ADVANCED_SCREEN);
    161             }
    162             Context context = this.getActivity().getApplicationContext();
    163             addPreferencesFromResource(R.xml.camera_preferences);
    164             PreferenceScreen advancedScreen =
    165                     (PreferenceScreen) findPreference(PREF_CATEGORY_ADVANCED);
    166 
    167             // If manual exposure not enabled, hide the Advanced screen.
    168             if (mHideAdvancedScreen) {
    169                 PreferenceScreen root = (PreferenceScreen) findPreference("prefscreen_top");
    170                 root.removePreference(advancedScreen);
    171             }
    172 
    173             // Allow the Helper to edit the full preference hierarchy, not the
    174             // sub tree we may show as root. See {@link #getPreferenceScreen()}.
    175             mGetSubPrefAsRoot = false;
    176             CameraSettingsActivityHelper.addAdditionalPreferences(this, context);
    177             mGetSubPrefAsRoot = true;
    178 
    179             mCamcorderProfileNames = getResources().getStringArray(R.array.camcorder_profile_names);
    180             mInfos = CameraAgentFactory
    181                     .getAndroidCameraAgent(context, CameraAgentFactory.CameraApi.API_1)
    182                     .getCameraDeviceInfo();
    183         }
    184 
    185         @Override
    186         public void onResume() {
    187             super.onResume();
    188             final Activity activity = this.getActivity();
    189 
    190             // Load the camera sizes.
    191             loadSizes();
    192 
    193             // Send loaded sizes to additional preferences.
    194             CameraSettingsActivityHelper.onSizesLoaded(this, mPictureSizes.backCameraSizes,
    195                     new ListPreferenceFiller() {
    196                         @Override
    197                         public void fill(List<Size> sizes, ListPreference preference) {
    198                             setEntriesForSelection(sizes, preference);
    199                         }
    200                     });
    201 
    202             // Make sure to hide settings for cameras that don't exist on this
    203             // device.
    204             setVisibilities();
    205 
    206             // Put in the summaries for the currently set values.
    207             final PreferenceScreen resolutionScreen =
    208                     (PreferenceScreen) findPreference(PREF_CATEGORY_RESOLUTION);
    209             fillEntriesAndSummaries(resolutionScreen);
    210             setPreferenceScreenIntent(resolutionScreen);
    211 
    212             final PreferenceScreen advancedScreen =
    213                     (PreferenceScreen) findPreference(PREF_CATEGORY_ADVANCED);
    214 
    215             if (!mHideAdvancedScreen) {
    216                 setPreferenceScreenIntent(advancedScreen);
    217             }
    218 
    219             Preference helpPref = findPreference(PREF_LAUNCH_HELP);
    220             helpPref.setOnPreferenceClickListener(
    221                     new OnPreferenceClickListener() {
    222                         @Override
    223                         public boolean onPreferenceClick(Preference preference) {
    224                             new GoogleHelpHelper(activity).launchGoogleHelp();
    225                             return true;
    226                         }
    227                     });
    228             getPreferenceScreen().getSharedPreferences()
    229                     .registerOnSharedPreferenceChangeListener(this);
    230         }
    231 
    232         /**
    233          * Configure home-as-up for sub-screens.
    234          */
    235         private void setPreferenceScreenIntent(final PreferenceScreen preferenceScreen) {
    236             Intent intent = new Intent(getActivity(), CameraSettingsActivity.class);
    237             intent.putExtra(PREF_SCREEN_EXTRA, preferenceScreen.getKey());
    238             preferenceScreen.setIntent(intent);
    239         }
    240 
    241         /**
    242          * This override allows the CameraSettingsFragment to be reused for
    243          * different nested PreferenceScreens within the single camera
    244          * preferences XML resource. If the fragment is constructed with a
    245          * desired preference key (delivered via an extra in the creation
    246          * intent), it is used to look up the nested PreferenceScreen and
    247          * returned here.
    248          */
    249         @Override
    250         public PreferenceScreen getPreferenceScreen() {
    251             PreferenceScreen root = super.getPreferenceScreen();
    252             if (!mGetSubPrefAsRoot || mPrefKey == null || root == null) {
    253                 return root;
    254             } else {
    255                 PreferenceScreen match = findByKey(root, mPrefKey);
    256                 if (match != null) {
    257                     return match;
    258                 } else {
    259                     throw new RuntimeException("key " + mPrefKey + " not found");
    260                 }
    261             }
    262         }
    263 
    264         private PreferenceScreen findByKey(PreferenceScreen parent, String key) {
    265             if (key.equals(parent.getKey())) {
    266                 return parent;
    267             } else {
    268                 for (int i = 0; i < parent.getPreferenceCount(); i++) {
    269                     Preference child = parent.getPreference(i);
    270                     if (child instanceof PreferenceScreen) {
    271                         PreferenceScreen match = findByKey((PreferenceScreen) child, key);
    272                         if (match != null) {
    273                             return match;
    274                         }
    275                     }
    276                 }
    277                 return null;
    278             }
    279         }
    280 
    281         /**
    282          * Depending on camera availability on the device, this removes settings
    283          * for cameras the device doesn't have.
    284          */
    285         private void setVisibilities() {
    286             PreferenceGroup resolutions =
    287                     (PreferenceGroup) findPreference(PREF_CATEGORY_RESOLUTION);
    288             if (mPictureSizes.backCameraSizes.isEmpty()) {
    289                 recursiveDelete(resolutions,
    290                         findPreference(Keys.KEY_PICTURE_SIZE_BACK));
    291                 recursiveDelete(resolutions,
    292                         findPreference(Keys.KEY_VIDEO_QUALITY_BACK));
    293             }
    294             if (mPictureSizes.frontCameraSizes.isEmpty()) {
    295                 recursiveDelete(resolutions,
    296                         findPreference(Keys.KEY_PICTURE_SIZE_FRONT));
    297                 recursiveDelete(resolutions,
    298                         findPreference(Keys.KEY_VIDEO_QUALITY_FRONT));
    299             }
    300         }
    301 
    302         /**
    303          * Recursively go through settings and fill entries and summaries of our
    304          * preferences.
    305          */
    306         private void fillEntriesAndSummaries(PreferenceGroup group) {
    307             for (int i = 0; i < group.getPreferenceCount(); ++i) {
    308                 Preference pref = group.getPreference(i);
    309                 if (pref instanceof PreferenceGroup) {
    310                     fillEntriesAndSummaries((PreferenceGroup) pref);
    311                 }
    312                 setSummary(pref);
    313                 setEntries(pref);
    314             }
    315         }
    316 
    317         /**
    318          * Recursively traverses the tree from the given group as the route and
    319          * tries to delete the preference. Traversal stops once the preference
    320          * was found and removed.
    321          */
    322         private boolean recursiveDelete(PreferenceGroup group, Preference preference) {
    323             if (group == null) {
    324                 Log.d(TAG, "attempting to delete from null preference group");
    325                 return false;
    326             }
    327             if (preference == null) {
    328                 Log.d(TAG, "attempting to delete null preference");
    329                 return false;
    330             }
    331             if (group.removePreference(preference)) {
    332                 // Removal was successful.
    333                 return true;
    334             }
    335 
    336             for (int i = 0; i < group.getPreferenceCount(); ++i) {
    337                 Preference pref = group.getPreference(i);
    338                 if (pref instanceof PreferenceGroup) {
    339                     if (recursiveDelete((PreferenceGroup) pref, preference)) {
    340                         return true;
    341                     }
    342                 }
    343             }
    344             return false;
    345         }
    346 
    347         @Override
    348         public void onPause() {
    349             super.onPause();
    350             getPreferenceScreen().getSharedPreferences()
    351                     .unregisterOnSharedPreferenceChangeListener(this);
    352         }
    353 
    354         @Override
    355         public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
    356             setSummary(findPreference(key));
    357         }
    358 
    359         /**
    360          * Set the entries for the given preference. The given preference needs
    361          * to be a {@link ListPreference}
    362          */
    363         private void setEntries(Preference preference) {
    364             if (!(preference instanceof ListPreference)) {
    365                 return;
    366             }
    367 
    368             ListPreference listPreference = (ListPreference) preference;
    369             if (listPreference.getKey().equals(Keys.KEY_PICTURE_SIZE_BACK)) {
    370                 setEntriesForSelection(mPictureSizes.backCameraSizes, listPreference);
    371             } else if (listPreference.getKey().equals(Keys.KEY_PICTURE_SIZE_FRONT)) {
    372                 setEntriesForSelection(mPictureSizes.frontCameraSizes, listPreference);
    373             } else if (listPreference.getKey().equals(Keys.KEY_VIDEO_QUALITY_BACK)) {
    374                 setEntriesForSelection(mPictureSizes.videoQualitiesBack.orNull(), listPreference);
    375             } else if (listPreference.getKey().equals(Keys.KEY_VIDEO_QUALITY_FRONT)) {
    376                 setEntriesForSelection(mPictureSizes.videoQualitiesFront.orNull(), listPreference);
    377             }
    378         }
    379 
    380         /**
    381          * Set the summary for the given preference. The given preference needs
    382          * to be a {@link ListPreference}.
    383          */
    384         private void setSummary(Preference preference) {
    385             if (!(preference instanceof ListPreference)) {
    386                 return;
    387             }
    388 
    389             ListPreference listPreference = (ListPreference) preference;
    390             if (listPreference.getKey().equals(Keys.KEY_PICTURE_SIZE_BACK)) {
    391                 setSummaryForSelection(mPictureSizes.backCameraSizes,
    392                         listPreference);
    393             } else if (listPreference.getKey().equals(Keys.KEY_PICTURE_SIZE_FRONT)) {
    394                 setSummaryForSelection(mPictureSizes.frontCameraSizes,
    395                         listPreference);
    396             } else if (listPreference.getKey().equals(Keys.KEY_VIDEO_QUALITY_BACK)) {
    397                 setSummaryForSelection(mPictureSizes.videoQualitiesBack.orNull(), listPreference);
    398             } else if (listPreference.getKey().equals(Keys.KEY_VIDEO_QUALITY_FRONT)) {
    399                 setSummaryForSelection(mPictureSizes.videoQualitiesFront.orNull(), listPreference);
    400             } else {
    401                 listPreference.setSummary(listPreference.getEntry());
    402             }
    403         }
    404 
    405         /**
    406          * Sets the entries for the given list preference.
    407          *
    408          * @param selectedSizes The possible S,M,L entries the user can choose
    409          *            from.
    410          * @param preference The preference to set the entries for.
    411          */
    412         private void setEntriesForSelection(List<Size> selectedSizes,
    413                 ListPreference preference) {
    414             if (selectedSizes == null) {
    415                 return;
    416             }
    417 
    418             String[] entries = new String[selectedSizes.size()];
    419             String[] entryValues = new String[selectedSizes.size()];
    420             for (int i = 0; i < selectedSizes.size(); i++) {
    421                 Size size = selectedSizes.get(i);
    422                 entries[i] = getSizeSummaryString(size);
    423                 entryValues[i] = SettingsUtil.sizeToSettingString(size);
    424             }
    425             preference.setEntries(entries);
    426             preference.setEntryValues(entryValues);
    427         }
    428 
    429         /**
    430          * Sets the entries for the given list preference.
    431          *
    432          * @param selectedQualities The possible S,M,L entries the user can
    433          *            choose from.
    434          * @param preference The preference to set the entries for.
    435          */
    436         private void setEntriesForSelection(SelectedVideoQualities selectedQualities,
    437                 ListPreference preference) {
    438             if (selectedQualities == null) {
    439                 return;
    440             }
    441 
    442             // Avoid adding double entries at the bottom of the list which
    443             // indicates that not at least 3 qualities are supported.
    444             ArrayList<String> entries = new ArrayList<String>();
    445             entries.add(mCamcorderProfileNames[selectedQualities.large]);
    446             if (selectedQualities.medium != selectedQualities.large) {
    447                 entries.add(mCamcorderProfileNames[selectedQualities.medium]);
    448             }
    449             if (selectedQualities.small != selectedQualities.medium) {
    450                 entries.add(mCamcorderProfileNames[selectedQualities.small]);
    451             }
    452             preference.setEntries(entries.toArray(new String[0]));
    453         }
    454 
    455         /**
    456          * Sets the summary for the given list preference.
    457          *
    458          * @param displayableSizes The human readable preferred sizes
    459          * @param preference The preference for which to set the summary.
    460          */
    461         private void setSummaryForSelection(List<Size> displayableSizes,
    462                                             ListPreference preference) {
    463             String setting = preference.getValue();
    464             if (setting == null || !setting.contains("x")) {
    465                 return;
    466             }
    467             Size settingSize = SettingsUtil.sizeFromSettingString(setting);
    468             if (settingSize == null || settingSize.area() == 0) {
    469                 return;
    470             }
    471             preference.setSummary(getSizeSummaryString(settingSize));
    472         }
    473 
    474         /**
    475          * Sets the summary for the given list preference.
    476          *
    477          * @param selectedQualities The selected video qualities.
    478          * @param preference The preference for which to set the summary.
    479          */
    480         private void setSummaryForSelection(SelectedVideoQualities selectedQualities,
    481                 ListPreference preference) {
    482             if (selectedQualities == null) {
    483                 return;
    484             }
    485 
    486             int selectedQuality = selectedQualities.getFromSetting(preference.getValue());
    487             preference.setSummary(mCamcorderProfileNames[selectedQuality]);
    488         }
    489 
    490         /**
    491          * This method gets the selected picture sizes for S,M,L and populates
    492          * {@link #mPictureSizes} accordingly.
    493          */
    494         private void loadSizes() {
    495             if (mInfos == null) {
    496                 Log.w(TAG, "null deviceInfo, cannot display resolution sizes");
    497                 return;
    498             }
    499             PictureSizeLoader loader = new PictureSizeLoader(getActivity().getApplicationContext());
    500             mPictureSizes = loader.computePictureSizes();
    501         }
    502 
    503         /**
    504          * @param size The photo resolution.
    505          * @return A human readable and translated string for labeling the
    506          *         picture size in megapixels.
    507          */
    508         private String getSizeSummaryString(Size size) {
    509             Size approximateSize = ResolutionUtil.getApproximateSize(size);
    510             String megaPixels = sMegaPixelFormat.format((size.width() * size.height()) / 1e6);
    511             int numerator = ResolutionUtil.aspectRatioNumerator(approximateSize);
    512             int denominator = ResolutionUtil.aspectRatioDenominator(approximateSize);
    513             String result = getResources().getString(
    514                     R.string.setting_summary_aspect_ratio_and_megapixels, numerator, denominator,
    515                     megaPixels);
    516             return result;
    517         }
    518     }
    519 }
    520