• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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