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