2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.camera.settings;
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;
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;
54 import java.text.DecimalFormat;
55 import java.util.ArrayList;
56 import java.util.List;
59 * Provides the settings UI for the Camera app.
61 public class CameraSettingsActivity extends FragmentActivity {
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.
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;
75 public void onCreate(Bundle savedInstanceState) {
76 super.onCreate(savedInstanceState);
78 FatalErrorHandler fatalErrorHandler = new FatalErrorHandlerImpl(this);
79 boolean hideAdvancedScreen = false;
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();
89 // Check if manual exposure is available, so we can decide whether to
90 // display Advanced screen.
92 CameraId frontCameraId = mOneCameraManager.findFirstCameraFacing(Facing.FRONT);
93 CameraId backCameraId = mOneCameraManager.findFirstCameraFacing(Facing.BACK);
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());
105 // Hides the option if neither front and back camera support exposure compensation.
106 if (!isExposureCompensationSupportedByFrontCamera &&
107 !isExposureCompensationSupportedByBackCamera) {
108 hideAdvancedScreen = true;
110 } catch (OneCameraAccessException e) {
111 fatalErrorHandler.onGenericCameraAccessFailure();
114 ActionBar actionBar = getActionBar();
115 actionBar.setDisplayHomeAsUpEnabled(true);
116 actionBar.setTitle(R.string.mode_settings);
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();
128 public boolean onMenuItemSelected(int featureId, MenuItem item) {
129 int itemId = item.getItemId();
130 if (itemId == android.R.id.home) {
137 public static class CameraSettingsFragment extends PreferenceFragment implements
138 OnSharedPreferenceChangeListener {
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;
151 // Selected resolutions for the different cameras and sizes.
152 private PictureSizes mPictureSizes;
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);
162 Context context = this.getActivity().getApplicationContext();
163 addPreferencesFromResource(R.xml.camera_preferences);
164 PreferenceScreen advancedScreen =
165 (PreferenceScreen) findPreference(PREF_CATEGORY_ADVANCED);
167 // If manual exposure not enabled, hide the Advanced screen.
168 if (mHideAdvancedScreen) {
169 PreferenceScreen root = (PreferenceScreen) findPreference("prefscreen_top");
170 root.removePreference(advancedScreen);
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;
179 mCamcorderProfileNames = getResources().getStringArray(R.array.camcorder_profile_names);
180 mInfos = CameraAgentFactory
181 .getAndroidCameraAgent(context, CameraAgentFactory.CameraApi.API_1)
182 .getCameraDeviceInfo();
186 public void onResume() {
188 final Activity activity = this.getActivity();
190 // Load the camera sizes.
193 // Send loaded sizes to additional preferences.
194 CameraSettingsActivityHelper.onSizesLoaded(this, mPictureSizes.backCameraSizes,
195 new ListPreferenceFiller() {
197 public void fill(List<Size> sizes, ListPreference preference) {
198 setEntriesForSelection(sizes, preference);
202 // Make sure to hide settings for cameras that don't exist on this
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);
212 final PreferenceScreen advancedScreen =
213 (PreferenceScreen) findPreference(PREF_CATEGORY_ADVANCED);
215 if (!mHideAdvancedScreen) {
216 setPreferenceScreenIntent(advancedScreen);
219 Preference helpPref = findPreference(PREF_LAUNCH_HELP);
220 helpPref.setOnPreferenceClickListener(
221 new OnPreferenceClickListener() {
223 public boolean onPreferenceClick(Preference preference) {
224 new GoogleHelpHelper(activity).launchGoogleHelp();
228 getPreferenceScreen().getSharedPreferences()
229 .registerOnSharedPreferenceChangeListener(this);
233 * Configure home-as-up for sub-screens.
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);
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
250 public PreferenceScreen getPreferenceScreen() {
251 PreferenceScreen root = super.getPreferenceScreen();
252 if (!mGetSubPrefAsRoot || mPrefKey == null || root == null) {
255 PreferenceScreen match = findByKey(root, mPrefKey);
259 throw new RuntimeException("key " + mPrefKey + " not found");
264 private PreferenceScreen findByKey(PreferenceScreen parent, String key) {
265 if (key.equals(parent.getKey())) {
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);
282 * Depending on camera availability on the device, this removes settings
283 * for cameras the device doesn't have.
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));
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));
303 * Recursively go through settings and fill entries and summaries of our
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);
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.
322 private boolean recursiveDelete(PreferenceGroup group, Preference preference) {
324 Log.d(TAG, "attempting to delete from null preference group");
327 if (preference == null) {
328 Log.d(TAG, "attempting to delete null preference");
331 if (group.removePreference(preference)) {
332 // Removal was successful.
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)) {
348 public void onPause() {
350 getPreferenceScreen().getSharedPreferences()
351 .unregisterOnSharedPreferenceChangeListener(this);
355 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
356 setSummary(findPreference(key));
360 * Set the entries for the given preference. The given preference needs
361 * to be a {@link ListPreference}
363 private void setEntries(Preference preference) {
364 if (!(preference instanceof ListPreference)) {
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);
381 * Set the summary for the given preference. The given preference needs
382 * to be a {@link ListPreference}.
384 private void setSummary(Preference preference) {
385 if (!(preference instanceof ListPreference)) {
389 ListPreference listPreference = (ListPreference) preference;
390 if (listPreference.getKey().equals(Keys.KEY_PICTURE_SIZE_BACK)) {
391 setSummaryForSelection(mPictureSizes.backCameraSizes,
393 } else if (listPreference.getKey().equals(Keys.KEY_PICTURE_SIZE_FRONT)) {
394 setSummaryForSelection(mPictureSizes.frontCameraSizes,
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);
401 listPreference.setSummary(listPreference.getEntry());
406 * Sets the entries for the given list preference.
408 * @param selectedSizes The possible S,M,L entries the user can choose
410 * @param preference The preference to set the entries for.
412 private void setEntriesForSelection(List<Size> selectedSizes,
413 ListPreference preference) {
414 if (selectedSizes == null) {
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);
425 preference.setEntries(entries);
426 preference.setEntryValues(entryValues);
430 * Sets the entries for the given list preference.
432 * @param selectedQualities The possible S,M,L entries the user can
434 * @param preference The preference to set the entries for.
436 private void setEntriesForSelection(SelectedVideoQualities selectedQualities,
437 ListPreference preference) {
438 if (selectedQualities == null) {
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]);
449 if (selectedQualities.small != selectedQualities.medium) {
450 entries.add(mCamcorderProfileNames[selectedQualities.small]);
452 preference.setEntries(entries.toArray(new String[0]));
456 * Sets the summary for the given list preference.
458 * @param displayableSizes The human readable preferred sizes
459 * @param preference The preference for which to set the summary.
461 private void setSummaryForSelection(List<Size> displayableSizes,
462 ListPreference preference) {
463 String setting = preference.getValue();
464 if (setting == null || !setting.contains("x")) {
467 Size settingSize = SettingsUtil.sizeFromSettingString(setting);
468 if (settingSize == null || settingSize.area() == 0) {
471 preference.setSummary(getSizeSummaryString(settingSize));
475 * Sets the summary for the given list preference.
477 * @param selectedQualities The selected video qualities.
478 * @param preference The preference for which to set the summary.
480 private void setSummaryForSelection(SelectedVideoQualities selectedQualities,
481 ListPreference preference) {
482 if (selectedQualities == null) {
486 int selectedQuality = selectedQualities.getFromSetting(preference.getValue());
487 preference.setSummary(mCamcorderProfileNames[selectedQuality]);
491 * This method gets the selected picture sizes for S,M,L and populates
492 * {@link #mPictureSizes} accordingly.
494 private void loadSizes() {
495 if (mInfos == null) {
496 Log.w(TAG, "null deviceInfo, cannot display resolution sizes");
499 PictureSizeLoader loader = new PictureSizeLoader(getActivity().getApplicationContext());
500 mPictureSizes = loader.computePictureSizes();
504 * @param size The photo resolution.
505 * @return A human readable and translated string for labeling the
506 * picture size in megapixels.
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,