• 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.ex.camera2.portability;
18 
19 import static android.hardware.camera2.CaptureRequest.*;
20 
21 import android.graphics.Matrix;
22 import android.graphics.Rect;
23 import android.graphics.RectF;
24 import android.hardware.camera2.CameraAccessException;
25 import android.hardware.camera2.CameraDevice;
26 import android.hardware.camera2.params.MeteringRectangle;
27 import android.location.Location;
28 import android.util.Range;
29 
30 import com.android.ex.camera2.portability.CameraCapabilities.FlashMode;
31 import com.android.ex.camera2.portability.CameraCapabilities.FocusMode;
32 import com.android.ex.camera2.portability.CameraCapabilities.SceneMode;
33 import com.android.ex.camera2.portability.CameraCapabilities.WhiteBalance;
34 import com.android.ex.camera2.portability.debug.Log;
35 import com.android.ex.camera2.utils.Camera2RequestSettingsSet;
36 
37 import java.util.List;
38 import java.util.Objects;
39 
40 /**
41  * The subclass of {@link CameraSettings} for Android Camera 2 API.
42  */
43 public class AndroidCamera2Settings extends CameraSettings {
44     private static final Log.Tag TAG = new Log.Tag("AndCam2Set");
45 
46     private final Builder mTemplateSettings;
47     private final Camera2RequestSettingsSet mRequestSettings;
48     /** Sensor's active array bounds. */
49     private final Rect mActiveArray;
50     /** Crop rectangle for digital zoom (measured WRT the active array). */
51     private final Rect mCropRectangle;
52     /** Bounds of visible preview portion (measured WRT the active array). */
53     private Rect mVisiblePreviewRectangle;
54 
55     /**
56      * Create a settings representation that answers queries of unspecified
57      * options in the same way as the provided template would.
58      *
59      * <p>The default settings provided by the given template are only ever used
60      * for reporting back to the client app (i.e. when it queries an option
61      * it didn't explicitly set first). {@link Camera2RequestSettingsSet}s
62      * generated by an instance of this class will have any settings not
63      * modified using one of that instance's mutators forced to default, so that
64      * their effective values when submitting a capture request will be those of
65      * the template that is provided to the camera framework at that time.</p>
66      *
67      * @param camera Device from which to draw default settings
68      *               (non-{@code null}).
69      * @param template Specific template to use for the defaults.
70      * @param activeArray Boundary coordinates of the sensor's active array
71      *                    (non-{@code null}).
72      * @param preview Dimensions of preview streams.
73      * @param photo Dimensions of captured images.
74      *
75      * @throws IllegalArgumentException If {@code camera} or {@code activeArray}
76      *                                  is {@code null}.
77      * @throws CameraAccessException Upon internal framework/driver failure.
78      */
AndroidCamera2Settings(CameraDevice camera, int template, Rect activeArray, Size preview, Size photo)79     public AndroidCamera2Settings(CameraDevice camera, int template, Rect activeArray,
80                                   Size preview, Size photo) throws CameraAccessException {
81         if (camera == null) {
82             throw new NullPointerException("camera must not be null");
83         }
84         if (activeArray == null) {
85             throw new NullPointerException("activeArray must not be null");
86         }
87 
88         mTemplateSettings = camera.createCaptureRequest(template);
89         mRequestSettings = new Camera2RequestSettingsSet();
90         mActiveArray = activeArray;
91         mCropRectangle = new Rect(0, 0, activeArray.width(), activeArray.height());
92 
93         mSizesLocked = false;
94 
95         Range<Integer> previewFpsRange = mTemplateSettings.get(CONTROL_AE_TARGET_FPS_RANGE);
96         if (previewFpsRange != null) {
97             setPreviewFpsRange(previewFpsRange.getLower(), previewFpsRange.getUpper());
98         }
99         setPreviewSize(preview);
100         // TODO: mCurrentPreviewFormat
101         setPhotoSize(photo);
102         mJpegCompressQuality = queryTemplateDefaultOrMakeOneUp(JPEG_QUALITY, (byte) 0);
103         // TODO: mCurrentPhotoFormat
104         // NB: We're assuming that templates won't be zoomed in by default.
105         mCurrentZoomRatio = CameraCapabilities.ZOOM_RATIO_UNZOOMED;
106         // TODO: mCurrentZoomIndex
107         mExposureCompensationIndex =
108                 queryTemplateDefaultOrMakeOneUp(CONTROL_AE_EXPOSURE_COMPENSATION, 0);
109 
110         mCurrentFlashMode = flashModeFromRequest();
111         Integer currentFocusMode = mTemplateSettings.get(CONTROL_AF_MODE);
112         if (currentFocusMode != null) {
113             mCurrentFocusMode = AndroidCamera2Capabilities.focusModeFromInt(currentFocusMode);
114         }
115         Integer currentSceneMode = mTemplateSettings.get(CONTROL_SCENE_MODE);
116         if (currentSceneMode != null) {
117             mCurrentSceneMode = AndroidCamera2Capabilities.sceneModeFromInt(currentSceneMode);
118         }
119         Integer whiteBalance = mTemplateSettings.get(CONTROL_AWB_MODE);
120         if (whiteBalance != null) {
121             mWhiteBalance = AndroidCamera2Capabilities.whiteBalanceFromInt(whiteBalance);
122         }
123 
124         mVideoStabilizationEnabled = queryTemplateDefaultOrMakeOneUp(
125                         CONTROL_VIDEO_STABILIZATION_MODE, CONTROL_VIDEO_STABILIZATION_MODE_OFF) ==
126                 CONTROL_VIDEO_STABILIZATION_MODE_ON;
127         mAutoExposureLocked = queryTemplateDefaultOrMakeOneUp(CONTROL_AE_LOCK, false);
128         mAutoWhiteBalanceLocked = queryTemplateDefaultOrMakeOneUp(CONTROL_AWB_LOCK, false);
129         // TODO: mRecordingHintEnabled
130         // TODO: mGpsData
131         android.util.Size exifThumbnailSize = mTemplateSettings.get(JPEG_THUMBNAIL_SIZE);
132         if (exifThumbnailSize != null) {
133             mExifThumbnailSize =
134                     new Size(exifThumbnailSize.getWidth(), exifThumbnailSize.getHeight());
135         }
136     }
137 
AndroidCamera2Settings(AndroidCamera2Settings other)138     public AndroidCamera2Settings(AndroidCamera2Settings other) {
139         super(other);
140         mTemplateSettings = other.mTemplateSettings;
141         mRequestSettings = new Camera2RequestSettingsSet(other.mRequestSettings);
142         mActiveArray = other.mActiveArray;
143         mCropRectangle = new Rect(other.mCropRectangle);
144     }
145 
146     @Override
copy()147     public CameraSettings copy() {
148         return new AndroidCamera2Settings(this);
149     }
150 
queryTemplateDefaultOrMakeOneUp(Key<T> key, T defaultDefault)151     private <T> T queryTemplateDefaultOrMakeOneUp(Key<T> key, T defaultDefault) {
152         T val = mTemplateSettings.get(key);
153         if (val != null) {
154             return val;
155         } else {
156             // Spoof the default so matchesTemplateDefault excludes this key from generated sets.
157             // This approach beats a simple sentinel because it provides basic boolean support.
158             mTemplateSettings.set(key, defaultDefault);
159             return defaultDefault;
160         }
161     }
162 
flashModeFromRequest()163     private FlashMode flashModeFromRequest() {
164         Integer autoExposure = mTemplateSettings.get(CONTROL_AE_MODE);
165         if (autoExposure != null) {
166             switch (autoExposure) {
167                 case CONTROL_AE_MODE_ON:
168                     return FlashMode.OFF;
169                 case CONTROL_AE_MODE_ON_AUTO_FLASH:
170                     return FlashMode.AUTO;
171                 case CONTROL_AE_MODE_ON_ALWAYS_FLASH: {
172                     if (mTemplateSettings.get(FLASH_MODE) == FLASH_MODE_TORCH) {
173                         return FlashMode.TORCH;
174                     } else {
175                         return FlashMode.ON;
176                     }
177                 }
178                 case CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE:
179                     return FlashMode.RED_EYE;
180             }
181         }
182         return null;
183     }
184 
185     @Override
setZoomRatio(float ratio)186     public void setZoomRatio(float ratio) {
187         super.setZoomRatio(ratio);
188 
189         // Compute the crop rectangle to be passed to the framework
190         mCropRectangle.set(0, 0,
191                 toIntConstrained(
192                         mActiveArray.width() / mCurrentZoomRatio, 0, mActiveArray.width()),
193                 toIntConstrained(
194                         mActiveArray.height() / mCurrentZoomRatio, 0, mActiveArray.height()));
195         mCropRectangle.offsetTo((mActiveArray.width() - mCropRectangle.width()) / 2,
196                 (mActiveArray.height() - mCropRectangle.height()) / 2);
197 
198         // Compute the effective crop rectangle to be used for computing focus/metering coordinates
199         mVisiblePreviewRectangle =
200                 effectiveCropRectFromRequested(mCropRectangle, mCurrentPreviewSize);
201     }
202 
matchesTemplateDefault(Key<?> setting)203     private boolean matchesTemplateDefault(Key<?> setting) {
204         if (setting == CONTROL_AE_REGIONS) {
205             return mMeteringAreas.size() == 0;
206         } else if (setting == CONTROL_AF_REGIONS) {
207             return mFocusAreas.size() == 0;
208         } else if (setting == CONTROL_AE_TARGET_FPS_RANGE) {
209             Range<Integer> defaultFpsRange = mTemplateSettings.get(CONTROL_AE_TARGET_FPS_RANGE);
210             return (mPreviewFpsRangeMin == 0 && mPreviewFpsRangeMax == 0) ||
211                     (defaultFpsRange != null && mPreviewFpsRangeMin == defaultFpsRange.getLower() &&
212                             mPreviewFpsRangeMax == defaultFpsRange.getUpper());
213         } else if (setting == JPEG_QUALITY) {
214             return Objects.equals(mJpegCompressQuality,
215                     mTemplateSettings.get(JPEG_QUALITY));
216         } else if (setting == CONTROL_AE_EXPOSURE_COMPENSATION) {
217             return Objects.equals(mExposureCompensationIndex,
218                     mTemplateSettings.get(CONTROL_AE_EXPOSURE_COMPENSATION));
219         } else if (setting == CONTROL_VIDEO_STABILIZATION_MODE) {
220             Integer videoStabilization = mTemplateSettings.get(CONTROL_VIDEO_STABILIZATION_MODE);
221             return (videoStabilization != null &&
222                     (mVideoStabilizationEnabled && videoStabilization ==
223                             CONTROL_VIDEO_STABILIZATION_MODE_ON) ||
224                     (!mVideoStabilizationEnabled && videoStabilization ==
225                             CONTROL_VIDEO_STABILIZATION_MODE_OFF));
226         } else if (setting == CONTROL_AE_LOCK) {
227             return Objects.equals(mAutoExposureLocked, mTemplateSettings.get(CONTROL_AE_LOCK));
228         } else if (setting == CONTROL_AWB_LOCK) {
229             return Objects.equals(mAutoWhiteBalanceLocked, mTemplateSettings.get(CONTROL_AWB_LOCK));
230         } else if (setting == JPEG_THUMBNAIL_SIZE) {
231             if (mExifThumbnailSize == null) {
232                 // It doesn't matter if this is true or false since setting this
233                 // to null in the request settings will use the default anyway.
234                 return false;
235             }
236             android.util.Size defaultThumbnailSize = mTemplateSettings.get(JPEG_THUMBNAIL_SIZE);
237             return (mExifThumbnailSize.width() == 0 && mExifThumbnailSize.height() == 0) ||
238                     (defaultThumbnailSize != null &&
239                             mExifThumbnailSize.width() == defaultThumbnailSize.getWidth() &&
240                             mExifThumbnailSize.height() == defaultThumbnailSize.getHeight());
241         }
242         Log.w(TAG, "Settings implementation checked default of unhandled option key");
243         // Since this class isn't equipped to handle it, claim it matches the default to prevent
244         // updateRequestSettingOrForceToDefault from going with the user-provided preference
245         return true;
246     }
247 
updateRequestSettingOrForceToDefault(Key<T> setting, T possibleChoice)248     private <T> void updateRequestSettingOrForceToDefault(Key<T> setting, T possibleChoice) {
249         mRequestSettings.set(setting, matchesTemplateDefault(setting) ? null : possibleChoice);
250     }
251 
getRequestSettings()252     public Camera2RequestSettingsSet getRequestSettings() {
253         updateRequestSettingOrForceToDefault(CONTROL_AE_REGIONS,
254                 legacyAreasToMeteringRectangles(mMeteringAreas));
255         updateRequestSettingOrForceToDefault(CONTROL_AF_REGIONS,
256                 legacyAreasToMeteringRectangles(mFocusAreas));
257         updateRequestSettingOrForceToDefault(CONTROL_AE_TARGET_FPS_RANGE,
258                 new Range(mPreviewFpsRangeMin, mPreviewFpsRangeMax));
259         // TODO: mCurrentPreviewFormat
260         updateRequestSettingOrForceToDefault(JPEG_QUALITY, mJpegCompressQuality);
261         // TODO: mCurrentPhotoFormat
262         mRequestSettings.set(SCALER_CROP_REGION, mCropRectangle);
263         // TODO: mCurrentZoomIndex
264         updateRequestSettingOrForceToDefault(CONTROL_AE_EXPOSURE_COMPENSATION,
265                 mExposureCompensationIndex);
266         updateRequestFlashMode();
267         updateRequestFocusMode();
268         updateRequestSceneMode();
269         updateRequestWhiteBalance();
270         updateRequestSettingOrForceToDefault(CONTROL_VIDEO_STABILIZATION_MODE,
271                 mVideoStabilizationEnabled ?
272                         CONTROL_VIDEO_STABILIZATION_MODE_ON : CONTROL_VIDEO_STABILIZATION_MODE_OFF);
273         // OIS shouldn't be on if software video stabilization is.
274         mRequestSettings.set(LENS_OPTICAL_STABILIZATION_MODE,
275                 mVideoStabilizationEnabled ? LENS_OPTICAL_STABILIZATION_MODE_OFF :
276                         null);
277         updateRequestSettingOrForceToDefault(CONTROL_AE_LOCK, mAutoExposureLocked);
278         updateRequestSettingOrForceToDefault(CONTROL_AWB_LOCK, mAutoWhiteBalanceLocked);
279         // TODO: mRecordingHintEnabled
280         updateRequestGpsData();
281         if (mExifThumbnailSize != null) {
282             updateRequestSettingOrForceToDefault(JPEG_THUMBNAIL_SIZE,
283                     new android.util.Size(
284                             mExifThumbnailSize.width(), mExifThumbnailSize.height()));
285         } else {
286             updateRequestSettingOrForceToDefault(JPEG_THUMBNAIL_SIZE, null);
287         }
288 
289         return mRequestSettings;
290     }
291 
legacyAreasToMeteringRectangles( List<android.hardware.Camera.Area> reference)292     private MeteringRectangle[] legacyAreasToMeteringRectangles(
293             List<android.hardware.Camera.Area> reference) {
294         MeteringRectangle[] transformed = null;
295         if (reference.size() > 0) {
296             transformed = new MeteringRectangle[reference.size()];
297             for (int index = 0; index < reference.size(); ++index) {
298                 android.hardware.Camera.Area source = reference.get(index);
299                 Rect rectangle = source.rect;
300 
301                 // Old API coordinates were [-1000,1000]; new ones are [0,ACTIVE_ARRAY_SIZE).
302                 // We're also going from preview image--relative to sensor active array--relative.
303                 double oldLeft = (rectangle.left + 1000) / 2000.0;
304                 double oldTop = (rectangle.top + 1000) / 2000.0;
305                 double oldRight = (rectangle.right + 1000) / 2000.0;
306                 double oldBottom = (rectangle.bottom + 1000) / 2000.0;
307                 int left = mCropRectangle.left + toIntConstrained(
308                         mCropRectangle.width() * oldLeft, 0, mCropRectangle.width() - 1);
309                 int top = mCropRectangle.top + toIntConstrained(
310                         mCropRectangle.height() * oldTop, 0, mCropRectangle.height() - 1);
311                 int right = mCropRectangle.left + toIntConstrained(
312                         mCropRectangle.width() * oldRight, 0, mCropRectangle.width() - 1);
313                 int bottom = mCropRectangle.top + toIntConstrained(
314                         mCropRectangle.height() * oldBottom, 0, mCropRectangle.height() - 1);
315                 transformed[index] = new MeteringRectangle(left, top, right - left, bottom - top,
316                         source.weight);
317             }
318         }
319         return transformed;
320     }
321 
toIntConstrained(double original, int min, int max)322     private int toIntConstrained(double original, int min, int max) {
323         original = Math.max(original, min);
324         original = Math.min(original, max);
325         return (int) original;
326     }
327 
updateRequestFlashMode()328     private void updateRequestFlashMode() {
329         Integer aeMode = null;
330         Integer flashMode = null;
331         if (mCurrentFlashMode != null) {
332             switch (mCurrentFlashMode) {
333                 case AUTO: {
334                     aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH;
335                     break;
336                 }
337                 case OFF: {
338                     aeMode = CONTROL_AE_MODE_ON;
339                     flashMode = FLASH_MODE_OFF;
340                     break;
341                 }
342                 case ON: {
343                     aeMode = CONTROL_AE_MODE_ON_ALWAYS_FLASH;
344                     flashMode = FLASH_MODE_SINGLE;
345                     break;
346                 }
347                 case TORCH: {
348                     flashMode = FLASH_MODE_TORCH;
349                     break;
350                 }
351                 case RED_EYE: {
352                     aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE;
353                     break;
354                 }
355                 default: {
356                     Log.w(TAG, "Unable to convert to API 2 flash mode: " + mCurrentFlashMode);
357                     break;
358                 }
359             }
360         }
361         mRequestSettings.set(CONTROL_AE_MODE, aeMode);
362         mRequestSettings.set(FLASH_MODE, flashMode);
363     }
364 
updateRequestFocusMode()365     private void updateRequestFocusMode() {
366         Integer mode = null;
367         if (mCurrentFocusMode != null) {
368             switch (mCurrentFocusMode) {
369                 case AUTO: {
370                     mode = CONTROL_AF_MODE_AUTO;
371                     break;
372                 }
373                 case CONTINUOUS_PICTURE: {
374                     mode = CONTROL_AF_MODE_CONTINUOUS_PICTURE;
375                     break;
376                 }
377                 case CONTINUOUS_VIDEO: {
378                     mode = CONTROL_AF_MODE_CONTINUOUS_VIDEO;
379                     break;
380                 }
381                 case EXTENDED_DOF: {
382                     mode = CONTROL_AF_MODE_EDOF;
383                     break;
384                 }
385                 case FIXED: {
386                     mode = CONTROL_AF_MODE_OFF;
387                     break;
388                 }
389                 // TODO: We cannot support INFINITY
390                 case MACRO: {
391                     mode = CONTROL_AF_MODE_MACRO;
392                     break;
393                 }
394                 default: {
395                     Log.w(TAG, "Unable to convert to API 2 focus mode: " + mCurrentFocusMode);
396                     break;
397                 }
398             }
399         }
400         mRequestSettings.set(CONTROL_AF_MODE, mode);
401     }
402 
updateRequestSceneMode()403     private void updateRequestSceneMode() {
404         Integer mode = null;
405         if (mCurrentSceneMode != null) {
406             switch (mCurrentSceneMode) {
407                 case AUTO: {
408                     mode = CONTROL_SCENE_MODE_DISABLED;
409                     break;
410                 }
411                 case ACTION: {
412                     mode = CONTROL_SCENE_MODE_ACTION;
413                     break;
414                 }
415                 case BARCODE: {
416                     mode = CONTROL_SCENE_MODE_BARCODE;
417                     break;
418                 }
419                 case BEACH: {
420                     mode = CONTROL_SCENE_MODE_BEACH;
421                     break;
422                 }
423                 case CANDLELIGHT: {
424                     mode = CONTROL_SCENE_MODE_CANDLELIGHT;
425                     break;
426                 }
427                 case FIREWORKS: {
428                     mode = CONTROL_SCENE_MODE_FIREWORKS;
429                     break;
430                 }
431                 case HDR: {
432                     mode = CONTROL_SCENE_MODE_HDR;
433                     break;
434                 }
435                 case LANDSCAPE: {
436                     mode = CONTROL_SCENE_MODE_LANDSCAPE;
437                     break;
438                 }
439                 case NIGHT: {
440                     mode = CONTROL_SCENE_MODE_NIGHT;
441                     break;
442                 }
443                 // TODO: We cannot support NIGHT_PORTRAIT
444                 case PARTY: {
445                     mode = CONTROL_SCENE_MODE_PARTY;
446                     break;
447                 }
448                 case PORTRAIT: {
449                     mode = CONTROL_SCENE_MODE_PORTRAIT;
450                     break;
451                 }
452                 case SNOW: {
453                     mode = CONTROL_SCENE_MODE_SNOW;
454                     break;
455                 }
456                 case SPORTS: {
457                     mode = CONTROL_SCENE_MODE_SPORTS;
458                     break;
459                 }
460                 case STEADYPHOTO: {
461                     mode = CONTROL_SCENE_MODE_STEADYPHOTO;
462                     break;
463                 }
464                 case SUNSET: {
465                     mode = CONTROL_SCENE_MODE_SUNSET;
466                     break;
467                 }
468                 case THEATRE: {
469                     mode = CONTROL_SCENE_MODE_THEATRE;
470                     break;
471                 }
472                 default: {
473                     Log.w(TAG, "Unable to convert to API 2 scene mode: " + mCurrentSceneMode);
474                     break;
475                 }
476             }
477         }
478         mRequestSettings.set(CONTROL_SCENE_MODE, mode);
479     }
480 
updateRequestWhiteBalance()481     private void updateRequestWhiteBalance() {
482         Integer mode = null;
483         if (mWhiteBalance != null) {
484             switch (mWhiteBalance) {
485                 case AUTO: {
486                     mode = CONTROL_AWB_MODE_AUTO;
487                     break;
488                 }
489                 case CLOUDY_DAYLIGHT: {
490                     mode = CONTROL_AWB_MODE_CLOUDY_DAYLIGHT;
491                     break;
492                 }
493                 case DAYLIGHT: {
494                     mode = CONTROL_AWB_MODE_DAYLIGHT;
495                     break;
496                 }
497                 case FLUORESCENT: {
498                     mode = CONTROL_AWB_MODE_FLUORESCENT;
499                     break;
500                 }
501                 case INCANDESCENT: {
502                     mode = CONTROL_AWB_MODE_INCANDESCENT;
503                     break;
504                 }
505                 case SHADE: {
506                     mode = CONTROL_AWB_MODE_SHADE;
507                     break;
508                 }
509                 case TWILIGHT: {
510                     mode = CONTROL_AWB_MODE_TWILIGHT;
511                     break;
512                 }
513                 case WARM_FLUORESCENT: {
514                     mode = CONTROL_AWB_MODE_WARM_FLUORESCENT;
515                     break;
516                 }
517                 default: {
518                     Log.w(TAG, "Unable to convert to API 2 white balance: " + mWhiteBalance);
519                     break;
520                 }
521             }
522         }
523         mRequestSettings.set(CONTROL_AWB_MODE, mode);
524     }
525 
updateRequestGpsData()526     private void updateRequestGpsData() {
527         if (mGpsData == null || mGpsData.processingMethod == null) {
528             // It's a hack since we always use GPS time stamp but does
529             // not use other fields sometimes. Setting processing
530             // method to null means the other fields should not be used.
531             mRequestSettings.set(JPEG_GPS_LOCATION, null);
532         } else {
533             Location location = new Location(mGpsData.processingMethod);
534             location.setTime(mGpsData.timeStamp);
535             location.setAltitude(mGpsData.altitude);
536             location.setLatitude(mGpsData.latitude);
537             location.setLongitude(mGpsData.longitude);
538             mRequestSettings.set(JPEG_GPS_LOCATION, location);
539         }
540     }
541 
542     /**
543      * Calculate the effective crop rectangle for this preview viewport;
544      * assumes the preview is centered to the sensor and scaled to fit across one of the dimensions
545      * without skewing.
546      *
547      * <p>Assumes the zoom level of the provided desired crop rectangle.</p>
548      *
549      * @param requestedCrop Desired crop rectangle, in active array space.
550      * @param previewSize Size of the preview buffer render target, in pixels (not in sensor space).
551      * @return A rectangle that serves as the preview stream's effective crop region (unzoomed), in
552      *          sensor space.
553      *
554      * @throws NullPointerException
555      *          If any of the args were {@code null}.
556      */
effectiveCropRectFromRequested(Rect requestedCrop, Size previewSize)557     private static Rect effectiveCropRectFromRequested(Rect requestedCrop, Size previewSize) {
558         float aspectRatioArray = requestedCrop.width() * 1.0f / requestedCrop.height();
559         float aspectRatioPreview = previewSize.width() * 1.0f / previewSize.height();
560 
561         float cropHeight, cropWidth;
562         if (aspectRatioPreview < aspectRatioArray) {
563             // The new width must be smaller than the height, so scale the width by AR
564             cropHeight = requestedCrop.height();
565             cropWidth = cropHeight * aspectRatioPreview;
566         } else {
567             // The new height must be smaller (or equal) than the width, so scale the height by AR
568             cropWidth = requestedCrop.width();
569             cropHeight = cropWidth / aspectRatioPreview;
570         }
571 
572         Matrix translateMatrix = new Matrix();
573         RectF cropRect = new RectF(/*left*/0, /*top*/0, cropWidth, cropHeight);
574 
575         // Now center the crop rectangle so its center is in the center of the active array
576         translateMatrix.setTranslate(requestedCrop.exactCenterX(), requestedCrop.exactCenterY());
577         translateMatrix.postTranslate(-cropRect.centerX(), -cropRect.centerY());
578 
579         translateMatrix.mapRect(/*inout*/cropRect);
580 
581         // Round the rect corners towards the nearest integer values
582         Rect result = new Rect();
583         cropRect.roundOut(result);
584         return result;
585     }
586 }
587