Merge "Fix calculation of power drain from BT and WiFi" into mnc-dev
diff --git a/api/current.txt b/api/current.txt
index 58ecc39..b35581c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -15242,6 +15242,7 @@
field public static final int ORIENTATION_UNDEFINED = 0; // 0x0
field public static final java.lang.String TAG_APERTURE = "FNumber";
field public static final java.lang.String TAG_DATETIME = "DateTime";
+ field public static final java.lang.String TAG_DATETIME_DIGITIZED = "DateTimeDigitized";
field public static final java.lang.String TAG_EXPOSURE_TIME = "ExposureTime";
field public static final java.lang.String TAG_FLASH = "Flash";
field public static final java.lang.String TAG_FOCAL_LENGTH = "FocalLength";
@@ -15260,6 +15261,9 @@
field public static final java.lang.String TAG_MAKE = "Make";
field public static final java.lang.String TAG_MODEL = "Model";
field public static final java.lang.String TAG_ORIENTATION = "Orientation";
+ field public static final java.lang.String TAG_SUBSEC_TIME = "SubSecTime";
+ field public static final java.lang.String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized";
+ field public static final java.lang.String TAG_SUBSEC_TIME_ORIG = "SubSecTimeOriginal";
field public static final java.lang.String TAG_WHITE_BALANCE = "WhiteBalance";
field public static final int WHITEBALANCE_AUTO = 0; // 0x0
field public static final int WHITEBALANCE_MANUAL = 1; // 0x1
@@ -23447,6 +23451,7 @@
method public static final int getGidForName(java.lang.String);
method public static final int getThreadPriority(int) throws java.lang.IllegalArgumentException;
method public static final int getUidForName(java.lang.String);
+ method public static final boolean is64Bit();
method public static final void killProcess(int);
method public static final int myPid();
method public static final int myTid();
diff --git a/api/system-current.txt b/api/system-current.txt
index e3e1787..3698b22 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -16483,6 +16483,7 @@
field public static final int ORIENTATION_UNDEFINED = 0; // 0x0
field public static final java.lang.String TAG_APERTURE = "FNumber";
field public static final java.lang.String TAG_DATETIME = "DateTime";
+ field public static final java.lang.String TAG_DATETIME_DIGITIZED = "DateTimeDigitized";
field public static final java.lang.String TAG_EXPOSURE_TIME = "ExposureTime";
field public static final java.lang.String TAG_FLASH = "Flash";
field public static final java.lang.String TAG_FOCAL_LENGTH = "FocalLength";
@@ -16501,6 +16502,9 @@
field public static final java.lang.String TAG_MAKE = "Make";
field public static final java.lang.String TAG_MODEL = "Model";
field public static final java.lang.String TAG_ORIENTATION = "Orientation";
+ field public static final java.lang.String TAG_SUBSEC_TIME = "SubSecTime";
+ field public static final java.lang.String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized";
+ field public static final java.lang.String TAG_SUBSEC_TIME_ORIG = "SubSecTimeOriginal";
field public static final java.lang.String TAG_WHITE_BALANCE = "WhiteBalance";
field public static final int WHITEBALANCE_AUTO = 0; // 0x0
field public static final int WHITEBALANCE_MANUAL = 1; // 0x1
@@ -25373,6 +25377,7 @@
method public static final int getGidForName(java.lang.String);
method public static final int getThreadPriority(int) throws java.lang.IllegalArgumentException;
method public static final int getUidForName(java.lang.String);
+ method public static final boolean is64Bit();
method public static final void killProcess(int);
method public static final int myPid();
method public static final int myTid();
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 009649f..dbb5146 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -21,6 +21,7 @@
import android.system.Os;
import android.util.Log;
import com.android.internal.os.Zygote;
+import dalvik.system.VMRuntime;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.IOException;
@@ -744,7 +745,14 @@
* @return Returns the number of milliseconds this process has return.
*/
public static final native long getElapsedCpuTime();
-
+
+ /**
+ * Returns true if the current process is a 64-bit runtime.
+ */
+ public static final boolean is64Bit() {
+ return VMRuntime.getRuntime().is64Bit();
+ }
+
/**
* Returns the identifier of this process, which can be used with
* {@link #killProcess} and {@link #sendSignal}.
diff --git a/core/java/android/util/LayoutDirection.java b/core/java/android/util/LayoutDirection.java
index 20af20b..03077e4 100644
--- a/core/java/android/util/LayoutDirection.java
+++ b/core/java/android/util/LayoutDirection.java
@@ -27,6 +27,12 @@
private LayoutDirection() {}
/**
+ * An undefined layout direction.
+ * @hide
+ */
+ public static final int UNDEFINED = -1;
+
+ /**
* Horizontal layout direction is from Left to Right.
*/
public static final int LTR = 0;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 342315b..5dd5ab8 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -1872,6 +1872,12 @@
public @interface ResolvedLayoutDir {}
/**
+ * A flag to indicate that the layout direction of this view has not been defined yet.
+ * @hide
+ */
+ public static final int LAYOUT_DIRECTION_UNDEFINED = LayoutDirection.UNDEFINED;
+
+ /**
* Horizontal layout direction of this view is from Left to Right.
* Use with {@link #setLayoutDirection}.
*/
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index c0395cf..687f9d0 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -298,6 +298,19 @@
/**
* Name of the package that owns this editor.
+ *
+ * <p><strong>IME authors:</strong> In API level 22
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1} and prior, do not trust this package
+ * name. The system had not verified the consistency between the package name here and
+ * application's uid. Consider to use {@link InputBinding#getUid()}, which is trustworthy.
+ * Starting from Android MNC, the system verifies the consistency between this package name
+ * and application uid before {@link EditorInfo} is passed to the input method as long as the
+ * application sets a non-empty package name.</p>
+ *
+ * <p><strong>Editor authors:</strong> Starting from Android MNC, the application is no longer
+ * able to establish input connections if the package name provided here is inconsistent with
+ * application's uid. Note that the system does accept an empty string for an arbitrary
+ * application uid.</p>
*/
public String packageName;
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index f153ce5..9d14254 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -185,6 +185,8 @@
private int mShowDividers;
private int mDividerPadding;
+ private int mLayoutDirection = View.LAYOUT_DIRECTION_UNDEFINED;
+
public LinearLayout(Context context) {
this(context, null);
}
@@ -1567,6 +1569,17 @@
}
}
+ @Override
+ public void onRtlPropertiesChanged(@ResolvedLayoutDir int layoutDirection) {
+ super.onRtlPropertiesChanged(layoutDirection);
+ if (layoutDirection != mLayoutDirection) {
+ mLayoutDirection = layoutDirection;
+ if (mOrientation == HORIZONTAL) {
+ requestLayout();
+ }
+ }
+ }
+
/**
* Position the children during a layout pass if the orientation of this
* LinearLayout is set to {@link #HORIZONTAL}.
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 8a26e7d..baa7a35 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -548,7 +548,7 @@
<!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permgrouplab_location">Location</string>
<!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permgroupdesc_location">access your location</string>
+ <string name="permgroupdesc_location">access this device\'s location</string>
<!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permgrouplab_socialInfo">Your social information</string>
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index aa5d43a..6bf5721 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -57,6 +57,16 @@
public static final String TAG_APERTURE = "FNumber";
/** Type is String. */
public static final String TAG_ISO = "ISOSpeedRatings";
+ /** Type is String. */
+ public static final String TAG_DATETIME_DIGITIZED = "DateTimeDigitized";
+ /** Type is int. */
+ public static final String TAG_SUBSEC_TIME = "SubSecTime";
+ /** Type is int. */
+ public static final String TAG_SUBSEC_TIME_ORIG = "SubSecTimeOriginal";
+ /** Type is int. */
+ public static final String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized";
+
+
/**
* @hide
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index de2f1f9..8a3aa5f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1087,4 +1087,7 @@
<!-- Alarm template for far alarms [CHAR LIMIT=25] -->
<string name="alarm_template_far">on <xliff:g id="when" example="Fri 7:00 AM">%1$s</xliff:g></string>
+ <!-- Accessibility label for Quick Settings detail screens [CHAR LIMIT=NONE] -->
+ <string name="accessibility_quick_settings_detail">Quick Settings, <xliff:g id="title" example="Wi-Fi">%s</xliff:g>.</string>
+
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index cd4f299..25e3d10 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -111,6 +111,8 @@
mDetailDoneButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
+ announceForAccessibility(
+ mContext.getString(R.string.accessibility_desc_quick_settings));
closeDetail();
}
});
@@ -392,6 +394,9 @@
mDetail.bringToFront();
mDetailContent.addView(r.detailView);
MetricsLogger.visible(mContext, detailAdapter.getMetricsCategory());
+ announceForAccessibility(mContext.getString(
+ R.string.accessibility_quick_settings_detail,
+ mContext.getString(detailAdapter.getTitle())));
setDetailRecord(r);
listener = mHideGridContentWhenDone;
if (r instanceof TileRecord) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
index 310a64c..29bea4d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
@@ -386,18 +386,8 @@
}
}
} else {
- if (mAutomute && !row.ss.muteSupported) {
- final boolean vmute = row.ss.level == 0;
- mController.setStreamVolume(stream, vmute ? row.lastAudibleLevel : 0);
- } else {
- final boolean mute = !row.ss.muted;
- mController.setStreamMute(stream, mute);
- if (mAutomute) {
- if (!mute && row.ss.level == 0) {
- mController.setStreamVolume(stream, 1);
- }
- }
- }
+ final boolean vmute = row.ss.level == 0;
+ mController.setStreamVolume(stream, vmute ? row.lastAudibleLevel : 0);
}
row.userAttempt = 0; // reset the grace period, slider should update immediately
}
@@ -589,6 +579,9 @@
if (ss.level > 0) {
row.lastAudibleLevel = ss.level;
}
+ if (ss.level == row.requestedLevel) {
+ row.requestedLevel = -1;
+ }
final boolean isRingStream = row.stream == AudioManager.STREAM_RING;
final boolean isSystemStream = row.stream == AudioManager.STREAM_SYSTEM;
final boolean isAlarmStream = row.stream == AudioManager.STREAM_ALARM;
@@ -664,7 +657,10 @@
row.icon.setContentDescription(ss.name);
// update slider
- updateVolumeRowSliderH(row, zenMuted);
+ final boolean enableSlider = !zenMuted;
+ final int vlevel = row.ss.muted && (isRingVibrate || !isRingStream && !zenMuted) ? 0
+ : row.ss.level;
+ updateVolumeRowSliderH(row, enableSlider, vlevel);
}
private void updateVolumeRowSliderTintH(VolumeRow row, boolean isActive) {
@@ -676,8 +672,8 @@
row.slider.setThumbTintList(tint);
}
- private void updateVolumeRowSliderH(VolumeRow row, boolean zenMuted) {
- row.slider.setEnabled(!zenMuted);
+ private void updateVolumeRowSliderH(VolumeRow row, boolean enable, int vlevel) {
+ row.slider.setEnabled(enable);
updateVolumeRowSliderTintH(row, row.stream == mActiveStream);
if (row.tracking) {
return; // don't update if user is sliding
@@ -694,7 +690,6 @@
row.userAttempt + USER_ATTEMPT_GRACE_PERIOD);
return; // don't update if visible and in grace period
}
- final int vlevel = row.ss.muted ? 0 : row.ss.level;
if (vlevel == level) {
if (mShowing && rowVisible) {
return; // don't clamp if visible
@@ -1018,7 +1013,7 @@
private StreamState ss;
private long userAttempt; // last user-driven slider change
private boolean tracking; // tracking slider touch
- private int requestedLevel;
+ private int requestedLevel = -1; // pending user-requested level via progress changed
private int iconRes;
private int iconMuteRes;
private boolean important;
diff --git a/services/core/java/com/android/server/AnyMotionDetector.java b/services/core/java/com/android/server/AnyMotionDetector.java
new file mode 100644
index 0000000..6390bcd
--- /dev/null
+++ b/services/core/java/com/android/server/AnyMotionDetector.java
@@ -0,0 +1,438 @@
+/*
+ * 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;
+
+import android.app.AlarmManager;
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import java.lang.Float;
+
+/**
+ * Determines if the device has been set upon a stationary object.
+ */
+public class AnyMotionDetector {
+ interface DeviceIdleCallback {
+ public void onAnyMotionResult(int result);
+ }
+
+ private static final String TAG = "AnyMotionDetector";
+
+ private static final boolean DEBUG = false;
+
+ /** Stationary status is unknown due to insufficient orientation measurements. */
+ public static final int RESULT_UNKNOWN = -1;
+
+ /** Device is stationary, e.g. still on a table. */
+ public static final int RESULT_STATIONARY = 0;
+
+ /** Device has been moved. */
+ public static final int RESULT_MOVED = 1;
+
+ /** Orientation measurements are being performed or are planned. */
+ private static final int STATE_INACTIVE = 0;
+
+ /** No orientation measurements are being performed or are planned. */
+ private static final int STATE_ACTIVE = 1;
+
+ /** Current measurement state. */
+ private int mState;
+
+ /** Threshold angle in degrees beyond which the device is considered moving. */
+ private final float THRESHOLD_ANGLE = 2f;
+
+ /** Threshold energy above which the device is considered moving. */
+ private final float THRESHOLD_ENERGY = 5f;
+
+ /** The duration of the accelerometer orientation measurement. */
+ private static final long ORIENTATION_MEASUREMENT_DURATION_MILLIS = 2500;
+
+ /** The maximum duration we will collect accelerometer data. */
+ private static final long ACCELEROMETER_DATA_TIMEOUT_MILLIS = 3000;
+
+ /** The interval between accelerometer orientation measurements. */
+ private static final long ORIENTATION_MEASUREMENT_INTERVAL_MILLIS = 5000;
+
+ /**
+ * The duration in milliseconds after which an orientation measurement is considered
+ * too stale to be used.
+ */
+ private static final int STALE_MEASUREMENT_TIMEOUT_MILLIS = 2 * 60 * 1000;
+
+ /** The accelerometer sampling interval. */
+ private static final int SAMPLING_INTERVAL_MILLIS = 40;
+
+ private AlarmManager mAlarmManager;
+ private final Handler mHandler;
+ private Intent mAlarmIntent;
+ private final Object mLock = new Object();
+ private Sensor mAccelSensor;
+ private SensorManager mSensorManager;
+ private PowerManager.WakeLock mWakeLock;
+
+ /** The time when detection was last performed. */
+ private long mDetectionStartTime;
+
+ /** The minimum number of samples required to detect AnyMotion. */
+ private int mNumSufficientSamples;
+
+ /** True if an orientation measurement is in progress. */
+ private boolean mMeasurementInProgress;
+
+ /** The most recent gravity vector. */
+ private Vector3 mCurrentGravityVector = null;
+
+ /** The second most recent gravity vector. */
+ private Vector3 mPreviousGravityVector = null;
+
+ /** Running sum of squared errors. */
+ private RunningSignalStats mRunningStats;
+
+ private DeviceIdleCallback mCallback = null;
+
+ public AnyMotionDetector(AlarmManager am, PowerManager pm, Handler handler, SensorManager sm,
+ DeviceIdleCallback callback) {
+ if (DEBUG) Slog.d(TAG, "AnyMotionDetector instantiated.");
+ mAlarmManager = am;
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+ mHandler = handler;
+ mSensorManager = sm;
+ mAccelSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ mMeasurementInProgress = false;
+ mState = STATE_INACTIVE;
+ mCallback = callback;
+ mRunningStats = new RunningSignalStats();
+ mNumSufficientSamples = (int) Math.ceil(
+ ((double)ORIENTATION_MEASUREMENT_DURATION_MILLIS / SAMPLING_INTERVAL_MILLIS));
+ if (DEBUG) Slog.d(TAG, "mNumSufficientSamples = " + mNumSufficientSamples);
+ }
+
+ /*
+ * Acquire accel data until we determine AnyMotion status.
+ */
+ public void checkForAnyMotion() {
+ if (DEBUG) Slog.d(TAG, "checkForAnyMotion(). mState = " + mState);
+ if (mState != STATE_ACTIVE) {
+ mState = STATE_ACTIVE;
+ if (DEBUG) Slog.d(TAG, "Moved from STATE_INACTIVE to STATE_ACTIVE.");
+ mCurrentGravityVector = null;
+ mPreviousGravityVector = null;
+ startOrientationMeasurement();
+ }
+ }
+
+ private void startOrientationMeasurement() {
+ if (DEBUG) Slog.d(TAG, "startOrientationMeasurement: mMeasurementInProgress=" +
+ mMeasurementInProgress + ", (mAccelSensor != null)=" + (mAccelSensor != null));
+
+ if (!mMeasurementInProgress && mAccelSensor != null) {
+ if (mSensorManager.registerListener(mListener, mAccelSensor,
+ SAMPLING_INTERVAL_MILLIS * 1000)) {
+ mWakeLock.acquire();
+ mMeasurementInProgress = true;
+ mDetectionStartTime = SystemClock.elapsedRealtime();
+ mRunningStats.reset();
+ }
+
+ Message msg = Message.obtain(mHandler, mMeasurementTimeout);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageDelayed(msg, ACCELEROMETER_DATA_TIMEOUT_MILLIS);
+ }
+ }
+
+ private int stopOrientationMeasurementLocked() {
+ if (DEBUG) Slog.d(TAG, "stopOrientationMeasurement. mMeasurementInProgress=" +
+ mMeasurementInProgress);
+ int status = RESULT_UNKNOWN;
+ if (mMeasurementInProgress) {
+ mSensorManager.unregisterListener(mListener);
+ mHandler.removeCallbacks(mMeasurementTimeout);
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ long detectionEndTime = SystemClock.elapsedRealtime();
+ mMeasurementInProgress = false;
+ mPreviousGravityVector = mCurrentGravityVector;
+ mCurrentGravityVector = mRunningStats.getRunningAverage();
+ if (DEBUG) {
+ Slog.d(TAG, "mRunningStats = " + mRunningStats.toString());
+ String currentGravityVectorString = (mCurrentGravityVector == null) ?
+ "null" : mCurrentGravityVector.toString();
+ String previousGravityVectorString = (mPreviousGravityVector == null) ?
+ "null" : mPreviousGravityVector.toString();
+ Slog.d(TAG, "mCurrentGravityVector = " + currentGravityVectorString);
+ Slog.d(TAG, "mPreviousGravityVector = " + previousGravityVectorString);
+ }
+ mRunningStats.reset();
+ status = getStationaryStatus();
+ if (DEBUG) Slog.d(TAG, "getStationaryStatus() returned " + status);
+ if (status != RESULT_UNKNOWN) {
+ if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE. status = " +
+ status);
+ mState = STATE_INACTIVE;
+ } else {
+ /*
+ * Unknown due to insufficient measurements. Schedule another orientation
+ * measurement.
+ */
+ if (DEBUG) Slog.d(TAG, "stopOrientationMeasurementLocked(): another measurement" +
+ " scheduled in " + ORIENTATION_MEASUREMENT_INTERVAL_MILLIS +
+ " milliseconds.");
+ Message msg = Message.obtain(mHandler, mSensorRestart);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageDelayed(msg, ORIENTATION_MEASUREMENT_INTERVAL_MILLIS);
+ }
+ }
+ return status;
+ }
+
+ /*
+ * Updates mStatus to the current AnyMotion status.
+ */
+ public int getStationaryStatus() {
+ if ((mPreviousGravityVector == null) || (mCurrentGravityVector == null)) {
+ return RESULT_UNKNOWN;
+ }
+ Vector3 previousGravityVectorNormalized = mPreviousGravityVector.normalized();
+ Vector3 currentGravityVectorNormalized = mCurrentGravityVector.normalized();
+ float angle = previousGravityVectorNormalized.angleBetween(currentGravityVectorNormalized);
+ if (DEBUG) Slog.d(TAG, "getStationaryStatus: angle = " + angle);
+ if ((angle < THRESHOLD_ANGLE) && (mRunningStats.getEnergy() < THRESHOLD_ENERGY)) {
+ return RESULT_STATIONARY;
+ } else if (Float.isNaN(angle)) {
+ /**
+ * Floating point rounding errors have caused the angle calcuation's dot product to
+ * exceed 1.0. In such case, we report RESULT_MOVED to prevent devices from rapidly
+ * retrying this measurement.
+ */
+ return RESULT_MOVED;
+ }
+ long diffTime = mCurrentGravityVector.timeMillisSinceBoot -
+ mPreviousGravityVector.timeMillisSinceBoot;
+ if (diffTime > STALE_MEASUREMENT_TIMEOUT_MILLIS) {
+ if (DEBUG) Slog.d(TAG, "getStationaryStatus: mPreviousGravityVector is too stale at " +
+ diffTime + " ms ago. Returning RESULT_UNKNOWN.");
+ return RESULT_UNKNOWN;
+ }
+ return RESULT_MOVED;
+ }
+
+ private final SensorEventListener mListener = new SensorEventListener() {
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ int status = RESULT_UNKNOWN;
+ synchronized (mLock) {
+ Vector3 accelDatum = new Vector3(SystemClock.elapsedRealtime(), event.values[0],
+ event.values[1], event.values[2]);
+ mRunningStats.accumulate(accelDatum);
+
+ // If we have enough samples, stop accelerometer data acquisition.
+ if (mRunningStats.getSampleCount() >= mNumSufficientSamples) {
+ status = stopOrientationMeasurementLocked();
+ }
+ }
+ if (status != RESULT_UNKNOWN) {
+ mCallback.onAnyMotionResult(status);
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ }
+ };
+
+ private final Runnable mSensorRestart = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ startOrientationMeasurement();
+ }
+ }
+ };
+
+ private final Runnable mMeasurementTimeout = new Runnable() {
+ @Override
+ public void run() {
+ int status = RESULT_UNKNOWN;
+ synchronized (mLock) {
+ if (DEBUG) Slog.i(TAG, "mMeasurementTimeout. Failed to collect sufficient accel " +
+ "data within " + ACCELEROMETER_DATA_TIMEOUT_MILLIS + " ms. Stopping " +
+ "orientation measurement.");
+ status = stopOrientationMeasurementLocked();
+ }
+ if (status != RESULT_UNKNOWN) {
+ mCallback.onAnyMotionResult(status);
+ }
+ }
+ };
+
+ /**
+ * A timestamped three dimensional vector and some vector operations.
+ */
+ private static class Vector3 {
+ public long timeMillisSinceBoot;
+ public float x;
+ public float y;
+ public float z;
+
+ public Vector3(long timeMillisSinceBoot, float x, float y, float z) {
+ this.timeMillisSinceBoot = timeMillisSinceBoot;
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ private float norm() {
+ return (float) Math.sqrt(dotProduct(this));
+ }
+
+ private Vector3 normalized() {
+ float mag = norm();
+ return new Vector3(timeMillisSinceBoot, x / mag, y / mag, z / mag);
+ }
+
+ /**
+ * Returns the angle between this 3D vector and another given 3D vector.
+ * Assumes both have already been normalized.
+ *
+ * @param other The other Vector3 vector.
+ * @return angle between this vector and the other given one.
+ */
+ public float angleBetween(Vector3 other) {
+ double degrees = Math.toDegrees(Math.acos(this.dotProduct(other)));
+ float returnValue = (float) degrees;
+ Slog.d(TAG, "angleBetween: this = " + this.toString() +
+ ", other = " + other.toString());
+ Slog.d(TAG, " degrees = " + degrees + ", returnValue = " + returnValue);
+ return returnValue;
+ }
+
+ @Override
+ public String toString() {
+ String msg = "";
+ msg += "timeMillisSinceBoot=" + timeMillisSinceBoot;
+ msg += " | x=" + x;
+ msg += ", y=" + y;
+ msg += ", z=" + z;
+ return msg;
+ }
+
+ public float dotProduct(Vector3 v) {
+ return x * v.x + y * v.y + z * v.z;
+ }
+
+ public Vector3 times(float val) {
+ return new Vector3(timeMillisSinceBoot, x * val, y * val, z * val);
+ }
+
+ public Vector3 plus(Vector3 v) {
+ return new Vector3(v.timeMillisSinceBoot, x + v.x, y + v.y, z + v.z);
+ }
+
+ public Vector3 minus(Vector3 v) {
+ return new Vector3(v.timeMillisSinceBoot, x - v.x, y - v.y, z - v.z);
+ }
+ }
+
+ /**
+ * Maintains running statistics on the signal revelant to AnyMotion detection, including:
+ * <ul>
+ * <li>running average.
+ * <li>running sum-of-squared-errors as the energy of the signal derivative.
+ * <ul>
+ */
+ private static class RunningSignalStats {
+ Vector3 previousVector;
+ Vector3 currentVector;
+ Vector3 runningSum;
+ float energy;
+ int sampleCount;
+
+ public RunningSignalStats() {
+ reset();
+ }
+
+ public void reset() {
+ previousVector = null;
+ currentVector = null;
+ runningSum = new Vector3(0, 0, 0, 0);
+ energy = 0;
+ sampleCount = 0;
+ }
+
+ /**
+ * Apply a 3D vector v as the next element in the running SSE.
+ */
+ public void accumulate(Vector3 v) {
+ if (v == null) {
+ if (DEBUG) Slog.i(TAG, "Cannot accumulate a null vector.");
+ return;
+ }
+ sampleCount++;
+ runningSum = runningSum.plus(v);
+ previousVector = currentVector;
+ currentVector = v;
+ if (previousVector != null) {
+ Vector3 dv = currentVector.minus(previousVector);
+ float incrementalEnergy = dv.x * dv.x + dv.y * dv.y + dv.z * dv.z;
+ energy += incrementalEnergy;
+ if (DEBUG) Slog.i(TAG, "Accumulated vector " + currentVector.toString() +
+ ", runningSum = " + runningSum.toString() +
+ ", incrementalEnergy = " + incrementalEnergy +
+ ", energy = " + energy);
+ }
+ }
+
+ public Vector3 getRunningAverage() {
+ if (sampleCount > 0) {
+ return runningSum.times((float)(1.0f / sampleCount));
+ }
+ return null;
+ }
+
+ public float getEnergy() {
+ return energy;
+ }
+
+ public int getSampleCount() {
+ return sampleCount;
+ }
+
+ @Override
+ public String toString() {
+ String msg = "";
+ String currentVectorString = (currentVector == null) ?
+ "null" : currentVector.toString();
+ String previousVectorString = (previousVector == null) ?
+ "null" : previousVector.toString();
+ msg += "previousVector = " + previousVectorString;
+ msg += ", currentVector = " + currentVectorString;
+ msg += ", sampleCount = " + sampleCount;
+ msg += ", energy = " + energy;
+ return msg;
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index e9759c3..4c7b523 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -85,10 +85,12 @@
/**
* Keeps track of device idleness and drives low power mode based on that.
*/
-public class DeviceIdleController extends SystemService {
+public class DeviceIdleController extends SystemService
+ implements AnyMotionDetector.DeviceIdleCallback {
private static final String TAG = "DeviceIdleController";
private static final boolean DEBUG = false;
+
private static final boolean COMPRESS_TIME = false;
public static final String SERVICE_NAME = "deviceidle";
@@ -96,6 +98,9 @@
private static final String ACTION_STEP_IDLE_STATE =
"com.android.server.device_idle.STEP_IDLE_STATE";
+ private static final String ACTION_ENTER_INACTIVE_STATE =
+ "com.android.server.device_idle.ENTER_INACTIVE_STATE";
+
// TODO: These need to be moved to system settings.
/**
@@ -104,26 +109,40 @@
* immediately after going inactive just because we don't want to be continually running
* the significant motion sensor whenever the screen is off.
*/
+
private static final long DEFAULT_INACTIVE_TIMEOUT = !COMPRESS_TIME ? 30*60*1000L
: 3 * 60 * 1000L;
+
+ /**
+ * If we don't receive a callback from AnyMotion in this amount of time, we will change from
+ * STATE_SENSING to STATE_INACTIVE, and any AnyMotion callbacks while not in STATE_SENSING will
+ * be ignored.
+ */
+ private static final long DEFAULT_SENSING_TIMEOUT = !DEBUG ? 5 * 60 * 1000L : 60 * 1000L;
+
/**
* This is the time, after seeing motion, that we wait after becoming inactive from
* that until we start looking for motion again.
*/
private static final long DEFAULT_MOTION_INACTIVE_TIMEOUT = !COMPRESS_TIME ? 10*60*1000L
: 60 * 1000L;
+
/**
* This is the time, after the inactive timeout elapses, that we will wait looking
* for significant motion until we truly consider the device to be idle.
*/
+
private static final long DEFAULT_IDLE_AFTER_INACTIVE_TIMEOUT = !COMPRESS_TIME ? 30*60*1000L
: 3 * 60 * 1000L;
+
/**
* This is the initial time, after being idle, that we will allow ourself to be back
* in the IDLE_PENDING state allowing the system to run normally until we return to idle.
*/
+
private static final long DEFAULT_IDLE_PENDING_TIMEOUT = !COMPRESS_TIME ? 5*60*1000L
: 30 * 1000L;
+
/**
* Maximum pending idle timeout (time spent running) we will be allowed to use.
*/
@@ -138,8 +157,10 @@
* This is the initial time that we want to sit in the idle state before waking up
* again to return to pending idle and allowing normal work to run.
*/
+
private static final long DEFAULT_IDLE_TIMEOUT = !COMPRESS_TIME ? 60*60*1000L
: 6 * 60 * 1000L;
+
/**
* Maximum idle duration we will be allowed to use.
*/
@@ -168,9 +189,11 @@
private DisplayManager mDisplayManager;
private SensorManager mSensorManager;
private Sensor mSigMotionSensor;
+ private PendingIntent mSensingAlarmIntent;
private PendingIntent mAlarmIntent;
private Intent mIdleIntent;
private Display mCurDisplay;
+ private AnyMotionDetector mAnyMotionDetector;
private boolean mIdleDisabled;
private boolean mScreenOn;
private boolean mCharging;
@@ -182,15 +205,18 @@
private static final int STATE_INACTIVE = 1;
/** Device is past the initial inactive period, and waiting for the next idle period. */
private static final int STATE_IDLE_PENDING = 2;
+ /** Device is currently sensing motion. */
+ private static final int STATE_SENSING = 3;
/** Device is in the idle state, trying to stay asleep as much as possible. */
- private static final int STATE_IDLE = 3;
+ private static final int STATE_IDLE = 4;
/** Device is in the idle state, but temporarily out of idle to do regular maintenance. */
- private static final int STATE_IDLE_MAINTENANCE = 4;
+ private static final int STATE_IDLE_MAINTENANCE = 5;
private static String stateToString(int state) {
switch (state) {
case STATE_ACTIVE: return "ACTIVE";
case STATE_INACTIVE: return "INACTIVE";
case STATE_IDLE_PENDING: return "IDLE_PENDING";
+ case STATE_SENSING: return "SENSING";
case STATE_IDLE: return "IDLE";
case STATE_IDLE_MAINTENANCE: return "IDLE_MAINTENANCE";
default: return Integer.toString(state);
@@ -247,6 +273,10 @@
synchronized (DeviceIdleController.this) {
stepIdleStateLocked();
}
+ } else if (ACTION_ENTER_INACTIVE_STATE.equals(intent.getAction())) {
+ synchronized (DeviceIdleController.this) {
+ enterInactiveStateLocked();
+ }
}
}
};
@@ -276,6 +306,24 @@
}
};
+ @Override
+ public void onAnyMotionResult(int result) {
+ if (DEBUG) Slog.d(TAG, "onAnyMotionResult(" + result + ")");
+ if (mState == STATE_SENSING) {
+ if (result == AnyMotionDetector.RESULT_STATIONARY) {
+ if (DEBUG) Slog.d(TAG, "RESULT_STATIONARY received.");
+ synchronized (this) {
+ stepIdleStateLocked();
+ }
+ } else if (result == AnyMotionDetector.RESULT_MOVED) {
+ if (DEBUG) Slog.d(TAG, "RESULT_MOVED received.");
+ synchronized (this) {
+ enterInactiveStateLocked();
+ }
+ }
+ }
+ }
+
static final int MSG_WRITE_CONFIG = 1;
static final int MSG_REPORT_IDLE_ON = 2;
static final int MSG_REPORT_IDLE_OFF = 3;
@@ -288,6 +336,7 @@
}
@Override public void handleMessage(Message msg) {
+ if (DEBUG) Slog.d(TAG, "handleMessage(" + msg.what + ")");
switch (msg.what) {
case MSG_WRITE_CONFIG: {
handleWriteConfigFile();
@@ -452,12 +501,21 @@
Context.DISPLAY_SERVICE);
mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
mSigMotionSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION);
+ mAnyMotionDetector = new AnyMotionDetector(
+ mAlarmManager,
+ (PowerManager) getContext().getSystemService(Context.POWER_SERVICE),
+ mHandler, mSensorManager, this);
Intent intent = new Intent(ACTION_STEP_IDLE_STATE)
.setPackage("android")
.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
mAlarmIntent = PendingIntent.getBroadcast(getContext(), 0, intent, 0);
+ Intent intentSensing = new Intent(ACTION_STEP_IDLE_STATE)
+ .setPackage("android")
+ .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mSensingAlarmIntent = PendingIntent.getBroadcast(getContext(), 0, intentSensing, 0);
+
mIdleIntent = new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
mIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
@@ -613,6 +671,7 @@
// because if there is anything shown we are going to be updating it at some
// frequency so can't be allowed to go into deep sleeps.
boolean screenOn = mCurDisplay.getState() != Display.STATE_OFF;;
+ if (DEBUG) Slog.d(TAG, "updateDisplayLocked: screenOn=" + screenOn);
if (!screenOn && mScreenOn) {
mScreenOn = false;
becomeInactiveIfAppropriateLocked();
@@ -623,6 +682,7 @@
}
void updateChargingLocked(boolean charging) {
+ if (DEBUG) Slog.i(TAG, "updateChargingLocked: charging=" + charging);
if (!charging && mCharging) {
mCharging = false;
becomeInactiveIfAppropriateLocked();
@@ -639,6 +699,7 @@
}
void becomeActiveLocked(String reason) {
+ if (DEBUG) Slog.i(TAG, "becomeActiveLocked, reason = " + reason);
if (mState != STATE_ACTIVE) {
EventLogTags.writeDeviceIdle(STATE_ACTIVE, reason);
scheduleReportActiveLocked(false);
@@ -652,10 +713,12 @@
}
void becomeInactiveIfAppropriateLocked() {
+ if (DEBUG) Slog.d(TAG, "becomeInactiveIfAppropriateLocked()");
if (!mScreenOn && !mCharging && !mIdleDisabled && mState == STATE_ACTIVE) {
// Screen has turned off; we are now going to become inactive and start
// waiting to see if we will ultimately go idle.
mState = STATE_INACTIVE;
+ if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE");
mNextIdlePendingDelay = 0;
mNextIdleDelay = 0;
scheduleAlarmLocked(mInactiveTimeout, false);
@@ -663,7 +726,17 @@
}
}
+ /**
+ * This is called when we've failed to receive a callback from AnyMotionDetector
+ * within the DEFAULT_SENSING_TIMEOUT, to return to STATE_INACTIVE.
+ */
+ void enterInactiveStateLocked() {
+ mInactiveTimeout = DEFAULT_INACTIVE_TIMEOUT;
+ becomeInactiveIfAppropriateLocked();
+ }
+
void stepIdleStateLocked() {
+ if (DEBUG) Slog.d(TAG, "stepIdleStateLocked: mState=" + mState);
EventLogTags.writeDeviceIdleStep();
final long now = SystemClock.elapsedRealtime();
@@ -685,26 +758,34 @@
mNextIdlePendingDelay = DEFAULT_IDLE_PENDING_TIMEOUT;
mNextIdleDelay = DEFAULT_IDLE_TIMEOUT;
mState = STATE_IDLE_PENDING;
+ if (DEBUG) Slog.d(TAG, "Moved from STATE_INACTIVE to STATE_IDLE_PENDING.");
EventLogTags.writeDeviceIdle(mState, "step");
break;
case STATE_IDLE_PENDING:
+ mState = STATE_SENSING;
+ if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE_PENDING to STATE_SENSING.");
+ scheduleSensingAlarmLocked(DEFAULT_SENSING_TIMEOUT);
+ mAnyMotionDetector.checkForAnyMotion();
+ break;
+ case STATE_SENSING:
+ cancelSensingAlarmLocked();
case STATE_IDLE_MAINTENANCE:
- // We have been waiting to become idle, and now it is time! This is the
- // only case where we want to use a wakeup alarm, because we do want to
- // drag the device out of its sleep state in this case to do the next
- // scheduled work.
scheduleAlarmLocked(mNextIdleDelay, true);
- mNextIdleDelay = (long)(mNextIdleDelay*DEFAULT_IDLE_FACTOR);
+ if (DEBUG) Slog.d(TAG, "Moved to STATE_IDLE. Next alarm in " + mNextIdleDelay +
+ " ms.");
+ mNextIdleDelay = (long)(mNextIdleDelay * DEFAULT_IDLE_FACTOR);
+ if (DEBUG) Slog.d(TAG, "Setting mNextIdleDelay = " + mNextIdleDelay);
if (mNextIdleDelay > DEFAULT_MAX_IDLE_TIMEOUT) {
mNextIdleDelay = DEFAULT_MAX_IDLE_TIMEOUT;
}
mState = STATE_IDLE;
- EventLogTags.writeDeviceIdle(mState, "step");
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
break;
case STATE_IDLE:
// We have been idling long enough, now it is time to do some work.
scheduleAlarmLocked(mNextIdlePendingDelay, false);
+ if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE to STATE_IDLE_MAINTENANCE. " +
+ "Next alarm in " + mNextIdlePendingDelay + " ms.");
mNextIdlePendingDelay = (long)(mNextIdlePendingDelay*DEFAULT_IDLE_PENDING_FACTOR);
if (mNextIdlePendingDelay > DEFAULT_MAX_IDLE_PENDING_TIMEOUT) {
mNextIdlePendingDelay = DEFAULT_MAX_IDLE_PENDING_TIMEOUT;
@@ -717,6 +798,7 @@
}
void significantMotionLocked() {
+ if (DEBUG) Slog.d(TAG, "significantMotionLocked()");
// When the sensor goes off, its trigger is automatically removed.
mSigMotionActive = false;
// The device is not yet active, so we want to go back to the pending idle
@@ -732,6 +814,7 @@
}
void startMonitoringSignificantMotion() {
+ if (DEBUG) Slog.d(TAG, "startMonitoringSignificantMotion()");
if (mSigMotionSensor != null && !mSigMotionActive) {
mSensorManager.requestTriggerSensor(mSigMotionListener, mSigMotionSensor);
mSigMotionActive = true;
@@ -739,6 +822,7 @@
}
void stopMonitoringSignificantMotion() {
+ if (DEBUG) Slog.d(TAG, "stopMonitoringSignificantMotion()");
if (mSigMotionActive) {
mSensorManager.cancelTriggerSensor(mSigMotionListener, mSigMotionSensor);
mSigMotionActive = false;
@@ -752,7 +836,13 @@
}
}
+ void cancelSensingAlarmLocked() {
+ if (DEBUG) Slog.d(TAG, "cancelSensingAlarmLocked()");
+ mAlarmManager.cancel(mSensingAlarmIntent);
+ }
+
void scheduleAlarmLocked(long delay, boolean idleUntil) {
+ if (DEBUG) Slog.d(TAG, "scheduleAlarmLocked(" + delay + ", " + idleUntil + ")");
if (mSigMotionSensor == null) {
// If there is no significant motion sensor on this device, then we won't schedule
// alarms, because we can't determine if the device is not moving. This effectively
@@ -770,6 +860,13 @@
}
}
+ void scheduleSensingAlarmLocked(long delay) {
+ if (DEBUG) Slog.d(TAG, "scheduleSensingAlarmLocked(" + delay + ")");
+ mNextAlarmTime = SystemClock.elapsedRealtime() + delay;
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ mNextAlarmTime, mSensingAlarmIntent);
+ }
+
private void updateWhitelistAppIdsLocked() {
mPowerSaveWhitelistAppIds.clear();
for (int i=0; i<mPowerSaveWhitelistApps.size(); i++) {
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index bc93268..95f1852 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -92,7 +92,7 @@
IPhoneStateListener callback;
IOnSubscriptionsChangedListener onSubscriptionsChangedListenerCallback;
- int callerUid;
+ int callerUserId;
int events;
@@ -100,6 +100,8 @@
int phoneId = SubscriptionManager.INVALID_PHONE_INDEX;
+ boolean canReadPhoneState;
+
boolean matchPhoneStateListenerEvent(int events) {
return (callback != null) && ((events & this.events) != 0);
}
@@ -114,8 +116,9 @@
+ " callback=" + callback
+ " onSubscriptionsChangedListenererCallback="
+ onSubscriptionsChangedListenerCallback
- + " callerUid=" + callerUid + " subId=" + subId + " phoneId=" + phoneId
- + " events=" + Integer.toHexString(events) + "}";
+ + " callerUserId=" + callerUserId + " subId=" + subId + " phoneId=" + phoneId
+ + " events=" + Integer.toHexString(events)
+ + " canReadPhoneState=" + canReadPhoneState + "}";
}
}
@@ -190,13 +193,15 @@
private PreciseDataConnectionState mPreciseDataConnectionState =
new PreciseDataConnectionState();
- static final int PHONE_STATE_PERMISSION_MASK =
+ static final int ENFORCE_PHONE_STATE_PERMISSION_MASK =
PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR |
+ PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR |
+ PhoneStateListener.LISTEN_VOLTE_STATE;
+
+ static final int CHECK_PHONE_STATE_PERMISSION_MASK =
PhoneStateListener.LISTEN_CALL_STATE |
PhoneStateListener.LISTEN_DATA_ACTIVITY |
- PhoneStateListener.LISTEN_DATA_CONNECTION_STATE |
- PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR |
- PhoneStateListener.LISTEN_VOLTE_STATE;;
+ PhoneStateListener.LISTEN_DATA_CONNECTION_STATE;
static final int PRECISE_PHONE_STATE_PERMISSION_MASK =
PhoneStateListener.LISTEN_PRECISE_CALL_STATE |
@@ -348,11 +353,10 @@
@Override
public void addOnSubscriptionsChangedListener(String callingPackage,
IOnSubscriptionsChangedListener callback) {
- int callerUid = UserHandle.getCallingUserId();
- int myUid = UserHandle.myUserId();
+ int callerUserId = UserHandle.getCallingUserId();
if (VDBG) {
- log("listen oscl: E pkg=" + callingPackage + " myUid=" + myUid
- + " callerUid=" + callerUid + " callback=" + callback
+ log("listen oscl: E pkg=" + callingPackage + " myUserId=" + UserHandle.myUserId()
+ + " callerUserId=" + callerUserId + " callback=" + callback
+ " callback.asBinder=" + callback.asBinder());
}
@@ -364,7 +368,7 @@
return;
}
- Record r = null;
+ final Record r;
synchronized (mRecords) {
// register
@@ -385,8 +389,9 @@
r.onSubscriptionsChangedListenerCallback = callback;
r.callingPackage = callingPackage;
- r.callerUid = callerUid;
+ r.callerUserId = callerUserId;
r.events = 0;
+ r.canReadPhoneState = true; // permission has been enforced above
if (DBG) {
log("listen oscl: Register r=" + r);
}
@@ -454,19 +459,18 @@
private void listen(String callingPackage, IPhoneStateListener callback, int events,
boolean notifyNow, int subId) {
- int callerUid = UserHandle.getCallingUserId();
- int myUid = UserHandle.myUserId();
+ int callerUserId = UserHandle.getCallingUserId();
if (VDBG) {
log("listen: E pkg=" + callingPackage + " events=0x" + Integer.toHexString(events)
- + " notifyNow=" + notifyNow + " subId=" + subId + " myUid=" + myUid
- + " callerUid=" + callerUid);
+ + " notifyNow=" + notifyNow + " subId=" + subId + " myUserId="
+ + UserHandle.myUserId() + " callerUserId=" + callerUserId);
}
if (events != PhoneStateListener.LISTEN_NONE) {
/* Checks permission and throws Security exception */
checkListenerPermission(events);
- if ((events & PHONE_STATE_PERMISSION_MASK) != 0) {
+ if ((events & ENFORCE_PHONE_STATE_PERMISSION_MASK) != 0) {
if (mAppOps.noteOp(AppOpsManager.OP_READ_PHONE_STATE, Binder.getCallingUid(),
callingPackage) != AppOpsManager.MODE_ALLOWED) {
return;
@@ -475,7 +479,7 @@
synchronized (mRecords) {
// register
- Record r = null;
+ Record r;
find_and_add: {
IBinder b = callback.asBinder();
final int N = mRecords.size();
@@ -493,7 +497,10 @@
r.callback = callback;
r.callingPackage = callingPackage;
- r.callerUid = callerUid;
+ r.callerUserId = callerUserId;
+ boolean isPhoneStateEvent = (events & (CHECK_PHONE_STATE_PERMISSION_MASK
+ | ENFORCE_PHONE_STATE_PERMISSION_MASK)) != 0;
+ r.canReadPhoneState = isPhoneStateEvent && canReadPhoneState(callingPackage);
// Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
// force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
@@ -558,7 +565,7 @@
if ((events & PhoneStateListener.LISTEN_CALL_STATE) != 0) {
try {
r.callback.onCallStateChanged(mCallState[phoneId],
- mCallIncomingNumber[phoneId]);
+ getCallIncomingNumber(r, phoneId));
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -638,6 +645,22 @@
}
}
+ private boolean canReadPhoneState(String callingPackage) {
+ boolean canReadPhoneState = mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED;
+ if (canReadPhoneState &&
+ mAppOps.noteOp(AppOpsManager.OP_READ_PHONE_STATE, Binder.getCallingUid(),
+ callingPackage) != AppOpsManager.MODE_ALLOWED) {
+ return false;
+ }
+ return canReadPhoneState;
+ }
+
+ private String getCallIncomingNumber(Record record, int phoneId) {
+ // Hide the number if record's process has no READ_PHONE_STATE permission
+ return record.canReadPhoneState ? mCallIncomingNumber[phoneId] : "";
+ }
+
private void remove(IBinder binder) {
synchronized (mRecords) {
final int recordCount = mRecords.size();
@@ -669,7 +692,8 @@
if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_CALL_STATE) &&
(r.subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)) {
try {
- r.callback.onCallStateChanged(state, incomingNumber);
+ String incomingNumberOrEmpty = r.canReadPhoneState ? incomingNumber : "";
+ r.callback.onCallStateChanged(state, incomingNumberOrEmpty);
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -699,7 +723,8 @@
(r.subId == subId) &&
(r.subId != SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)) {
try {
- r.callback.onCallStateChanged(state, incomingNumber);
+ String incomingNumberOrEmpty = getCallIncomingNumber(r, phoneId);
+ r.callback.onCallStateChanged(state, incomingNumberOrEmpty);
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -1538,7 +1563,7 @@
}
- if ((events & PHONE_STATE_PERMISSION_MASK) != 0) {
+ if ((events & ENFORCE_PHONE_STATE_PERMISSION_MASK) != 0) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.READ_PHONE_STATE, null);
}
@@ -1572,10 +1597,10 @@
boolean valid = false;
try {
foregroundUser = ActivityManager.getCurrentUser();
- valid = r.callerUid == foregroundUser && r.matchPhoneStateListenerEvent(events);
+ valid = r.callerUserId == foregroundUser && r.matchPhoneStateListenerEvent(events);
if (DBG | DBG_LOC) {
log("validateEventsAndUserLocked: valid=" + valid
- + " r.callerUid=" + r.callerUid + " foregroundUser=" + foregroundUser
+ + " r.callerUserId=" + r.callerUserId + " foregroundUser=" + foregroundUser
+ " r.events=" + r.events + " events=" + events);
}
} finally {
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index bb210f1..0042414 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -838,7 +838,8 @@
* <p>
* This could be in response to a preview request via
* {@link #onRequestConnectionDataUsage()}, or as a periodic update by the
- * {@link VideoProvider}.
+ * {@link VideoProvider}. Where periodic updates of data usage are provided, they should be
+ * provided at most for every 1 MB of data transferred and no more than once every 10 sec.
* <p>
* Received by the {@link InCallService} via
* {@link InCallService.VideoCall.Callback#onCallDataUsageChanged(long)}.
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index f7f4425..fb985ce 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -413,6 +413,8 @@
/**
* Clears the video call callback set via {@link #registerCallback}.
+ *
+ * @param callback The video call callback to clear.
*/
public abstract void unregisterCallback(VideoCall.Callback callback);
@@ -524,7 +526,8 @@
/**
* The {@link InCallService} extends this class to provide a means of receiving callbacks
- * from the {@link Connection.VideoProvider}.<p>
+ * from the {@link Connection.VideoProvider}.
+ * <p>
* When the {@link InCallService} receives the
* {@link Call.Callback#onVideoCallChanged(Call, VideoCall)} callback, it should create an
* instance its {@link VideoCall.Callback} implementation and set it on the
@@ -533,7 +536,7 @@
public static abstract class Callback {
/**
* Called when the {@link Connection.VideoProvider} receives a session modification
- * request is received from the peer device.
+ * request from the peer device.
* <p>
* The {@link InCallService} may potentially prompt the user to confirm whether they
* wish to accept the request, or decide to automatically accept the request. In either
diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java
index d62c08e..8f7b82f 100644
--- a/telecomm/java/android/telecom/RemoteConnection.java
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -22,6 +22,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.hardware.camera2.CameraManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -207,29 +208,111 @@
public void onExtrasChanged(RemoteConnection connection, @Nullable Bundle extras) {}
}
+ /**
+ * {@link RemoteConnection.VideoProvider} associated with a {@link RemoteConnection}. Used to
+ * receive video related events and control the video associated with a
+ * {@link RemoteConnection}.
+ *
+ * @see Connection.VideoProvider
+ */
public static class VideoProvider {
+ /**
+ * Callback class used by the {@link RemoteConnection.VideoProvider} to relay events from
+ * the {@link Connection.VideoProvider}.
+ */
public abstract static class Callback {
+ /**
+ * Reports a session modification request received from the
+ * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}.
+ *
+ * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method.
+ * @param videoProfile The requested video call profile.
+ * @see InCallService.VideoCall.Callback#onSessionModifyRequestReceived(VideoProfile)
+ * @see Connection.VideoProvider#receiveSessionModifyRequest(VideoProfile)
+ */
public void onSessionModifyRequestReceived(
VideoProvider videoProvider,
VideoProfile videoProfile) {}
+ /**
+ * Reports a session modification response received from the
+ * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}.
+ *
+ * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method.
+ * @param status Status of the session modify request.
+ * @param requestedProfile The original request which was sent to the peer device.
+ * @param responseProfile The actual profile changes made by the peer device.
+ * @see InCallService.VideoCall.Callback#onSessionModifyResponseReceived(int,
+ * VideoProfile, VideoProfile)
+ * @see Connection.VideoProvider#receiveSessionModifyResponse(int, VideoProfile,
+ * VideoProfile)
+ */
public void onSessionModifyResponseReceived(
VideoProvider videoProvider,
int status,
VideoProfile requestedProfile,
VideoProfile responseProfile) {}
+ /**
+ * Reports a call session event received from the {@link Connection.VideoProvider}
+ * associated with a {@link RemoteConnection}.
+ *
+ * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method.
+ * @param event The event.
+ * @see InCallService.VideoCall.Callback#onCallSessionEvent(int)
+ * @see Connection.VideoProvider#handleCallSessionEvent(int)
+ */
public void onCallSessionEvent(VideoProvider videoProvider, int event) {}
- public void onPeerDimensionsChanged(VideoProvider videoProvider, int width, int height) {}
+ /**
+ * Reports a change in the peer video dimensions received from the
+ * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}.
+ *
+ * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method.
+ * @param width The updated peer video width.
+ * @param height The updated peer video height.
+ * @see InCallService.VideoCall.Callback#onPeerDimensionsChanged(int, int)
+ * @see Connection.VideoProvider#changePeerDimensions(int, int)
+ */
+ public void onPeerDimensionsChanged(VideoProvider videoProvider, int width,
+ int height) {}
+ /**
+ * Reports a change in the data usage (in bytes) received from the
+ * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}.
+ *
+ * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method.
+ * @param dataUsage The updated data usage (in bytes).
+ * @see InCallService.VideoCall.Callback#onCallDataUsageChanged(long)
+ * @see Connection.VideoProvider#setCallDataUsage(long)
+ */
public void onCallDataUsageChanged(VideoProvider videoProvider, long dataUsage) {}
+ /**
+ * Reports a change in the capabilities of the current camera, received from the
+ * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}.
+ *
+ * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method.
+ * @param cameraCapabilities The changed camera capabilities.
+ * @see InCallService.VideoCall.Callback#onCameraCapabilitiesChanged(
+ * VideoProfile.CameraCapabilities)
+ * @see Connection.VideoProvider#changeCameraCapabilities(
+ * VideoProfile.CameraCapabilities)
+ */
public void onCameraCapabilitiesChanged(
VideoProvider videoProvider,
VideoProfile.CameraCapabilities cameraCapabilities) {}
+ /**
+ * Reports a change in the video quality received from the
+ * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}.
+ *
+ * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method.
+ * @param videoQuality The updated peer video quality.
+ * @see InCallService.VideoCall.Callback#onVideoQualityChanged(int)
+ * @see Connection.VideoProvider#changeVideoQuality(int)
+ */
public void onVideoQualityChanged(VideoProvider videoProvider, int videoQuality) {}
}
@@ -316,14 +399,32 @@
}
}
+ /**
+ * Registers a callback to receive commands and state changes for video calls.
+ *
+ * @param l The video call callback.
+ */
public void registerCallback(Callback l) {
mCallbacks.add(l);
}
+ /**
+ * Clears the video call callback set via {@link #registerCallback}.
+ *
+ * @param l The video call callback to clear.
+ */
public void unregisterCallback(Callback l) {
mCallbacks.remove(l);
}
+ /**
+ * Sets the camera to be used for the outgoing video for the
+ * {@link RemoteConnection.VideoProvider}.
+ *
+ * @param cameraId The id of the camera (use ids as reported by
+ * {@link CameraManager#getCameraIdList()}).
+ * @see Connection.VideoProvider#onSetCamera(String)
+ */
public void setCamera(String cameraId) {
try {
mVideoProviderBinder.setCamera(cameraId);
@@ -331,6 +432,13 @@
}
}
+ /**
+ * Sets the surface to be used for displaying a preview of what the user's camera is
+ * currently capturing for the {@link RemoteConnection.VideoProvider}.
+ *
+ * @param surface The {@link Surface}.
+ * @see Connection.VideoProvider#onSetPreviewSurface(Surface)
+ */
public void setPreviewSurface(Surface surface) {
try {
mVideoProviderBinder.setPreviewSurface(surface);
@@ -338,6 +446,13 @@
}
}
+ /**
+ * Sets the surface to be used for displaying the video received from the remote device for
+ * the {@link RemoteConnection.VideoProvider}.
+ *
+ * @param surface The {@link Surface}.
+ * @see Connection.VideoProvider#onSetDisplaySurface(Surface)
+ */
public void setDisplaySurface(Surface surface) {
try {
mVideoProviderBinder.setDisplaySurface(surface);
@@ -345,6 +460,13 @@
}
}
+ /**
+ * Sets the device orientation, in degrees, for the {@link RemoteConnection.VideoProvider}.
+ * Assumes that a standard portrait orientation of the device is 0 degrees.
+ *
+ * @param rotation The device orientation, in degrees.
+ * @see Connection.VideoProvider#onSetDeviceOrientation(int)
+ */
public void setDeviceOrientation(int rotation) {
try {
mVideoProviderBinder.setDeviceOrientation(rotation);
@@ -352,6 +474,12 @@
}
}
+ /**
+ * Sets camera zoom ratio for the {@link RemoteConnection.VideoProvider}.
+ *
+ * @param value The camera zoom ratio.
+ * @see Connection.VideoProvider#onSetZoom(float)
+ */
public void setZoom(float value) {
try {
mVideoProviderBinder.setZoom(value);
@@ -359,6 +487,14 @@
}
}
+ /**
+ * Issues a request to modify the properties of the current video session for the
+ * {@link RemoteConnection.VideoProvider}.
+ *
+ * @param fromProfile The video profile prior to the request.
+ * @param toProfile The video profile with the requested changes made.
+ * @see Connection.VideoProvider#onSendSessionModifyRequest(VideoProfile, VideoProfile)
+ */
public void sendSessionModifyRequest(VideoProfile fromProfile, VideoProfile toProfile) {
try {
mVideoProviderBinder.sendSessionModifyRequest(fromProfile, toProfile);
@@ -366,6 +502,13 @@
}
}
+ /**
+ * Provides a response to a request to change the current call video session
+ * properties for the {@link RemoteConnection.VideoProvider}.
+ *
+ * @param responseProfile The response call video properties.
+ * @see Connection.VideoProvider#onSendSessionModifyResponse(VideoProfile)
+ */
public void sendSessionModifyResponse(VideoProfile responseProfile) {
try {
mVideoProviderBinder.sendSessionModifyResponse(responseProfile);
@@ -373,6 +516,12 @@
}
}
+ /**
+ * Issues a request to retrieve the capabilities of the current camera for the
+ * {@link RemoteConnection.VideoProvider}.
+ *
+ * @see Connection.VideoProvider#onRequestCameraCapabilities()
+ */
public void requestCameraCapabilities() {
try {
mVideoProviderBinder.requestCameraCapabilities();
@@ -380,6 +529,12 @@
}
}
+ /**
+ * Issues a request to retrieve the data usage (in bytes) of the video portion of the
+ * {@link RemoteConnection} for the {@link RemoteConnection.VideoProvider}.
+ *
+ * @see Connection.VideoProvider#onRequestConnectionDataUsage()
+ */
public void requestCallDataUsage() {
try {
mVideoProviderBinder.requestCallDataUsage();
@@ -387,6 +542,12 @@
}
}
+ /**
+ * Sets the {@link Uri} of an image to be displayed to the peer device when the video signal
+ * is paused, for the {@link RemoteConnection.VideoProvider}.
+ *
+ * @see Connection.VideoProvider#onSetPauseImage(Uri)
+ */
public void setPauseImage(Uri uri) {
try {
mVideoProviderBinder.setPauseImage(uri);
diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index d192288..16472c8 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -120,8 +120,7 @@
/**
* Listen for changes to the device call state.
* {@more}
- * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE
- * READ_PHONE_STATE}
+ *
* @see #onCallStateChanged
*/
public static final int LISTEN_CALL_STATE = 0x00000020;
@@ -137,8 +136,6 @@
* Listen for changes to the direction of data traffic on the data
* connection (cellular).
* {@more}
- * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE
- * READ_PHONE_STATE}
* Example: The status bar uses this to display the appropriate
* data-traffic icon.
*
@@ -388,6 +385,10 @@
/**
* Callback invoked when device call state changes.
+ * @param state call state
+ * @param incomingNumber incoming call phone number. If application does not have
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} permission, an empty
+ * string will be passed as an argument.
*
* @see TelephonyManager#CALL_STATE_IDLE
* @see TelephonyManager#CALL_STATE_RINGING