OrientationManagerImpl.java revision 846d3abfe3da2fa2a5593c7d40a196005408bed1
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) { 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 private int calculateCurrentScreenOrientation() { 157 int displayRotation = getDisplayRotation(); 158 // Display rotation >= 180 means we need to use the REVERSE landscape/portrait 159 boolean standard = displayRotation < 180; 160 if (mActivity.getResources().getConfiguration().orientation 161 == Configuration.ORIENTATION_LANDSCAPE) { 162 return standard 163 ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE 164 : ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; 165 } else { 166 if (displayRotation == 90 || displayRotation == 270) { 167 // If displayRotation = 90 or 270 then we are on a landscape 168 // device. On landscape devices, portrait is a 90 degree 169 // clockwise rotation from landscape, so we need 170 // to flip which portrait we pick as display rotation is counter clockwise 171 standard = !standard; 172 } 173 return standard 174 ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT 175 : ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; 176 } 177 } 178 179 // This listens to the device orientation, so we can update the compensation. 180 private class MyOrientationEventListener extends OrientationEventListener { 181 public MyOrientationEventListener(Context context) { 182 super(context); 183 } 184 185 @Override 186 public void onOrientationChanged(int orientation) { 187 // We keep the last known orientation. So if the user first orient 188 // the camera then point the camera to floor or sky, we still have 189 // the correct orientation. 190 if (orientation == ORIENTATION_UNKNOWN) { 191 return; 192 } 193 final int roundedOrientation = roundOrientation(orientation, 0); 194 195 for (OrientationChangeCallback l : mListeners) { 196 l.postOrientationChangeCallback(roundedOrientation); 197 } 198 } 199 } 200 201 @Override 202 public int getDisplayRotation() { 203 return getDisplayRotation(mActivity); 204 } 205 206 private static int roundOrientation(int orientation, int orientationHistory) { 207 boolean changeOrientation = false; 208 if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) { 209 changeOrientation = true; 210 } else { 211 int dist = Math.abs(orientation - orientationHistory); 212 dist = Math.min(dist, 360 - dist); 213 changeOrientation = (dist >= 45 + ORIENTATION_HYSTERESIS); 214 } 215 if (changeOrientation) { 216 return ((orientation + 45) / 90 * 90) % 360; 217 } 218 return orientationHistory; 219 } 220 221 private static int getDisplayRotation(Activity activity) { 222 int rotation = activity.getWindowManager().getDefaultDisplay() 223 .getRotation(); 224 switch (rotation) { 225 case Surface.ROTATION_0: return 0; 226 case Surface.ROTATION_90: return 90; 227 case Surface.ROTATION_180: return 180; 228 case Surface.ROTATION_270: return 270; 229 } 230 return 0; 231 } 232} 233