OrientationManagerImpl.java revision 5850115399516c9186cc0a8231eb1b554f570441
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 17package com.android.camera.app; 18 19import android.app.Activity; 20import android.content.ContentResolver; 21import android.content.Context; 22import android.content.pm.ActivityInfo; 23import android.content.res.Configuration; 24import android.os.Handler; 25import android.provider.Settings; 26import android.util.Log; 27import android.view.OrientationEventListener; 28import android.view.Surface; 29 30import com.android.camera.util.ApiHelper; 31 32import java.util.ArrayList; 33import 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 */ 40public class OrientationManagerImpl implements OrientationManager { 41 private static final String TAG = "OrientationManagerImpl"; 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) return; 136 mOrientationLocked = true; 137 if (ApiHelper.HAS_ORIENTATION_LOCK) { 138 mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED); 139 } else { 140 mActivity.setRequestedOrientation(calculateCurrentScreenOrientation()); 141 } 142 } 143 144 @Override 145 public void unlockOrientation() { 146 if (!mOrientationLocked || mRotationLockedSetting) return; 147 mOrientationLocked = false; 148 Log.d(TAG, "unlock orientation"); 149 mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR); 150 } 151 152 private int calculateCurrentScreenOrientation() { 153 int displayRotation = getDisplayRotation(); 154 // Display rotation >= 180 means we need to use the REVERSE landscape/portrait 155 boolean standard = displayRotation < 180; 156 if (mActivity.getResources().getConfiguration().orientation 157 == Configuration.ORIENTATION_LANDSCAPE) { 158 return standard 159 ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE 160 : ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; 161 } else { 162 if (displayRotation == 90 || displayRotation == 270) { 163 // If displayRotation = 90 or 270 then we are on a landscape 164 // device. On landscape devices, portrait is a 90 degree 165 // clockwise rotation from landscape, so we need 166 // to flip which portrait we pick as display rotation is counter clockwise 167 standard = !standard; 168 } 169 return standard 170 ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT 171 : ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; 172 } 173 } 174 175 // This listens to the device orientation, so we can update the compensation. 176 private class MyOrientationEventListener extends OrientationEventListener { 177 public MyOrientationEventListener(Context context) { 178 super(context); 179 } 180 181 @Override 182 public void onOrientationChanged(int orientation) { 183 // We keep the last known orientation. So if the user first orient 184 // the camera then point the camera to floor or sky, we still have 185 // the correct orientation. 186 if (orientation == ORIENTATION_UNKNOWN) return; 187 final int roundedOrientation = roundOrientation(orientation, 0); 188 189 for (OrientationChangeCallback l : mListeners) { 190 l.postOrientationChangeCallback(roundedOrientation); 191 } 192 } 193 } 194 195 @Override 196 public int getDisplayRotation() { 197 return getDisplayRotation(mActivity); 198 } 199 200 private static int roundOrientation(int orientation, int orientationHistory) { 201 boolean changeOrientation = false; 202 if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) { 203 changeOrientation = true; 204 } else { 205 int dist = Math.abs(orientation - orientationHistory); 206 dist = Math.min(dist, 360 - dist); 207 changeOrientation = (dist >= 45 + ORIENTATION_HYSTERESIS); 208 } 209 if (changeOrientation) { 210 return ((orientation + 45) / 90 * 90) % 360; 211 } 212 return orientationHistory; 213 } 214 215 private static int getDisplayRotation(Activity activity) { 216 int rotation = activity.getWindowManager().getDefaultDisplay() 217 .getRotation(); 218 switch (rotation) { 219 case Surface.ROTATION_0: return 0; 220 case Surface.ROTATION_90: return 90; 221 case Surface.ROTATION_180: return 180; 222 case Surface.ROTATION_270: return 270; 223 } 224 return 0; 225 } 226} 227