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( 357 new String[] {Manifest.permission.ACCESS_COARSE_LOCATION, 358 Manifest.permission.ACCESS_FINE_LOCATION}, 359 PERMISSION_REQUEST_CODE); 360 } 361 } 362 } 363 364 /** 365 * Set the entries for the given preference. The given preference needs 366 * to be a {@link ListPreference} 367 */ setEntries(Preference preference)368 private void setEntries(Preference preference) { 369 if (!(preference instanceof ListPreference)) { 370 return; 371 } 372 373 ListPreference listPreference = (ListPreference) preference; 374 if (listPreference.getKey().equals(Keys.KEY_PICTURE_SIZE_BACK)) { 375 setEntriesForSelection(mPictureSizes.backCameraSizes, listPreference); 376 } else if (listPreference.getKey().equals(Keys.KEY_PICTURE_SIZE_FRONT)) { 377 setEntriesForSelection(mPictureSizes.frontCameraSizes, listPreference); 378 } else if (listPreference.getKey().equals(Keys.KEY_VIDEO_QUALITY_BACK)) { 379 setEntriesForSelection(mPictureSizes.videoQualitiesBack.orNull(), listPreference); 380 } else if (listPreference.getKey().equals(Keys.KEY_VIDEO_QUALITY_FRONT)) { 381 setEntriesForSelection(mPictureSizes.videoQualitiesFront.orNull(), listPreference); 382 } 383 } 384 385 /** 386 * Set the summary for the given preference. The given preference needs 387 * to be a {@link ListPreference}. 388 */ setSummary(Preference preference)389 private void setSummary(Preference preference) { 390 if (!(preference instanceof ListPreference)) { 391 return; 392 } 393 394 ListPreference listPreference = (ListPreference) preference; 395 if (listPreference.getKey().equals(Keys.KEY_PICTURE_SIZE_BACK)) { 396 setSummaryForSelection(mPictureSizes.backCameraSizes, 397 listPreference); 398 } else if (listPreference.getKey().equals(Keys.KEY_PICTURE_SIZE_FRONT)) { 399 setSummaryForSelection(mPictureSizes.frontCameraSizes, 400 listPreference); 401 } else if (listPreference.getKey().equals(Keys.KEY_VIDEO_QUALITY_BACK)) { 402 setSummaryForSelection(mPictureSizes.videoQualitiesBack.orNull(), listPreference); 403 } else if (listPreference.getKey().equals(Keys.KEY_VIDEO_QUALITY_FRONT)) { 404 setSummaryForSelection(mPictureSizes.videoQualitiesFront.orNull(), listPreference); 405 } else { 406 listPreference.setSummary(listPreference.getEntry()); 407 } 408 } 409 410 /** 411 * Sets the entries for the given list preference. 412 * 413 * @param selectedSizes The possible S,M,L entries the user can choose 414 * from. 415 * @param preference The preference to set the entries for. 416 */ setEntriesForSelection(List<Size> selectedSizes, ListPreference preference)417 private void setEntriesForSelection(List<Size> selectedSizes, 418 ListPreference preference) { 419 if (selectedSizes == null) { 420 return; 421 } 422 423 String[] entries = new String[selectedSizes.size()]; 424 String[] entryValues = new String[selectedSizes.size()]; 425 for (int i = 0; i < selectedSizes.size(); i++) { 426 Size size = selectedSizes.get(i); 427 entries[i] = getSizeSummaryString(size); 428 entryValues[i] = SettingsUtil.sizeToSettingString(size); 429 } 430 preference.setEntries(entries); 431 preference.setEntryValues(entryValues); 432 } 433 434 /** 435 * Sets the entries for the given list preference. 436 * 437 * @param selectedQualities The possible S,M,L entries the user can 438 * choose from. 439 * @param preference The preference to set the entries for. 440 */ setEntriesForSelection(SelectedVideoQualities selectedQualities, ListPreference preference)441 private void setEntriesForSelection(SelectedVideoQualities selectedQualities, 442 ListPreference preference) { 443 if (selectedQualities == null) { 444 return; 445 } 446 447 // Avoid adding double entries at the bottom of the list which 448 // indicates that not at least 3 qualities are supported. 449 ArrayList<String> entries = new ArrayList<String>(); 450 entries.add(mCamcorderProfileNames[selectedQualities.large]); 451 if (selectedQualities.medium != selectedQualities.large) { 452 entries.add(mCamcorderProfileNames[selectedQualities.medium]); 453 } 454 if (selectedQualities.small != selectedQualities.medium) { 455 entries.add(mCamcorderProfileNames[selectedQualities.small]); 456 } 457 preference.setEntries(entries.toArray(new String[0])); 458 } 459 460 /** 461 * Sets the summary for the given list preference. 462 * 463 * @param displayableSizes The human readable preferred sizes 464 * @param preference The preference for which to set the summary. 465 */ setSummaryForSelection(List<Size> displayableSizes, ListPreference preference)466 private void setSummaryForSelection(List<Size> displayableSizes, 467 ListPreference preference) { 468 String setting = preference.getValue(); 469 if (setting == null || !setting.contains("x")) { 470 return; 471 } 472 Size settingSize = SettingsUtil.sizeFromSettingString(setting); 473 if (settingSize == null || settingSize.area() == 0) { 474 return; 475 } 476 preference.setSummary(getSizeSummaryString(settingSize)); 477 } 478 479 /** 480 * Sets the summary for the given list preference. 481 * 482 * @param selectedQualities The selected video qualities. 483 * @param preference The preference for which to set the summary. 484 */ setSummaryForSelection(SelectedVideoQualities selectedQualities, ListPreference preference)485 private void setSummaryForSelection(SelectedVideoQualities selectedQualities, 486 ListPreference preference) { 487 if (selectedQualities == null) { 488 return; 489 } 490 491 int selectedQuality = selectedQualities.getFromSetting(preference.getValue()); 492 preference.setSummary(mCamcorderProfileNames[selectedQuality]); 493 } 494 495 /** 496 * This method gets the selected picture sizes for S,M,L and populates 497 * {@link #mPictureSizes} accordingly. 498 */ loadSizes()499 private void loadSizes() { 500 if (mInfos == null) { 501 Log.w(TAG, "null deviceInfo, cannot display resolution sizes"); 502 return; 503 } 504 PictureSizeLoader loader = new PictureSizeLoader(getActivity().getApplicationContext()); 505 mPictureSizes = loader.computePictureSizes(); 506 loader.release(); 507 } 508 509 /** 510 * @param size The photo resolution. 511 * @return A human readable and translated string for labeling the 512 * picture size in megapixels. 513 */ getSizeSummaryString(Size size)514 private String getSizeSummaryString(Size size) { 515 Size approximateSize = ResolutionUtil.getApproximateSize(size); 516 String megaPixels = sMegaPixelFormat.format((size.width() * size.height()) / 1e6); 517 int numerator = ResolutionUtil.aspectRatioNumerator(approximateSize); 518 int denominator = ResolutionUtil.aspectRatioDenominator(approximateSize); 519 String result = getResources().getString( 520 R.string.setting_summary_aspect_ratio_and_megapixels, numerator, denominator, 521 megaPixels); 522 return result; 523 } 524 } 525 } 526