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.os.Handler;
     25 import android.provider.Settings;
     26 import android.view.OrientationEventListener;
     27 import android.view.Surface;
     28 
     29 import com.android.camera.debug.Log;
     30 import com.android.camera.util.ApiHelper;
     31 
     32 import java.util.ArrayList;
     33 import java.util.List;
     34 
     35 /**
     36  * The implementation of {@link com.android.camera.app.OrientationManager}
     37  * by {@link android.view.OrientationEventListener}.
     38  * TODO: make this class package-private
     39  */
     40 public class OrientationManagerImpl implements OrientationManager {
     41     private static final Log.Tag TAG = new Log.Tag("OrientMgrImpl");
     42 
     43     // Orientation hysteresis amount used in rounding, in degrees
     44     private static final int ORIENTATION_HYSTERESIS = 5;
     45 
     46     private final Activity mActivity;
     47     private final MyOrientationEventListener mOrientationListener;
     48     // If the framework orientation is locked.
     49     private boolean mOrientationLocked = false;
     50 
     51     // This is true if "Settings -> Display -> Rotation Lock" is checked. We
     52     // don't allow the orientation to be unlocked if the value is true.
     53     private boolean mRotationLockedSetting = false;
     54 
     55     private final List<OrientationChangeCallback> mListeners =
     56             new ArrayList<OrientationChangeCallback>();
     57 
     58     private static class OrientationChangeCallback {
     59         private final Handler mHandler;
     60         private final OnOrientationChangeListener mListener;
     61 
     62         OrientationChangeCallback(Handler handler, OnOrientationChangeListener listener) {
     63             mHandler = handler;
     64             mListener = listener;
     65         }
     66 
     67         public void postOrientationChangeCallback(final int orientation) {
     68             mHandler.post(new Runnable() {
     69                 @Override
     70                 public void run() {
     71                     mListener.onOrientationChanged(orientation);
     72                 }
     73             });
     74         }
     75 
     76         @Override
     77         public boolean equals(Object o) {
     78             if (o != null && o instanceof OrientationChangeCallback) {
     79                 OrientationChangeCallback c = (OrientationChangeCallback) o;
     80                 if (mHandler == c.mHandler && mListener == c.mListener) {
     81                     return true;
     82                 }
     83                 return false;
     84             }
     85             return false;
     86         }
     87     }
     88 
     89     public OrientationManagerImpl(Activity activity) {
     90         mActivity = activity;
     91         mOrientationListener = new MyOrientationEventListener(activity);
     92     }
     93 
     94     public void resume() {
     95         ContentResolver resolver = mActivity.getContentResolver();
     96         mRotationLockedSetting = Settings.System.getInt(
     97                 resolver, Settings.System.ACCELEROMETER_ROTATION, 0) != 1;
     98         mOrientationListener.enable();
     99     }
    100 
    101     public void pause() {
    102         mOrientationListener.disable();
    103     }
    104 
    105     ////////////////////////////////////////////////////////////////////////////
    106     //  Orientation handling
    107     //
    108     //  We can choose to lock the framework orientation or not. If we lock the
    109     //  framework orientation, we calculate a a compensation value according to
    110     //  current device orientation and send it to listeners. If we don't lock
    111     //  the framework orientation, we always set the compensation value to 0.
    112     ////////////////////////////////////////////////////////////////////////////
    113 
    114     @Override
    115     public void addOnOrientationChangeListener(Handler handler,
    116             OnOrientationChangeListener listener) {
    117         OrientationChangeCallback callback = new OrientationChangeCallback(handler, listener);
    118         if (mListeners.contains(callback)) {
    119             return;
    120         }
    121         mListeners.add(callback);
    122     }
    123 
    124     @Override
    125     public void removeOnOrientationChangeListener(Handler handler,
    126             OnOrientationChangeListener listener) {
    127         OrientationChangeCallback callback = new OrientationChangeCallback(handler, listener);
    128         if (!mListeners.remove(callback)) {
    129             Log.v(TAG, "Removing non-existing listener.");
    130         }
    131     }
    132 
    133     @Override
    134     public void lockOrientation() {
    135         if (mOrientationLocked || mRotationLockedSetting) {
    136             return;
    137         }
    138         mOrientationLocked = true;
    139         if (ApiHelper.HAS_ORIENTATION_LOCK) {
    140             mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
    141         } else {
    142             mActivity.setRequestedOrientation(calculateCurrentScreenOrientation());
    143         }
    144     }
    145 
    146     @Override
    147     public void unlockOrientation() {
    148         if (!mOrientationLocked || mRotationLockedSetting) {
    149             return;
    150         }
    151         mOrientationLocked = false;
    152         Log.d(TAG, "unlock orientation");
    153         mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
    154     }
    155 
    156     @Override
    157     public boolean isOrientationLocked() {
    158         return (mOrientationLocked || mRotationLockedSetting);
    159     }
    160 
    161     private int calculateCurrentScreenOrientation() {
    162         int displayRotation = getDisplayRotation();
    163         // Display rotation >= 180 means we need to use the REVERSE landscape/portrait
    164         boolean standard = displayRotation < 180;
    165         if (mActivity.getResources().getConfiguration().orientation
    166                 == Configuration.ORIENTATION_LANDSCAPE) {
    167             return standard
    168                     ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
    169                     : ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
    170         } else {
    171             if (displayRotation == 90 || displayRotation == 270) {
    172                 // If displayRotation = 90 or 270 then we are on a landscape
    173                 // device. On landscape devices, portrait is a 90 degree
    174                 // clockwise rotation from landscape, so we need
    175                 // to flip which portrait we pick as display rotation is counter clockwise
    176                 standard = !standard;
    177             }
    178             return standard
    179                     ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
    180                     : ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
    181         }
    182     }
    183 
    184     // This listens to the device orientation, so we can update the compensation.
    185     private class MyOrientationEventListener extends OrientationEventListener {
    186         public MyOrientationEventListener(Context context) {
    187             super(context);
    188         }
    189 
    190         @Override
    191         public void onOrientationChanged(int orientation) {
    192             // We keep the last known orientation. So if the user first orient
    193             // the camera then point the camera to floor or sky, we still have
    194             // the correct orientation.
    195             if (orientation == ORIENTATION_UNKNOWN) {
    196                 return;
    197             }
    198             // TODO: We have two copies of the rounding method: one is CameraUtil.roundOrientation
    199             // and the other is OrientationManagerImpl.roundOrientation. The same computation is
    200             // done twice when orientation is changed. We should remove the duplicate. b/17440795
    201             final int roundedOrientation = roundOrientation(orientation, 0);
    202             for (OrientationChangeCallback l : mListeners) {
    203                 l.postOrientationChangeCallback(roundedOrientation);
    204             }
    205         }
    206     }
    207 
    208     @Override
    209     public int getDisplayRotation() {
    210         return getDisplayRotation(mActivity);
    211     }
    212 
    213     private static int roundOrientation(int orientation, int orientationHistory) {
    214         boolean changeOrientation = false;
    215         if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) {
    216             changeOrientation = true;
    217         } else {
    218             int dist = Math.abs(orientation - orientationHistory);
    219             dist = Math.min(dist, 360 - dist);
    220             changeOrientation = (dist >= 45 + ORIENTATION_HYSTERESIS);
    221         }
    222         if (changeOrientation) {
    223             return ((orientation + 45) / 90 * 90) % 360;
    224         }
    225         return orientationHistory;
    226     }
    227 
    228     private static int getDisplayRotation(Activity activity) {
    229         int rotation = activity.getWindowManager().getDefaultDisplay()
    230                 .getRotation();
    231         switch (rotation) {
    232             case Surface.ROTATION_0: return 0;
    233             case Surface.ROTATION_90: return 90;
    234             case Surface.ROTATION_180: return 180;
    235             case Surface.ROTATION_270: return 270;
    236         }
    237         return 0;
    238     }
    239 }
    240