/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.audio; import android.content.Context; import android.media.AudioSystem; import android.os.Handler; import android.util.Log; import android.view.OrientationEventListener; import android.view.Surface; import android.view.WindowManager; import com.android.server.policy.WindowOrientationListener; /** * Class to handle device rotation events for AudioService, and forward device rotation * to the audio HALs through AudioSystem. * * The role of this class is to monitor device orientation changes, and upon rotation, * verify the UI orientation. In case of a change, send the new orientation, in increments * of 90deg, through AudioSystem. * * Note that even though we're responding to device orientation events, we always * query the display rotation so audio stays in sync with video/dialogs. This is * done with .getDefaultDisplay().getRotation() from WINDOW_SERVICE. */ class RotationHelper { private static final String TAG = "AudioService.RotationHelper"; private static AudioOrientationListener sOrientationListener; private static AudioWindowOrientationListener sWindowOrientationListener; private static final Object sRotationLock = new Object(); private static int sDeviceRotation = Surface.ROTATION_0; // R/W synchronized on sRotationLock private static Context sContext; /** * post conditions: * - (sWindowOrientationListener != null) xor (sOrientationListener != null) * - sWindowOrientationListener xor sOrientationListener is enabled * - sContext != null */ static void init(Context context, Handler handler) { if (context == null) { throw new IllegalArgumentException("Invalid null context"); } sContext = context; sWindowOrientationListener = new AudioWindowOrientationListener(context, handler); sWindowOrientationListener.enable(); if (!sWindowOrientationListener.canDetectOrientation()) { // cannot use com.android.server.policy.WindowOrientationListener, revert to public // orientation API Log.i(TAG, "Not using WindowOrientationListener, reverting to OrientationListener"); sWindowOrientationListener.disable(); sWindowOrientationListener = null; sOrientationListener = new AudioOrientationListener(context); sOrientationListener.enable(); } } static void enable() { if (sWindowOrientationListener != null) { sWindowOrientationListener.enable(); } else { sOrientationListener.enable(); } updateOrientation(); } static void disable() { if (sWindowOrientationListener != null) { sWindowOrientationListener.disable(); } else { sOrientationListener.disable(); } } /** * Query current display rotation and publish the change if any. */ static void updateOrientation() { // Even though we're responding to device orientation events, // use display rotation so audio stays in sync with video/dialogs int newRotation = ((WindowManager) sContext.getSystemService( Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation(); synchronized(sRotationLock) { if (newRotation != sDeviceRotation) { sDeviceRotation = newRotation; publishRotation(sDeviceRotation); } } } private static void publishRotation(int rotation) { Log.v(TAG, "publishing device rotation =" + rotation + " (x90deg)"); switch (rotation) { case Surface.ROTATION_0: AudioSystem.setParameters("rotation=0"); break; case Surface.ROTATION_90: AudioSystem.setParameters("rotation=90"); break; case Surface.ROTATION_180: AudioSystem.setParameters("rotation=180"); break; case Surface.ROTATION_270: AudioSystem.setParameters("rotation=270"); break; default: Log.e(TAG, "Unknown device rotation"); } } /** * Uses android.view.OrientationEventListener */ final static class AudioOrientationListener extends OrientationEventListener { AudioOrientationListener(Context context) { super(context); } @Override public void onOrientationChanged(int orientation) { updateOrientation(); } } /** * Uses com.android.server.policy.WindowOrientationListener */ final static class AudioWindowOrientationListener extends WindowOrientationListener { private static RotationCheckThread sRotationCheckThread; AudioWindowOrientationListener(Context context, Handler handler) { super(context, handler); } public void onProposedRotationChanged(int rotation) { updateOrientation(); if (sRotationCheckThread != null) { sRotationCheckThread.endCheck(); } sRotationCheckThread = new RotationCheckThread(); sRotationCheckThread.beginCheck(); } } /** * When com.android.server.policy.WindowOrientationListener report an orientation change, * the UI may not have rotated yet. This thread polls with gradually increasing delays * the new orientation. */ final static class RotationCheckThread extends Thread { // how long to wait between each rotation check private final int[] WAIT_TIMES_MS = { 10, 20, 50, 100, 100, 200, 200, 500 }; private int mWaitCounter; private final Object mCounterLock = new Object(); RotationCheckThread() { super("RotationCheck"); } void beginCheck() { synchronized(mCounterLock) { mWaitCounter = 0; } try { start(); } catch (IllegalStateException e) { } } void endCheck() { synchronized(mCounterLock) { mWaitCounter = WAIT_TIMES_MS.length; } } public void run() { while (mWaitCounter < WAIT_TIMES_MS.length) { int waitTimeMs; synchronized(mCounterLock) { waitTimeMs = mWaitCounter < WAIT_TIMES_MS.length ? WAIT_TIMES_MS[mWaitCounter] : 0; mWaitCounter++; } try { if (waitTimeMs > 0) { sleep(waitTimeMs); updateOrientation(); } } catch (InterruptedException e) { } } } } }