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 onCreate(Bundle savedInstanceState)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 onMenuItemSelected(int featureId, MenuItem item)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 onCreate(Bundle savedInstanceState)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 onResume()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 */ setPreferenceScreenIntent(final PreferenceScreen preferenceScreen)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 getPreferenceScreen()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 findByKey(PreferenceScreen parent, String key)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 */ setVisibilities()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 */ fillEntriesAndSummaries(PreferenceGroup group)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 */ recursiveDelete(PreferenceGroup group, Preference preference)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 onPause()348 public void onPause() { 349 super.onPause(); 350 getPreferenceScreen().getSharedPreferences() 351 .unregisterOnSharedPreferenceChangeListener(this); 352 } 353 354 @Override onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)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 */ setEntries(Preference preference)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 */ setSummary(Preference preference)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 */ setEntriesForSelection(List<Size> selectedSizes, ListPreference preference)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 */ setEntriesForSelection(SelectedVideoQualities selectedQualities, ListPreference preference)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 */ setSummaryForSelection(List<Size> displayableSizes, ListPreference preference)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 */ setSummaryForSelection(SelectedVideoQualities selectedQualities, ListPreference preference)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 */ loadSizes()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 */ getSizeSummaryString(Size size)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