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