Home | History | Annotate | Download | only in portability
      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      */
     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 
    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
    147     public CameraSettings copy() {
    148         return new AndroidCamera2Settings(this);
    149     }
    150 
    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 
    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
    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 
    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             android.util.Size defaultThumbnailSize = mTemplateSettings.get(JPEG_THUMBNAIL_SIZE);
    232             return (mExifThumbnailSize.width() == 0 && mExifThumbnailSize.height() == 0) ||
    233                     (defaultThumbnailSize != null &&
    234                             mExifThumbnailSize.width() == defaultThumbnailSize.getWidth() &&
    235                             mExifThumbnailSize.height() == defaultThumbnailSize.getHeight());
    236         }
    237         Log.w(TAG, "Settings implementation checked default of unhandled option key");
    238         // Since this class isn't equipped to handle it, claim it matches the default to prevent
    239         // updateRequestSettingOrForceToDefault from going with the user-provided preference
    240         return true;
    241     }
    242 
    243     private <T> void updateRequestSettingOrForceToDefault(Key<T> setting, T possibleChoice) {
    244         mRequestSettings.set(setting, matchesTemplateDefault(setting) ? null : possibleChoice);
    245     }
    246 
    247     public Camera2RequestSettingsSet getRequestSettings() {
    248         updateRequestSettingOrForceToDefault(CONTROL_AE_REGIONS,
    249                 legacyAreasToMeteringRectangles(mMeteringAreas));
    250         updateRequestSettingOrForceToDefault(CONTROL_AF_REGIONS,
    251                 legacyAreasToMeteringRectangles(mFocusAreas));
    252         updateRequestSettingOrForceToDefault(CONTROL_AE_TARGET_FPS_RANGE,
    253                 new Range(mPreviewFpsRangeMin, mPreviewFpsRangeMax));
    254         // TODO: mCurrentPreviewFormat
    255         updateRequestSettingOrForceToDefault(JPEG_QUALITY, mJpegCompressQuality);
    256         // TODO: mCurrentPhotoFormat
    257         mRequestSettings.set(SCALER_CROP_REGION, mCropRectangle);
    258         // TODO: mCurrentZoomIndex
    259         updateRequestSettingOrForceToDefault(CONTROL_AE_EXPOSURE_COMPENSATION,
    260                 mExposureCompensationIndex);
    261         updateRequestFlashMode();
    262         updateRequestFocusMode();
    263         updateRequestSceneMode();
    264         updateRequestWhiteBalance();
    265         updateRequestSettingOrForceToDefault(CONTROL_VIDEO_STABILIZATION_MODE,
    266                 mVideoStabilizationEnabled ?
    267                         CONTROL_VIDEO_STABILIZATION_MODE_ON : CONTROL_VIDEO_STABILIZATION_MODE_OFF);
    268         // OIS shouldn't be on if software video stabilization is.
    269         mRequestSettings.set(LENS_OPTICAL_STABILIZATION_MODE,
    270                 mVideoStabilizationEnabled ? LENS_OPTICAL_STABILIZATION_MODE_OFF :
    271                         null);
    272         updateRequestSettingOrForceToDefault(CONTROL_AE_LOCK, mAutoExposureLocked);
    273         updateRequestSettingOrForceToDefault(CONTROL_AWB_LOCK, mAutoWhiteBalanceLocked);
    274         // TODO: mRecordingHintEnabled
    275         updateRequestGpsData();
    276         updateRequestSettingOrForceToDefault(JPEG_THUMBNAIL_SIZE,
    277                 new android.util.Size(
    278                         mExifThumbnailSize.width(), mExifThumbnailSize.height()));
    279 
    280         return mRequestSettings;
    281     }
    282 
    283     private MeteringRectangle[] legacyAreasToMeteringRectangles(
    284             List<android.hardware.Camera.Area> reference) {
    285         MeteringRectangle[] transformed = null;
    286         if (reference.size() > 0) {
    287             transformed = new MeteringRectangle[reference.size()];
    288             for (int index = 0; index < reference.size(); ++index) {
    289                 android.hardware.Camera.Area source = reference.get(index);
    290                 Rect rectangle = source.rect;
    291 
    292                 // Old API coordinates were [-1000,1000]; new ones are [0,ACTIVE_ARRAY_SIZE).
    293                 // We're also going from preview image--relative to sensor active array--relative.
    294                 double oldLeft = (rectangle.left + 1000) / 2000.0;
    295                 double oldTop = (rectangle.top + 1000) / 2000.0;
    296                 double oldRight = (rectangle.right + 1000) / 2000.0;
    297                 double oldBottom = (rectangle.bottom + 1000) / 2000.0;
    298                 int left = mCropRectangle.left + toIntConstrained(
    299                         mCropRectangle.width() * oldLeft, 0, mCropRectangle.width() - 1);
    300                 int top = mCropRectangle.top + toIntConstrained(
    301                         mCropRectangle.height() * oldTop, 0, mCropRectangle.height() - 1);
    302                 int right = mCropRectangle.left + toIntConstrained(
    303                         mCropRectangle.width() * oldRight, 0, mCropRectangle.width() - 1);
    304                 int bottom = mCropRectangle.top + toIntConstrained(
    305                         mCropRectangle.height() * oldBottom, 0, mCropRectangle.height() - 1);
    306                 transformed[index] = new MeteringRectangle(left, top, right - left, bottom - top,
    307                         source.weight);
    308             }
    309         }
    310         return transformed;
    311     }
    312 
    313     private int toIntConstrained(double original, int min, int max) {
    314         original = Math.max(original, min);
    315         original = Math.min(original, max);
    316         return (int) original;
    317     }
    318 
    319     private void updateRequestFlashMode() {
    320         Integer aeMode = null;
    321         Integer flashMode = null;
    322         if (mCurrentFlashMode != null) {
    323             switch (mCurrentFlashMode) {
    324                 case AUTO: {
    325                     aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH;
    326                     break;
    327                 }
    328                 case OFF: {
    329                     aeMode = CONTROL_AE_MODE_ON;
    330                     flashMode = FLASH_MODE_OFF;
    331                     break;
    332                 }
    333                 case ON: {
    334                     aeMode = CONTROL_AE_MODE_ON_ALWAYS_FLASH;
    335                     flashMode = FLASH_MODE_SINGLE;
    336                     break;
    337                 }
    338                 case TORCH: {
    339                     flashMode = FLASH_MODE_TORCH;
    340                     break;
    341                 }
    342                 case RED_EYE: {
    343                     aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE;
    344                     break;
    345                 }
    346                 default: {
    347                     Log.w(TAG, "Unable to convert to API 2 flash mode: " + mCurrentFlashMode);
    348                     break;
    349                 }
    350             }
    351         }
    352         mRequestSettings.set(CONTROL_AE_MODE, aeMode);
    353         mRequestSettings.set(FLASH_MODE, flashMode);
    354     }
    355 
    356     private void updateRequestFocusMode() {
    357         Integer mode = null;
    358         if (mCurrentFocusMode != null) {
    359             switch (mCurrentFocusMode) {
    360                 case AUTO: {
    361                     mode = CONTROL_AF_MODE_AUTO;
    362                     break;
    363                 }
    364                 case CONTINUOUS_PICTURE: {
    365                     mode = CONTROL_AF_MODE_CONTINUOUS_PICTURE;
    366                     break;
    367                 }
    368                 case CONTINUOUS_VIDEO: {
    369                     mode = CONTROL_AF_MODE_CONTINUOUS_VIDEO;
    370                     break;
    371                 }
    372                 case EXTENDED_DOF: {
    373                     mode = CONTROL_AF_MODE_EDOF;
    374                     break;
    375                 }
    376                 case FIXED: {
    377                     mode = CONTROL_AF_MODE_OFF;
    378                     break;
    379                 }
    380                 // TODO: We cannot support INFINITY
    381                 case MACRO: {
    382                     mode = CONTROL_AF_MODE_MACRO;
    383                     break;
    384                 }
    385                 default: {
    386                     Log.w(TAG, "Unable to convert to API 2 focus mode: " + mCurrentFocusMode);
    387                     break;
    388                 }
    389             }
    390         }
    391         mRequestSettings.set(CONTROL_AF_MODE, mode);
    392     }
    393 
    394     private void updateRequestSceneMode() {
    395         Integer mode = null;
    396         if (mCurrentSceneMode != null) {
    397             switch (mCurrentSceneMode) {
    398                 case AUTO: {
    399                     mode = CONTROL_SCENE_MODE_DISABLED;
    400                     break;
    401                 }
    402                 case ACTION: {
    403                     mode = CONTROL_SCENE_MODE_ACTION;
    404                     break;
    405                 }
    406                 case BARCODE: {
    407                     mode = CONTROL_SCENE_MODE_BARCODE;
    408                     break;
    409                 }
    410                 case BEACH: {
    411                     mode = CONTROL_SCENE_MODE_BEACH;
    412                     break;
    413                 }
    414                 case CANDLELIGHT: {
    415                     mode = CONTROL_SCENE_MODE_CANDLELIGHT;
    416                     break;
    417                 }
    418                 case FIREWORKS: {
    419                     mode = CONTROL_SCENE_MODE_FIREWORKS;
    420                     break;
    421                 }
    422                 case HDR: {
    423                     mode = LegacyVendorTags.CONTROL_SCENE_MODE_HDR;
    424                     break;
    425                 }
    426                 case LANDSCAPE: {
    427                     mode = CONTROL_SCENE_MODE_LANDSCAPE;
    428                     break;
    429                 }
    430                 case NIGHT: {
    431                     mode = CONTROL_SCENE_MODE_NIGHT;
    432                     break;
    433                 }
    434                 // TODO: We cannot support NIGHT_PORTRAIT
    435                 case PARTY: {
    436                     mode = CONTROL_SCENE_MODE_PARTY;
    437                     break;
    438                 }
    439                 case PORTRAIT: {
    440                     mode = CONTROL_SCENE_MODE_PORTRAIT;
    441                     break;
    442                 }
    443                 case SNOW: {
    444                     mode = CONTROL_SCENE_MODE_SNOW;
    445                     break;
    446                 }
    447                 case SPORTS: {
    448                     mode = CONTROL_SCENE_MODE_SPORTS;
    449                     break;
    450                 }
    451                 case STEADYPHOTO: {
    452                     mode = CONTROL_SCENE_MODE_STEADYPHOTO;
    453                     break;
    454                 }
    455                 case SUNSET: {
    456                     mode = CONTROL_SCENE_MODE_SUNSET;
    457                     break;
    458                 }
    459                 case THEATRE: {
    460                     mode = CONTROL_SCENE_MODE_THEATRE;
    461                     break;
    462                 }
    463                 default: {
    464                     Log.w(TAG, "Unable to convert to API 2 scene mode: " + mCurrentSceneMode);
    465                     break;
    466                 }
    467             }
    468         }
    469         mRequestSettings.set(CONTROL_SCENE_MODE, mode);
    470     }
    471 
    472     private void updateRequestWhiteBalance() {
    473         Integer mode = null;
    474         if (mWhiteBalance != null) {
    475             switch (mWhiteBalance) {
    476                 case AUTO: {
    477                     mode = CONTROL_AWB_MODE_AUTO;
    478                     break;
    479                 }
    480                 case CLOUDY_DAYLIGHT: {
    481                     mode = CONTROL_AWB_MODE_CLOUDY_DAYLIGHT;
    482                     break;
    483                 }
    484                 case DAYLIGHT: {
    485                     mode = CONTROL_AWB_MODE_DAYLIGHT;
    486                     break;
    487                 }
    488                 case FLUORESCENT: {
    489                     mode = CONTROL_AWB_MODE_FLUORESCENT;
    490                     break;
    491                 }
    492                 case INCANDESCENT: {
    493                     mode = CONTROL_AWB_MODE_INCANDESCENT;
    494                     break;
    495                 }
    496                 case SHADE: {
    497                     mode = CONTROL_AWB_MODE_SHADE;
    498                     break;
    499                 }
    500                 case TWILIGHT: {
    501                     mode = CONTROL_AWB_MODE_TWILIGHT;
    502                     break;
    503                 }
    504                 case WARM_FLUORESCENT: {
    505                     mode = CONTROL_AWB_MODE_WARM_FLUORESCENT;
    506                     break;
    507                 }
    508                 default: {
    509                     Log.w(TAG, "Unable to convert to API 2 white balance: " + mWhiteBalance);
    510                     break;
    511                 }
    512             }
    513         }
    514         mRequestSettings.set(CONTROL_AWB_MODE, mode);
    515     }
    516 
    517     private void updateRequestGpsData() {
    518         if (mGpsData == null || mGpsData.processingMethod == null) {
    519             // It's a hack since we always use GPS time stamp but does
    520             // not use other fields sometimes. Setting processing
    521             // method to null means the other fields should not be used.
    522             mRequestSettings.set(JPEG_GPS_LOCATION, null);
    523         } else {
    524             Location location = new Location(mGpsData.processingMethod);
    525             location.setTime(mGpsData.timeStamp);
    526             location.setAltitude(mGpsData.altitude);
    527             location.setLatitude(mGpsData.latitude);
    528             location.setLongitude(mGpsData.longitude);
    529             mRequestSettings.set(JPEG_GPS_LOCATION, location);
    530         }
    531     }
    532 
    533     /**
    534      * Calculate the effective crop rectangle for this preview viewport;
    535      * assumes the preview is centered to the sensor and scaled to fit across one of the dimensions
    536      * without skewing.
    537      *
    538      * <p>Assumes the zoom level of the provided desired crop rectangle.</p>
    539      *
    540      * @param requestedCrop Desired crop rectangle, in active array space.
    541      * @param previewSize Size of the preview buffer render target, in pixels (not in sensor space).
    542      * @return A rectangle that serves as the preview stream's effective crop region (unzoomed), in
    543      *          sensor space.
    544      *
    545      * @throws NullPointerException
    546      *          If any of the args were {@code null}.
    547      */
    548     private static Rect effectiveCropRectFromRequested(Rect requestedCrop, Size previewSize) {
    549         float aspectRatioArray = requestedCrop.width() * 1.0f / requestedCrop.height();
    550         float aspectRatioPreview = previewSize.width() * 1.0f / previewSize.height();
    551 
    552         float cropHeight, cropWidth;
    553         if (aspectRatioPreview < aspectRatioArray) {
    554             // The new width must be smaller than the height, so scale the width by AR
    555             cropHeight = requestedCrop.height();
    556             cropWidth = cropHeight * aspectRatioPreview;
    557         } else {
    558             // The new height must be smaller (or equal) than the width, so scale the height by AR
    559             cropWidth = requestedCrop.width();
    560             cropHeight = cropWidth / aspectRatioPreview;
    561         }
    562 
    563         Matrix translateMatrix = new Matrix();
    564         RectF cropRect = new RectF(/*left*/0, /*top*/0, cropWidth, cropHeight);
    565 
    566         // Now center the crop rectangle so its center is in the center of the active array
    567         translateMatrix.setTranslate(requestedCrop.exactCenterX(), requestedCrop.exactCenterY());
    568         translateMatrix.postTranslate(-cropRect.centerX(), -cropRect.centerY());
    569 
    570         translateMatrix.mapRect(/*inout*/cropRect);
    571 
    572         // Round the rect corners towards the nearest integer values
    573         Rect result = new Rect();
    574         cropRect.roundOut(result);
    575         return result;
    576     }
    577 }
    578