/*
* Copyright (c) 2013 The MITRE Corporation, All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this work 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 org.mitre.svmp.client;
import android.hardware.SensorManager;
import android.os.Handler;
import android.util.Log;
import android.view.Display;
import android.view.OrientationEventListener;
import android.view.Surface;
import org.mitre.svmp.activities.AppRTCActivity;
import org.mitre.svmp.common.Utility;
import org.mitre.svmp.protocol.SVMPProtocol.Request;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/**
* @author Joe Portner
* When a new screen rotation is detected, this listener sends a message to the VM to update its rotation accordingly
*/
public class RotationHandler extends OrientationEventListener {
private static final String TAG = RotationHandler.class.getName();
// the current rotation has a wider allowance of degrees before triggering a rotation change event
private static final int ANGLE_ALLOWANCE = 130;
// the number of milliseconds we wait before triggering a rotation change event
private static final int TIME_ALLOWANCE = 1200;
private AppRTCActivity activity;
private boolean running = false;
private int rotation = 0; // valid values are: 0, 1, 2, 3
private int proposedRotation = 0;
private int currentMin;
private int currentMax;
private Handler taskHandler = null;
private RotateTask rotateTask = null;
public RotationHandler(AppRTCActivity activity) {
super(activity, SensorManager.SENSOR_DELAY_NORMAL);
this.activity = activity;
}
public void initRotationUpdates() {
if (canDetectOrientation()) {
running = true;
taskHandler = new Handler();
// get current rotation
rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
proposedRotation = rotation;
setCurrentMinMax();
// enable listener for rotation changes
enable();
Log.d(TAG, "Can detect orientation, RotationHandler has been enabled");
// send initial rotation
sendRotationInfo();
}
else
Log.d(TAG, "Can NOT detect orientation, RotationHandler has NOT been enabled");
}
public void cleanupRotationUpdates() {
running = false;
disable();
}
@Override
public void onOrientationChanged(int i) {
if (i != ORIENTATION_UNKNOWN) {
int newRotation = getUpdatedRotation(i);
if (rotateTask == null && rotation != newRotation) {
proposedRotation = newRotation;
rotateTask = new RotateTask();
taskHandler.postDelayed(rotateTask, TIME_ALLOWANCE);
}
else if (rotateTask != null && rotation != newRotation && proposedRotation != newRotation && newRotation != 2) {
// sometimes the user may continue to rotate the screen
// (for instance, from 90 to 0, then to 270, before the rotation change has triggered)
// note: only change a screen rotation that's in progress if the newRotation isn't ROTATION_180
// (180 is upside down, which some devices don't support - rotate to ROTATION_270 or ROTATION_90 first)
proposedRotation = newRotation;
}
else if (rotateTask != null && rotation == newRotation) {
// we've reached our original rotation before the proposedRotation could trigger a change
// cancel the RotateTask and remove it
proposedRotation = rotation;
rotateTask.cancel();
rotateTask = null;
}
}
}
// takes input of 0 to 359, outputs the rotation detected (0, 1, 2, or 3)
// weighted based on current rotation, i.e. has an angle allowance greater than 90 degrees
private int getUpdatedRotation(int degrees) {
int value = rotation;
if (outsideCurrentMinMax(degrees)) {
if (degrees >= 315 || degrees < 45)
value = Surface.ROTATION_0;
else if (degrees >= 45 && degrees < 135)
value = Surface.ROTATION_270;
else if (degrees >= 135 && degrees < 225)
value = Surface.ROTATION_180;
else if (degrees >= 225 && degrees < 315)
value = Surface.ROTATION_90;
}
return value;
}
// called when the rotation is changed
// sets the current minimum and maximum values that will trigger another rotation change
private void setCurrentMinMax() {
int multiplier = 0;
if (rotation == 1)
multiplier = 3;
else if (rotation == 2)
multiplier = 2;
else if (rotation == 3)
multiplier = 1;
// the minimum and maximum degrees for the current rotation should correspond to the ANGLE_ALLOWANCE
currentMin = (multiplier*90) - (ANGLE_ALLOWANCE/2);
currentMax = (multiplier*90) + (ANGLE_ALLOWANCE/2);
if (currentMin < 0)
currentMin += 360;
}
private boolean outsideCurrentMinMax(int degrees) {
boolean value;
if (rotation == 0)
value = degrees < currentMin && degrees > currentMax;
else
value = degrees < currentMin || degrees > currentMax;
return value;
}
private void sendRotationInfo() {
if (activity.isConnected()) {
// construct a Request object
Request request = Utility.toRequest_RotationInfo(rotation);
// send the Request to the VM
activity.sendMessage(request);
}
}
private class RotateTask implements Runnable {
private boolean cancelled = false;
@Override
public void run() {
if (running && !cancelled) {
// the task has finished and the current rotation has not changed back to the original rotation
// trigger a rotation change message
rotation = proposedRotation;
setCurrentMinMax();
rotateTask = null;
sendRotationInfo();
}
}
public void cancel() {
cancelled = true;
}
}
}