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 androidx.fragment.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 CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.API_1); 184 } 185 186 @Override onResume()187 public void onResume() { 188 super.onResume(); 189 final Activity activity = this.getActivity(); 190 191 // Load the camera sizes. 192 loadSizes(); 193 194 // Send loaded sizes to additional preferences. 195 CameraSettingsActivityHelper.onSizesLoaded(this, mPictureSizes.backCameraSizes, 196 new ListPreferenceFiller() { 197 @Override 198 public void fill(List<Size> sizes, ListPreference preference) { 199 setEntriesForSelection(sizes, preference); 200 } 201 }); 202 203 // Make sure to hide settings for cameras that don't exist on this 204 // device. 205 setVisibilities(); 206 207 // Put in the summaries for the currently set values. 208 final PreferenceScreen resolutionScreen = 209 (PreferenceScreen) findPreference(PREF_CATEGORY_RESOLUTION); 210 fillEntriesAndSummaries(resolutionScreen); 211 setPreferenceScreenIntent(resolutionScreen); 212 213 final PreferenceScreen advancedScreen = 214 (PreferenceScreen) findPreference(PREF_CATEGORY_ADVANCED); 215 216 if (!mHideAdvancedScreen) { 217 setPreferenceScreenIntent(advancedScreen); 218 } 219 220 Preference helpPref = findPreference(PREF_LAUNCH_HELP); 221 helpPref.setOnPreferenceClickListener( 222 new OnPreferenceClickListener() { 223 @Override 224 public boolean onPreferenceClick(Preference preference) { 225 new GoogleHelpHelper(activity).launchGoogleHelp(); 226 return true; 227 } 228 }); 229 getPreferenceScreen().getSharedPreferences() 230 .registerOnSharedPreferenceChangeListener(this); 231 } 232 233 /** 234 * Configure home-as-up for sub-screens. 235 */ setPreferenceScreenIntent(final PreferenceScreen preferenceScreen)236 private void setPreferenceScreenIntent(final PreferenceScreen preferenceScreen) { 237 Intent intent = new Intent(getActivity(), CameraSettingsActivity.class); 238 intent.putExtra(PREF_SCREEN_EXTRA, preferenceScreen.getKey()); 239 preferenceScreen.setIntent(intent); 240 } 241 242 /** 243 * This override allows the CameraSettingsFragment to be reused for 244 * different nested PreferenceScreens within the single camera 245 * preferences XML resource. If the fragment is constructed with a 246 * desired preference key (delivered via an extra in the creation 247 * intent), it is used to look up the nested PreferenceScreen and 248 * returned here. 249 */ 250 @Override getPreferenceScreen()251 public PreferenceScreen getPreferenceScreen() { 252 PreferenceScreen root = super.getPreferenceScreen(); 253 if (!mGetSubPrefAsRoot || mPrefKey == null || root == null) { 254 return root; 255 } else { 256 PreferenceScreen match = findByKey(root, mPrefKey); 257 if (match != null) { 258 return match; 259 } else { 260 throw new RuntimeException("key " + mPrefKey + " not found"); 261 } 262 } 263 } 264 findByKey(PreferenceScreen parent, String key)265 private PreferenceScreen findByKey(PreferenceScreen parent, String key) { 266 if (key.equals(parent.getKey())) { 267 return parent; 268 } else { 269 for (int i = 0; i < parent.getPreferenceCount(); i++) { 270 Preference child = parent.getPreference(i); 271 if (child instanceof PreferenceScreen) { 272 PreferenceScreen match = findByKey((PreferenceScreen) child, key); 273 if (match != null) { 274 return match; 275 } 276 } 277 } 278 return null; 279 } 280 } 281 282 /** 283 * Depending on camera availability on the device, this removes settings 284 * for cameras the device doesn't have. 285 */ setVisibilities()286 private void setVisibilities() { 287 PreferenceGroup resolutions = 288 (PreferenceGroup) findPreference(PREF_CATEGORY_RESOLUTION); 289 if (mPictureSizes.backCameraSizes.isEmpty()) { 290 recursiveDelete(resolutions, 291 findPreference(Keys.KEY_PICTURE_SIZE_BACK)); 292 recursiveDelete(resolutions, 293 findPreference(Keys.KEY_VIDEO_QUALITY_BACK)); 294 } 295 if (mPictureSizes.frontCameraSizes.isEmpty()) { 296 recursiveDelete(resolutions, 297 findPreference(Keys.KEY_PICTURE_SIZE_FRONT)); 298 recursiveDelete(resolutions, 299 findPreference(Keys.KEY_VIDEO_QUALITY_FRONT)); 300 } 301 } 302 303 /** 304 * Recursively go through settings and fill entries and summaries of our 305 * preferences. 306 */ fillEntriesAndSummaries(PreferenceGroup group)307 private void fillEntriesAndSummaries(PreferenceGroup group) { 308 for (int i = 0; i < group.getPreferenceCount(); ++i) { 309 Preference pref = group.getPreference(i); 310 if (pref instanceof PreferenceGroup) { 311 fillEntriesAndSummaries((PreferenceGroup) pref); 312 } 313 setSummary(pref); 314 setEntries(pref); 315 } 316 } 317 318 /** 319 * Recursively traverses the tree from the given group as the route and 320 * tries to delete the preference. Traversal stops once the preference 321 * was found and removed. 322 */ recursiveDelete(PreferenceGroup group, Preference preference)323 private boolean recursiveDelete(PreferenceGroup group, Preference preference) { 324 if (group == null) { 325 Log.d(TAG, "attempting to delete from null preference group"); 326 return false; 327 } 328 if (preference == null) { 329 Log.d(TAG, "attempting to delete null preference"); 330 return false; 331 } 332 if (group.removePreference(preference)) { 333 // Removal was successful. 334 return true; 335 } 336 337 for (int i = 0; i < group.getPreferenceCount(); ++i) { 338 Preference pref = group.getPreference(i); 339 if (pref instanceof PreferenceGroup) { 340 if (recursiveDelete((PreferenceGroup) pref, preference)) { 341 return true; 342 } 343 } 344 } 345 return false; 346 } 347 348 @Override onPause()349 public void onPause() { 350 super.onPause(); 351 getPreferenceScreen().getSharedPreferences() 352 .unregisterOnSharedPreferenceChangeListener(this); 353 } 354 355 @Override onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)356 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 357 setSummary(findPreference(key)); 358 } 359 360 /** 361 * Set the entries for the given preference. The given preference needs 362 * to be a {@link ListPreference} 363 */ setEntries(Preference preference)364 private void setEntries(Preference preference) { 365 if (!(preference instanceof ListPreference)) { 366 return; 367 } 368 369 ListPreference listPreference = (ListPreference) preference; 370 if (listPreference.getKey().equals(Keys.KEY_PICTURE_SIZE_BACK)) { 371 setEntriesForSelection(mPictureSizes.backCameraSizes, listPreference); 372 } else if (listPreference.getKey().equals(Keys.KEY_PICTURE_SIZE_FRONT)) { 373 setEntriesForSelection(mPictureSizes.frontCameraSizes, listPreference); 374 } else if (listPreference.getKey().equals(Keys.KEY_VIDEO_QUALITY_BACK)) { 375 setEntriesForSelection(mPictureSizes.videoQualitiesBack.orNull(), listPreference); 376 } else if (listPreference.getKey().equals(Keys.KEY_VIDEO_QUALITY_FRONT)) { 377 setEntriesForSelection(mPictureSizes.videoQualitiesFront.orNull(), listPreference); 378 } 379 } 380 381 /** 382 * Set the summary for the given preference. The given preference needs 383 * to be a {@link ListPreference}. 384 */ setSummary(Preference preference)385 private void setSummary(Preference preference) { 386 if (!(preference instanceof ListPreference)) { 387 return; 388 } 389 390 ListPreference listPreference = (ListPreference) preference; 391 if (listPreference.getKey().equals(Keys.KEY_PICTURE_SIZE_BACK)) { 392 setSummaryForSelection(mPictureSizes.backCameraSizes, 393 listPreference); 394 } else if (listPreference.getKey().equals(Keys.KEY_PICTURE_SIZE_FRONT)) { 395 setSummaryForSelection(mPictureSizes.frontCameraSizes, 396 listPreference); 397 } else if (listPreference.getKey().equals(Keys.KEY_VIDEO_QUALITY_BACK)) { 398 setSummaryForSelection(mPictureSizes.videoQualitiesBack.orNull(), listPreference); 399 } else if (listPreference.getKey().equals(Keys.KEY_VIDEO_QUALITY_FRONT)) { 400 setSummaryForSelection(mPictureSizes.videoQualitiesFront.orNull(), listPreference); 401 } else { 402 listPreference.setSummary(listPreference.getEntry()); 403 } 404 } 405 406 /** 407 * Sets the entries for the given list preference. 408 * 409 * @param selectedSizes The possible S,M,L entries the user can choose 410 * from. 411 * @param preference The preference to set the entries for. 412 */ setEntriesForSelection(List<Size> selectedSizes, ListPreference preference)413 private void setEntriesForSelection(List<Size> selectedSizes, 414 ListPreference preference) { 415 if (selectedSizes == null) { 416 return; 417 } 418 419 String[] entries = new String[selectedSizes.size()]; 420 String[] entryValues = new String[selectedSizes.size()]; 421 for (int i = 0; i < selectedSizes.size(); i++) { 422 Size size = selectedSizes.get(i); 423 entries[i] = getSizeSummaryString(size); 424 entryValues[i] = SettingsUtil.sizeToSettingString(size); 425 } 426 preference.setEntries(entries); 427 preference.setEntryValues(entryValues); 428 } 429 430 /** 431 * Sets the entries for the given list preference. 432 * 433 * @param selectedQualities The possible S,M,L entries the user can 434 * choose from. 435 * @param preference The preference to set the entries for. 436 */ setEntriesForSelection(SelectedVideoQualities selectedQualities, ListPreference preference)437 private void setEntriesForSelection(SelectedVideoQualities selectedQualities, 438 ListPreference preference) { 439 if (selectedQualities == null) { 440 return; 441 } 442 443 // Avoid adding double entries at the bottom of the list which 444 // indicates that not at least 3 qualities are supported. 445 ArrayList<String> entries = new ArrayList<String>(); 446 entries.add(mCamcorderProfileNames[selectedQualities.large]); 447 if (selectedQualities.medium != selectedQualities.large) { 448 entries.add(mCamcorderProfileNames[selectedQualities.medium]); 449 } 450 if (selectedQualities.small != selectedQualities.medium) { 451 entries.add(mCamcorderProfileNames[selectedQualities.small]); 452 } 453 preference.setEntries(entries.toArray(new String[0])); 454 } 455 456 /** 457 * Sets the summary for the given list preference. 458 * 459 * @param displayableSizes The human readable preferred sizes 460 * @param preference The preference for which to set the summary. 461 */ setSummaryForSelection(List<Size> displayableSizes, ListPreference preference)462 private void setSummaryForSelection(List<Size> displayableSizes, 463 ListPreference preference) { 464 String setting = preference.getValue(); 465 if (setting == null || !setting.contains("x")) { 466 return; 467 } 468 Size settingSize = SettingsUtil.sizeFromSettingString(setting); 469 if (settingSize == null || settingSize.area() == 0) { 470 return; 471 } 472 preference.setSummary(getSizeSummaryString(settingSize)); 473 } 474 475 /** 476 * Sets the summary for the given list preference. 477 * 478 * @param selectedQualities The selected video qualities. 479 * @param preference The preference for which to set the summary. 480 */ setSummaryForSelection(SelectedVideoQualities selectedQualities, ListPreference preference)481 private void setSummaryForSelection(SelectedVideoQualities selectedQualities, 482 ListPreference preference) { 483 if (selectedQualities == null) { 484 return; 485 } 486 487 int selectedQuality = selectedQualities.getFromSetting(preference.getValue()); 488 preference.setSummary(mCamcorderProfileNames[selectedQuality]); 489 } 490 491 /** 492 * This method gets the selected picture sizes for S,M,L and populates 493 * {@link #mPictureSizes} accordingly. 494 */ loadSizes()495 private void loadSizes() { 496 if (mInfos == null) { 497 Log.w(TAG, "null deviceInfo, cannot display resolution sizes"); 498 return; 499 } 500 PictureSizeLoader loader = new PictureSizeLoader(getActivity().getApplicationContext()); 501 mPictureSizes = loader.computePictureSizes(); 502 loader.release(); 503 } 504 505 /** 506 * @param size The photo resolution. 507 * @return A human readable and translated string for labeling the 508 * picture size in megapixels. 509 */ getSizeSummaryString(Size size)510 private String getSizeSummaryString(Size size) { 511 Size approximateSize = ResolutionUtil.getApproximateSize(size); 512 String megaPixels = sMegaPixelFormat.format((size.width() * size.height()) / 1e6); 513 int numerator = ResolutionUtil.aspectRatioNumerator(approximateSize); 514 int denominator = ResolutionUtil.aspectRatioDenominator(approximateSize); 515 String result = getResources().getString( 516 R.string.setting_summary_aspect_ratio_and_megapixels, numerator, denominator, 517 megaPixels); 518 return result; 519 } 520 } 521 } 522