Home | History | Annotate | Download | only in app
      1 /*
      2  * Copyright (C) 2013 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.app;
     18 
     19 import android.app.Activity;
     20 import android.content.ContentResolver;
     21 import android.content.Context;
     22 import android.content.pm.ActivityInfo;
     23 import android.content.res.Configuration;
     24 import android.graphics.Point;
     25 import android.os.Handler;
     26 import android.provider.Settings;
     27 import android.view.Display;
     28 import android.view.OrientationEventListener;
     29 import android.view.Surface;
     30 import android.view.WindowManager;
     31 
     32 import com.android.camera.debug.Log;
     33 import com.android.camera.util.AndroidServices;
     34 import com.android.camera.util.ApiHelper;
     35 import com.android.camera.util.CameraUtil;
     36 
     37 import java.util.ArrayList;
     38 import java.util.List;
     39 
     40 /**
     41  * The implementation of {@link com.android.camera.app.OrientationManager}
     42  * by {@link android.view.OrientationEventListener}.
     43  */
     44 public class OrientationManagerImpl implements OrientationManager {
     45     private static final Log.Tag TAG = new Log.Tag("OrientMgrImpl");
     46 
     47     // DeviceOrientation hysteresis amount used in rounding, in degrees
     48     private static final int ORIENTATION_HYSTERESIS = 5;
     49 
     50     private final Activity mActivity;
     51 
     52     // The handler used to invoke listener callback.
     53     private final Handler mHandler;
     54 
     55     private final MyOrientationEventListener mOrientationListener;
     56 
     57     // We keep the last known orientation. So if the user first orient
     58     // the camera then point the camera to floor or sky, we still have
     59     // the correct orientation.
     60     private DeviceOrientation mLastDeviceOrientation = DeviceOrientation.CLOCKWISE_0;
     61 
     62     // If the framework orientation is locked.
     63     private boolean mOrientationLocked = false;
     64 
     65     // This is true if "Settings -> Display -> Rotation Lock" is checked. We
     66     // don't allow the orientation to be unlocked if the value is true.
     67     private boolean mRotationLockedSetting = false;
     68 
     69     private final List<OnOrientationChangeListener> mListeners =
     70             new ArrayList<OnOrientationChangeListener>();
     71 
     72     private final boolean mIsDefaultToPortrait;
     73 
     74     /**
     75      * Instantiates a new orientation manager.
     76      *
     77      * @param activity The main activity object.
     78      * @param handler The handler used to invoke listener callback.
     79      */
     80     public OrientationManagerImpl(Activity activity, Handler handler) {
     81         mActivity = activity;
     82         mOrientationListener = new MyOrientationEventListener(activity);
     83         mHandler = handler;
     84         mIsDefaultToPortrait = isDefaultToPortrait(activity);
     85     }
     86 
     87     public void resume() {
     88         ContentResolver resolver = mActivity.getContentResolver();
     89         mRotationLockedSetting = Settings.System.getInt(
     90                 resolver, Settings.System.ACCELEROMETER_ROTATION, 0) != 1;
     91         mOrientationListener.enable();
     92     }
     93 
     94     public void pause() {
     95         mOrientationListener.disable();
     96     }
     97 
     98     @Override
     99     public DeviceNaturalOrientation getDeviceNaturalOrientation() {
    100         return mIsDefaultToPortrait ? DeviceNaturalOrientation.PORTRAIT :
    101                 DeviceNaturalOrientation.LANDSCAPE;
    102     }
    103 
    104     @Override
    105     public DeviceOrientation getDeviceOrientation() {
    106         return mLastDeviceOrientation;
    107     }
    108 
    109     @Override
    110     public DeviceOrientation getDisplayRotation() {
    111         return DeviceOrientation.from((360 - CameraUtil.getDisplayRotation()) % 360);
    112     }
    113 
    114     @Override
    115     public void addOnOrientationChangeListener(OnOrientationChangeListener listener) {
    116         if (mListeners.contains(listener)) {
    117             return;
    118         }
    119         mListeners.add(listener);
    120     }
    121 
    122     @Override
    123     public void removeOnOrientationChangeListener(OnOrientationChangeListener listener) {
    124         if (!mListeners.remove(listener)) {
    125             Log.v(TAG, "Removing non-existing listener.");
    126         }
    127     }
    128 
    129     @Override
    130     public boolean isInLandscape() {
    131         int roundedOrientationDegrees = mLastDeviceOrientation.getDegrees();
    132         if (mIsDefaultToPortrait) {
    133             if (roundedOrientationDegrees % 180 == 90) {
    134                 return true;
    135             }
    136         } else {
    137             if (roundedOrientationDegrees % 180 == 0) {
    138                 return true;
    139             }
    140         }
    141         return false;
    142     }
    143 
    144     @Override
    145     public boolean isInPortrait() {
    146         return !isInLandscape();
    147     }
    148 
    149     @Override
    150     public void lockOrientation() {
    151         if (mOrientationLocked || mRotationLockedSetting) {
    152             return;
    153         }
    154         mOrientationLocked = true;
    155         if (ApiHelper.HAS_ORIENTATION_LOCK) {
    156             mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
    157         } else {
    158             mActivity.setRequestedOrientation(calculateCurrentScreenOrientation());
    159         }
    160     }
    161 
    162     @Override
    163     public void unlockOrientation() {
    164         if (!mOrientationLocked || mRotationLockedSetting) {
    165             return;
    166         }
    167         mOrientationLocked = false;
    168         Log.d(TAG, "unlock orientation");
    169         mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
    170     }
    171 
    172     @Override
    173     public boolean isOrientationLocked() {
    174         return (mOrientationLocked || mRotationLockedSetting);
    175     }
    176 
    177     private int calculateCurrentScreenOrientation() {
    178         int displayRotation = getDisplayRotation(mActivity);
    179         // Display rotation >= 180 means we need to use the REVERSE landscape/portrait
    180         boolean standard = displayRotation < 180;
    181         if (mActivity.getResources().getConfiguration().orientation
    182                 == Configuration.ORIENTATION_LANDSCAPE) {
    183             return standard
    184                     ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
    185                     : ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
    186         } else {
    187             if (displayRotation == 90 || displayRotation == 270) {
    188                 // If displayRotation = 90 or 270 then we are on a landscape
    189                 // device. On landscape devices, portrait is a 90 degree
    190                 // clockwise rotation from landscape, so we need
    191                 // to flip which portrait we pick as display rotation is counter clockwise
    192                 standard = !standard;
    193             }
    194             return standard
    195                     ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
    196                     : ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
    197         }
    198     }
    199 
    200     // This listens to the device orientation, so we can update the compensation.
    201     private class MyOrientationEventListener extends OrientationEventListener {
    202         public MyOrientationEventListener(Context context) {
    203             super(context);
    204         }
    205 
    206         @Override
    207         public void onOrientationChanged(int orientation) {
    208             if (orientation == ORIENTATION_UNKNOWN) {
    209                 return;
    210             }
    211 
    212             final DeviceOrientation roundedDeviceOrientation =
    213                     roundOrientation(mLastDeviceOrientation, orientation);
    214             if (roundedDeviceOrientation == mLastDeviceOrientation) {
    215                 return;
    216             }
    217             Log.v(TAG, "orientation changed (from:to) " + mLastDeviceOrientation +
    218                     ":" + roundedDeviceOrientation);
    219             mLastDeviceOrientation = roundedDeviceOrientation;
    220 
    221             for (final OnOrientationChangeListener listener : mListeners) {
    222                 mHandler.post(new Runnable() {
    223                     @Override
    224                     public void run() {
    225                         listener.onOrientationChanged(OrientationManagerImpl.this, roundedDeviceOrientation);
    226                     }
    227                 });
    228             }
    229         }
    230     }
    231 
    232     private static DeviceOrientation roundOrientation(DeviceOrientation oldDeviceOrientation,
    233                                                       int newRawOrientation) {
    234         int dist = Math.abs(newRawOrientation - oldDeviceOrientation.getDegrees());
    235         dist = Math.min(dist, 360 - dist);
    236         boolean isOrientationChanged = (dist >= 45 + ORIENTATION_HYSTERESIS);
    237 
    238         if (isOrientationChanged) {
    239             int newRoundedOrientation = ((newRawOrientation + 45) / 90 * 90) % 360;
    240             switch (newRoundedOrientation) {
    241                 case 0:
    242                     return DeviceOrientation.CLOCKWISE_0;
    243                 case 90:
    244                     return DeviceOrientation.CLOCKWISE_90;
    245                 case 180:
    246                     return DeviceOrientation.CLOCKWISE_180;
    247                 case 270:
    248                     return DeviceOrientation.CLOCKWISE_270;
    249             }
    250         }
    251         return oldDeviceOrientation;
    252     }
    253 
    254     private static int getDisplayRotation(Activity activity) {
    255         int rotation = activity.getWindowManager().getDefaultDisplay()
    256                 .getRotation();
    257         switch (rotation) {
    258             case Surface.ROTATION_0: return 0;
    259             case Surface.ROTATION_90: return 90;
    260             case Surface.ROTATION_180: return 180;
    261             case Surface.ROTATION_270: return 270;
    262         }
    263         return 0;
    264     }
    265 
    266     /**
    267      * Calculate the default orientation of the device based on the width and
    268      * height of the display when rotation = 0 (i.e. natural width and height)
    269      *
    270      * @param context current context
    271      * @return whether the default orientation of the device is portrait
    272      */
    273     private static boolean isDefaultToPortrait(Context context) {
    274         Display currentDisplay = AndroidServices.instance().provideWindowManager()
    275                 .getDefaultDisplay();
    276         Point displaySize = new Point();
    277         currentDisplay.getSize(displaySize);
    278         int orientation = currentDisplay.getRotation();
    279         int naturalWidth, naturalHeight;
    280         if (orientation == Surface.ROTATION_0 || orientation == Surface.ROTATION_180) {
    281             naturalWidth = displaySize.x;
    282             naturalHeight = displaySize.y;
    283         } else {
    284             naturalWidth = displaySize.y;
    285             naturalHeight = displaySize.x;
    286         }
    287         return naturalWidth < naturalHeight;
    288     }
    289 }
    290