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             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 
    248     private <T> void updateRequestSettingOrForceToDefault(Key<T> setting, T possibleChoice) {
    249         mRequestSettings.set(setting, matchesTemplateDefault(setting) ? null : possibleChoice);
    250     }
    251 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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      */
    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