Merge "Support for nested bundles in setApplicationRestrictions"
diff --git a/api/current.txt b/api/current.txt
index 8dd8f20..b3d5fd0 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2736,6 +2736,7 @@
}
public class AccountManager {
+ method public boolean accountAuthenticated(android.accounts.Account);
method public android.accounts.AccountManagerFuture<android.os.Bundle> addAccount(java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
method public boolean addAccountExplicitly(android.accounts.Account, java.lang.String, android.os.Bundle);
method public void addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean);
@@ -2796,6 +2797,7 @@
field public static final java.lang.String KEY_ERROR_CODE = "errorCode";
field public static final java.lang.String KEY_ERROR_MESSAGE = "errorMessage";
field public static final java.lang.String KEY_INTENT = "intent";
+ field public static final java.lang.String KEY_LAST_AUTHENTICATE_TIME_MILLIS_EPOCH = "lastAuthenticatedTimeMillisEpoch";
field public static final java.lang.String KEY_PASSWORD = "password";
field public static final java.lang.String KEY_USERDATA = "userdata";
field public static final java.lang.String LOGIN_ACCOUNTS_CHANGED_ACTION = "android.accounts.LOGIN_ACCOUNTS_CHANGED";
@@ -11169,6 +11171,7 @@
field public static final int NV21 = 17; // 0x11
field public static final int PRIVATE = 34; // 0x22
field public static final int RAW10 = 37; // 0x25
+ field public static final int RAW12 = 38; // 0x26
field public static final int RAW_SENSOR = 32; // 0x20
field public static final int RGB_565 = 4; // 0x4
field public static final int UNKNOWN = 0; // 0x0
@@ -22541,6 +22544,7 @@
field public static java.lang.String DIRECTORY_RINGTONES;
field public static final java.lang.String MEDIA_BAD_REMOVAL = "bad_removal";
field public static final java.lang.String MEDIA_CHECKING = "checking";
+ field public static final java.lang.String MEDIA_EJECTING = "ejecting";
field public static final java.lang.String MEDIA_MOUNTED = "mounted";
field public static final java.lang.String MEDIA_MOUNTED_READ_ONLY = "mounted_ro";
field public static final java.lang.String MEDIA_NOFS = "nofs";
@@ -26855,6 +26859,7 @@
method public void ioReceive();
method public void ioSend();
method public deprecated synchronized void resize(int);
+ method public void setAutoPadding(boolean);
method public void setFromFieldPacker(int, android.renderscript.FieldPacker);
method public void setFromFieldPacker(int, int, android.renderscript.FieldPacker);
method public void setFromFieldPacker(int, int, int, int, android.renderscript.FieldPacker);
@@ -27502,6 +27507,41 @@
method public android.renderscript.ScriptGroup create();
}
+ public class ScriptGroup2 extends android.renderscript.BaseObj {
+ ctor public ScriptGroup2(long, android.renderscript.RenderScript);
+ method public java.lang.Object[] execute(java.lang.Object...);
+ }
+
+ public static final class ScriptGroup2.Binding {
+ ctor public ScriptGroup2.Binding(android.renderscript.Script.FieldID, java.lang.Object);
+ method public android.renderscript.Script.FieldID getField();
+ method public java.lang.Object getValue();
+ }
+
+ public static final class ScriptGroup2.Builder {
+ ctor public ScriptGroup2.Builder(android.renderscript.RenderScript);
+ method public android.renderscript.ScriptGroup2.UnboundValue addInput();
+ method public android.renderscript.ScriptGroup2.Closure addInvoke(android.renderscript.Script.InvokeID, java.lang.Object[], java.util.Map<android.renderscript.Script.FieldID, java.lang.Object>);
+ method public android.renderscript.ScriptGroup2.Closure addInvoke(android.renderscript.Script.InvokeID, java.lang.Object...);
+ method public android.renderscript.ScriptGroup2.Closure addKernel(android.renderscript.Script.KernelID, android.renderscript.Type, java.lang.Object[], java.util.Map<android.renderscript.Script.FieldID, java.lang.Object>);
+ method public android.renderscript.ScriptGroup2.Closure addKernel(android.renderscript.Script.KernelID, android.renderscript.Type, java.lang.Object...);
+ method public android.renderscript.ScriptGroup2 create(android.renderscript.ScriptGroup2.Future...);
+ }
+
+ public static class ScriptGroup2.Closure extends android.renderscript.BaseObj {
+ ctor public ScriptGroup2.Closure(long, android.renderscript.RenderScript);
+ ctor public ScriptGroup2.Closure(android.renderscript.RenderScript, android.renderscript.Script.KernelID, android.renderscript.Type, java.lang.Object[], java.util.Map<android.renderscript.Script.FieldID, java.lang.Object>);
+ ctor public ScriptGroup2.Closure(android.renderscript.RenderScript, android.renderscript.Script.InvokeID, java.lang.Object[], java.util.Map<android.renderscript.Script.FieldID, java.lang.Object>);
+ method public android.renderscript.ScriptGroup2.Future getGlobal(android.renderscript.Script.FieldID);
+ method public android.renderscript.ScriptGroup2.Future getReturn();
+ }
+
+ public static class ScriptGroup2.Future {
+ }
+
+ public static class ScriptGroup2.UnboundValue {
+ }
+
public abstract class ScriptIntrinsic extends android.renderscript.Script {
}
@@ -34294,8 +34334,10 @@
method public long getTimeDelta();
method public boolean isInProgress();
method public boolean isQuickScaleEnabled();
+ method public boolean isSecondaryButtonScaleEnabled();
method public boolean onTouchEvent(android.view.MotionEvent);
method public void setQuickScaleEnabled(boolean);
+ method public void setSecondaryButtonScaleEnabled(boolean);
}
public static abstract interface ScaleGestureDetector.OnScaleGestureListener {
diff --git a/api/system-current.txt b/api/system-current.txt
index b1b5cf3..9462054 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2815,6 +2815,7 @@
}
public class AccountManager {
+ method public boolean accountAuthenticated(android.accounts.Account);
method public android.accounts.AccountManagerFuture<android.os.Bundle> addAccount(java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
method public boolean addAccountExplicitly(android.accounts.Account, java.lang.String, android.os.Bundle);
method public void addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean);
@@ -2875,6 +2876,7 @@
field public static final java.lang.String KEY_ERROR_CODE = "errorCode";
field public static final java.lang.String KEY_ERROR_MESSAGE = "errorMessage";
field public static final java.lang.String KEY_INTENT = "intent";
+ field public static final java.lang.String KEY_LAST_AUTHENTICATE_TIME_MILLIS_EPOCH = "lastAuthenticatedTimeMillisEpoch";
field public static final java.lang.String KEY_PASSWORD = "password";
field public static final java.lang.String KEY_USERDATA = "userdata";
field public static final java.lang.String LOGIN_ACCOUNTS_CHANGED_ACTION = "android.accounts.LOGIN_ACCOUNTS_CHANGED";
@@ -11455,6 +11457,7 @@
field public static final int NV21 = 17; // 0x11
field public static final int PRIVATE = 34; // 0x22
field public static final int RAW10 = 37; // 0x25
+ field public static final int RAW12 = 38; // 0x26
field public static final int RAW_SENSOR = 32; // 0x20
field public static final int RGB_565 = 4; // 0x4
field public static final int UNKNOWN = 0; // 0x0
@@ -14204,6 +14207,7 @@
method public void setHdmiMhlVendorCommandListener(android.hardware.hdmi.HdmiTvClient.HdmiMhlVendorCommandListener);
method public void setInputChangeListener(android.hardware.hdmi.HdmiTvClient.InputChangeListener);
method public void setRecordListener(android.hardware.hdmi.HdmiRecordListener);
+ method public void setSystemAudioMode(boolean, android.hardware.hdmi.HdmiTvClient.SelectCallback);
method public void setSystemAudioMute(boolean);
method public void setSystemAudioVolume(int, int, int);
method public void startOneTouchRecord(int, android.hardware.hdmi.HdmiRecordSources.RecordSource);
@@ -24404,6 +24408,7 @@
field public static java.lang.String DIRECTORY_RINGTONES;
field public static final java.lang.String MEDIA_BAD_REMOVAL = "bad_removal";
field public static final java.lang.String MEDIA_CHECKING = "checking";
+ field public static final java.lang.String MEDIA_EJECTING = "ejecting";
field public static final java.lang.String MEDIA_MOUNTED = "mounted";
field public static final java.lang.String MEDIA_MOUNTED_READ_ONLY = "mounted_ro";
field public static final java.lang.String MEDIA_NOFS = "nofs";
@@ -28833,6 +28838,7 @@
method public void ioReceive();
method public void ioSend();
method public deprecated synchronized void resize(int);
+ method public void setAutoPadding(boolean);
method public void setFromFieldPacker(int, android.renderscript.FieldPacker);
method public void setFromFieldPacker(int, int, android.renderscript.FieldPacker);
method public void setFromFieldPacker(int, int, int, int, android.renderscript.FieldPacker);
@@ -29480,6 +29486,41 @@
method public android.renderscript.ScriptGroup create();
}
+ public class ScriptGroup2 extends android.renderscript.BaseObj {
+ ctor public ScriptGroup2(long, android.renderscript.RenderScript);
+ method public java.lang.Object[] execute(java.lang.Object...);
+ }
+
+ public static final class ScriptGroup2.Binding {
+ ctor public ScriptGroup2.Binding(android.renderscript.Script.FieldID, java.lang.Object);
+ method public android.renderscript.Script.FieldID getField();
+ method public java.lang.Object getValue();
+ }
+
+ public static final class ScriptGroup2.Builder {
+ ctor public ScriptGroup2.Builder(android.renderscript.RenderScript);
+ method public android.renderscript.ScriptGroup2.UnboundValue addInput();
+ method public android.renderscript.ScriptGroup2.Closure addInvoke(android.renderscript.Script.InvokeID, java.lang.Object[], java.util.Map<android.renderscript.Script.FieldID, java.lang.Object>);
+ method public android.renderscript.ScriptGroup2.Closure addInvoke(android.renderscript.Script.InvokeID, java.lang.Object...);
+ method public android.renderscript.ScriptGroup2.Closure addKernel(android.renderscript.Script.KernelID, android.renderscript.Type, java.lang.Object[], java.util.Map<android.renderscript.Script.FieldID, java.lang.Object>);
+ method public android.renderscript.ScriptGroup2.Closure addKernel(android.renderscript.Script.KernelID, android.renderscript.Type, java.lang.Object...);
+ method public android.renderscript.ScriptGroup2 create(android.renderscript.ScriptGroup2.Future...);
+ }
+
+ public static class ScriptGroup2.Closure extends android.renderscript.BaseObj {
+ ctor public ScriptGroup2.Closure(long, android.renderscript.RenderScript);
+ ctor public ScriptGroup2.Closure(android.renderscript.RenderScript, android.renderscript.Script.KernelID, android.renderscript.Type, java.lang.Object[], java.util.Map<android.renderscript.Script.FieldID, java.lang.Object>);
+ ctor public ScriptGroup2.Closure(android.renderscript.RenderScript, android.renderscript.Script.InvokeID, java.lang.Object[], java.util.Map<android.renderscript.Script.FieldID, java.lang.Object>);
+ method public android.renderscript.ScriptGroup2.Future getGlobal(android.renderscript.Script.FieldID);
+ method public android.renderscript.ScriptGroup2.Future getReturn();
+ }
+
+ public static class ScriptGroup2.Future {
+ }
+
+ public static class ScriptGroup2.UnboundValue {
+ }
+
public abstract class ScriptIntrinsic extends android.renderscript.Script {
}
@@ -36835,8 +36876,10 @@
method public long getTimeDelta();
method public boolean isInProgress();
method public boolean isQuickScaleEnabled();
+ method public boolean isSecondaryButtonScaleEnabled();
method public boolean onTouchEvent(android.view.MotionEvent);
method public void setQuickScaleEnabled(boolean);
+ method public void setSecondaryButtonScaleEnabled(boolean);
}
public static abstract interface ScaleGestureDetector.OnScaleGestureListener {
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 6957435c..480d171 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -203,6 +203,14 @@
public static final String KEY_USERDATA = "userdata";
/**
+ * Bundle key used to supply the last time the credentials of the account
+ * were authenticated successfully. Time is specified in milliseconds since
+ * epoch.
+ */
+ public static final String KEY_LAST_AUTHENTICATE_TIME_MILLIS_EPOCH =
+ "lastAuthenticatedTimeMillisEpoch";
+
+ /**
* Authenticators using 'customTokens' option will also get the UID of the
* caller
*/
@@ -663,6 +671,31 @@
}
/**
+ * Informs the system that the account has been authenticated recently. This
+ * recency may be used by other applications to verify the account. This
+ * should be called only when the user has entered correct credentials for
+ * the account.
+ * <p>
+ * It is not safe to call this method from the main thread. As such, call it
+ * from another thread.
+ * <p>
+ * This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and should be
+ * called from the account's authenticator.
+ *
+ * @param account The {@link Account} to be updated.
+ */
+ public boolean accountAuthenticated(Account account) {
+ if (account == null)
+ throw new IllegalArgumentException("account is null");
+ try {
+ return mService.accountAuthenticated(account);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
* Rename the specified {@link Account}. This is equivalent to removing
* the existing account and adding a new renamed account with the old
* account's user data.
@@ -1544,15 +1577,20 @@
* with these fields if activity or password was supplied and
* the account was successfully verified:
* <ul>
- * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account created
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account verified
* <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
* <li> {@link #KEY_BOOLEAN_RESULT} - true to indicate success
* </ul>
*
* If no activity or password was specified, the returned Bundle contains
- * only {@link #KEY_INTENT} with the {@link Intent} needed to launch the
- * password prompt. If an error occurred,
- * {@link AccountManagerFuture#getResult()} throws:
+ * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+ * password prompt.
+ *
+ * <p>Also the returning Bundle may contain {@link
+ * #KEY_LAST_AUTHENTICATE_TIME_MILLIS_EPOCH} indicating the last time the
+ * credential was validated/created.
+ *
+ * If an error occurred,{@link AccountManagerFuture#getResult()} throws:
* <ul>
* <li> {@link AuthenticatorException} if the authenticator failed to respond
* <li> {@link OperationCanceledException} if the operation was canceled for
@@ -1625,9 +1663,9 @@
* <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
* </ul>
*
- * If no activity was specified, the returned Bundle contains only
+ * If no activity was specified, the returned Bundle contains
* {@link #KEY_INTENT} with the {@link Intent} needed to launch the
- * password prompt. If an error occurred,
+ * password prompt. If an error occurred,
* {@link AccountManagerFuture#getResult()} throws:
* <ul>
* <li> {@link AuthenticatorException} if the authenticator failed to respond
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index aa41161..04b3c88 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -67,6 +67,7 @@
boolean expectActivityLaunch);
void confirmCredentialsAsUser(in IAccountManagerResponse response, in Account account,
in Bundle options, boolean expectActivityLaunch, int userId);
+ boolean accountAuthenticated(in Account account);
void getAuthTokenLabel(in IAccountManagerResponse response, String accountType,
String authTokenType);
diff --git a/core/java/android/animation/FloatKeyframeSet.java b/core/java/android/animation/FloatKeyframeSet.java
index 56da940..0173079 100644
--- a/core/java/android/animation/FloatKeyframeSet.java
+++ b/core/java/android/animation/FloatKeyframeSet.java
@@ -118,13 +118,14 @@
FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(i);
if (fraction < nextKeyframe.getFraction()) {
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
- if (interpolator != null) {
- fraction = interpolator.getInterpolation(fraction);
- }
float intervalFraction = (fraction - prevKeyframe.getFraction()) /
(nextKeyframe.getFraction() - prevKeyframe.getFraction());
float prevValue = prevKeyframe.getFloatValue();
float nextValue = nextKeyframe.getFloatValue();
+ // Apply interpolator on the proportional duration.
+ if (interpolator != null) {
+ intervalFraction = interpolator.getInterpolation(intervalFraction);
+ }
return mEvaluator == null ?
prevValue + intervalFraction * (nextValue - prevValue) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
diff --git a/core/java/android/animation/IntKeyframeSet.java b/core/java/android/animation/IntKeyframeSet.java
index 12a4bf9..73f9af1 100644
--- a/core/java/android/animation/IntKeyframeSet.java
+++ b/core/java/android/animation/IntKeyframeSet.java
@@ -117,13 +117,14 @@
IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(i);
if (fraction < nextKeyframe.getFraction()) {
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
- if (interpolator != null) {
- fraction = interpolator.getInterpolation(fraction);
- }
float intervalFraction = (fraction - prevKeyframe.getFraction()) /
(nextKeyframe.getFraction() - prevKeyframe.getFraction());
int prevValue = prevKeyframe.getIntValue();
int nextValue = nextKeyframe.getIntValue();
+ // Apply interpolator on the proportional duration.
+ if (interpolator != null) {
+ intervalFraction = interpolator.getInterpolation(intervalFraction);
+ }
return mEvaluator == null ?
prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
diff --git a/core/java/android/animation/KeyframeSet.java b/core/java/android/animation/KeyframeSet.java
index c80e162..32edd4d 100644
--- a/core/java/android/animation/KeyframeSet.java
+++ b/core/java/android/animation/KeyframeSet.java
@@ -201,7 +201,6 @@
* @return The animated value.
*/
public Object getValue(float fraction) {
-
// Special-case optimization for the common case of only two keyframes
if (mNumKeyframes == 2) {
if (mInterpolator != null) {
@@ -238,12 +237,13 @@
Keyframe nextKeyframe = mKeyframes.get(i);
if (fraction < nextKeyframe.getFraction()) {
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
- if (interpolator != null) {
- fraction = interpolator.getInterpolation(fraction);
- }
final float prevFraction = prevKeyframe.getFraction();
float intervalFraction = (fraction - prevFraction) /
(nextKeyframe.getFraction() - prevFraction);
+ // Apply interpolator on the proportional duration.
+ if (interpolator != null) {
+ intervalFraction = interpolator.getInterpolation(intervalFraction);
+ }
return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
nextKeyframe.getValue());
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 59fe490..a0f40f6 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -402,13 +402,7 @@
new CachedServiceFetcher<StorageManager>() {
@Override
public StorageManager createService(ContextImpl ctx) {
- try {
- return new StorageManager(
- ctx.getContentResolver(), ctx.mMainThread.getHandler().getLooper());
- } catch (RemoteException rex) {
- Log.e(TAG, "Failed to create StorageManager", rex);
- return null;
- }
+ return new StorageManager(ctx, ctx.mMainThread.getHandler().getLooper());
}});
registerService(Context.TELEPHONY_SERVICE, TelephonyManager.class,
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index e20057d..4b81fd4 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2750,15 +2750,21 @@
}
}
- addSharedLibrariesForBackwardCompatibility(owner);
+ modifySharedLibrariesForBackwardCompatibility(owner);
return true;
}
- private static void addSharedLibrariesForBackwardCompatibility(Package owner) {
- if (owner.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1) {
- owner.usesLibraries = ArrayUtils.add(owner.usesLibraries, "org.apache.http.legacy");
- }
+ private static void modifySharedLibrariesForBackwardCompatibility(Package owner) {
+ // "org.apache.http.legacy" is now a part of the boot classpath so it doesn't need
+ // to be an explicit dependency.
+ //
+ // A future change will remove this library from the boot classpath, at which point
+ // all apps that target SDK 21 and earlier will have it automatically added to their
+ // dependency lists.
+ owner.usesLibraries = ArrayUtils.remove(owner.usesLibraries, "org.apache.http.legacy");
+ owner.usesOptionalLibraries = ArrayUtils.remove(owner.usesOptionalLibraries,
+ "org.apache.http.legacy");
}
/**
diff --git a/core/java/android/hardware/ICameraService.aidl b/core/java/android/hardware/ICameraService.aidl
index d5dfaf6..9bc2f46 100644
--- a/core/java/android/hardware/ICameraService.aidl
+++ b/core/java/android/hardware/ICameraService.aidl
@@ -75,4 +75,11 @@
out BinderHolder device);
int setTorchMode(String CameraId, boolean enabled, IBinder clientBinder);
+
+ /**
+ * Notify the camera service of a system event. Should only be called from system_server.
+ *
+ * Callers require the android.permission.CAMERA_SEND_SYSTEM_EVENTS permission.
+ */
+ oneway void notifySystemEvent(int eventId, int arg0);
}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index a0217c2..1503bf5 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -991,7 +991,8 @@
* <ul>
* <li>Processed (but stalling): any non-RAW format with a stallDurations > 0.
* Typically JPEG format (ImageFormat#JPEG).</li>
- * <li>Raw formats: ImageFormat#RAW_SENSOR, ImageFormat#RAW10 and ImageFormat#RAW_OPAQUE.</li>
+ * <li>Raw formats: ImageFormat#RAW_SENSOR, ImageFormat#RAW10, ImageFormat#RAW12,
+ * and ImageFormat#RAW_OPAQUE.</li>
* <li>Processed (but not-stalling): any non-RAW format without a stall duration.
* Typically ImageFormat#YUV_420_888, ImageFormat#NV21, ImageFormat#YV12.</li>
* </ul>
@@ -1023,6 +1024,7 @@
* <ul>
* <li>ImageFormat#RAW_SENSOR</li>
* <li>ImageFormat#RAW10</li>
+ * <li>ImageFormat#RAW12</li>
* <li>Opaque <code>RAW</code></li>
* </ul>
* <p>LEGACY mode devices ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} <code>==</code> LEGACY)
diff --git a/core/java/android/hardware/hdmi/HdmiTvClient.java b/core/java/android/hardware/hdmi/HdmiTvClient.java
index a94c1da..a336e5c 100644
--- a/core/java/android/hardware/hdmi/HdmiTvClient.java
+++ b/core/java/android/hardware/hdmi/HdmiTvClient.java
@@ -168,7 +168,22 @@
}
/**
- * Sets system audio volume
+ * Sets system audio mode.
+ *
+ * @param enabled set to {@code true} to enable the mode; otherwise {@code false}
+ * @param callback callback to get the result with
+ * @throws {@link IllegalArgumentException} if the {@code callback} is null
+ */
+ public void setSystemAudioMode(boolean enabled, SelectCallback callback) {
+ try {
+ mService.setSystemAudioMode(enabled, getCallbackWrapper(callback));
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to set system audio mode:", e);
+ }
+ }
+
+ /**
+ * Sets system audio volume.
*
* @param oldIndex current volume index
* @param newIndex volume index to be set
@@ -183,7 +198,7 @@
}
/**
- * Sets system audio mute status
+ * Sets system audio mute status.
*
* @param mute {@code true} if muted; otherwise, {@code false}
*/
@@ -196,7 +211,7 @@
}
/**
- * Sets record listener
+ * Sets record listener.
*
* @param listener
*/
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index a0e2bf8..3abccbc 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -40,6 +40,7 @@
import android.util.ArrayMap;
import android.util.Log;
+import com.android.internal.net.VpnConfig;
import com.android.internal.telephony.ITelephony;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.util.Protocol;
@@ -2447,6 +2448,38 @@
}
/**
+ * Resets all connectivity manager settings back to factory defaults.
+ * @hide
+ */
+ public void factoryReset() {
+ // Turn airplane mode off
+ setAirplaneMode(false);
+
+ // Untether
+ for (String tether : getTetheredIfaces()) {
+ untether(tether);
+ }
+
+ // Turn VPN off
+ try {
+ VpnConfig vpnConfig = mService.getVpnConfig();
+ if (vpnConfig != null) {
+ if (vpnConfig.legacy) {
+ mService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN);
+ } else {
+ // Prevent this app from initiating VPN connections in the future without
+ // user intervention.
+ mService.setVpnPackageAuthorization(false);
+
+ mService.prepareVpn(vpnConfig.user, VpnConfig.LEGACY_VPN);
+ }
+ }
+ } catch (RemoteException e) {
+ // Well, we tried
+ }
+ }
+
+ /**
* Binds the current process to {@code network}. All Sockets created in the future
* (and not explicitly bound via a bound SocketFactory from
* {@link Network#getSocketFactory() Network.getSocketFactory()}) will be bound to
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index a8e7757..a7ffee9 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -180,6 +180,33 @@
}
/**
+ * Resets network policy settings back to factory defaults.
+ *
+ * @hide
+ */
+ public void factoryReset(String subscriber) {
+ // Turn mobile data limit off
+ NetworkPolicy[] policies = getNetworkPolicies();
+ NetworkTemplate template = NetworkTemplate.buildTemplateMobileAll(subscriber);
+ for (NetworkPolicy policy : policies) {
+ if (policy.template.equals(template)) {
+ policy.limitBytes = NetworkPolicy.LIMIT_DISABLED;
+ policy.inferred = false;
+ policy.clearSnooze();
+ }
+ }
+ setNetworkPolicies(policies);
+
+ // Turn restrict background data off
+ setRestrictBackground(false);
+
+ // Remove app's "restrict background data" flag
+ for (int uid : getUidsWithPolicy(POLICY_REJECT_METERED_BACKGROUND)) {
+ setUidPolicy(uid, NetworkPolicyManager.POLICY_NONE);
+ }
+ }
+
+ /**
* Compute the last cycle boundary for the given {@link NetworkPolicy}. For
* example, if cycle day is 20th, and today is June 15th, it will return May
* 20th. When cycle day doesn't exist in current month, it snaps to the 1st
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 3f18519..ff3de2b 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -51,6 +51,8 @@
public static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
/** @hide */
public static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
+ /** @hide */
+ public static final long TB_IN_BYTES = GB_IN_BYTES * 1024;
/**
* Special UID value used when collecting {@link NetworkStatsHistory} for
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 975bfc2..2db976e 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -18,16 +18,11 @@
import android.app.admin.DevicePolicyManager;
import android.content.Context;
-import android.os.storage.IMountService;
+import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
-import android.text.TextUtils;
import android.util.Log;
-import com.google.android.collect.Lists;
-
import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
/**
* Provides access to environment variables.
@@ -36,11 +31,9 @@
private static final String TAG = "Environment";
private static final String ENV_EXTERNAL_STORAGE = "EXTERNAL_STORAGE";
- private static final String ENV_EMULATED_STORAGE_SOURCE = "EMULATED_STORAGE_SOURCE";
- private static final String ENV_EMULATED_STORAGE_TARGET = "EMULATED_STORAGE_TARGET";
- private static final String ENV_MEDIA_STORAGE = "MEDIA_STORAGE";
- private static final String ENV_SECONDARY_STORAGE = "SECONDARY_STORAGE";
private static final String ENV_ANDROID_ROOT = "ANDROID_ROOT";
+ private static final String ENV_ANDROID_DATA = "ANDROID_DATA";
+ private static final String ENV_ANDROID_STORAGE = "ANDROID_STORAGE";
private static final String ENV_OEM_ROOT = "OEM_ROOT";
private static final String ENV_VENDOR_ROOT = "VENDOR_ROOT";
@@ -57,12 +50,10 @@
public static final String DIRECTORY_ANDROID = DIR_ANDROID;
private static final File DIR_ANDROID_ROOT = getDirectory(ENV_ANDROID_ROOT, "/system");
+ private static final File DIR_ANDROID_DATA = getDirectory(ENV_ANDROID_DATA, "/data");
+ private static final File DIR_ANDROID_STORAGE = getDirectory(ENV_ANDROID_STORAGE, "/storage");
private static final File DIR_OEM_ROOT = getDirectory(ENV_OEM_ROOT, "/oem");
private static final File DIR_VENDOR_ROOT = getDirectory(ENV_VENDOR_ROOT, "/vendor");
- private static final File DIR_MEDIA_STORAGE = getDirectory(ENV_MEDIA_STORAGE, "/data/media");
-
- private static final String CANONCIAL_EMULATED_STORAGE_TARGET = getCanonicalPathOrNull(
- ENV_EMULATED_STORAGE_TARGET);
private static final String SYSTEM_PROPERTY_EFS_ENABLED = "persist.security.efs.enabled";
@@ -81,73 +72,24 @@
/** {@hide} */
public static class UserEnvironment {
- // TODO: generalize further to create package-specific environment
-
- /** External storage dirs, as visible to vold */
- private final File[] mExternalDirsForVold;
- /** External storage dirs, as visible to apps */
- private final File[] mExternalDirsForApp;
- /** Primary emulated storage dir for direct access */
- private final File mEmulatedDirForDirect;
+ private final int mUserId;
public UserEnvironment(int userId) {
- // See storage config details at http://source.android.com/tech/storage/
- String rawExternalStorage = System.getenv(ENV_EXTERNAL_STORAGE);
- String rawEmulatedSource = System.getenv(ENV_EMULATED_STORAGE_SOURCE);
- String rawEmulatedTarget = System.getenv(ENV_EMULATED_STORAGE_TARGET);
+ mUserId = userId;
+ }
- String rawMediaStorage = System.getenv(ENV_MEDIA_STORAGE);
- if (TextUtils.isEmpty(rawMediaStorage)) {
- rawMediaStorage = "/data/media";
+ public File[] getExternalDirs() {
+ final StorageVolume[] volumes = StorageManager.getVolumeList(mUserId);
+ final File[] dirs = new File[volumes.length];
+ for (int i = 0; i < volumes.length; i++) {
+ dirs[i] = volumes[i].getPathFile();
}
-
- ArrayList<File> externalForVold = Lists.newArrayList();
- ArrayList<File> externalForApp = Lists.newArrayList();
-
- if (!TextUtils.isEmpty(rawEmulatedTarget)) {
- // Device has emulated storage; external storage paths should have
- // userId burned into them.
- final String rawUserId = Integer.toString(userId);
- final File emulatedSourceBase = new File(rawEmulatedSource);
- final File emulatedTargetBase = new File(rawEmulatedTarget);
- final File mediaBase = new File(rawMediaStorage);
-
- // /storage/emulated/0
- externalForVold.add(buildPath(emulatedSourceBase, rawUserId));
- externalForApp.add(buildPath(emulatedTargetBase, rawUserId));
- // /data/media/0
- mEmulatedDirForDirect = buildPath(mediaBase, rawUserId);
-
- } else {
- // Device has physical external storage; use plain paths.
- if (TextUtils.isEmpty(rawExternalStorage)) {
- Log.w(TAG, "EXTERNAL_STORAGE undefined; falling back to default");
- rawExternalStorage = "/storage/sdcard0";
- }
-
- // /storage/sdcard0
- externalForVold.add(new File(rawExternalStorage));
- externalForApp.add(new File(rawExternalStorage));
- // /data/media
- mEmulatedDirForDirect = new File(rawMediaStorage);
- }
-
- // Splice in any secondary storage paths, but only for owner
- final String rawSecondaryStorage = System.getenv(ENV_SECONDARY_STORAGE);
- if (!TextUtils.isEmpty(rawSecondaryStorage) && userId == UserHandle.USER_OWNER) {
- for (String secondaryPath : rawSecondaryStorage.split(":")) {
- externalForVold.add(new File(secondaryPath));
- externalForApp.add(new File(secondaryPath));
- }
- }
-
- mExternalDirsForVold = externalForVold.toArray(new File[externalForVold.size()]);
- mExternalDirsForApp = externalForApp.toArray(new File[externalForApp.size()]);
+ return dirs;
}
@Deprecated
public File getExternalStorageDirectory() {
- return mExternalDirsForApp[0];
+ return getExternalDirs()[0];
}
@Deprecated
@@ -155,60 +97,36 @@
return buildExternalStoragePublicDirs(type)[0];
}
- public File[] getExternalDirsForVold() {
- return mExternalDirsForVold;
- }
-
- public File[] getExternalDirsForApp() {
- return mExternalDirsForApp;
- }
-
- public File getMediaDir() {
- return mEmulatedDirForDirect;
- }
-
public File[] buildExternalStoragePublicDirs(String type) {
- return buildPaths(mExternalDirsForApp, type);
+ return buildPaths(getExternalDirs(), type);
}
public File[] buildExternalStorageAndroidDataDirs() {
- return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA);
+ return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA);
}
public File[] buildExternalStorageAndroidObbDirs() {
- return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_OBB);
+ return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_OBB);
}
public File[] buildExternalStorageAppDataDirs(String packageName) {
- return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName);
- }
-
- public File[] buildExternalStorageAppDataDirsForVold(String packageName) {
- return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_DATA, packageName);
+ return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA, packageName);
}
public File[] buildExternalStorageAppMediaDirs(String packageName) {
- return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_MEDIA, packageName);
- }
-
- public File[] buildExternalStorageAppMediaDirsForVold(String packageName) {
- return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_MEDIA, packageName);
+ return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_MEDIA, packageName);
}
public File[] buildExternalStorageAppObbDirs(String packageName) {
- return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_OBB, packageName);
- }
-
- public File[] buildExternalStorageAppObbDirsForVold(String packageName) {
- return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_OBB, packageName);
+ return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_OBB, packageName);
}
public File[] buildExternalStorageAppFilesDirs(String packageName) {
- return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName, DIR_FILES);
+ return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA, packageName, DIR_FILES);
}
public File[] buildExternalStorageAppCacheDirs(String packageName) {
- return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName, DIR_CACHE);
+ return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA, packageName, DIR_CACHE);
}
}
@@ -220,6 +138,11 @@
return DIR_ANDROID_ROOT;
}
+ /** {@hide} */
+ public static File getStorageDirectory() {
+ return DIR_ANDROID_STORAGE;
+ }
+
/**
* Return root directory of the "oem" partition holding OEM customizations,
* if any. If present, the partition is mounted read-only.
@@ -270,17 +193,6 @@
}
/**
- * Return directory used for internal media storage, which is protected by
- * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE}.
- *
- * @hide
- */
- public static File getMediaStorageDirectory() {
- throwIfUserRequired();
- return sCurrentUser.getMediaDir();
- }
-
- /**
* Return the system directory for a user. This is for use by system services to store
* files relating to the user. This directory will be automatically deleted when the user
* is removed.
@@ -389,7 +301,7 @@
*/
public static File getExternalStorageDirectory() {
throwIfUserRequired();
- return sCurrentUser.getExternalDirsForApp()[0];
+ return sCurrentUser.getExternalDirs()[0];
}
/** {@hide} */
@@ -402,18 +314,6 @@
return buildPath(getLegacyExternalStorageDirectory(), DIR_ANDROID, DIR_OBB);
}
- /** {@hide} */
- public static File getEmulatedStorageSource(int userId) {
- // /mnt/shell/emulated/0
- return new File(System.getenv(ENV_EMULATED_STORAGE_SOURCE), String.valueOf(userId));
- }
-
- /** {@hide} */
- public static File getEmulatedStorageObbSource() {
- // /mnt/shell/emulated/obb
- return new File(System.getenv(ENV_EMULATED_STORAGE_SOURCE), DIR_OBB);
- }
-
/**
* Standard directory in which to place any audio files that should be
* in the regular list of music for the user.
@@ -683,6 +583,13 @@
public static final String MEDIA_UNMOUNTABLE = "unmountable";
/**
+ * Storage state if the media is in the process of being ejected.
+ *
+ * @see #getExternalStorageState(File)
+ */
+ public static final String MEDIA_EJECTING = "ejecting";
+
+ /**
* Returns the current state of the primary "external" storage device.
*
* @see #getExternalStorageDirectory()
@@ -693,7 +600,7 @@
* {@link #MEDIA_BAD_REMOVAL}, or {@link #MEDIA_UNMOUNTABLE}.
*/
public static String getExternalStorageState() {
- final File externalDir = sCurrentUser.getExternalDirsForApp()[0];
+ final File externalDir = sCurrentUser.getExternalDirs()[0];
return getExternalStorageState(externalDir);
}
@@ -716,17 +623,12 @@
* {@link #MEDIA_BAD_REMOVAL}, or {@link #MEDIA_UNMOUNTABLE}.
*/
public static String getExternalStorageState(File path) {
- final StorageVolume volume = getStorageVolume(path);
+ final StorageVolume volume = StorageManager.getStorageVolume(path, UserHandle.myUserId());
if (volume != null) {
- final IMountService mountService = IMountService.Stub.asInterface(
- ServiceManager.getService("mount"));
- try {
- return mountService.getVolumeState(volume.getPath());
- } catch (RemoteException e) {
- }
+ return volume.getState();
+ } else {
+ return MEDIA_UNKNOWN;
}
-
- return Environment.MEDIA_UNKNOWN;
}
/**
@@ -738,7 +640,7 @@
*/
public static boolean isExternalStorageRemovable() {
if (isStorageDisabled()) return false;
- final File externalDir = sCurrentUser.getExternalDirsForApp()[0];
+ final File externalDir = sCurrentUser.getExternalDirs()[0];
return isExternalStorageRemovable(externalDir);
}
@@ -753,7 +655,7 @@
* device.
*/
public static boolean isExternalStorageRemovable(File path) {
- final StorageVolume volume = getStorageVolume(path);
+ final StorageVolume volume = StorageManager.getStorageVolume(path, UserHandle.myUserId());
if (volume != null) {
return volume.isRemovable();
} else {
@@ -771,7 +673,7 @@
*/
public static boolean isExternalStorageEmulated() {
if (isStorageDisabled()) return false;
- final File externalDir = sCurrentUser.getExternalDirsForApp()[0];
+ final File externalDir = sCurrentUser.getExternalDirs()[0];
return isExternalStorageEmulated(externalDir);
}
@@ -784,7 +686,7 @@
* device.
*/
public static boolean isExternalStorageEmulated(File path) {
- final StorageVolume volume = getStorageVolume(path);
+ final StorageVolume volume = StorageManager.getStorageVolume(path, UserHandle.myUserId());
if (volume != null) {
return volume.isEmulated();
} else {
@@ -797,19 +699,6 @@
return path == null ? new File(defaultPath) : new File(path);
}
- private static String getCanonicalPathOrNull(String variableName) {
- String path = System.getenv(variableName);
- if (path == null) {
- return null;
- }
- try {
- return new File(path).getCanonicalPath();
- } catch (IOException e) {
- Log.w(TAG, "Unable to resolve canonical path for " + path);
- return null;
- }
- }
-
/** {@hide} */
public static void setUserRequired(boolean userRequired) {
sUserRequired = userRequired;
@@ -856,28 +745,6 @@
return SystemProperties.getBoolean("config.disable_storage", false);
}
- private static StorageVolume getStorageVolume(File path) {
- try {
- path = path.getCanonicalFile();
- } catch (IOException e) {
- return null;
- }
-
- try {
- final IMountService mountService = IMountService.Stub.asInterface(
- ServiceManager.getService("mount"));
- final StorageVolume[] volumes = mountService.getVolumeList();
- for (StorageVolume volume : volumes) {
- if (FileUtils.contains(volume.getPathFile(), path)) {
- return volume;
- }
- }
- } catch (RemoteException e) {
- }
-
- return null;
- }
-
/**
* If the given path exists on emulated external storage, return the
* translated backing path hosted on internal storage. This bypasses any
@@ -891,26 +758,7 @@
* @hide
*/
public static File maybeTranslateEmulatedPathToInternal(File path) {
- // Fast return if not emulated, or missing variables
- if (!Environment.isExternalStorageEmulated()
- || CANONCIAL_EMULATED_STORAGE_TARGET == null) {
- return path;
- }
-
- try {
- final String rawPath = path.getCanonicalPath();
- if (rawPath.startsWith(CANONCIAL_EMULATED_STORAGE_TARGET)) {
- final File internalPath = new File(DIR_MEDIA_STORAGE,
- rawPath.substring(CANONCIAL_EMULATED_STORAGE_TARGET.length()));
- if (internalPath.exists()) {
- return internalPath;
- }
- }
- } catch (IOException e) {
- Log.w(TAG, "Failed to resolve canonical path for " + path);
- }
-
- // Unable to translate to internal path; use original
+ // TODO: bring back this optimization
return path;
}
}
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 0a724a1..b302f95 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -369,6 +369,23 @@
* {@link File#getCanonicalFile()} to avoid symlink or path traversal
* attacks.
*/
+ public static boolean contains(File[] dirs, File file) {
+ for (File dir : dirs) {
+ if (contains(dir, file)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Test if a file lives under the given directory, either as a direct child
+ * or a distant grandchild.
+ * <p>
+ * Both files <em>must</em> have been resolved using
+ * {@link File#getCanonicalFile()} to avoid symlink or path traversal
+ * attacks.
+ */
public static boolean contains(File dir, File file) {
if (file == null) return false;
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 0de9c70..355ec8c 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -637,10 +637,8 @@
if ((debugFlags & Zygote.DEBUG_ENABLE_ASSERT) != 0) {
argsForZygote.add("--enable-assert");
}
- if (mountExternal == Zygote.MOUNT_EXTERNAL_MULTIUSER) {
- argsForZygote.add("--mount-external-multiuser");
- } else if (mountExternal == Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL) {
- argsForZygote.add("--mount-external-multiuser-all");
+ if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {
+ argsForZygote.add("--mount-external-default");
}
argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index 6209c2a..fef12d1 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -757,12 +757,13 @@
return _result;
}
- public StorageVolume[] getVolumeList() throws RemoteException {
+ public StorageVolume[] getVolumeList(int userId) throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
StorageVolume[] _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeInt(userId);
mRemote.transact(Stub.TRANSACTION_getVolumeList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArray(StorageVolume.CREATOR);
@@ -1308,7 +1309,8 @@
}
case TRANSACTION_getVolumeList: {
data.enforceInterface(DESCRIPTOR);
- StorageVolume[] result = getVolumeList();
+ int userId = data.readInt();
+ StorageVolume[] result = getVolumeList(userId);
reply.writeNoException();
reply.writeTypedArray(result, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
return true;
@@ -1630,7 +1632,7 @@
/**
* Returns list of all mountable volumes.
*/
- public StorageVolume[] getVolumeList() throws RemoteException;
+ public StorageVolume[] getVolumeList(int userId) throws RemoteException;
/**
* Gets the path on the filesystem for the ASEC container itself.
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 2785ee8..532bf2c 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -18,19 +18,23 @@
import static android.net.TrafficStats.MB_IN_BYTES;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Context;
import android.os.Environment;
+import android.os.FileUtils;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.Settings;
import android.util.Log;
import android.util.SparseArray;
+import libcore.util.EmptyArray;
+
import com.android.internal.util.Preconditions;
import java.io.File;
@@ -60,6 +64,7 @@
public class StorageManager {
private static final String TAG = "StorageManager";
+ private final Context mContext;
private final ContentResolver mResolver;
/*
@@ -311,8 +316,9 @@
*
* @hide
*/
- public StorageManager(ContentResolver resolver, Looper tgtLooper) throws RemoteException {
- mResolver = resolver;
+ public StorageManager(Context context, Looper tgtLooper) {
+ mContext = context;
+ mResolver = context.getContentResolver();
mTgtLooper = tgtLooper;
mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
if (mMountService == null) {
@@ -548,17 +554,46 @@
return null;
}
+ /** {@hide} */
+ public @Nullable StorageVolume getStorageVolume(File file) {
+ return getStorageVolume(getVolumeList(), file);
+ }
+
+ /** {@hide} */
+ public static @Nullable StorageVolume getStorageVolume(File file, int userId) {
+ return getStorageVolume(getVolumeList(userId), file);
+ }
+
+ /** {@hide} */
+ private static @Nullable StorageVolume getStorageVolume(StorageVolume[] volumes, File file) {
+ File canonicalFile = null;
+ try {
+ canonicalFile = file.getCanonicalFile();
+ } catch (IOException ignored) {
+ canonicalFile = null;
+ }
+ for (StorageVolume volume : volumes) {
+ if (volume.getPathFile().equals(file)) {
+ return volume;
+ }
+ if (FileUtils.contains(volume.getPathFile(), canonicalFile)) {
+ return volume;
+ }
+ }
+ return null;
+ }
+
/**
* Gets the state of a volume via its mountpoint.
* @hide
*/
- public String getVolumeState(String mountPoint) {
- if (mMountService == null) return Environment.MEDIA_REMOVED;
- try {
- return mMountService.getVolumeState(mountPoint);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to get volume state", e);
- return null;
+ @Deprecated
+ public @NonNull String getVolumeState(String mountPoint) {
+ final StorageVolume vol = getStorageVolume(new File(mountPoint));
+ if (vol != null) {
+ return vol.getState();
+ } else {
+ return Environment.MEDIA_UNKNOWN;
}
}
@@ -566,20 +601,22 @@
* Returns list of all mountable volumes.
* @hide
*/
- public StorageVolume[] getVolumeList() {
- if (mMountService == null) return new StorageVolume[0];
+ public @NonNull StorageVolume[] getVolumeList() {
try {
- Parcelable[] list = mMountService.getVolumeList();
- if (list == null) return new StorageVolume[0];
- int length = list.length;
- StorageVolume[] result = new StorageVolume[length];
- for (int i = 0; i < length; i++) {
- result[i] = (StorageVolume)list[i];
- }
- return result;
+ return mMountService.getVolumeList(mContext.getUserId());
} catch (RemoteException e) {
- Log.e(TAG, "Failed to get volume list", e);
- return null;
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /** {@hide} */
+ public static @NonNull StorageVolume[] getVolumeList(int userId) {
+ final IMountService mountService = IMountService.Stub.asInterface(
+ ServiceManager.getService("mount"));
+ try {
+ return mountService.getVolumeList(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -587,9 +624,9 @@
* Returns list of paths for all mountable volumes.
* @hide
*/
- public String[] getVolumePaths() {
+ @Deprecated
+ public @NonNull String[] getVolumePaths() {
StorageVolume[] volumes = getVolumeList();
- if (volumes == null) return null;
int count = volumes.length;
String[] paths = new String[count];
for (int i = 0; i < count; i++) {
@@ -599,21 +636,21 @@
}
/** {@hide} */
- public StorageVolume getPrimaryVolume() {
+ public @NonNull StorageVolume getPrimaryVolume() {
return getPrimaryVolume(getVolumeList());
}
/** {@hide} */
- public static StorageVolume getPrimaryVolume(StorageVolume[] volumes) {
+ public static @NonNull StorageVolume getPrimaryVolume(StorageVolume[] volumes) {
for (StorageVolume volume : volumes) {
if (volume.isPrimary()) {
return volume;
}
}
- Log.w(TAG, "No primary storage defined");
- return null;
+ throw new IllegalStateException("Missing primary storage");
}
+ /** {@hide} */
private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;
private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500 * MB_IN_BYTES;
private static final long DEFAULT_FULL_THRESHOLD_BYTES = MB_IN_BYTES;
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 06565f1..0c391ca 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -17,6 +17,7 @@
package android.os.storage;
import android.content.Context;
+import android.net.TrafficStats;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
@@ -34,52 +35,58 @@
*/
public class StorageVolume implements Parcelable {
- // TODO: switch to more durable token
- private int mStorageId;
+ private final String mId;
+ private final int mStorageId;
private final File mPath;
private final int mDescriptionId;
private final boolean mPrimary;
private final boolean mRemovable;
private final boolean mEmulated;
- private final int mMtpReserveSpace;
+ private final long mMtpReserveSize;
private final boolean mAllowMassStorage;
/** Maximum file size for the storage, or zero for no limit */
private final long mMaxFileSize;
/** When set, indicates exclusive ownership of this volume */
private final UserHandle mOwner;
- private String mUuid;
- private String mUserLabel;
- private String mState;
+ private final String mUuid;
+ private final String mUserLabel;
+ private final String mState;
// StorageVolume extra for ACTION_MEDIA_REMOVED, ACTION_MEDIA_UNMOUNTED, ACTION_MEDIA_CHECKING,
// ACTION_MEDIA_NOFS, ACTION_MEDIA_MOUNTED, ACTION_MEDIA_SHARED, ACTION_MEDIA_UNSHARED,
// ACTION_MEDIA_BAD_REMOVAL, ACTION_MEDIA_UNMOUNTABLE and ACTION_MEDIA_EJECT broadcasts.
public static final String EXTRA_STORAGE_VOLUME = "storage_volume";
- public StorageVolume(File path, int descriptionId, boolean primary, boolean removable,
- boolean emulated, int mtpReserveSpace, boolean allowMassStorage, long maxFileSize,
- UserHandle owner) {
+ public StorageVolume(String id, int storageId, File path, int descriptionId, boolean primary,
+ boolean removable, boolean emulated, long mtpReserveSize, boolean allowMassStorage,
+ long maxFileSize, UserHandle owner, String uuid, String userLabel, String state) {
+ mId = id;
+ mStorageId = storageId;
mPath = path;
mDescriptionId = descriptionId;
mPrimary = primary;
mRemovable = removable;
mEmulated = emulated;
- mMtpReserveSpace = mtpReserveSpace;
+ mMtpReserveSize = mtpReserveSize;
mAllowMassStorage = allowMassStorage;
mMaxFileSize = maxFileSize;
mOwner = owner;
+ mUuid = uuid;
+ mUserLabel = userLabel;
+ mState = state;
}
private StorageVolume(Parcel in) {
+ mId = in.readString();
mStorageId = in.readInt();
mPath = new File(in.readString());
mDescriptionId = in.readInt();
mPrimary = in.readInt() != 0;
mRemovable = in.readInt() != 0;
mEmulated = in.readInt() != 0;
- mMtpReserveSpace = in.readInt();
+ mMtpReserveSize = in.readLong();
mAllowMassStorage = in.readInt() != 0;
mMaxFileSize = in.readLong();
mOwner = in.readParcelable(null);
@@ -88,10 +95,8 @@
mState = in.readString();
}
- public static StorageVolume fromTemplate(StorageVolume template, File path, UserHandle owner) {
- return new StorageVolume(path, template.mDescriptionId, template.mPrimary,
- template.mRemovable, template.mEmulated, template.mMtpReserveSpace,
- template.mAllowMassStorage, template.mMaxFileSize, owner);
+ public String getId() {
+ return mId;
}
/**
@@ -153,15 +158,6 @@
}
/**
- * Do not call this unless you are MountService
- */
- public void setStorageId(int index) {
- // storage ID is 0x00010001 for primary storage,
- // then 0x00020001, 0x00030001, etc. for secondary storages
- mStorageId = ((index + 1) << 16) + 1;
- }
-
- /**
* Number of megabytes of space to leave unallocated by MTP.
* MTP will subtract this value from the free space it reports back
* to the host via GetStorageInfo, and will not allow new files to
@@ -174,7 +170,7 @@
* @return MTP reserve space
*/
public int getMtpReserveSpace() {
- return mMtpReserveSpace;
+ return (int) (mMtpReserveSize / TrafficStats.MB_IN_BYTES);
}
/**
@@ -199,10 +195,6 @@
return mOwner;
}
- public void setUuid(String uuid) {
- mUuid = uuid;
- }
-
public String getUuid() {
return mUuid;
}
@@ -222,18 +214,10 @@
}
}
- public void setUserLabel(String userLabel) {
- mUserLabel = userLabel;
- }
-
public String getUserLabel() {
return mUserLabel;
}
- public void setState(String state) {
- mState = state;
- }
-
public String getState() {
return mState;
}
@@ -262,13 +246,14 @@
public void dump(IndentingPrintWriter pw) {
pw.println("StorageVolume:");
pw.increaseIndent();
+ pw.printPair("mId", mId);
pw.printPair("mStorageId", mStorageId);
pw.printPair("mPath", mPath);
pw.printPair("mDescriptionId", mDescriptionId);
pw.printPair("mPrimary", mPrimary);
pw.printPair("mRemovable", mRemovable);
pw.printPair("mEmulated", mEmulated);
- pw.printPair("mMtpReserveSpace", mMtpReserveSpace);
+ pw.printPair("mMtpReserveSize", mMtpReserveSize);
pw.printPair("mAllowMassStorage", mAllowMassStorage);
pw.printPair("mMaxFileSize", mMaxFileSize);
pw.printPair("mOwner", mOwner);
@@ -297,13 +282,14 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeString(mId);
parcel.writeInt(mStorageId);
parcel.writeString(mPath.toString());
parcel.writeInt(mDescriptionId);
parcel.writeInt(mPrimary ? 1 : 0);
parcel.writeInt(mRemovable ? 1 : 0);
parcel.writeInt(mEmulated ? 1 : 0);
- parcel.writeInt(mMtpReserveSpace);
+ parcel.writeLong(mMtpReserveSize);
parcel.writeInt(mAllowMassStorage ? 1 : 0);
parcel.writeLong(mMaxFileSize);
parcel.writeParcelable(mOwner, flags);
diff --git a/core/java/android/security/keymaster/OperationResult.java b/core/java/android/security/keymaster/OperationResult.java
index 4fc9d24..9b46ad3 100644
--- a/core/java/android/security/keymaster/OperationResult.java
+++ b/core/java/android/security/keymaster/OperationResult.java
@@ -28,6 +28,7 @@
public class OperationResult implements Parcelable {
public final int resultCode;
public final IBinder token;
+ public final long operationHandle;
public final int inputConsumed;
public final byte[] output;
@@ -45,6 +46,7 @@
protected OperationResult(Parcel in) {
resultCode = in.readInt();
token = in.readStrongBinder();
+ operationHandle = in.readLong();
inputConsumed = in.readInt();
output = in.createByteArray();
}
@@ -58,6 +60,7 @@
public void writeToParcel(Parcel out, int flags) {
out.writeInt(resultCode);
out.writeStrongBinder(token);
+ out.writeLong(operationHandle);
out.writeInt(inputConsumed);
out.writeByteArray(output);
}
diff --git a/core/java/android/util/DebugUtils.java b/core/java/android/util/DebugUtils.java
index 84d9ce8..c44f42b 100644
--- a/core/java/android/util/DebugUtils.java
+++ b/core/java/android/util/DebugUtils.java
@@ -17,8 +17,10 @@
package android.util;
import java.io.PrintWriter;
-import java.lang.reflect.Method;
+import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import java.util.Locale;
/**
@@ -203,4 +205,57 @@
outBuilder.append(suffix);
return outBuilder.toString();
}
+
+ /**
+ * Use prefixed constants (static final values) on given class to turn value
+ * into human-readable string.
+ *
+ * @hide
+ */
+ public static String valueToString(Class<?> clazz, String prefix, int value) {
+ for (Field field : clazz.getDeclaredFields()) {
+ final int modifiers = field.getModifiers();
+ if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)
+ && field.getType().equals(int.class) && field.getName().startsWith(prefix)) {
+ try {
+ if (value == field.getInt(null)) {
+ return field.getName().substring(prefix.length());
+ }
+ } catch (IllegalAccessException ignored) {
+ }
+ }
+ }
+ return Integer.toString(value);
+ }
+
+ /**
+ * Use prefixed constants (static final values) on given class to turn flags
+ * into human-readable string.
+ *
+ * @hide
+ */
+ public static String flagsToString(Class<?> clazz, String prefix, int flags) {
+ final StringBuilder res = new StringBuilder();
+
+ for (Field field : clazz.getDeclaredFields()) {
+ final int modifiers = field.getModifiers();
+ if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)
+ && field.getType().equals(int.class) && field.getName().startsWith(prefix)) {
+ try {
+ final int value = field.getInt(null);
+ if ((flags & value) != 0) {
+ flags &= ~value;
+ res.append(field.getName().substring(prefix.length())).append('|');
+ }
+ } catch (IllegalAccessException ignored) {
+ }
+ }
+ }
+ if (flags != 0 || res.length() == 0) {
+ res.append(Integer.toHexString(flags));
+ } else {
+ res.deleteCharAt(res.length() - 1);
+ }
+ return res.toString();
+ }
}
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index 6508cca..5cf2c5c 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -130,6 +130,7 @@
private float mFocusY;
private boolean mQuickScaleEnabled;
+ private boolean mButtonScaleEnabled;
private float mCurrSpan;
private float mPrevSpan;
@@ -151,14 +152,17 @@
private int mTouchHistoryDirection;
private long mTouchHistoryLastAcceptedTime;
private int mTouchMinMajor;
- private MotionEvent mDoubleTapEvent;
- private int mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
private final Handler mHandler;
+ private float mAnchoredScaleStartX;
+ private float mAnchoredScaleStartY;
+ private int mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
+
private static final long TOUCH_STABILIZE_TIME = 128; // ms
- private static final int DOUBLE_TAP_MODE_NONE = 0;
- private static final int DOUBLE_TAP_MODE_IN_PROGRESS = 1;
private static final float SCALE_FACTOR = .5f;
+ private static final int ANCHORED_SCALE_MODE_NONE = 0;
+ private static final int ANCHORED_SCALE_MODE_DOUBLE_TAP = 1;
+ private static final int ANCHORED_SCALE_MODE_BUTTON = 2;
/**
@@ -310,8 +314,17 @@
mGestureDetector.onTouchEvent(event);
}
+ final int count = event.getPointerCount();
+ final int toolType = event.getToolType(0);
+ final boolean isButtonTool = toolType == MotionEvent.TOOL_TYPE_STYLUS
+ || toolType == MotionEvent.TOOL_TYPE_MOUSE;
+ final boolean isAnchoredScaleButtonDown = isButtonTool && (count == 1)
+ && (event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0;
+
+ final boolean anchoredScaleCancelled =
+ mAnchoredScaleMode == ANCHORED_SCALE_MODE_BUTTON && !isAnchoredScaleButtonDown;
final boolean streamComplete = action == MotionEvent.ACTION_UP ||
- action == MotionEvent.ACTION_CANCEL;
+ action == MotionEvent.ACTION_CANCEL || anchoredScaleCancelled;
if (action == MotionEvent.ACTION_DOWN || streamComplete) {
// Reset any scale in progress with the listener.
@@ -321,11 +334,11 @@
mListener.onScaleEnd(this);
mInProgress = false;
mInitialSpan = 0;
- mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
- } else if (mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS && streamComplete) {
+ mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
+ } else if (inAnchoredScaleMode() && streamComplete) {
mInProgress = false;
mInitialSpan = 0;
- mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
+ mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
}
if (streamComplete) {
@@ -334,25 +347,32 @@
}
}
+ if (!mInProgress && mButtonScaleEnabled && !inAnchoredScaleMode()
+ && !streamComplete && isAnchoredScaleButtonDown) {
+ // Start of a button scale gesture
+ mAnchoredScaleStartX = event.getX();
+ mAnchoredScaleStartY = event.getY();
+ mAnchoredScaleMode = ANCHORED_SCALE_MODE_BUTTON;
+ mInitialSpan = 0;
+ }
+
final boolean configChanged = action == MotionEvent.ACTION_DOWN ||
action == MotionEvent.ACTION_POINTER_UP ||
- action == MotionEvent.ACTION_POINTER_DOWN;
-
+ action == MotionEvent.ACTION_POINTER_DOWN || anchoredScaleCancelled;
final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
final int skipIndex = pointerUp ? event.getActionIndex() : -1;
// Determine focal point
float sumX = 0, sumY = 0;
- final int count = event.getPointerCount();
final int div = pointerUp ? count - 1 : count;
final float focusX;
final float focusY;
- if (mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS) {
- // In double tap mode, the focal pt is always where the double tap
- // gesture started
- focusX = mDoubleTapEvent.getX();
- focusY = mDoubleTapEvent.getY();
+ if (inAnchoredScaleMode()) {
+ // In anchored scale mode, the focal pt is always where the double tap
+ // or button down gesture started
+ focusX = mAnchoredScaleStartX;
+ focusY = mAnchoredScaleStartY;
if (event.getY() < focusY) {
mEventBeforeOrAboveStartingGestureEvent = true;
} else {
@@ -390,7 +410,7 @@
final float spanX = devX * 2;
final float spanY = devY * 2;
final float span;
- if (inDoubleTapMode()) {
+ if (inAnchoredScaleMode()) {
span = spanY;
} else {
span = (float) Math.hypot(spanX, spanY);
@@ -402,11 +422,10 @@
final boolean wasInProgress = mInProgress;
mFocusX = focusX;
mFocusY = focusY;
- if (!inDoubleTapMode() && mInProgress && (span < mMinSpan || configChanged)) {
+ if (!inAnchoredScaleMode() && mInProgress && (span < mMinSpan || configChanged)) {
mListener.onScaleEnd(this);
mInProgress = false;
mInitialSpan = span;
- mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
}
if (configChanged) {
mPrevSpanX = mCurrSpanX = spanX;
@@ -414,7 +433,7 @@
mInitialSpan = mPrevSpan = mCurrSpan = span;
}
- final int minSpan = inDoubleTapMode() ? mSpanSlop : mMinSpan;
+ final int minSpan = inAnchoredScaleMode() ? mSpanSlop : mMinSpan;
if (!mInProgress && span >= minSpan &&
(wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) {
mPrevSpanX = mCurrSpanX = spanX;
@@ -447,9 +466,8 @@
return true;
}
-
- private boolean inDoubleTapMode() {
- return mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS;
+ private boolean inAnchoredScaleMode() {
+ return mAnchoredScaleMode != ANCHORED_SCALE_MODE_NONE;
}
/**
@@ -466,8 +484,9 @@
@Override
public boolean onDoubleTap(MotionEvent e) {
// Double tap: start watching for a swipe
- mDoubleTapEvent = e;
- mDoubleTapMode = DOUBLE_TAP_MODE_IN_PROGRESS;
+ mAnchoredScaleStartX = e.getX();
+ mAnchoredScaleStartY = e.getY();
+ mAnchoredScaleMode = ANCHORED_SCALE_MODE_DOUBLE_TAP;
return true;
}
};
@@ -484,6 +503,27 @@
}
/**
+ * Sets whether the associates {@link OnScaleGestureListener} should receive onScale callbacks
+ * when the user presses a {@value MotionEvent#BUTTON_SECONDARY} (right mouse button, stylus
+ * first button) and drags the pointer on the screen. Note that this is enabled by default if
+ * the app targets API 23 and newer.
+ *
+ * @param scales true to enable stylus or mouse scaling, false to disable.
+ */
+ public void setSecondaryButtonScaleEnabled(boolean scales) {
+ mButtonScaleEnabled = scales;
+ }
+
+ /**
+ * Return whether the button scale gesture, in which the user presses a
+ * {@value MotionEvent#BUTTON_SECONDARY} (right mouse button, stylus first button) and drags the
+ * pointer on the screen, should perform scaling. {@see #setButtonScaleEnabled(boolean)}.
+ */
+ public boolean isSecondaryButtonScaleEnabled() {
+ return mButtonScaleEnabled;
+ }
+
+ /**
* Returns {@code true} if a scale gesture is in progress.
*/
public boolean isInProgress() {
@@ -586,7 +626,7 @@
* @return The current scaling factor.
*/
public float getScaleFactor() {
- if (inDoubleTapMode()) {
+ if (inAnchoredScaleMode()) {
// Drag is moving up; the further away from the gesture
// start, the smaller the span should be, the closer,
// the larger the span, and therefore the larger the scale
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index f9729a2..32b99a8 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2939,7 +2939,27 @@
* The default callback provides a subset of Select All, Cut, Copy and Paste actions, depending
* on which of these this TextView supports.
*/
- private class SelectionActionModeCallback implements ActionMode.Callback {
+ private class SelectionActionModeCallback extends ActionMode.Callback2 {
+ private final Path mSelectionPath = new Path();
+ private final RectF mSelectionBounds = new RectF();
+
+ private int mSelectionHandleHeight;
+ private int mInsertionHandleHeight;
+
+ public SelectionActionModeCallback() {
+ SelectionModifierCursorController selectionController = getSelectionController();
+ if (selectionController.mStartHandle == null) {
+ selectionController.initDrawables();
+ selectionController.initHandles();
+ }
+ mSelectionHandleHeight = Math.max(
+ mSelectHandleLeft.getMinimumHeight(), mSelectHandleRight.getMinimumHeight());
+ InsertionPointCursorController insertionController = getInsertionController();
+ if (insertionController != null) {
+ insertionController.getHandle();
+ mInsertionHandleHeight = mSelectHandleCenter.getMinimumHeight();
+ }
+ }
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
@@ -2956,13 +2976,6 @@
mode.setSubtitle(null);
mode.setTitleOptionalHint(true);
- menu.add(0, TextView.ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
- setIcon(styledAttributes.getResourceId(
- R.styleable.SelectionModeDrawables_actionModeSelectAllDrawable, 0)).
- setAlphabeticShortcut('a').
- setShowAsAction(
- MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
-
if (mTextView.canCut()) {
menu.add(0, TextView.ID_CUT, 0, com.android.internal.R.string.cut).
setIcon(styledAttributes.getResourceId(
@@ -2990,6 +3003,13 @@
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
}
+ menu.add(0, TextView.ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
+ setIcon(styledAttributes.getResourceId(
+ R.styleable.SelectionModeDrawables_actionModeSelectAllDrawable, 0)).
+ setAlphabeticShortcut('a').
+ setShowAsAction(
+ MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+
if (mTextView.isSuggestionsEnabled() && isCursorInsideSuggestionSpan()) {
menu.add(0, TextView.ID_REPLACE, 0, com.android.internal.R.string.replace).
setShowAsAction(
@@ -3057,6 +3077,40 @@
mSelectionActionMode = null;
}
+
+ @Override
+ public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
+ if (!view.equals(mTextView) || mTextView.getLayout() == null) {
+ super.onGetContentRect(mode, view, outRect);
+ return;
+ }
+ if (mTextView.getSelectionStart() != mTextView.getSelectionEnd()) {
+ // We have a selection.
+ mSelectionPath.reset();
+ mTextView.getLayout().getSelectionPath(
+ mTextView.getSelectionStart(), mTextView.getSelectionEnd(), mSelectionPath);
+ mSelectionPath.computeBounds(mSelectionBounds, true);
+ mSelectionBounds.bottom += mSelectionHandleHeight;
+ } else {
+ // We have a single cursor.
+ int line = mTextView.getLayout().getLineForOffset(mTextView.getSelectionStart());
+ float primaryHorizontal =
+ mTextView.getLayout().getPrimaryHorizontal(mTextView.getSelectionStart());
+ mSelectionBounds.set(
+ primaryHorizontal,
+ mTextView.getLayout().getLineTop(line),
+ primaryHorizontal + 1,
+ mTextView.getLayout().getLineTop(line + 1) + mInsertionHandleHeight);
+ }
+ // Take TextView's padding and scroll into account.
+ int textHorizontalOffset = mTextView.viewportToContentHorizontalOffset();
+ int textVerticalOffset = mTextView.viewportToContentVerticalOffset();
+ outRect.set(
+ (int) Math.floor(mSelectionBounds.left + textHorizontalOffset),
+ (int) Math.floor(mSelectionBounds.top + textVerticalOffset),
+ (int) Math.ceil(mSelectionBounds.right + textHorizontalOffset),
+ (int) Math.ceil(mSelectionBounds.bottom + textVerticalOffset));
+ }
}
private void onReplace() {
diff --git a/core/java/com/android/internal/midi/EventScheduler.java b/core/java/com/android/internal/midi/EventScheduler.java
index 7526609..7b9a48c 100644
--- a/core/java/com/android/internal/midi/EventScheduler.java
+++ b/core/java/com/android/internal/midi/EventScheduler.java
@@ -27,10 +27,11 @@
public class EventScheduler {
private static final long NANOS_PER_MILLI = 1000000;
- private final Object lock = new Object();
+ private final Object mLock = new Object();
private SortedMap<Long, FastEventQueue> mEventBuffer;
private FastEventQueue mEventPool = null;
private int mMaxPoolSize = 200;
+ private boolean mClosed;
public EventScheduler() {
mEventBuffer = new TreeMap<Long, FastEventQueue>();
@@ -146,7 +147,7 @@
* @param event
*/
public void add(SchedulableEvent event) {
- synchronized (lock) {
+ synchronized (mLock) {
FastEventQueue list = mEventBuffer.get(event.getTimestamp());
if (list == null) {
long lowestTime = mEventBuffer.isEmpty() ? Long.MAX_VALUE
@@ -156,7 +157,7 @@
// If the event we added is earlier than the previous earliest
// event then notify any threads waiting for the next event.
if (event.getTimestamp() < lowestTime) {
- lock.notify();
+ mLock.notify();
}
} else {
list.add(event);
@@ -183,7 +184,7 @@
*/
public SchedulableEvent getNextEvent(long time) {
SchedulableEvent event = null;
- synchronized (lock) {
+ synchronized (mLock) {
if (!mEventBuffer.isEmpty()) {
long lowestTime = mEventBuffer.firstKey();
// Is it time for this list to be processed?
@@ -206,9 +207,9 @@
*/
public SchedulableEvent waitNextEvent() throws InterruptedException {
SchedulableEvent event = null;
- while (true) {
- long millisToWait = Integer.MAX_VALUE;
- synchronized (lock) {
+ synchronized (mLock) {
+ while (!mClosed) {
+ long millisToWait = Integer.MAX_VALUE;
if (!mEventBuffer.isEmpty()) {
long now = System.nanoTime();
long lowestTime = mEventBuffer.firstKey();
@@ -228,9 +229,16 @@
}
}
}
- lock.wait((int) millisToWait);
+ mLock.wait((int) millisToWait);
}
}
return event;
}
+
+ public void close() {
+ synchronized (mLock) {
+ mClosed = true;
+ mLock.notify();
+ }
+ }
}
diff --git a/core/java/com/android/internal/midi/MidiEventScheduler.java b/core/java/com/android/internal/midi/MidiEventScheduler.java
index 3a1d3fc..42d70f6 100644
--- a/core/java/com/android/internal/midi/MidiEventScheduler.java
+++ b/core/java/com/android/internal/midi/MidiEventScheduler.java
@@ -28,10 +28,16 @@
// Maintain a pool of scheduled events to reduce memory allocation.
// This pool increases performance by about 14%.
private final static int POOL_EVENT_SIZE = 16;
- private MidiReceiver mReceiver = new SchedulingReceiver();
- private class SchedulingReceiver extends MidiReceiver
- {
+ private final MidiReceiver[] mReceivers;
+
+ private class SchedulingReceiver extends MidiReceiver {
+ private final int mPortNumber;
+
+ public SchedulingReceiver(int portNumber) {
+ mPortNumber = portNumber;
+ }
+
/**
* Store these bytes in the EventScheduler to be delivered at the specified
* time.
@@ -41,12 +47,14 @@
throws IOException {
MidiEvent event = createScheduledEvent(msg, offset, count, timestamp);
if (event != null) {
+ event.portNumber = mPortNumber;
add(event);
}
}
}
public static class MidiEvent extends SchedulableEvent {
+ public int portNumber;
public int count = 0;
public byte[] data;
@@ -72,6 +80,17 @@
}
}
+ public MidiEventScheduler() {
+ this(0);
+ }
+
+ public MidiEventScheduler(int portCount) {
+ mReceivers = new MidiReceiver[portCount];
+ for (int i = 0; i < portCount; i++) {
+ mReceivers[i] = new SchedulingReceiver(i);
+ }
+ }
+
/**
* Create an event that contains the message.
*/
@@ -113,7 +132,15 @@
* @return the MidiReceiver
*/
public MidiReceiver getReceiver() {
- return mReceiver;
+ return mReceivers[0];
+ }
+
+ /**
+ * This MidiReceiver will write date to the scheduling buffer.
+ * @return the MidiReceiver
+ */
+ public MidiReceiver getReceiver(int portNumber) {
+ return mReceivers[portNumber];
}
}
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 8674a21..75b6446 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -41,15 +41,10 @@
/** enable the JIT compiler */
public static final int DEBUG_ENABLE_JIT = 1 << 5;
-
/** No external storage should be mounted. */
public static final int MOUNT_EXTERNAL_NONE = 0;
- /** Single-user external storage should be mounted. */
- public static final int MOUNT_EXTERNAL_SINGLEUSER = 1;
- /** Multi-user external storage should be mounted. */
- public static final int MOUNT_EXTERNAL_MULTIUSER = 2;
- /** All multi-user external storage should be mounted. */
- public static final int MOUNT_EXTERNAL_MULTIUSER_ALL = 3;
+ /** Default user-specific external storage should be mounted. */
+ public static final int MOUNT_EXTERNAL_DEFAULT = 1;
private static final ZygoteHooks VM_HOOKS = new ZygoteHooks();
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 4d405b2..9106ccd 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -514,10 +514,8 @@
"Duplicate arg specified");
}
niceName = arg.substring(arg.indexOf('=') + 1);
- } else if (arg.equals("--mount-external-multiuser")) {
- mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER;
- } else if (arg.equals("--mount-external-multiuser-all")) {
- mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL;
+ } else if (arg.equals("--mount-external-default")) {
+ mountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT;
} else if (arg.equals("--query-abi-list")) {
abiListQuery = true;
} else if (arg.startsWith("--instruction-set=")) {
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 34de6c5..a4c91b3 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -558,6 +558,8 @@
char dex2oatXmxFlagsBuf[sizeof("-Xmx")-1 + PROPERTY_VALUE_MAX];
char dex2oatCompilerFilterBuf[sizeof("--compiler-filter=")-1 + PROPERTY_VALUE_MAX];
char dex2oatImageCompilerFilterBuf[sizeof("--compiler-filter=")-1 + PROPERTY_VALUE_MAX];
+ char dex2oatThreadsBuf[sizeof("-j")-1 + PROPERTY_VALUE_MAX];
+ char dex2oatThreadsImageBuf[sizeof("-j")-1 + PROPERTY_VALUE_MAX];
char dex2oatFlagsBuf[PROPERTY_VALUE_MAX];
char dex2oatImageFlagsBuf[PROPERTY_VALUE_MAX];
char extraOptsBuf[PROPERTY_VALUE_MAX];
@@ -733,6 +735,9 @@
parseCompilerOption("dalvik.vm.dex2oat-filter", dex2oatCompilerFilterBuf,
"--compiler-filter=", "-Xcompiler-option");
}
+ parseCompilerOption("dalvik.vm.dex2oat-threads", dex2oatThreadsBuf, "-j", "-Xcompiler-option");
+ parseCompilerOption("dalvik.vm.image-dex2oat-threads", dex2oatThreadsImageBuf, "-j",
+ "-Ximage-compiler-option");
property_get("dalvik.vm.dex2oat-flags", dex2oatFlagsBuf, "");
parseExtraOpts(dex2oatFlagsBuf, "-Xcompiler-option");
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index c020020..87c58d6 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -48,11 +48,6 @@
static jclass gLineBreaks_class;
static JLineBreaksID gLineBreaks_fieldID;
-static const int CHAR_SPACE = 0x20;
-static const int CHAR_TAB = 0x09;
-static const int CHAR_NEWLINE = 0x0a;
-static const int CHAR_ZWSP = 0x200b;
-
// set text and set a number of parameters for creating a layout (width, tabstops, strategy)
static void nSetupParagraph(JNIEnv* env, jclass, jlong nativePtr, jcharArray text, jint length,
jfloat firstWidth, jint firstWidthLineLimit, jfloat restWidth,
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 2bfeadb..76db5d3 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -65,9 +65,7 @@
// Must match values in com.android.internal.os.Zygote.
enum MountExternalKind {
MOUNT_EXTERNAL_NONE = 0,
- MOUNT_EXTERNAL_SINGLEUSER = 1,
- MOUNT_EXTERNAL_MULTIUSER = 2,
- MOUNT_EXTERNAL_MULTIUSER_ALL = 3,
+ MOUNT_EXTERNAL_DEFAULT = 1,
};
static void RuntimeAbort(JNIEnv* env) {
@@ -269,57 +267,16 @@
// See storage config details at http://source.android.com/tech/storage/
userid_t user_id = multiuser_get_user_id(uid);
- // Create bind mounts to expose external storage
- if (mount_mode == MOUNT_EXTERNAL_MULTIUSER || mount_mode == MOUNT_EXTERNAL_MULTIUSER_ALL) {
- // These paths must already be created by init.rc
- const char* source = getenv("EMULATED_STORAGE_SOURCE");
- const char* target = getenv("EMULATED_STORAGE_TARGET");
- const char* legacy = getenv("EXTERNAL_STORAGE");
- if (source == NULL || target == NULL || legacy == NULL) {
- ALOGW("Storage environment undefined; unable to provide external storage");
- return false;
- }
+ // Bind mount user-specific storage into place
+ const String8 source(String8::format("/mnt/user/%d", user_id));
+ const String8 target(String8::format("/storage/self"));
- // Prepare source paths
+ if (fs_prepare_dir(source.string(), 0755, 0, 0) == -1) {
+ return false;
+ }
- // /mnt/shell/emulated/0
- const String8 source_user(String8::format("%s/%d", source, user_id));
- // /storage/emulated/0
- const String8 target_user(String8::format("%s/%d", target, user_id));
-
- if (fs_prepare_dir(source_user.string(), 0000, 0, 0) == -1
- || fs_prepare_dir(target_user.string(), 0000, 0, 0) == -1) {
- return false;
- }
-
- if (mount_mode == MOUNT_EXTERNAL_MULTIUSER_ALL) {
- // Mount entire external storage tree for all users
- if (TEMP_FAILURE_RETRY(mount(source, target, NULL, MS_BIND, NULL)) == -1) {
- ALOGW("Failed to mount %s to %s: %s", source, target, strerror(errno));
- return false;
- }
- } else {
- // Only mount user-specific external storage
- if (TEMP_FAILURE_RETRY(mount(source_user.string(), target_user.string(), NULL,
- MS_BIND, NULL)) == -1) {
- ALOGW("Failed to mount %s to %s: %s", source_user.string(), target_user.string(),
- strerror(errno));
- return false;
- }
- }
-
- if (fs_prepare_dir(legacy, 0000, 0, 0) == -1) {
- return false;
- }
-
- // Finally, mount user-specific path into place for legacy users
- if (TEMP_FAILURE_RETRY(
- mount(target_user.string(), legacy, NULL, MS_BIND | MS_REC, NULL)) == -1) {
- ALOGW("Failed to mount %s to %s: %s", target_user.string(), legacy, strerror(errno));
- return false;
- }
- } else {
- ALOGW("Mount mode %d unsupported", mount_mode);
+ if (TEMP_FAILURE_RETRY(mount(source.string(), target.string(), NULL, MS_BIND, NULL)) == -1) {
+ ALOGW("Failed to mount %s to %s: %s", source.string(), target.string(), strerror(errno));
return false;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0b1b807..1cb0455 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1221,6 +1221,14 @@
android:label="@string/permlab_cameraDisableTransmitLed"
android:description="@string/permdesc_cameraDisableTransmitLed" />
+ <!-- Allows sending the camera service notifications about system-wide events.
+ @hide -->
+ <permission android:name="android.permission.CAMERA_SEND_SYSTEM_EVENTS"
+ android:permissionGroup="android.permission-group.CAMERA"
+ android:protectionLevel="signature|system"
+ android:label="@string/permdesc_cameraSendSystemEvent"
+ android:description="@string/permdesc_cameraSendSystemEvent" />
+
<!-- =========================================== -->
<!-- Permissions associated with telephony state -->
<!-- =========================================== -->
@@ -3179,9 +3187,9 @@
</intent-filter>
</receiver>
- <receiver android:name="com.android.server.updates.TZInfoInstallReceiver" >
+ <receiver android:name="com.android.server.updates.TzDataInstallReceiver" >
<intent-filter>
- <action android:name="android.intent.action.UPDATE_TZINFO" />
+ <action android:name="android.intent.action.UPDATE_TZDATA" />
<data android:scheme="content" android:host="*" android:mimeType="*/*" />
</intent-filter>
</receiver>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 702510a..0a77014 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1758,6 +1758,8 @@
<string name="permlab_cameraDisableTransmitLed">disable transmit indicator LED when camera is in use</string>
<!-- Description of a camera app permission, listed so the user can choose whether or not they want to allow it to disable the may-transmit light indicator. -->
<string name="permdesc_cameraDisableTransmitLed">Allows a pre-installed system application to disable the camera use indicator LED.</string>
+ <!-- Description of a camera app permission, listed so that the user can send the camera service notifications about system-wide events. -->
+ <string name="permdesc_cameraSendSystemEvent">Allows a pre-installed system application to send the camera service system events.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_brick" product="tablet">permanently disable tablet</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 18302ce..19352c9 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2194,6 +2194,10 @@
<java-symbol type="dimen" name="day_picker_padding_top"/>
<java-symbol type="dimen" name="date_picker_day_of_week_height"/>
+ <java-symbol type="string" name="storage_internal" />
+ <java-symbol type="string" name="storage_sd_card" />
+ <java-symbol type="string" name="storage_usb" />
+
<java-symbol type="id" name="accessibility_action_show_on_screen" />
<!-- Floating toolbar -->
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index ada8c12..c63c8ba 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -356,6 +356,108 @@
public static final int RAW10 = 0x25;
/**
+ * <p>
+ * Android 12-bit raw format
+ * </p>
+ * <p>
+ * This is a single-plane, 12-bit per pixel, densely packed (in each row),
+ * unprocessed format, usually representing raw Bayer-pattern images coming
+ * from an image sensor.
+ * </p>
+ * <p>
+ * In an image buffer with this format, starting from the first pixel of each
+ * row, each two consecutive pixels are packed into 3 bytes (24 bits). The first
+ * and second byte contains the top 8 bits of first and second pixel. The third
+ * byte contains the 4 least significant bits of the two pixels, the exact layout
+ * data for each two consecutive pixels is illustrated below (Pi[j] stands for
+ * the jth bit of the ith pixel):
+ * </p>
+ * <table>
+ * <thead>
+ * <tr>
+ * <th align="center"></th>
+ * <th align="center">bit 7</th>
+ * <th align="center">bit 6</th>
+ * <th align="center">bit 5</th>
+ * <th align="center">bit 4</th>
+ * <th align="center">bit 3</th>
+ * <th align="center">bit 2</th>
+ * <th align="center">bit 1</th>
+ * <th align="center">bit 0</th>
+ * </tr>
+ * </thead> <tbody>
+ * <tr>
+ * <td align="center">Byte 0:</td>
+ * <td align="center">P0[11]</td>
+ * <td align="center">P0[10]</td>
+ * <td align="center">P0[ 9]</td>
+ * <td align="center">P0[ 8]</td>
+ * <td align="center">P0[ 7]</td>
+ * <td align="center">P0[ 6]</td>
+ * <td align="center">P0[ 5]</td>
+ * <td align="center">P0[ 4]</td>
+ * </tr>
+ * <tr>
+ * <td align="center">Byte 1:</td>
+ * <td align="center">P1[11]</td>
+ * <td align="center">P1[10]</td>
+ * <td align="center">P1[ 9]</td>
+ * <td align="center">P1[ 8]</td>
+ * <td align="center">P1[ 7]</td>
+ * <td align="center">P1[ 6]</td>
+ * <td align="center">P1[ 5]</td>
+ * <td align="center">P1[ 4]</td>
+ * </tr>
+ * <tr>
+ * <td align="center">Byte 2:</td>
+ * <td align="center">P1[ 3]</td>
+ * <td align="center">P1[ 2]</td>
+ * <td align="center">P1[ 1]</td>
+ * <td align="center">P1[ 0]</td>
+ * <td align="center">P0[ 3]</td>
+ * <td align="center">P0[ 2]</td>
+ * <td align="center">P0[ 1]</td>
+ * <td align="center">P0[ 0]</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ * <p>
+ * This format assumes
+ * <ul>
+ * <li>a width multiple of 4 pixels</li>
+ * <li>an even height</li>
+ * </ul>
+ * </p>
+ *
+ * <pre>size = row stride * height</pre> where the row stride is in <em>bytes</em>,
+ * not pixels.
+ *
+ * <p>
+ * Since this is a densely packed format, the pixel stride is always 0. The
+ * application must use the pixel data layout defined in above table to
+ * access each row data. When row stride is equal to {@code width * (12 / 8)}, there
+ * will be no padding bytes at the end of each row, the entire image data is
+ * densely packed. When stride is larger than {@code width * (12 / 8)}, padding
+ * bytes will be present at the end of each row.
+ * </p>
+ * <p>
+ * For example, the {@link android.media.Image} object can provide data in
+ * this format from a {@link android.hardware.camera2.CameraDevice} (if
+ * supported) through a {@link android.media.ImageReader} object. The
+ * {@link android.media.Image#getPlanes() Image#getPlanes()} will return a
+ * single plane containing the pixel data. The pixel stride is always 0 in
+ * {@link android.media.Image.Plane#getPixelStride()}, and the
+ * {@link android.media.Image.Plane#getRowStride()} describes the vertical
+ * neighboring pixel distance (in bytes) between adjacent rows.
+ * </p>
+ *
+ * @see android.media.Image
+ * @see android.media.ImageReader
+ * @see android.hardware.camera2.CameraDevice
+ */
+ public static final int RAW12 = 0x26;
+
+ /**
* Android dense depth image format.
*
* Each pixel is 16 bits, representing a depth ranging measurement from
@@ -445,6 +547,8 @@
return 16;
case RAW10:
return 10;
+ case RAW12:
+ return 12;
}
return -1;
}
@@ -472,6 +576,7 @@
case YUV_420_888:
case RAW_SENSOR:
case RAW10:
+ case RAW12:
case DEPTH16:
case DEPTH_POINT_CLOUD:
case PRIVATE:
diff --git a/libs/hwui/ClipArea.cpp b/libs/hwui/ClipArea.cpp
index 852204a..eb520b4 100644
--- a/libs/hwui/ClipArea.cpp
+++ b/libs/hwui/ClipArea.cpp
@@ -234,7 +234,7 @@
bool ClipArea::clipRegion(const SkRegion& region, SkRegion::Op op) {
enterRegionMode();
mClipRegion.op(region, op);
- setClipRectToRegionBounds();
+ onClipRegionUpdated();
return true;
}
@@ -263,6 +263,9 @@
bool ClipArea::rectangleModeClipRectWithTransform(const Rect& r,
const mat4* transform, SkRegion::Op op) {
+ // TODO: we should be able to handle kReplace_Op efficiently without
+ // going through RegionMode and later falling back into RectangleMode.
+
if (op != SkRegion::kIntersect_Op) {
enterRegionMode();
return regionModeClipRectWithTransform(r, transform, op);
@@ -324,15 +327,16 @@
*/
void ClipArea::enterRegionMode() {
- if (mMode != kModeRegion) {
- if (mMode == kModeRectangle) {
+ Mode oldMode = mMode;
+ mMode = kModeRegion;
+ if (oldMode != kModeRegion) {
+ if (oldMode == kModeRectangle) {
mClipRegion.setRect(mClipRect.left, mClipRect.top,
mClipRect.right, mClipRect.bottom);
} else {
mClipRegion = mRectangleList.convertToRegion(createViewportRegion());
- setClipRectToRegionBounds();
+ onClipRegionUpdated();
}
- mMode = kModeRegion;
}
}
@@ -342,7 +346,7 @@
SkRegion transformedRectRegion;
regionFromPath(transformedRect, transformedRectRegion);
mClipRegion.op(transformedRectRegion, op);
- setClipRectToRegionBounds();
+ onClipRegionUpdated();
return true;
}
@@ -352,12 +356,13 @@
transform, op);
}
-void ClipArea::setClipRectToRegionBounds() {
+void ClipArea::onClipRegionUpdated() {
if (!mClipRegion.isEmpty()) {
mClipRect.set(mClipRegion.getBounds());
if (mClipRegion.isRect()) {
mClipRegion.setEmpty();
+ enterRectangleMode();
}
} else {
mClipRect.setEmpty();
diff --git a/libs/hwui/ClipArea.h b/libs/hwui/ClipArea.h
index 16e6df9..e284af0 100644
--- a/libs/hwui/ClipArea.h
+++ b/libs/hwui/ClipArea.h
@@ -144,7 +144,7 @@
float bottom, const mat4* transform, SkRegion::Op op);
void ensureClipRegion();
- void setClipRectToRegionBounds();
+ void onClipRegionUpdated();
bool clipRegionOp(float left, float top, float right, float bottom,
SkRegion::Op op);
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 48f5dc1..65be9e1 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -138,7 +138,7 @@
int regionSize = ashmem_get_size_region(ashmemfd);
if (regionSize < static_cast<int>(sizeof(ProfileData))) {
ALOGW("Ashmem region is too small! Received %d, required %u",
- regionSize, sizeof(ProfileData));
+ regionSize, static_cast<unsigned int>(sizeof(ProfileData)));
return;
}
ProfileData* newData = reinterpret_cast<ProfileData*>(
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 1255276..9ea6722 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -553,15 +553,8 @@
boolean isimage = MediaFile.isImageFileType(mFileType);
if (isaudio || isvideo || isimage) {
- if (mExternalIsEmulated && path.startsWith(mExternalStoragePath)) {
- // try to rewrite the path to bypass the sd card fuse layer
- String directPath = Environment.getMediaStorageDirectory() +
- path.substring(mExternalStoragePath.length());
- File f = new File(directPath);
- if (f.exists()) {
- path = directPath;
- }
- }
+ path = Environment.maybeTranslateEmulatedPathToInternal(new File(path))
+ .getAbsolutePath();
}
// we only extract metadata for audio and video files
diff --git a/media/java/android/mtp/MtpStorage.java b/media/java/android/mtp/MtpStorage.java
index e20eabc..7b8102b 100644
--- a/media/java/android/mtp/MtpStorage.java
+++ b/media/java/android/mtp/MtpStorage.java
@@ -59,7 +59,7 @@
*
* @return the storage ID
*/
- public static int getStorageId(int index) {
+ public static int getStorageIdForIndex(int index) {
// storage ID is 0x00010001 for primary storage,
// then 0x00020001, 0x00030001, etc. for secondary storages
return ((index + 1) << 16) + 1;
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 9fc7e8e..708c083 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -431,6 +431,19 @@
pData = buffer->data;
dataSize = buffer->stride * buffer->height;
break;
+ case HAL_PIXEL_FORMAT_RAW12:
+ // Single plane 10bpp bayer data.
+ ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
+ LOG_ALWAYS_FATAL_IF(buffer->width % 4,
+ "Width is not multiple of 4 %d", buffer->width);
+ LOG_ALWAYS_FATAL_IF(buffer->height % 2,
+ "Height is not even %d", buffer->height);
+ LOG_ALWAYS_FATAL_IF(buffer->stride < (buffer->width * 12 / 8),
+ "stride (%d) should be at least %d",
+ buffer->stride, buffer->width * 12 / 8);
+ pData = buffer->data;
+ dataSize = buffer->stride * buffer->height;
+ break;
case HAL_PIXEL_FORMAT_RGBA_8888:
case HAL_PIXEL_FORMAT_RGBX_8888:
// Single plane, 32bpp.
@@ -492,8 +505,10 @@
break;
case HAL_PIXEL_FORMAT_BLOB:
case HAL_PIXEL_FORMAT_RAW10:
- // Blob is used for JPEG data, RAW10 is used for 10-bit raw data, they are
- // single plane, row and pixel strides are 0.
+ case HAL_PIXEL_FORMAT_RAW12:
+ // Blob is used for JPEG data, RAW10 and RAW12 is used for 10-bit and 12-bit raw data,
+ // those are single plane data with pixel stride 0 since they don't really have a
+ // well defined pixel stride
ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
pixelStride = 0;
break;
@@ -549,12 +564,14 @@
rowStride = (idx == 0) ? buffer->stride : ALIGN(buffer->stride / 2, 16);
break;
case HAL_PIXEL_FORMAT_BLOB:
- // Blob is used for JPEG data, RAW10 is used for 10-bit raw data, they are
- // single plane, row and pixel strides are 0.
+ // Blob is used for JPEG data. It is single plane and has 0 row stride and
+ // 0 pixel stride
ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
rowStride = 0;
break;
case HAL_PIXEL_FORMAT_RAW10:
+ case HAL_PIXEL_FORMAT_RAW12:
+ // RAW10 and RAW12 are used for 10-bit and 12-bit raw data, they are single plane
ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
rowStride = buffer->stride;
break;
diff --git a/packages/DocumentsUI/res/menu/mode_directory.xml b/packages/DocumentsUI/res/menu/mode_directory.xml
index 0a3645f..695060d 100644
--- a/packages/DocumentsUI/res/menu/mode_directory.xml
+++ b/packages/DocumentsUI/res/menu/mode_directory.xml
@@ -29,4 +29,8 @@
android:icon="@drawable/ic_menu_delete"
android:title="@string/menu_delete"
android:showAsAction="always" />
+ <item
+ android:id="@+id/menu_select_all"
+ android:title="@string/menu_select_all"
+ android:showAsAction="never" />
</menu>
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index 268ce18..4ad337d 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -46,6 +46,8 @@
<string name="menu_delete">Delete</string>
<!-- Menu item title that selects the current directory [CHAR LIMIT=48] -->
<string name="menu_select">Select \"<xliff:g id="directory" example="My Directory">^1</xliff:g>\"</string>
+ <!-- Menu item title that selects all documents in the current directory [CHAR LIMIT=24] -->
+ <string name="menu_select_all">Select All</string>
<!-- Menu item that reveals internal storage built into the device [CHAR LIMIT=24] -->
<string name="menu_advanced_show" product="nosdcard">Show internal storage</string>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index f55912c..a75dc42 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -501,6 +501,14 @@
mode.finish();
return true;
+ } else if (id == R.id.menu_select_all) {
+ int count = mCurrentView.getCount();
+ for (int i = 0; i < count; i++) {
+ mCurrentView.setItemChecked(i, true);
+ }
+ updateDisplayState();
+ return true;
+
} else {
return false;
}
diff --git a/rs/java/android/renderscript/Allocation.java b/rs/java/android/renderscript/Allocation.java
index c4daaa9..2b2e611 100644
--- a/rs/java/android/renderscript/Allocation.java
+++ b/rs/java/android/renderscript/Allocation.java
@@ -273,8 +273,8 @@
}
/**
- * @hide
* Enable/Disable AutoPadding for Vec3 elements.
+ * By default: Diabled.
*
* @param useAutoPadding True: enable AutoPadding; flase: disable AutoPadding
*
diff --git a/rs/java/android/renderscript/ScriptGroup2.java b/rs/java/android/renderscript/ScriptGroup2.java
index 4a56572..a5da519 100644
--- a/rs/java/android/renderscript/ScriptGroup2.java
+++ b/rs/java/android/renderscript/ScriptGroup2.java
@@ -24,25 +24,39 @@
import java.util.Map;
/**
+ * ScriptGroup2 is a new, enhanced API for script groups.
+ * A script group is a collection of kernels or invocable functions, with
+ * data dependencies defined among them. A script group is launched for
+ * execution as a whole, rather than launching each kernel or invocable function
+ * separately. Once created, a script group can be repeatedly used with
+ * different inputs.
+ * <p>
+ * In the new ScriptGroup2 API, a script group is modeled using closures.
+ * A closure, in this context, is defined as a function call to a kernel or
+ * invocable function. Each function argument or global variable accessed inside
+ * the function is bound to 1) a known value, 2) a script group input, or 3) a
+ * future. A future is the output of a closure, i.e., the return value of the
+ * function or a global variable written by that function.
+ * <p>
+ * A script group is a directed acyclic graph (DAG), in which closures are the
+ * vertices and the dependencies among them are the edges.
+ * The way the ScriptGroup2 API is designed makes cycles impossible in a script
+ * group. For example, it is impossible to make forward references to futures,
+ * i.e., it is impossible to set as input to a closure the future from itself or
+ * a future from another closure that directly or indirectly depends on it.
+ * <p>
+ * Grouping kernels and invocable functions together allows to execute them more
+ * efficiently. Runtime and compiler optimizations are applied to script
+ * groups, to reduce computation or communication overhead, and to make more
+ * efficient use of the CPU and the GPU.
+ */
-******************************
-You have tried to change the API from what has been previously approved.
-
-To make these errors go away, you have two choices:
-1) You can add "@hide" javadoc comments to the methods, etc. listed in the
-errors above.
-
-2) You can update current.txt by executing the following command:
-make update-api
-
-To submit the revised current.txt to the main Android repository,
-you will need approval.
-******************************
-
-@hide Pending Android public API approval.
-*/
public class ScriptGroup2 extends BaseObj {
+ /**
+ * An opaque class for closures
+ */
+
public static class Closure extends BaseObj {
private Allocation mReturnValue;
private Map<Script.FieldID, Object> mBindings;
@@ -132,7 +146,6 @@
UnboundValue unbound = (UnboundValue)obj;
unbound.addReference(this, fieldID);
} else {
- // TODO(yangni): Verify obj not a future.
retrieveValueAndDependenceInfo(rs, i, obj, values,
sizes, depClosures, depFieldIDs);
}
@@ -174,6 +187,12 @@
sizes[index] = vs.size;
}
+ /**
+ * Returns the future for the return value
+ *
+ * @return a future
+ */
+
public Future getReturn() {
if (mReturnFuture == null) {
mReturnFuture = new Future(this, null, mReturnValue);
@@ -182,6 +201,13 @@
return mReturnFuture;
}
+ /**
+ * Returns the future for a global variable
+ *
+ * @param field the field ID for the global variable
+ * @return a future
+ */
+
public Future getGlobal(Script.FieldID field) {
Future f = mGlobalFuture.get(field);
@@ -234,6 +260,10 @@
}
}
+ /**
+ * An opaque class for futures
+ */
+
public static class Future {
Closure mClosure;
Script.FieldID mFieldID;
@@ -250,6 +280,10 @@
Object getValue() { return mValue; }
}
+ /**
+ * An opaque class for unbound values (a.k.a. script group inputs)
+ */
+
public static class UnboundValue {
// Either mFieldID or mArgIndex should be set but not both.
List<Pair<Closure, Script.FieldID>> mFieldID;
@@ -309,6 +343,13 @@
setID(id);
}
+ /**
+ * Executes a script group
+ *
+ * @param inputs inputs to the script group
+ * @return outputs of the script group as an array of objects
+ */
+
public Object[] execute(Object... inputs) {
if (inputs.length < mInputs.size()) {
Log.e(TAG, this.toString() + " receives " + inputs.length + " inputs, " +
@@ -343,32 +384,95 @@
}
/**
- @hide Pending Android public API approval.
- */
+ * A class representing a binding of a value to a global variable in a
+ * kernel or invocable function. Such a binding can be used to create a
+ * closure.
+ */
+
public static final class Binding {
- public Script.FieldID mField;
- public Object mValue;
+ private Script.FieldID mField;
+ private Object mValue;
+
+ /**
+ * Returns a Binding object that binds value to field
+ *
+ * @param field the Script.FieldID of the global variable
+ * @param value the value
+ */
+
public Binding(Script.FieldID field, Object value) {
mField = field;
mValue = value;
}
+
+ /**
+ * Returns the field ID
+ */
+
+ public Script.FieldID getField() { return mField; }
+
+ /**
+ * Returns the value
+ */
+
+ public Object getValue() { return mValue; }
}
/**
- @hide Pending Android public API approval.
- */
+ * The builder class to create a script group.
+ * <p>
+ * Closures are created using the {@link #addKernel} or {@link #addInvoke}
+ * methods.
+ * When a closure is created, futures from previously created closures
+ * can be used as inputs.
+ * Unbound values can be used as inputs to create closures as well.
+ * An unbound value is created using the {@link #addInput} method.
+ * Unbound values become inputs to the script group to be created,
+ * in the order that they are added.
+ * A script group is created by a call to the {@link #create} method, which
+ * accepts an array of futures as the outputs for the script group.
+ * <p>
+ * Closures in a script group can be evaluated in any order as long as the
+ * following conditions are met.
+ * First, a closure must be evaluated before any other closures that take its
+ * futures as inputs.
+ * Second, all closures added before an invoke closure must be evaluated
+ * before it.
+ * Third, all closures added after an invoke closure must be evaluated after
+ * it.
+ * <p>
+ * As a special case, the order that the closures are added is a legal
+ * evaluation order. However, other evaluation orders are allowed, including
+ * concurrently evaluating independent closures.
+ */
+
public static final class Builder {
RenderScript mRS;
List<Closure> mClosures;
List<UnboundValue> mInputs;
private static final String TAG = "ScriptGroup2.Builder";
+ /**
+ * Returns a Builder object
+ *
+ * @param rs the RenderScript context
+ */
public Builder(RenderScript rs) {
mRS = rs;
mClosures = new ArrayList<Closure>();
mInputs = new ArrayList<UnboundValue>();
}
+ /**
+ * Adds a closure for a kernel
+ *
+ * @param k Kernel ID for the kernel function
+ * @param returnType Allocation type for the return value
+ * @param args arguments to the kernel function
+ * @param globalBindings bindings for global variables
+ * @return a closure
+ */
+
public Closure addKernel(Script.KernelID k, Type returnType, Object[] args,
Map<Script.FieldID, Object> globalBindings) {
Closure c = new Closure(mRS, k, returnType, args, globalBindings);
@@ -376,6 +480,15 @@
return c;
}
+ /**
+ * Adds a closure for an invocable function
+ *
+ * @param invoke Invoke ID for the invocable function
+ * @param args arguments to the invocable function
+ * @param globalBindings bindings for global variables
+ * @return a closure
+ */
+
public Closure addInvoke(Script.InvokeID invoke, Object[] args,
Map<Script.FieldID, Object> globalBindings) {
Closure c = new Closure(mRS, invoke, args, globalBindings);
@@ -383,12 +496,25 @@
return c;
}
+ /**
+ * Adds a script group input
+ *
+ * @return a unbound value that can be used to create a closure
+ */
public UnboundValue addInput() {
UnboundValue unbound = new UnboundValue();
mInputs.add(unbound);
return unbound;
}
+ /**
+ * Adds a closure for a kernel
+ *
+ * @param k Kernel ID for the kernel function
+ * @param argsAndBindings arguments followed by bindings for global variables
+ * @return a closure
+ */
+
public Closure addKernel(Script.KernelID k, Type returnType, Object... argsAndBindings) {
ArrayList<Object> args = new ArrayList<Object>();
Map<Script.FieldID, Object> bindingMap = new HashMap<Script.FieldID, Object>();
@@ -398,6 +524,14 @@
return addKernel(k, returnType, args.toArray(), bindingMap);
}
+ /**
+ * Adds a closure for an invocable function
+ *
+ * @param invoke Invoke ID for the invocable function
+ * @param argsAndBindings arguments followed by bindings for global variables
+ * @return a closure
+ */
+
public Closure addInvoke(Script.InvokeID invoke, Object... argsAndBindings) {
ArrayList<Object> args = new ArrayList<Object>();
Map<Script.FieldID, Object> bindingMap = new HashMap<Script.FieldID, Object>();
@@ -407,6 +541,13 @@
return addInvoke(invoke, args.toArray(), bindingMap);
}
+ /**
+ * Creates a script group
+ *
+ * @param outputs futures intended as outputs of the script group
+ * @return a script group
+ */
+
public ScriptGroup2 create(Future... outputs) {
ScriptGroup2 ret = new ScriptGroup2(mRS, mClosures, mInputs, outputs);
return ret;
@@ -428,7 +569,7 @@
return false;
}
Binding b = (Binding)argsAndBindings[i];
- bindingMap.put(b.mField, b.mValue);
+ bindingMap.put(b.getField(), b.getValue());
}
return true;
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 45b0fb2..5cc59e5 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -3938,16 +3938,6 @@
return;
}
- // Don't proceed unless we have already established package metadata
- // for the current dataset via a key/value backup pass.
- File stateDir = new File(mBaseStateDir, transport.transportDirName());
- File pmState = new File(stateDir, PACKAGE_MANAGER_SENTINEL);
- if (pmState.length() <= 0) {
- Slog.i(TAG, "Full backup requested but dataset not yet initialized "
- + "via k/v backup pass; ignoring");
- return;
- }
-
// Set up to send data to the transport
final int N = mPackages.size();
for (int i = 0; i < N; i++) {
@@ -4296,6 +4286,31 @@
writeFullBackupScheduleAsync();
}
+ private boolean fullBackupAllowable(IBackupTransport transport) {
+ if (transport == null) {
+ Slog.w(TAG, "Transport not present; full data backup not performed");
+ return false;
+ }
+
+ // Don't proceed unless we have already established package metadata
+ // for the current dataset via a key/value backup pass.
+ try {
+ File stateDir = new File(mBaseStateDir, transport.transportDirName());
+ File pmState = new File(stateDir, PACKAGE_MANAGER_SENTINEL);
+ if (pmState.length() <= 0) {
+ if (DEBUG) {
+ Slog.i(TAG, "Full backup requested but dataset not yet initialized");
+ }
+ return false;
+ }
+ } catch (Exception e) {
+ Slog.w(TAG, "Unable to contact transport");
+ return false;
+ }
+
+ return true;
+ }
+
/**
* Conditions are right for a full backup operation, so run one. The model we use is
* to perform one app backup per scheduled job execution, and to reschedule the job
@@ -4307,6 +4322,7 @@
boolean beginFullBackup(FullBackupJob scheduledJob) {
long now = System.currentTimeMillis();
FullBackupEntry entry = null;
+ long latency = MIN_FULL_BACKUP_INTERVAL;
if (!mEnabled || !mProvisioned) {
// Backups are globally disabled, so don't proceed. We also don't reschedule
@@ -4338,17 +4354,41 @@
return false;
}
- entry = mFullBackupQueue.get(0);
- long timeSinceRun = now - entry.lastBackup;
- if (timeSinceRun < MIN_FULL_BACKUP_INTERVAL) {
- // It's too early to back up the next thing in the queue, so bow out
+ // At this point we know that we have work to do, just not right now. Any
+ // exit without actually running backups will also require that we
+ // reschedule the job.
+ boolean runBackup = true;
+
+ if (!fullBackupAllowable(getTransport(mCurrentTransport))) {
if (MORE_DEBUG) {
- Slog.i(TAG, "Device ready but too early to back up next app");
+ Slog.i(TAG, "Preconditions not met; not running full backup");
}
- final long latency = MIN_FULL_BACKUP_INTERVAL - timeSinceRun;
+ runBackup = false;
+ // Typically this means we haven't run a key/value backup yet. Back off
+ // full-backup operations by the key/value job's run interval so that
+ // next time we run, we are likely to be able to make progress.
+ latency = KeyValueBackupJob.BATCH_INTERVAL;
+ }
+
+ if (runBackup) {
+ entry = mFullBackupQueue.get(0);
+ long timeSinceRun = now - entry.lastBackup;
+ runBackup = (timeSinceRun >= MIN_FULL_BACKUP_INTERVAL);
+ if (!runBackup) {
+ // It's too early to back up the next thing in the queue, so bow out
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Device ready but too early to back up next app");
+ }
+ // Wait until the next app in the queue falls due for a full data backup
+ latency = MIN_FULL_BACKUP_INTERVAL - timeSinceRun;
+ }
+ }
+
+ if (!runBackup) {
+ final long deferTime = latency; // pin for the closure
mBackupHandler.post(new Runnable() {
@Override public void run() {
- FullBackupJob.schedule(mContext, latency);
+ FullBackupJob.schedule(mContext, deferTime);
}
});
return false;
@@ -8489,27 +8529,31 @@
throw new IllegalStateException("Restore supported only for the device owner");
}
- if (DEBUG) {
- Slog.d(TAG, "fullTransportBackup()");
- }
-
- CountDownLatch latch = new CountDownLatch(1);
- PerformFullTransportBackupTask task =
- new PerformFullTransportBackupTask(null, pkgNames, false, null, latch);
- (new Thread(task, "full-transport-master")).start();
- do {
- try {
- latch.await();
- break;
- } catch (InterruptedException e) {
- // Just go back to waiting for the latch to indicate completion
+ if (!fullBackupAllowable(getTransport(mCurrentTransport))) {
+ Slog.i(TAG, "Full backup not currently possible -- key/value backup not yet run?");
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, "fullTransportBackup()");
}
- } while (true);
- // We just ran a backup on these packages, so kick them to the end of the queue
- final long now = System.currentTimeMillis();
- for (String pkg : pkgNames) {
- enqueueFullBackup(pkg, now);
+ CountDownLatch latch = new CountDownLatch(1);
+ PerformFullTransportBackupTask task =
+ new PerformFullTransportBackupTask(null, pkgNames, false, null, latch);
+ (new Thread(task, "full-transport-master")).start();
+ do {
+ try {
+ latch.await();
+ break;
+ } catch (InterruptedException e) {
+ // Just go back to waiting for the latch to indicate completion
+ }
+ } while (true);
+
+ // We just ran a backup on these packages, so kick them to the end of the queue
+ final long now = System.currentTimeMillis();
+ for (String pkg : pkgNames) {
+ enqueueFullBackup(pkg, now);
+ }
}
if (DEBUG) {
diff --git a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
index dc1c9d5..a4489c1 100644
--- a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
+++ b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
@@ -41,7 +41,7 @@
// Once someone asks for a backup, this is how long we hold off, batching
// up additional requests, before running the actual backup pass. Privileged
// callers can always trigger an immediate pass via BackupManager.backupNow().
- private static final long BATCH_INTERVAL = 4 * AlarmManager.INTERVAL_HOUR;
+ static final long BATCH_INTERVAL = 4 * AlarmManager.INTERVAL_HOUR;
// Random variation in next-backup scheduling time to avoid server load spikes
private static final int FUZZ_MILLIS = 10 * 60 * 1000;
diff --git a/services/core/Android.mk b/services/core/Android.mk
index 1a0fa34..64b6134 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -10,5 +10,6 @@
java/com/android/server/am/EventLogTags.logtags
LOCAL_JAVA_LIBRARIES := telephony-common
+LOCAL_STATIC_JAVA_LIBRARIES := tzdata_update
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index b8d9ec5..61286e8 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -16,29 +16,21 @@
package com.android.server;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
import android.Manifest;
import android.app.ActivityManagerNative;
import android.app.AppOpsManager;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.content.res.ObbInfo;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.hardware.usb.UsbManager;
+import android.mtp.MtpStorage;
import android.net.Uri;
import android.os.Binder;
import android.os.Environment;
import android.os.Environment.UserEnvironment;
+import android.os.FileUtils;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -59,26 +51,24 @@
import android.os.storage.StorageResultCode;
import android.os.storage.StorageVolume;
import android.text.TextUtils;
-import android.util.AttributeSet;
+import android.util.ArrayMap;
+import android.util.DebugUtils;
import android.util.Slog;
-import android.util.Xml;
+import android.util.SparseArray;
+
+import libcore.util.EmptyArray;
+import libcore.util.HexEncoding;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IMediaContainerService;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
-import com.android.internal.util.XmlUtils;
import com.android.server.NativeDaemonConnector.Command;
import com.android.server.NativeDaemonConnector.SensitiveArg;
-import com.android.server.am.ActivityManagerService;
import com.android.server.pm.PackageManagerService;
-import com.android.server.pm.UserManagerService;
import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
-import libcore.util.HexEncoding;
-
-import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileDescriptor;
@@ -101,7 +91,6 @@
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
-import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -110,19 +99,18 @@
import javax.crypto.spec.PBEKeySpec;
/**
- * MountService implements back-end services for platform storage
- * management.
- * @hide - Applications should use android.os.storage.StorageManager
- * to access the MountService.
+ * Service responsible for various storage media. Connects to {@code vold} to
+ * watch for and manage dynamically added storage, such as SD cards and USB mass
+ * storage. Also decides how storage should be presented to users on the device.
*/
class MountService extends IMountService.Stub
implements INativeDaemonConnectorCallbacks, Watchdog.Monitor {
+ // TODO: finish enforcing UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA
+
// Static direct instance pointer for the tightly-coupled idle service to use
static MountService sSelf = null;
- // TODO: listen for user creation/deletion
-
public static class Lifecycle extends SystemService {
private MountService mMountService;
@@ -142,6 +130,16 @@
mMountService.systemReady();
}
}
+
+ @Override
+ public void onStartUser(int userHandle) {
+ mMountService.onStartUser(userHandle);
+ }
+
+ @Override
+ public void onCleanupUser(int userHandle) {
+ mMountService.onCleanupUser(userHandle);
+ }
}
private static final boolean LOCAL_LOGD = false;
@@ -159,21 +157,7 @@
/** Maximum number of ASEC containers allowed to be mounted. */
private static final int MAX_CONTAINERS = 250;
- /*
- * Internal vold volume state constants
- */
- class VolumeState {
- public static final int Init = -1;
- public static final int NoMedia = 0;
- public static final int Idle = 1;
- public static final int Pending = 2;
- public static final int Checking = 3;
- public static final int Mounted = 4;
- public static final int Unmounting = 5;
- public static final int Formatting = 6;
- public static final int Shared = 7;
- public static final int SharedMnt = 8;
- }
+ private static final String PROP_PRIMARY_PHYSICAL = "ro.vold.primary_physical";
/*
* Internal vold response code constants
@@ -209,12 +193,19 @@
/*
* 600 series - Unsolicited broadcasts.
*/
- public static final int VolumeStateChange = 605;
- public static final int VolumeUuidChange = 613;
- public static final int VolumeUserLabelChange = 614;
- public static final int VolumeDiskInserted = 630;
- public static final int VolumeDiskRemoved = 631;
- public static final int VolumeBadRemoval = 632;
+ public static final int DISK_CREATED = 640;
+ public static final int DISK_SIZE_CHANGED = 641;
+ public static final int DISK_LABEL_CHANGED = 642;
+ public static final int DISK_VOLUME_CREATED = 643;
+ public static final int DISK_DESTROYED = 649;
+
+ public static final int VOLUME_CREATED = 650;
+ public static final int VOLUME_STATE_CHANGED = 651;
+ public static final int VOLUME_FS_TYPE_CHANGED = 652;
+ public static final int VOLUME_FS_UUID_CHANGED = 653;
+ public static final int VOLUME_FS_LABEL_CHANGED = 654;
+ public static final int VOLUME_PATH_CHANGED = 655;
+ public static final int VOLUME_DESTROYED = 659;
/*
* 700 series - fstrim
@@ -222,6 +213,243 @@
public static final int FstrimCompleted = 700;
}
+ private static SparseArray<String> sStateToEnvironment = new SparseArray<>();
+ private static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>();
+
+ static {
+ sStateToEnvironment.put(Volume.STATE_UNMOUNTED, Environment.MEDIA_UNMOUNTED);
+ sStateToEnvironment.put(Volume.STATE_MOUNTING, Environment.MEDIA_CHECKING);
+ sStateToEnvironment.put(Volume.STATE_MOUNTED, Environment.MEDIA_MOUNTED);
+ sStateToEnvironment.put(Volume.STATE_FORMATTING, Environment.MEDIA_UNMOUNTED);
+ sStateToEnvironment.put(Volume.STATE_UNMOUNTING, Environment.MEDIA_EJECTING);
+
+ sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTED, Intent.ACTION_MEDIA_UNMOUNTED);
+ sEnvironmentToBroadcast.put(Environment.MEDIA_CHECKING, Intent.ACTION_MEDIA_CHECKING);
+ sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED, Intent.ACTION_MEDIA_MOUNTED);
+ sEnvironmentToBroadcast.put(Environment.MEDIA_EJECTING, Intent.ACTION_MEDIA_EJECT);
+ }
+
+ /**
+ * <em>Never</em> hold the lock while performing downcalls into vold, since
+ * unsolicited events can suddenly appear to update data structures.
+ */
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private int[] mStartedUsers = EmptyArray.INT;
+ @GuardedBy("mLock")
+ private ArrayMap<String, Disk> mDisks = new ArrayMap<>();
+ @GuardedBy("mLock")
+ private ArrayMap<String, Volume> mVolumes = new ArrayMap<>();
+
+ @Deprecated
+ private Volume findVolumeByLegacyPath(String legacyPath) {
+ synchronized (mLock) {
+ for (Volume vol : mVolumes.values()) {
+ if (vol.path != null && legacyPath.startsWith(vol.path)) {
+ return vol;
+ }
+ }
+ }
+ Slog.w(TAG, "Failed to find volume for path " + legacyPath);
+ return null;
+ }
+
+ /**
+ * Framework-side twin of android::vold::Disk
+ */
+ private class Disk {
+ public static final int FLAG_ADOPTABLE = 1 << 0;
+ public static final int FLAG_DEFAULT_PRIMARY = 1 << 1;
+ public static final int FLAG_SD = 1 << 2;
+ public static final int FLAG_USB = 1 << 3;
+
+ public final String id;
+ public final int flags;
+ public long size;
+ public String label;
+
+ public ArrayList<Volume> volumes = new ArrayList<>();
+
+ public Disk(String id, int flags) {
+ this.id = id;
+ this.flags = flags;
+ }
+
+ public void partitionPublic() throws NativeDaemonConnectorException {
+ mConnector.execute("volume", "partition", id, "public");
+ }
+
+ public void partitionPrivate() throws NativeDaemonConnectorException {
+ mConnector.execute("volume", "partition", id, "private");
+ }
+
+ public void partitionMixed(int frac) throws NativeDaemonConnectorException {
+ mConnector.execute("volume", "partition", id, "mixed", frac);
+ }
+
+ public void dump(IndentingPrintWriter pw) {
+ pw.println("Disk:");
+ pw.increaseIndent();
+ pw.printPair("id", id);
+ pw.printPair("flags", DebugUtils.flagsToString(getClass(), "FLAG_", flags));
+ pw.printPair("size", size);
+ pw.printPair("label", label);
+ pw.decreaseIndent();
+ pw.println();
+ }
+ }
+
+ private static int sNextMtpIndex = 1;
+
+ /**
+ * Framework-side twin of android::vold::VolumeBase
+ */
+ private class Volume {
+ public static final String ID_EMULATED_INTERNAL = "emulated";
+
+ public static final int TYPE_PUBLIC = 0;
+ public static final int TYPE_PRIVATE = 1;
+ public static final int TYPE_EMULATED = 2;
+ public static final int TYPE_ASEC = 3;
+ public static final int TYPE_OBB = 4;
+
+ public static final int STATE_UNMOUNTED = 0;
+ public static final int STATE_MOUNTING = 1;
+ public static final int STATE_MOUNTED = 2;
+ public static final int STATE_FORMATTING = 3;
+ public static final int STATE_UNMOUNTING = 4;
+
+ public static final int FLAG_PRIMARY = 1 << 0;
+ public static final int FLAG_VISIBLE = 1 << 1;
+
+ /** vold state */
+ public final String id;
+ public final int type;
+ public int flags = 0;
+ public int userId = -1;
+ public int state = STATE_UNMOUNTED;
+ public String fsType;
+ public String fsUuid;
+ public String fsLabel;
+ public String path = "/dev/null";
+
+ /** Framework state */
+ public final int mtpIndex;
+
+ public Disk disk;
+
+ public Volume(String id, int type) {
+ this.id = id;
+ this.type = type;
+
+ if (ID_EMULATED_INTERNAL.equals(id)) {
+ mtpIndex = 0;
+ } else {
+ mtpIndex = sNextMtpIndex++;
+ }
+ }
+
+ public boolean isPrimary() {
+ return (flags & FLAG_PRIMARY) != 0;
+ }
+
+ public boolean isVisible() {
+ return (flags & FLAG_VISIBLE) != 0;
+ }
+
+ public boolean isVisibleToUser(int userId) {
+ if (type == TYPE_PUBLIC && this.userId == userId) {
+ return isVisible();
+ } else if (type == TYPE_EMULATED) {
+ return isVisible();
+ } else {
+ return false;
+ }
+ }
+
+ public void mount() throws NativeDaemonConnectorException {
+ mConnector.execute("volume", "mount", id, flags, userId);
+ }
+
+ public void unmount() throws NativeDaemonConnectorException {
+ mConnector.execute("volume", "unmount", id);
+ }
+
+ public void format() throws NativeDaemonConnectorException {
+ mConnector.execute("volume", "format", id);
+ }
+
+ public StorageVolume buildVolumeForUser(int userId) {
+ final File userPath;
+ final boolean removable;
+ final boolean emulated;
+ final boolean allowMassStorage = false;
+ final int mtpStorageId = MtpStorage.getStorageIdForIndex(mtpIndex);
+ final String envState = sStateToEnvironment.get(state);
+
+ int descriptionId = com.android.internal.R.string.unknownName;
+ long mtpReserveSize = 0;
+ long maxFileSize = 0;
+
+ if (type == TYPE_EMULATED) {
+ userPath = new File(path, Integer.toString(userId));
+ emulated = true;
+ mtpReserveSize = StorageManager.from(mContext).getStorageLowBytes(userPath);
+ descriptionId = com.android.internal.R.string.storage_internal;
+
+ if (ID_EMULATED_INTERNAL.equals(id)) {
+ removable = false;
+ } else {
+ removable = true;
+ }
+
+ } else if (type == TYPE_PUBLIC) {
+ userPath = new File(path);
+ emulated = false;
+ removable = true;
+
+ if (disk != null) {
+ if ((disk.flags & Disk.FLAG_SD) != 0) {
+ descriptionId = com.android.internal.R.string.storage_sd_card;
+ } else if ((disk.flags & Disk.FLAG_USB) != 0) {
+ descriptionId = com.android.internal.R.string.storage_usb;
+ }
+ }
+
+ if ("vfat".equals(fsType)) {
+ maxFileSize = 4294967295L;
+ }
+
+ } else {
+ throw new IllegalStateException("Unexpected volume type " + type);
+ }
+
+ return new StorageVolume(id, mtpStorageId, userPath, descriptionId, isPrimary(),
+ removable, emulated, mtpReserveSize, allowMassStorage, maxFileSize,
+ new UserHandle(userId), fsUuid, fsLabel, envState);
+ }
+
+ public void dump(IndentingPrintWriter pw) {
+ pw.println("Volume:");
+ pw.increaseIndent();
+ pw.printPair("id", id);
+ pw.printPair("type", DebugUtils.valueToString(getClass(), "TYPE_", type));
+ pw.printPair("flags", DebugUtils.flagsToString(getClass(), "FLAG_", flags));
+ pw.printPair("userId", userId);
+ pw.printPair("state", DebugUtils.valueToString(getClass(), "STATE_", state));
+ pw.println();
+ pw.printPair("fsType", fsType);
+ pw.printPair("fsUuid", fsUuid);
+ pw.printPair("fsLabel", fsLabel);
+ pw.println();
+ pw.printPair("path", path);
+ pw.printPair("mtpIndex", mtpIndex);
+ pw.decreaseIndent();
+ pw.println();
+ }
+ }
+
/** List of crypto types.
* These must match CRYPT_TYPE_XXX in cryptfs.h AND their
* corresponding commands in CommandListener.cpp */
@@ -231,33 +459,20 @@
private final Context mContext;
private final NativeDaemonConnector mConnector;
- private final Object mVolumesLock = new Object();
-
- /** When defined, base template for user-specific {@link StorageVolume}. */
- private StorageVolume mEmulatedTemplate;
-
- // TODO: separate storage volumes on per-user basis
-
- @GuardedBy("mVolumesLock")
- private final ArrayList<StorageVolume> mVolumes = Lists.newArrayList();
- /** Map from path to {@link StorageVolume} */
- @GuardedBy("mVolumesLock")
- private final HashMap<String, StorageVolume> mVolumesByPath = Maps.newHashMap();
- /** Map from path to state */
- @GuardedBy("mVolumesLock")
- private final HashMap<String, String> mVolumeStates = Maps.newHashMap();
-
private volatile boolean mSystemReady = false;
+ private volatile boolean mDaemonConnected = false;
private PackageManagerService mPms;
- private boolean mUmsEnabling;
- private boolean mUmsAvailable = false;
// Used as a lock for methods that register/unregister listeners.
final private ArrayList<MountServiceBinderListener> mListeners =
new ArrayList<MountServiceBinderListener>();
+
private final CountDownLatch mConnectedSignal = new CountDownLatch(1);
private final CountDownLatch mAsecsScanned = new CountDownLatch(1);
- private boolean mSendUmsConnectedOnBoot = false;
+
+ private final Object mUnmountLock = new Object();
+ @GuardedBy("mUnmountLock")
+ private CountDownLatch mUnmountSignal;
/**
* Private hash of currently mounted secure containers.
@@ -366,6 +581,7 @@
final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection();
class DefaultContainerConnection implements ServiceConnection {
+ @Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (DEBUG_OBB)
Slog.i(TAG, "onServiceConnected");
@@ -373,6 +589,7 @@
mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_MCS_BOUND, imcs));
}
+ @Override
public void onServiceDisconnected(ComponentName name) {
if (DEBUG_OBB)
Slog.i(TAG, "onServiceDisconnected");
@@ -388,177 +605,27 @@
private long mLastMaintenance;
// Handler messages
- private static final int H_UNMOUNT_PM_UPDATE = 1;
- private static final int H_UNMOUNT_PM_DONE = 2;
- private static final int H_UNMOUNT_MS = 3;
- private static final int H_SYSTEM_READY = 4;
- private static final int H_FSTRIM = 5;
-
- private static final int RETRY_UNMOUNT_DELAY = 30; // in ms
- private static final int MAX_UNMOUNT_RETRIES = 4;
-
- class UnmountCallBack {
- final String path;
- final boolean force;
- final boolean removeEncryption;
- int retries;
-
- UnmountCallBack(String path, boolean force, boolean removeEncryption) {
- retries = 0;
- this.path = path;
- this.force = force;
- this.removeEncryption = removeEncryption;
- }
-
- void handleFinished() {
- if (DEBUG_UNMOUNT) Slog.i(TAG, "Unmounting " + path);
- doUnmountVolume(path, true, removeEncryption);
- }
- }
-
- class UmsEnableCallBack extends UnmountCallBack {
- final String method;
-
- UmsEnableCallBack(String path, String method, boolean force) {
- super(path, force, false);
- this.method = method;
- }
-
- @Override
- void handleFinished() {
- super.handleFinished();
- doShareUnshareVolume(path, method, true);
- }
- }
-
- class ShutdownCallBack extends UnmountCallBack {
- MountShutdownLatch mMountShutdownLatch;
- ShutdownCallBack(String path, final MountShutdownLatch mountShutdownLatch) {
- super(path, true, false);
- mMountShutdownLatch = mountShutdownLatch;
- }
-
- @Override
- void handleFinished() {
- int ret = doUnmountVolume(path, true, removeEncryption);
- Slog.i(TAG, "Unmount completed: " + path + ", result code: " + ret);
- mMountShutdownLatch.countDown();
- }
- }
-
- static class MountShutdownLatch {
- private IMountShutdownObserver mObserver;
- private AtomicInteger mCount;
-
- MountShutdownLatch(final IMountShutdownObserver observer, int count) {
- mObserver = observer;
- mCount = new AtomicInteger(count);
- }
-
- void countDown() {
- boolean sendShutdown = false;
- if (mCount.decrementAndGet() == 0) {
- sendShutdown = true;
- }
- if (sendShutdown && mObserver != null) {
- try {
- mObserver.onShutDownComplete(StorageResultCode.OperationSucceeded);
- } catch (RemoteException e) {
- Slog.w(TAG, "RemoteException when shutting down");
- }
- }
- }
- }
+ private static final int H_SYSTEM_READY = 1;
+ private static final int H_DAEMON_CONNECTED = 2;
+ private static final int H_SHUTDOWN = 3;
+ private static final int H_FSTRIM = 4;
+ private static final int H_VOLUME_MOUNT = 5;
+ private static final int H_VOLUME_BROADCAST = 6;
class MountServiceHandler extends Handler {
- ArrayList<UnmountCallBack> mForceUnmounts = new ArrayList<UnmountCallBack>();
- boolean mUpdatingStatus = false;
-
- MountServiceHandler(Looper l) {
- super(l);
+ public MountServiceHandler(Looper looper) {
+ super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case H_UNMOUNT_PM_UPDATE: {
- if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_UPDATE");
- UnmountCallBack ucb = (UnmountCallBack) msg.obj;
- mForceUnmounts.add(ucb);
- if (DEBUG_UNMOUNT) Slog.i(TAG, " registered = " + mUpdatingStatus);
- // Register only if needed.
- if (!mUpdatingStatus) {
- if (DEBUG_UNMOUNT) Slog.i(TAG, "Updating external media status on PackageManager");
- mUpdatingStatus = true;
- mPms.updateExternalMediaStatus(false, true);
- }
- break;
- }
- case H_UNMOUNT_PM_DONE: {
- if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_DONE");
- if (DEBUG_UNMOUNT) Slog.i(TAG, "Updated status. Processing requests");
- mUpdatingStatus = false;
- int size = mForceUnmounts.size();
- int sizeArr[] = new int[size];
- int sizeArrN = 0;
- // Kill processes holding references first
- ActivityManagerService ams = (ActivityManagerService)
- ServiceManager.getService("activity");
- for (int i = 0; i < size; i++) {
- UnmountCallBack ucb = mForceUnmounts.get(i);
- String path = ucb.path;
- boolean done = false;
- if (!ucb.force) {
- done = true;
- } else {
- int pids[] = getStorageUsers(path);
- if (pids == null || pids.length == 0) {
- done = true;
- } else {
- // Eliminate system process here?
- ams.killPids(pids, "unmount media", true);
- // Confirm if file references have been freed.
- pids = getStorageUsers(path);
- if (pids == null || pids.length == 0) {
- done = true;
- }
- }
- }
- if (!done && (ucb.retries < MAX_UNMOUNT_RETRIES)) {
- // Retry again
- Slog.i(TAG, "Retrying to kill storage users again");
- mHandler.sendMessageDelayed(
- mHandler.obtainMessage(H_UNMOUNT_PM_DONE,
- ucb.retries++),
- RETRY_UNMOUNT_DELAY);
- } else {
- if (ucb.retries >= MAX_UNMOUNT_RETRIES) {
- Slog.i(TAG, "Failed to unmount media inspite of " +
- MAX_UNMOUNT_RETRIES + " retries. Forcibly killing processes now");
- }
- sizeArr[sizeArrN++] = i;
- mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_MS,
- ucb));
- }
- }
- // Remove already processed elements from list.
- for (int i = (sizeArrN-1); i >= 0; i--) {
- mForceUnmounts.remove(sizeArr[i]);
- }
- break;
- }
- case H_UNMOUNT_MS: {
- if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_MS");
- UnmountCallBack ucb = (UnmountCallBack) msg.obj;
- ucb.handleFinished();
- break;
- }
case H_SYSTEM_READY: {
- try {
- handleSystemReady();
- } catch (Exception ex) {
- Slog.e(TAG, "Boot-time mount exception", ex);
- }
+ handleSystemReady();
+ break;
+ }
+ case H_DAEMON_CONNECTED: {
+ handleDaemonConnected();
break;
}
case H_FSTRIM: {
@@ -589,29 +656,68 @@
}
break;
}
+ case H_SHUTDOWN: {
+ final IMountShutdownObserver obs = (IMountShutdownObserver) msg.obj;
+ boolean success = false;
+ try {
+ success = mConnector.execute("volume", "shutdown").isClassOk();
+ } catch (NativeDaemonConnectorException ignored) {
+ }
+ if (obs != null) {
+ try {
+ obs.onShutDownComplete(success ? 0 : -1);
+ } catch (RemoteException ignored) {
+ }
+ }
+ break;
+ }
+ case H_VOLUME_MOUNT: {
+ final Volume vol = (Volume) msg.obj;
+ try {
+ vol.mount();
+ } catch (NativeDaemonConnectorException ignored) {
+ }
+ break;
+ }
+ case H_VOLUME_BROADCAST: {
+ final StorageVolume userVol = (StorageVolume) msg.obj;
+ final String state = userVol.getState();
+ Slog.d(TAG, "Volume " + userVol.getId() + " broadcasting " + state + " to "
+ + userVol.getOwner());
+
+ final String action = sEnvironmentToBroadcast.get(state);
+ if (action != null) {
+ final Intent intent = new Intent(action,
+ Uri.fromFile(userVol.getPathFile()));
+ intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, userVol);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcastAsUser(intent, userVol.getOwner());
+ }
+ break;
+ }
}
}
- };
+ }
private final Handler mHandler;
@Override
public void waitForAsecScan() {
- waitForLatch(mAsecsScanned);
+ waitForLatch(mAsecsScanned, "mAsecsScanned");
}
private void waitForReady() {
- waitForLatch(mConnectedSignal);
+ waitForLatch(mConnectedSignal, "mConnectedSignal");
}
- private void waitForLatch(CountDownLatch latch) {
+ private void waitForLatch(CountDownLatch latch, String condition) {
for (;;) {
try {
if (latch.await(5000, TimeUnit.MILLISECONDS)) {
return;
} else {
Slog.w(TAG, "Thread " + Thread.currentThread().getName()
- + " still waiting for MountService ready...");
+ + " still waiting for " + condition + "...");
}
} catch (InterruptedException e) {
Slog.w(TAG, "Interrupt while waiting for MountService to be ready.");
@@ -628,103 +734,72 @@
}
private void handleSystemReady() {
- // Snapshot current volume states since it's not safe to call into vold
- // while holding locks.
- final HashMap<String, String> snapshot;
- synchronized (mVolumesLock) {
- snapshot = new HashMap<String, String>(mVolumeStates);
- }
+ resetIfReadyAndConnected();
- for (Map.Entry<String, String> entry : snapshot.entrySet()) {
- final String path = entry.getKey();
- final String state = entry.getValue();
-
- if (state.equals(Environment.MEDIA_UNMOUNTED)) {
- int rc = doMountVolume(path);
- if (rc != StorageResultCode.OperationSucceeded) {
- Slog.e(TAG, String.format("Boot-time mount failed (%d)",
- rc));
- }
- } else if (state.equals(Environment.MEDIA_SHARED)) {
- /*
- * Bootstrap UMS enabled state since vold indicates
- * the volume is shared (runtime restart while ums enabled)
- */
- notifyVolumeStateChange(null, path, VolumeState.NoMedia,
- VolumeState.Shared);
- }
- }
-
- // Push mounted state for all emulated storage
- synchronized (mVolumesLock) {
- for (StorageVolume volume : mVolumes) {
- if (volume.isEmulated()) {
- updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED);
- }
- }
- }
-
- /*
- * If UMS was connected on boot, send the connected event
- * now that we're up.
- */
- if (mSendUmsConnectedOnBoot) {
- sendUmsIntent(true);
- mSendUmsConnectedOnBoot = false;
- }
-
- /*
- * Start scheduling nominally-daily fstrim operations
- */
+ // Start scheduling nominally-daily fstrim operations
MountServiceIdler.scheduleIdlePass(mContext);
}
- private final BroadcastReceiver mUserReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
- if (userId == -1) return;
- final UserHandle user = new UserHandle(userId);
+ private void resetIfReadyAndConnected() {
+ Slog.d(TAG, "Thinking about reset, mSystemReady=" + mSystemReady
+ + ", mDaemonConnected=" + mDaemonConnected);
+ if (mSystemReady && mDaemonConnected) {
+ mDisks.clear();
+ mVolumes.clear();
- final String action = intent.getAction();
- if (Intent.ACTION_USER_ADDED.equals(action)) {
- synchronized (mVolumesLock) {
- createEmulatedVolumeForUserLocked(user);
- }
-
- } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
- synchronized (mVolumesLock) {
- final List<StorageVolume> toRemove = Lists.newArrayList();
- for (StorageVolume volume : mVolumes) {
- if (user.equals(volume.getOwner())) {
- toRemove.add(volume);
- }
- }
- for (StorageVolume volume : toRemove) {
- removeVolumeLocked(volume);
- }
- }
+ try {
+ mConnector.execute("volume", "reset");
+ } catch (NativeDaemonConnectorException e) {
+ Slog.w(TAG, "Failed to reset vold", e);
}
}
- };
+ }
- private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- boolean available = (intent.getBooleanExtra(UsbManager.USB_CONNECTED, false) &&
- intent.getBooleanExtra(UsbManager.USB_FUNCTION_MASS_STORAGE, false));
- notifyShareAvailabilityChange(available);
+ private void onStartUser(int userId) {
+ Slog.d(TAG, "onStartUser " + userId);
+
+ // We purposefully block here to make sure that user-specific
+ // staging area is ready so it's ready for zygote-forked apps to
+ // bind mount against.
+ try {
+ mConnector.execute("volume", "start_user", userId);
+ } catch (NativeDaemonConnectorException ignored) {
}
- };
+
+ // Record user as started so newly mounted volumes kick off events
+ // correctly, then synthesize events for any already-mounted volumes.
+ synchronized (mVolumes) {
+ for (Volume vol : mVolumes.values()) {
+ if (vol.isVisibleToUser(userId) && vol.state == Volume.STATE_MOUNTED) {
+ final StorageVolume userVol = vol.buildVolumeForUser(userId);
+ mHandler.obtainMessage(H_VOLUME_BROADCAST, userVol).sendToTarget();
+ }
+ }
+ mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userId);
+ }
+ }
+
+ private void onCleanupUser(int userId) {
+ Slog.d(TAG, "onCleanupUser " + userId);
+
+ try {
+ mConnector.execute("volume", "cleanup_user", userId);
+ } catch (NativeDaemonConnectorException ignored) {
+ }
+
+ synchronized (mVolumes) {
+ mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userId);
+ }
+ }
private final class MountServiceBinderListener implements IBinder.DeathRecipient {
final IMountServiceListener mListener;
MountServiceBinderListener(IMountServiceListener listener) {
mListener = listener;
-
}
+ @Override
public void binderDied() {
if (LOCAL_LOGD) Slog.d(TAG, "An IMountServiceListener has died!");
synchronized (mListeners) {
@@ -741,7 +816,7 @@
// Binder entry point for kicking off an immediate fstrim
@Override
public void runMaintenance() {
- validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+ enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
runIdleMaintenance(null);
}
@@ -750,144 +825,35 @@
return mLastMaintenance;
}
- private void doShareUnshareVolume(String path, String method, boolean enable) {
- // TODO: Add support for multiple share methods
- if (!method.equals("ums")) {
- throw new IllegalArgumentException(String.format("Method %s not supported", method));
- }
-
- try {
- mConnector.execute("volume", enable ? "share" : "unshare", path, method);
- } catch (NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to share/unshare", e);
- }
- }
-
- private void updatePublicVolumeState(StorageVolume volume, String state) {
- final String path = volume.getPath();
- final String oldState;
- synchronized (mVolumesLock) {
- oldState = mVolumeStates.put(path, state);
- volume.setState(state);
- }
-
- if (state.equals(oldState)) {
- Slog.w(TAG, String.format("Duplicate state transition (%s -> %s) for %s",
- state, state, path));
- return;
- }
-
- Slog.d(TAG, "volume state changed for " + path + " (" + oldState + " -> " + state + ")");
-
- // Tell PackageManager about changes to primary volume state, but only
- // when not emulated.
- if (volume.isPrimary() && !volume.isEmulated()) {
- if (Environment.MEDIA_UNMOUNTED.equals(state)) {
- mPms.updateExternalMediaStatus(false, false);
-
- /*
- * Some OBBs might have been unmounted when this volume was
- * unmounted, so send a message to the handler to let it know to
- * remove those from the list of mounted OBBS.
- */
- mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(
- OBB_FLUSH_MOUNT_STATE, path));
- } else if (Environment.MEDIA_MOUNTED.equals(state)) {
- mPms.updateExternalMediaStatus(true, false);
- }
- }
-
- synchronized (mListeners) {
- for (int i = mListeners.size() -1; i >= 0; i--) {
- MountServiceBinderListener bl = mListeners.get(i);
- try {
- bl.mListener.onStorageStateChanged(path, oldState, state);
- } catch (RemoteException rex) {
- Slog.e(TAG, "Listener dead");
- mListeners.remove(i);
- } catch (Exception ex) {
- Slog.e(TAG, "Listener failed", ex);
- }
- }
- }
- }
-
/**
* Callback from NativeDaemonConnector
*/
+ @Override
public void onDaemonConnected() {
+ mDaemonConnected = true;
+ mHandler.obtainMessage(H_DAEMON_CONNECTED).sendToTarget();
+ }
+
+ private void handleDaemonConnected() {
+ resetIfReadyAndConnected();
+
/*
- * Since we'll be calling back into the NativeDaemonConnector,
- * we need to do our work in a new thread.
+ * Now that we've done our initialization, release
+ * the hounds!
*/
- new Thread("MountService#onDaemonConnected") {
- @Override
- public void run() {
- /**
- * Determine media state and UMS detection status
- */
- try {
- final String[] vols = NativeDaemonEvent.filterMessageList(
- mConnector.executeForList("volume", "list", "broadcast"),
- VoldResponseCode.VolumeListResult);
- for (String volstr : vols) {
- String[] tok = volstr.split(" ");
- // FMT: <label> <mountpoint> <state>
- String path = tok[1];
- String state = Environment.MEDIA_REMOVED;
+ mConnectedSignal.countDown();
- final StorageVolume volume;
- synchronized (mVolumesLock) {
- volume = mVolumesByPath.get(path);
- }
+ // On an encrypted device we can't see system properties yet, so pull
+ // the system locale out of the mount service.
+ if ("".equals(SystemProperties.get("vold.encrypt_progress"))) {
+ copyLocaleFromMountService();
+ }
- int st = Integer.parseInt(tok[2]);
- if (st == VolumeState.NoMedia) {
- state = Environment.MEDIA_REMOVED;
- } else if (st == VolumeState.Idle) {
- state = Environment.MEDIA_UNMOUNTED;
- } else if (st == VolumeState.Mounted) {
- state = Environment.MEDIA_MOUNTED;
- Slog.i(TAG, "Media already mounted on daemon connection");
- } else if (st == VolumeState.Shared) {
- state = Environment.MEDIA_SHARED;
- Slog.i(TAG, "Media shared on daemon connection");
- } else {
- throw new Exception(String.format("Unexpected state %d", st));
- }
+ // Let package manager load internal ASECs.
+ mPms.scanAvailableAsecs();
- if (state != null) {
- if (DEBUG_EVENTS) Slog.i(TAG, "Updating valid state " + state);
- updatePublicVolumeState(volume, state);
- }
- }
- } catch (Exception e) {
- Slog.e(TAG, "Error processing initial volume state", e);
- final StorageVolume primary = getPrimaryPhysicalVolume();
- if (primary != null) {
- updatePublicVolumeState(primary, Environment.MEDIA_REMOVED);
- }
- }
-
- /*
- * Now that we've done our initialization, release
- * the hounds!
- */
- mConnectedSignal.countDown();
-
- // On an encrypted device we can't see system properties yet, so pull
- // the system locale out of the mount service.
- if ("".equals(SystemProperties.get("vold.encrypt_progress"))) {
- copyLocaleFromMountService();
- }
-
- // Let package manager load internal ASECs.
- mPms.scanAvailableAsecs();
-
- // Notify people waiting for ASECs to be scanned that it's done.
- mAsecsScanned.countDown();
- }
- }.start();
+ // Notify people waiting for ASECs to be scanned that it's done.
+ mAsecsScanned.countDown();
}
private void copyLocaleFromMountService() {
@@ -919,6 +885,7 @@
/**
* Callback from NativeDaemonConnector
*/
+ @Override
public boolean onCheckHoldWakeLock(int code) {
return false;
}
@@ -926,347 +893,182 @@
/**
* Callback from NativeDaemonConnector
*/
+ @Override
public boolean onEvent(int code, String raw, String[] cooked) {
- if (DEBUG_EVENTS) {
- StringBuilder builder = new StringBuilder();
- builder.append("onEvent::");
- builder.append(" raw= " + raw);
- if (cooked != null) {
- builder.append(" cooked = " );
- for (String str : cooked) {
- builder.append(" " + str);
- }
- }
- Slog.i(TAG, builder.toString());
+ synchronized (mLock) {
+ return onEventLocked(code, raw, cooked);
}
- if (code == VoldResponseCode.VolumeStateChange) {
- /*
- * One of the volumes we're managing has changed state.
- * Format: "NNN Volume <label> <path> state changed
- * from <old_#> (<old_str>) to <new_#> (<new_str>)"
- */
- notifyVolumeStateChange(
- cooked[2], cooked[3], Integer.parseInt(cooked[7]),
- Integer.parseInt(cooked[10]));
- } else if (code == VoldResponseCode.VolumeUuidChange) {
- // Format: nnn <label> <path> <uuid>
- final String path = cooked[2];
- final String uuid = (cooked.length > 3) ? cooked[3] : null;
+ }
- final StorageVolume vol = mVolumesByPath.get(path);
- if (vol != null) {
- vol.setUuid(uuid);
+ private boolean onEventLocked(int code, String raw, String[] cooked) {
+ switch (code) {
+ case VoldResponseCode.DISK_CREATED: {
+ if (cooked.length != 3) break;
+ final String id = cooked[1];
+ final int flags = Integer.parseInt(cooked[2]);
+ mDisks.put(id, new Disk(id, flags));
+ break;
}
-
- } else if (code == VoldResponseCode.VolumeUserLabelChange) {
- // Format: nnn <label> <path> <label>
- final String path = cooked[2];
- final String userLabel = (cooked.length > 3) ? cooked[3] : null;
-
- final StorageVolume vol = mVolumesByPath.get(path);
- if (vol != null) {
- vol.setUserLabel(userLabel);
- }
-
- } else if ((code == VoldResponseCode.VolumeDiskInserted) ||
- (code == VoldResponseCode.VolumeDiskRemoved) ||
- (code == VoldResponseCode.VolumeBadRemoval)) {
- // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>)
- // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>)
- // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>)
- String action = null;
- final String label = cooked[2];
- final String path = cooked[3];
- int major = -1;
- int minor = -1;
-
- try {
- String devComp = cooked[6].substring(1, cooked[6].length() -1);
- String[] devTok = devComp.split(":");
- major = Integer.parseInt(devTok[0]);
- minor = Integer.parseInt(devTok[1]);
- } catch (Exception ex) {
- Slog.e(TAG, "Failed to parse major/minor", ex);
- }
-
- final StorageVolume volume;
- final String state;
- synchronized (mVolumesLock) {
- volume = mVolumesByPath.get(path);
- state = mVolumeStates.get(path);
- }
-
- if (code == VoldResponseCode.VolumeDiskInserted) {
- new Thread("MountService#VolumeDiskInserted") {
- @Override
- public void run() {
- try {
- int rc;
- if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
- Slog.w(TAG, String.format("Insertion mount failed (%d)", rc));
- }
- } catch (Exception ex) {
- Slog.w(TAG, "Failed to mount media on insertion", ex);
- }
- }
- }.start();
- } else if (code == VoldResponseCode.VolumeDiskRemoved) {
- /*
- * This event gets trumped if we're already in BAD_REMOVAL state
- */
- if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) {
- return true;
+ case VoldResponseCode.DISK_SIZE_CHANGED: {
+ if (cooked.length != 3) break;
+ final Disk disk = mDisks.get(cooked[1]);
+ if (disk != null) {
+ disk.size = Long.parseLong(cooked[2]);
}
- /* Send the media unmounted event first */
- if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first");
- updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
- sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL);
+ break;
+ }
+ case VoldResponseCode.DISK_LABEL_CHANGED: {
+ if (cooked.length != 3) break;
+ final Disk disk = mDisks.get(cooked[1]);
+ if (disk != null) {
+ disk.label = cooked[2];
+ }
+ break;
+ }
+ case VoldResponseCode.DISK_VOLUME_CREATED: {
+ if (cooked.length != 3) break;
+ final Disk disk = mDisks.get(cooked[1]);
+ final Volume vol = mVolumes.get(cooked[2]);
+ if (disk != null && vol != null) {
+ disk.volumes.add(vol);
+ }
+ break;
+ }
+ case VoldResponseCode.DISK_DESTROYED: {
+ if (cooked.length != 2) break;
+ mDisks.remove(cooked[1]);
+ break;
+ }
- if (DEBUG_EVENTS) Slog.i(TAG, "Sending media removed");
- updatePublicVolumeState(volume, Environment.MEDIA_REMOVED);
- action = Intent.ACTION_MEDIA_REMOVED;
- } else if (code == VoldResponseCode.VolumeBadRemoval) {
- if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first");
- /* Send the media unmounted event first */
- updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
- sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL);
+ case VoldResponseCode.VOLUME_CREATED: {
+ if (cooked.length != 3) break;
+ final String id = cooked[1];
+ final int type = Integer.parseInt(cooked[2]);
+ final Volume vol = new Volume(id, type);
+ mVolumes.put(id, vol);
+ onVolumeCreatedLocked(vol);
+ break;
+ }
+ case VoldResponseCode.VOLUME_STATE_CHANGED: {
+ if (cooked.length != 3) break;
+ final Volume vol = mVolumes.get(cooked[1]);
+ if (vol != null) {
+ final int oldState = vol.state;
+ final int newState = Integer.parseInt(cooked[2]);
+ vol.state = newState;
+ onVolumeStateChangedLocked(vol, oldState, newState);
+ }
+ break;
+ }
+ case VoldResponseCode.VOLUME_FS_TYPE_CHANGED: {
+ if (cooked.length != 3) break;
+ final Volume vol = mVolumes.get(cooked[1]);
+ if (vol != null) {
+ vol.fsType = cooked[2];
+ }
+ break;
+ }
+ case VoldResponseCode.VOLUME_FS_UUID_CHANGED: {
+ if (cooked.length != 3) break;
+ final Volume vol = mVolumes.get(cooked[1]);
+ if (vol != null) {
+ vol.fsUuid = cooked[2];
+ }
+ break;
+ }
+ case VoldResponseCode.VOLUME_FS_LABEL_CHANGED: {
+ if (cooked.length != 3) break;
+ final Volume vol = mVolumes.get(cooked[1]);
+ if (vol != null) {
+ vol.fsLabel = cooked[2];
+ }
+ break;
+ }
+ case VoldResponseCode.VOLUME_PATH_CHANGED: {
+ if (cooked.length != 3) break;
+ final Volume vol = mVolumes.get(cooked[1]);
+ if (vol != null) {
+ vol.path = cooked[2];
+ }
+ break;
+ }
+ case VoldResponseCode.VOLUME_DESTROYED: {
+ if (cooked.length != 2) break;
+ mVolumes.remove(cooked[1]);
+ break;
+ }
- if (DEBUG_EVENTS) Slog.i(TAG, "Sending media bad removal");
- updatePublicVolumeState(volume, Environment.MEDIA_BAD_REMOVAL);
- action = Intent.ACTION_MEDIA_BAD_REMOVAL;
- } else if (code == VoldResponseCode.FstrimCompleted) {
+ case VoldResponseCode.FstrimCompleted: {
EventLogTags.writeFstrimFinish(SystemClock.elapsedRealtime());
- } else {
- Slog.e(TAG, String.format("Unknown code {%d}", code));
+ break;
}
-
- if (action != null) {
- sendStorageIntent(action, volume, UserHandle.ALL);
+ default: {
+ Slog.d(TAG, "Unhandled vold event " + code);
}
- } else {
- return false;
}
return true;
}
- private void notifyVolumeStateChange(String label, String path, int oldState, int newState) {
- final StorageVolume volume;
- final String state;
- synchronized (mVolumesLock) {
- volume = mVolumesByPath.get(path);
- state = getVolumeState(path);
- }
+ private void onVolumeCreatedLocked(Volume vol) {
+ final boolean primaryPhysical = SystemProperties.getBoolean(PROP_PRIMARY_PHYSICAL, false);
+ if (vol.type == Volume.TYPE_EMULATED && !primaryPhysical) {
+ vol.flags |= Volume.FLAG_PRIMARY;
+ vol.flags |= Volume.FLAG_VISIBLE;
+ mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
- if (DEBUG_EVENTS) Slog.i(TAG, "notifyVolumeStateChange::" + state);
-
- String action = null;
-
- if (oldState == VolumeState.Shared && newState != oldState) {
- if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_UNSHARED intent");
- sendStorageIntent(Intent.ACTION_MEDIA_UNSHARED, volume, UserHandle.ALL);
- }
-
- if (newState == VolumeState.Init) {
- } else if (newState == VolumeState.NoMedia) {
- // NoMedia is handled via Disk Remove events
- } else if (newState == VolumeState.Idle) {
- /*
- * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or
- * if we're in the process of enabling UMS
- */
- if (!state.equals(
- Environment.MEDIA_BAD_REMOVAL) && !state.equals(
- Environment.MEDIA_NOFS) && !state.equals(
- Environment.MEDIA_UNMOUNTABLE) && !getUmsEnabling()) {
- if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state for media bad removal nofs and unmountable");
- updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
- action = Intent.ACTION_MEDIA_UNMOUNTED;
+ } else if (vol.type == Volume.TYPE_PUBLIC) {
+ if (primaryPhysical) {
+ vol.flags |= Volume.FLAG_PRIMARY;
}
- } else if (newState == VolumeState.Pending) {
- } else if (newState == VolumeState.Checking) {
- if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state checking");
- updatePublicVolumeState(volume, Environment.MEDIA_CHECKING);
- action = Intent.ACTION_MEDIA_CHECKING;
- } else if (newState == VolumeState.Mounted) {
- if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state mounted");
- updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED);
- action = Intent.ACTION_MEDIA_MOUNTED;
- } else if (newState == VolumeState.Unmounting) {
- action = Intent.ACTION_MEDIA_EJECT;
- } else if (newState == VolumeState.Formatting) {
- } else if (newState == VolumeState.Shared) {
- if (DEBUG_EVENTS) Slog.i(TAG, "Updating volume state media mounted");
- /* Send the media unmounted event first */
- updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
- sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL);
+ vol.flags |= Volume.FLAG_VISIBLE;
+ vol.userId = UserHandle.USER_OWNER;
+ mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
- if (DEBUG_EVENTS) Slog.i(TAG, "Updating media shared");
- updatePublicVolumeState(volume, Environment.MEDIA_SHARED);
- action = Intent.ACTION_MEDIA_SHARED;
- if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_SHARED intent");
- } else if (newState == VolumeState.SharedMnt) {
- Slog.e(TAG, "Live shared mounts not supported yet!");
- return;
} else {
- Slog.e(TAG, "Unhandled VolumeState {" + newState + "}");
- }
-
- if (action != null) {
- sendStorageIntent(action, volume, UserHandle.ALL);
+ Slog.d(TAG, "Skipping automatic mounting of " + vol);
}
}
- private int doMountVolume(String path) {
- int rc = StorageResultCode.OperationSucceeded;
-
- final StorageVolume volume;
- synchronized (mVolumesLock) {
- volume = mVolumesByPath.get(path);
+ private void onVolumeStateChangedLocked(Volume vol, int oldState, int newState) {
+ // Kick state changed event towards all started users. Any users
+ // started after this point will trigger additional
+ // user-specific broadcasts.
+ for (int userId : mStartedUsers) {
+ if (vol.isVisibleToUser(userId)) {
+ final StorageVolume userVol = vol.buildVolumeForUser(userId);
+ mHandler.obtainMessage(H_VOLUME_BROADCAST, userVol).sendToTarget();
+ }
}
- if (!volume.isEmulated() && hasUserRestriction(UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA)) {
- Slog.w(TAG, "User has restriction DISALLOW_MOUNT_PHYSICAL_MEDIA; cannot mount volume.");
- return StorageResultCode.OperationFailedInternalError;
- }
+ // Tell PackageManager about changes to primary volume state, but only
+ // when not emulated.
+ if (vol.isPrimary() && vol.type == Volume.TYPE_PUBLIC) {
+ if (vol.state == Volume.STATE_MOUNTED) {
+ mPms.updateExternalMediaStatus(true, false);
- if (DEBUG_EVENTS) Slog.i(TAG, "doMountVolume: Mouting " + path);
- try {
- mConnector.execute("volume", "mount", path);
- } catch (NativeDaemonConnectorException e) {
- /*
- * Mount failed for some reason
- */
- String action = null;
- int code = e.getCode();
- if (code == VoldResponseCode.OpFailedNoMedia) {
+ } else if (vol.state == Volume.STATE_UNMOUNTING) {
+ mPms.updateExternalMediaStatus(false, false);
+
+ // TODO: this should eventually be handled by new ObbVolume state changes
/*
- * Attempt to mount but no media inserted
+ * Some OBBs might have been unmounted when this volume was
+ * unmounted, so send a message to the handler to let it know to
+ * remove those from the list of mounted OBBS.
*/
- rc = StorageResultCode.OperationFailedNoMedia;
- } else if (code == VoldResponseCode.OpFailedMediaBlank) {
- if (DEBUG_EVENTS) Slog.i(TAG, " updating volume state :: media nofs");
- /*
- * Media is blank or does not contain a supported filesystem
- */
- updatePublicVolumeState(volume, Environment.MEDIA_NOFS);
- action = Intent.ACTION_MEDIA_NOFS;
- rc = StorageResultCode.OperationFailedMediaBlank;
- } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
- if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state media corrupt");
- /*
- * Volume consistency check failed
- */
- updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTABLE);
- action = Intent.ACTION_MEDIA_UNMOUNTABLE;
- rc = StorageResultCode.OperationFailedMediaCorrupt;
- } else {
- rc = StorageResultCode.OperationFailedInternalError;
- }
-
- /*
- * Send broadcast intent (if required for the failure)
- */
- if (action != null) {
- sendStorageIntent(action, volume, UserHandle.ALL);
+ mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(
+ OBB_FLUSH_MOUNT_STATE, vol.path));
}
}
- return rc;
- }
+ final String oldEnvState = sStateToEnvironment.get(oldState);
+ final String newEnvState = sStateToEnvironment.get(newState);
- /*
- * If force is not set, we do not unmount if there are
- * processes holding references to the volume about to be unmounted.
- * If force is set, all the processes holding references need to be
- * killed via the ActivityManager before actually unmounting the volume.
- * This might even take a while and might be retried after timed delays
- * to make sure we dont end up in an instable state and kill some core
- * processes.
- * If removeEncryption is set, force is implied, and the system will remove any encryption
- * mapping set on the volume when unmounting.
- */
- private int doUnmountVolume(String path, boolean force, boolean removeEncryption) {
- if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) {
- return VoldResponseCode.OpFailedVolNotMounted;
- }
-
- /*
- * Force a GC to make sure AssetManagers in other threads of the
- * system_server are cleaned up. We have to do this since AssetManager
- * instances are kept as a WeakReference and it's possible we have files
- * open on the external storage.
- */
- Runtime.getRuntime().gc();
-
- // Redundant probably. But no harm in updating state again.
- mPms.updateExternalMediaStatus(false, false);
- try {
- final Command cmd = new Command("volume", "unmount", path);
- if (removeEncryption) {
- cmd.appendArg("force_and_revert");
- } else if (force) {
- cmd.appendArg("force");
- }
- mConnector.execute(cmd);
- // We unmounted the volume. None of the asec containers are available now.
- synchronized (mAsecMountSet) {
- mAsecMountSet.clear();
- }
- return StorageResultCode.OperationSucceeded;
- } catch (NativeDaemonConnectorException e) {
- // Don't worry about mismatch in PackageManager since the
- // call back will handle the status changes any way.
- int code = e.getCode();
- if (code == VoldResponseCode.OpFailedVolNotMounted) {
- return StorageResultCode.OperationFailedStorageNotMounted;
- } else if (code == VoldResponseCode.OpFailedStorageBusy) {
- return StorageResultCode.OperationFailedStorageBusy;
- } else {
- return StorageResultCode.OperationFailedInternalError;
- }
- }
- }
-
- private int doFormatVolume(String path) {
- try {
- mConnector.execute("volume", "format", path);
- return StorageResultCode.OperationSucceeded;
- } catch (NativeDaemonConnectorException e) {
- int code = e.getCode();
- if (code == VoldResponseCode.OpFailedNoMedia) {
- return StorageResultCode.OperationFailedNoMedia;
- } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
- return StorageResultCode.OperationFailedMediaCorrupt;
- } else {
- return StorageResultCode.OperationFailedInternalError;
- }
- }
- }
-
- private boolean doGetVolumeShared(String path, String method) {
- final NativeDaemonEvent event;
- try {
- event = mConnector.execute("volume", "shared", path, method);
- } catch (NativeDaemonConnectorException ex) {
- Slog.e(TAG, "Failed to read response to volume shared " + path + " " + method);
- return false;
- }
-
- if (event.getCode() == VoldResponseCode.ShareEnabledResult) {
- return event.getMessage().endsWith("enabled");
- } else {
- return false;
- }
- }
-
- private void notifyShareAvailabilityChange(final boolean avail) {
synchronized (mListeners) {
- mUmsAvailable = avail;
for (int i = mListeners.size() -1; i >= 0; i--) {
MountServiceBinderListener bl = mListeners.get(i);
try {
- bl.mListener.onUsbMassStorageConnectionChanged(avail);
+ bl.mListener.onStorageStateChanged(vol.path, oldEnvState, newEnvState);
} catch (RemoteException rex) {
Slog.e(TAG, "Listener dead");
mListeners.remove(i);
@@ -1275,221 +1077,19 @@
}
}
}
-
- if (mSystemReady == true) {
- sendUmsIntent(avail);
- } else {
- mSendUmsConnectedOnBoot = avail;
- }
-
- final StorageVolume primary = getPrimaryPhysicalVolume();
- if (avail == false && primary != null
- && Environment.MEDIA_SHARED.equals(getVolumeState(primary.getPath()))) {
- final String path = primary.getPath();
- /*
- * USB mass storage disconnected while enabled
- */
- new Thread("MountService#AvailabilityChange") {
- @Override
- public void run() {
- try {
- int rc;
- Slog.w(TAG, "Disabling UMS after cable disconnect");
- doShareUnshareVolume(path, "ums", false);
- if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
- Slog.e(TAG, String.format(
- "Failed to remount {%s} on UMS enabled-disconnect (%d)",
- path, rc));
- }
- } catch (Exception ex) {
- Slog.w(TAG, "Failed to mount media on UMS enabled-disconnect", ex);
- }
- }
- }.start();
- }
}
- private void sendStorageIntent(String action, StorageVolume volume, UserHandle user) {
- final Intent intent = new Intent(action, Uri.parse("file://" + volume.getPath()));
- intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, volume);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- Slog.d(TAG, "sendStorageIntent " + intent + " to " + user);
- mContext.sendBroadcastAsUser(intent, user);
+ private void enforcePermission(String perm) {
+ mContext.enforceCallingOrSelfPermission(perm, perm);
}
- private void sendUmsIntent(boolean c) {
- mContext.sendBroadcastAsUser(
- new Intent((c ? Intent.ACTION_UMS_CONNECTED : Intent.ACTION_UMS_DISCONNECTED)),
- UserHandle.ALL);
- }
-
- private void validatePermission(String perm) {
- if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException(String.format("Requires %s permission", perm));
- }
- }
-
- private boolean hasUserRestriction(String restriction) {
+ private void enforceUserRestriction(String restriction) {
UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- return um.hasUserRestriction(restriction, Binder.getCallingUserHandle());
- }
-
- private void validateUserRestriction(String restriction) {
- if (hasUserRestriction(restriction)) {
+ if (um.hasUserRestriction(restriction, Binder.getCallingUserHandle())) {
throw new SecurityException("User has restriction " + restriction);
}
}
- // Storage list XML tags
- private static final String TAG_STORAGE_LIST = "StorageList";
- private static final String TAG_STORAGE = "storage";
-
- private void readStorageListLocked() {
- mVolumes.clear();
- mVolumeStates.clear();
-
- Resources resources = mContext.getResources();
-
- int id = com.android.internal.R.xml.storage_list;
- XmlResourceParser parser = resources.getXml(id);
- AttributeSet attrs = Xml.asAttributeSet(parser);
-
- try {
- XmlUtils.beginDocument(parser, TAG_STORAGE_LIST);
- while (true) {
- XmlUtils.nextElement(parser);
-
- String element = parser.getName();
- if (element == null) break;
-
- if (TAG_STORAGE.equals(element)) {
- TypedArray a = resources.obtainAttributes(attrs,
- com.android.internal.R.styleable.Storage);
-
- String path = a.getString(
- com.android.internal.R.styleable.Storage_mountPoint);
- int descriptionId = a.getResourceId(
- com.android.internal.R.styleable.Storage_storageDescription, -1);
- CharSequence description = a.getText(
- com.android.internal.R.styleable.Storage_storageDescription);
- boolean primary = a.getBoolean(
- com.android.internal.R.styleable.Storage_primary, false);
- boolean removable = a.getBoolean(
- com.android.internal.R.styleable.Storage_removable, false);
- boolean emulated = a.getBoolean(
- com.android.internal.R.styleable.Storage_emulated, false);
- int mtpReserve = a.getInt(
- com.android.internal.R.styleable.Storage_mtpReserve, 0);
- boolean allowMassStorage = a.getBoolean(
- com.android.internal.R.styleable.Storage_allowMassStorage, false);
- // resource parser does not support longs, so XML value is in megabytes
- long maxFileSize = a.getInt(
- com.android.internal.R.styleable.Storage_maxFileSize, 0) * 1024L * 1024L;
-
- Slog.d(TAG, "got storage path: " + path + " description: " + description +
- " primary: " + primary + " removable: " + removable +
- " emulated: " + emulated + " mtpReserve: " + mtpReserve +
- " allowMassStorage: " + allowMassStorage +
- " maxFileSize: " + maxFileSize);
-
- if (emulated) {
- // For devices with emulated storage, we create separate
- // volumes for each known user.
- mEmulatedTemplate = new StorageVolume(null, descriptionId, true, false,
- true, mtpReserve, false, maxFileSize, null);
-
- final UserManagerService userManager = UserManagerService.getInstance();
- for (UserInfo user : userManager.getUsers(false)) {
- createEmulatedVolumeForUserLocked(user.getUserHandle());
- }
-
- } else {
- if (path == null || description == null) {
- Slog.e(TAG, "Missing storage path or description in readStorageList");
- } else {
- final StorageVolume volume = new StorageVolume(new File(path),
- descriptionId, primary, removable, emulated, mtpReserve,
- allowMassStorage, maxFileSize, null);
- addVolumeLocked(volume);
-
- // Until we hear otherwise, treat as unmounted
- mVolumeStates.put(volume.getPath(), Environment.MEDIA_UNMOUNTED);
- volume.setState(Environment.MEDIA_UNMOUNTED);
- }
- }
-
- a.recycle();
- }
- }
- } catch (XmlPullParserException e) {
- throw new RuntimeException(e);
- } catch (IOException e) {
- throw new RuntimeException(e);
- } finally {
- // Compute storage ID for each physical volume; emulated storage is
- // always 0 when defined.
- int index = isExternalStorageEmulated() ? 1 : 0;
- for (StorageVolume volume : mVolumes) {
- if (!volume.isEmulated()) {
- volume.setStorageId(index++);
- }
- }
- parser.close();
- }
- }
-
- /**
- * Create and add new {@link StorageVolume} for given {@link UserHandle}
- * using {@link #mEmulatedTemplate} as template.
- */
- private void createEmulatedVolumeForUserLocked(UserHandle user) {
- if (mEmulatedTemplate == null) {
- throw new IllegalStateException("Missing emulated volume multi-user template");
- }
-
- final UserEnvironment userEnv = new UserEnvironment(user.getIdentifier());
- final File path = userEnv.getExternalStorageDirectory();
- final StorageVolume volume = StorageVolume.fromTemplate(mEmulatedTemplate, path, user);
- volume.setStorageId(0);
- addVolumeLocked(volume);
-
- if (mSystemReady) {
- updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED);
- } else {
- // Place stub status for early callers to find
- mVolumeStates.put(volume.getPath(), Environment.MEDIA_MOUNTED);
- volume.setState(Environment.MEDIA_MOUNTED);
- }
- }
-
- private void addVolumeLocked(StorageVolume volume) {
- Slog.d(TAG, "addVolumeLocked() " + volume);
- mVolumes.add(volume);
- final StorageVolume existing = mVolumesByPath.put(volume.getPath(), volume);
- if (existing != null) {
- throw new IllegalStateException(
- "Volume at " + volume.getPath() + " already exists: " + existing);
- }
- }
-
- private void removeVolumeLocked(StorageVolume volume) {
- Slog.d(TAG, "removeVolumeLocked() " + volume);
- mVolumes.remove(volume);
- mVolumesByPath.remove(volume.getPath());
- mVolumeStates.remove(volume.getPath());
- }
-
- private StorageVolume getPrimaryPhysicalVolume() {
- synchronized (mVolumesLock) {
- for (StorageVolume volume : mVolumes) {
- if (volume.isPrimary() && !volume.isEmulated()) {
- return volume;
- }
- }
- }
- return null;
- }
-
/**
* Constructs a new MountService instance
*
@@ -1500,10 +1100,6 @@
mContext = context;
- synchronized (mVolumesLock) {
- readStorageListLocked();
- }
-
// XXX: This will go away soon in favor of IMountServiceObserver
mPms = (PackageManagerService) ServiceManager.getService("package");
@@ -1511,19 +1107,6 @@
hthread.start();
mHandler = new MountServiceHandler(hthread.getLooper());
- // Watch for user changes
- final IntentFilter userFilter = new IntentFilter();
- userFilter.addAction(Intent.ACTION_USER_ADDED);
- userFilter.addAction(Intent.ACTION_USER_REMOVED);
- mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);
-
- // Watch for USB changes on primary volume
- final StorageVolume primary = getPrimaryPhysicalVolume();
- if (primary != null && primary.allowMassStorage()) {
- mContext.registerReceiver(
- mUsbReceiver, new IntentFilter(UsbManager.ACTION_USB_STATE), null, mHandler);
- }
-
// Add OBB Action Handler to MountService thread.
mObbActionHandler = new ObbActionHandler(IoThread.get().getLooper());
@@ -1550,6 +1133,7 @@
*/
mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25,
null);
+ mConnector.setDebug(true);
Thread thread = new Thread(mConnector, VOLD_TAG);
thread.start();
@@ -1593,201 +1177,118 @@
}
}
+ @Override
public void shutdown(final IMountShutdownObserver observer) {
- validatePermission(android.Manifest.permission.SHUTDOWN);
+ enforcePermission(android.Manifest.permission.SHUTDOWN);
Slog.i(TAG, "Shutting down");
- synchronized (mVolumesLock) {
- // Get all volumes to be unmounted.
- MountShutdownLatch mountShutdownLatch = new MountShutdownLatch(observer,
- mVolumeStates.size());
-
- for (String path : mVolumeStates.keySet()) {
- String state = mVolumeStates.get(path);
-
- if (state.equals(Environment.MEDIA_SHARED)) {
- /*
- * If the media is currently shared, unshare it.
- * XXX: This is still dangerous!. We should not
- * be rebooting at *all* if UMS is enabled, since
- * the UMS host could have dirty FAT cache entries
- * yet to flush.
- */
- setUsbMassStorageEnabled(false);
- } else if (state.equals(Environment.MEDIA_CHECKING)) {
- /*
- * If the media is being checked, then we need to wait for
- * it to complete before being able to proceed.
- */
- // XXX: @hackbod - Should we disable the ANR timer here?
- int retries = 30;
- while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException iex) {
- Slog.e(TAG, "Interrupted while waiting for media", iex);
- break;
- }
- state = Environment.getExternalStorageState();
- }
- if (retries == 0) {
- Slog.e(TAG, "Timed out waiting for media to check");
- }
- }
-
- if (state.equals(Environment.MEDIA_MOUNTED)) {
- // Post a unmount message.
- ShutdownCallBack ucb = new ShutdownCallBack(path, mountShutdownLatch);
- mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
- } else if (observer != null) {
- /*
- * Count down, since nothing will be done. The observer will be
- * notified when we are done so shutdown sequence can continue.
- */
- mountShutdownLatch.countDown();
- Slog.i(TAG, "Unmount completed: " + path +
- ", result code: " + StorageResultCode.OperationSucceeded);
- }
- }
- }
+ mHandler.obtainMessage(H_SHUTDOWN, observer).sendToTarget();
}
- private boolean getUmsEnabling() {
- synchronized (mListeners) {
- return mUmsEnabling;
- }
- }
-
- private void setUmsEnabling(boolean enable) {
- synchronized (mListeners) {
- mUmsEnabling = enable;
- }
- }
-
+ @Override
+ @Deprecated
public boolean isUsbMassStorageConnected() {
- waitForReady();
-
- if (getUmsEnabling()) {
- return true;
- }
- synchronized (mListeners) {
- return mUmsAvailable;
- }
+ return false;
}
+ @Override
+ @Deprecated
public void setUsbMassStorageEnabled(boolean enable) {
- waitForReady();
- validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
- validateUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER);
-
- final StorageVolume primary = getPrimaryPhysicalVolume();
- if (primary == null) return;
-
- // TODO: Add support for multiple share methods
-
- /*
- * If the volume is mounted and we're enabling then unmount it
- */
- String path = primary.getPath();
- String vs = getVolumeState(path);
- String method = "ums";
- if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
- // Override for isUsbMassStorageEnabled()
- setUmsEnabling(enable);
- UmsEnableCallBack umscb = new UmsEnableCallBack(path, method, true);
- mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, umscb));
- // Clear override
- setUmsEnabling(false);
- }
- /*
- * If we disabled UMS then mount the volume
- */
- if (!enable) {
- doShareUnshareVolume(path, method, enable);
- if (doMountVolume(path) != StorageResultCode.OperationSucceeded) {
- Slog.e(TAG, "Failed to remount " + path +
- " after disabling share method " + method);
- /*
- * Even though the mount failed, the unshare didn't so don't indicate an error.
- * The mountVolume() call will have set the storage state and sent the necessary
- * broadcasts.
- */
- }
- }
+ throw new UnsupportedOperationException();
}
+ @Override
+ @Deprecated
public boolean isUsbMassStorageEnabled() {
- waitForReady();
-
- final StorageVolume primary = getPrimaryPhysicalVolume();
- if (primary != null) {
- return doGetVolumeShared(primary.getPath(), "ums");
- } else {
- return false;
- }
+ return false;
}
/**
* @return state of the volume at the specified mount point
*/
+ @Override
+ @Deprecated
public String getVolumeState(String mountPoint) {
- synchronized (mVolumesLock) {
- String state = mVolumeStates.get(mountPoint);
- if (state == null) {
- Slog.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
- if (SystemProperties.get("vold.encrypt_progress").length() != 0) {
- state = Environment.MEDIA_REMOVED;
- } else {
- throw new IllegalArgumentException();
- }
- }
+ // TODO: pretend that we're unmounted when encrypting?
+ // SystemProperties.get("vold.encrypt_progress")
- return state;
- }
+ final Volume vol = findVolumeByLegacyPath(mountPoint);
+ return sStateToEnvironment.get(vol.state);
}
@Override
public boolean isExternalStorageEmulated() {
- return mEmulatedTemplate != null;
+ return Environment.isExternalStorageEmulated();
}
+ @Override
public int mountVolume(String path) {
- validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+ enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
waitForReady();
- return doMountVolume(path);
+
+ final Volume vol = findVolumeByLegacyPath(path);
+ if (vol != null) {
+ if (vol.type == Volume.TYPE_PUBLIC || vol.type == Volume.TYPE_PRIVATE) {
+ enforceUserRestriction(UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA);
+ }
+ try {
+ vol.mount();
+ return 0;
+ } catch (NativeDaemonConnectorException ignored) {
+ }
+ } else {
+ Slog.w(TAG, "Unknown volume for path " + path);
+ }
+ return -1;
}
+ @Override
public void unmountVolume(String path, boolean force, boolean removeEncryption) {
- validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+ enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
waitForReady();
- String volState = getVolumeState(path);
- if (DEBUG_UNMOUNT) {
- Slog.i(TAG, "Unmounting " + path
- + " force = " + force
- + " removeEncryption = " + removeEncryption);
+ final Volume vol = findVolumeByLegacyPath(path);
+ if (vol != null) {
+ // TODO: expand PMS to know about multiple volumes
+ if (vol.isPrimary()) {
+ synchronized (mUnmountLock) {
+ mUnmountSignal = new CountDownLatch(1);
+ mPms.updateExternalMediaStatus(false, true);
+ waitForLatch(mUnmountSignal, "mUnmountSignal");
+ mUnmountSignal = null;
+ }
+ }
+
+ try {
+ vol.unmount();
+ } catch (NativeDaemonConnectorException ignored) {
+ }
+ } else {
+ Slog.w(TAG, "Unknown volume for path " + path);
}
- if (Environment.MEDIA_UNMOUNTED.equals(volState) ||
- Environment.MEDIA_REMOVED.equals(volState) ||
- Environment.MEDIA_SHARED.equals(volState) ||
- Environment.MEDIA_UNMOUNTABLE.equals(volState)) {
- // Media already unmounted or cannot be unmounted.
- // TODO return valid return code when adding observer call back.
- return;
- }
- UnmountCallBack ucb = new UnmountCallBack(path, force, removeEncryption);
- mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
}
+ @Override
public int formatVolume(String path) {
- validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
+ enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
waitForReady();
- return doFormatVolume(path);
+ final Volume vol = findVolumeByLegacyPath(path);
+ if (vol != null) {
+ try {
+ vol.format();
+ return 0;
+ } catch (NativeDaemonConnectorException ignored) {
+ }
+ } else {
+ Slog.w(TAG, "Unknown volume for path " + path);
+ }
+ return -1;
}
+ @Override
public int[] getStorageUsers(String path) {
- validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+ enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
waitForReady();
try {
final String[] r = NativeDaemonEvent.filterMessageList(
@@ -1813,22 +1314,20 @@
}
private void warnOnNotMounted() {
- final StorageVolume primary = getPrimaryPhysicalVolume();
- if (primary != null) {
- boolean mounted = false;
- try {
- mounted = Environment.MEDIA_MOUNTED.equals(getVolumeState(primary.getPath()));
- } catch (IllegalArgumentException e) {
- }
-
- if (!mounted) {
- Slog.w(TAG, "getSecureContainerList() called when storage not mounted");
+ synchronized (mLock) {
+ for (Volume vol : mVolumes.values()) {
+ if (vol.isPrimary() && vol.state == Volume.STATE_MOUNTED) {
+ // Cool beans, we have a mounted primary volume
+ return;
+ }
}
}
+
+ Slog.w(TAG, "No primary storage mounted!");
}
public String[] getSecureContainerList() {
- validatePermission(android.Manifest.permission.ASEC_ACCESS);
+ enforcePermission(android.Manifest.permission.ASEC_ACCESS);
waitForReady();
warnOnNotMounted();
@@ -1842,7 +1341,7 @@
public int createSecureContainer(String id, int sizeMb, String fstype, String key,
int ownerUid, boolean external) {
- validatePermission(android.Manifest.permission.ASEC_CREATE);
+ enforcePermission(android.Manifest.permission.ASEC_CREATE);
waitForReady();
warnOnNotMounted();
@@ -1864,7 +1363,7 @@
@Override
public int resizeSecureContainer(String id, int sizeMb, String key) {
- validatePermission(android.Manifest.permission.ASEC_CREATE);
+ enforcePermission(android.Manifest.permission.ASEC_CREATE);
waitForReady();
warnOnNotMounted();
@@ -1878,7 +1377,7 @@
}
public int finalizeSecureContainer(String id) {
- validatePermission(android.Manifest.permission.ASEC_CREATE);
+ enforcePermission(android.Manifest.permission.ASEC_CREATE);
warnOnNotMounted();
int rc = StorageResultCode.OperationSucceeded;
@@ -1895,7 +1394,7 @@
}
public int fixPermissionsSecureContainer(String id, int gid, String filename) {
- validatePermission(android.Manifest.permission.ASEC_CREATE);
+ enforcePermission(android.Manifest.permission.ASEC_CREATE);
warnOnNotMounted();
int rc = StorageResultCode.OperationSucceeded;
@@ -1912,7 +1411,7 @@
}
public int destroySecureContainer(String id, boolean force) {
- validatePermission(android.Manifest.permission.ASEC_DESTROY);
+ enforcePermission(android.Manifest.permission.ASEC_DESTROY);
waitForReady();
warnOnNotMounted();
@@ -1952,7 +1451,7 @@
}
public int mountSecureContainer(String id, String key, int ownerUid, boolean readOnly) {
- validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
+ enforcePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
waitForReady();
warnOnNotMounted();
@@ -1982,7 +1481,7 @@
}
public int unmountSecureContainer(String id, boolean force) {
- validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
+ enforcePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
waitForReady();
warnOnNotMounted();
@@ -2025,7 +1524,7 @@
}
public boolean isSecureContainerMounted(String id) {
- validatePermission(android.Manifest.permission.ASEC_ACCESS);
+ enforcePermission(android.Manifest.permission.ASEC_ACCESS);
waitForReady();
warnOnNotMounted();
@@ -2035,7 +1534,7 @@
}
public int renameSecureContainer(String oldId, String newId) {
- validatePermission(android.Manifest.permission.ASEC_RENAME);
+ enforcePermission(android.Manifest.permission.ASEC_RENAME);
waitForReady();
warnOnNotMounted();
@@ -2060,7 +1559,7 @@
}
public String getSecureContainerPath(String id) {
- validatePermission(android.Manifest.permission.ASEC_ACCESS);
+ enforcePermission(android.Manifest.permission.ASEC_ACCESS);
waitForReady();
warnOnNotMounted();
@@ -2081,7 +1580,7 @@
}
public String getSecureContainerFilesystemPath(String id) {
- validatePermission(android.Manifest.permission.ASEC_ACCESS);
+ enforcePermission(android.Manifest.permission.ASEC_ACCESS);
waitForReady();
warnOnNotMounted();
@@ -2101,8 +1600,13 @@
}
}
+ @Override
public void finishMediaUpdate() {
- mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE);
+ if (mUnmountSignal != null) {
+ mUnmountSignal.countDown();
+ } else {
+ Slog.w(TAG, "Odd, nobody asked to unmount?");
+ }
}
private boolean isUidOwnerOfPackageOrSystem(String packageName, int callerUid) {
@@ -2477,109 +1981,84 @@
Context.APP_OPS_SERVICE);
appOps.checkPackage(Binder.getCallingUid(), callingPkg);
+ File appFile = null;
try {
- appPath = new File(appPath).getCanonicalPath();
+ appFile = new File(appPath).getCanonicalFile();
} catch (IOException e) {
Slog.e(TAG, "Failed to resolve " + appPath + ": " + e);
return -1;
}
- if (!appPath.endsWith("/")) {
- appPath = appPath + "/";
- }
-
// Try translating the app path into a vold path, but require that it
// belong to the calling package.
- String voldPath = maybeTranslatePathForVold(appPath,
- userEnv.buildExternalStorageAppDataDirs(callingPkg),
- userEnv.buildExternalStorageAppDataDirsForVold(callingPkg));
- if (voldPath != null) {
+ if (FileUtils.contains(userEnv.buildExternalStorageAppDataDirs(callingPkg), appFile) ||
+ FileUtils.contains(userEnv.buildExternalStorageAppObbDirs(callingPkg), appFile) ||
+ FileUtils.contains(userEnv.buildExternalStorageAppMediaDirs(callingPkg), appFile)) {
+ appPath = appFile.getAbsolutePath();
+ if (!appPath.endsWith("/")) {
+ appPath = appPath + "/";
+ }
+
try {
- mConnector.execute("volume", "mkdirs", voldPath);
+ mConnector.execute("volume", "mkdirs", appPath);
return 0;
} catch (NativeDaemonConnectorException e) {
return e.getCode();
}
}
- voldPath = maybeTranslatePathForVold(appPath,
- userEnv.buildExternalStorageAppObbDirs(callingPkg),
- userEnv.buildExternalStorageAppObbDirsForVold(callingPkg));
- if (voldPath != null) {
- try {
- mConnector.execute("volume", "mkdirs", voldPath);
- return 0;
- } catch (NativeDaemonConnectorException e) {
- return e.getCode();
- }
- }
-
- voldPath = maybeTranslatePathForVold(appPath,
- userEnv.buildExternalStorageAppMediaDirs(callingPkg),
- userEnv.buildExternalStorageAppMediaDirsForVold(callingPkg));
- if (voldPath != null) {
- try {
- mConnector.execute("volume", "mkdirs", voldPath);
- return 0;
- } catch (NativeDaemonConnectorException e) {
- return e.getCode();
- }
- }
-
- throw new SecurityException("Invalid mkdirs path: " + appPath);
- }
-
- /**
- * Translate the given path from an app-visible path to a vold-visible path,
- * but only if it's under the given whitelisted paths.
- *
- * @param path a canonicalized app-visible path.
- * @param appPaths list of app-visible paths that are allowed.
- * @param voldPaths list of vold-visible paths directly corresponding to the
- * allowed app-visible paths argument.
- * @return a vold-visible path representing the original path, or
- * {@code null} if the given path didn't have an app-to-vold
- * mapping.
- */
- @VisibleForTesting
- public static String maybeTranslatePathForVold(
- String path, File[] appPaths, File[] voldPaths) {
- if (appPaths.length != voldPaths.length) {
- throw new IllegalStateException("Paths must be 1:1 mapping");
- }
-
- for (int i = 0; i < appPaths.length; i++) {
- final String appPath = appPaths[i].getAbsolutePath() + "/";
- if (path.startsWith(appPath)) {
- path = new File(voldPaths[i], path.substring(appPath.length()))
- .getAbsolutePath();
- if (!path.endsWith("/")) {
- path = path + "/";
- }
- return path;
- }
- }
- return null;
+ throw new SecurityException("Invalid mkdirs path: " + appFile);
}
@Override
- public StorageVolume[] getVolumeList() {
- final int callingUserId = UserHandle.getCallingUserId();
- final boolean accessAll = (mContext.checkPermission(
- android.Manifest.permission.ACCESS_ALL_EXTERNAL_STORAGE,
- Binder.getCallingPid(), Binder.getCallingUid()) == PERMISSION_GRANTED);
+ public StorageVolume[] getVolumeList(int userId) {
+ if (UserHandle.getCallingUserId() != userId) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_ALL_EXTERNAL_STORAGE, "getVolumeList");
+ }
- synchronized (mVolumesLock) {
- final ArrayList<StorageVolume> filtered = Lists.newArrayList();
- for (StorageVolume volume : mVolumes) {
- final UserHandle owner = volume.getOwner();
- final boolean ownerMatch = owner == null || owner.getIdentifier() == callingUserId;
- if (accessAll || ownerMatch) {
- filtered.add(volume);
+ final ArrayList<StorageVolume> res = Lists.newArrayList();
+ boolean foundPrimary = false;
+ synchronized (mLock) {
+ for (Volume vol : mVolumes.values()) {
+ if (vol.isVisibleToUser(userId)) {
+ final StorageVolume userVol = vol.buildVolumeForUser(userId);
+ if (vol.isPrimary()) {
+ res.add(0, userVol);
+ foundPrimary = true;
+ } else {
+ res.add(userVol);
+ }
}
}
- return filtered.toArray(new StorageVolume[filtered.size()]);
}
+
+ if (!foundPrimary) {
+ Slog.w(TAG, "No primary storage defined yet; hacking together a stub");
+
+ final boolean primaryPhysical = SystemProperties.getBoolean(
+ PROP_PRIMARY_PHYSICAL, false);
+
+ final String id = "stub_primary";
+ final File path = Environment.getLegacyExternalStorageDirectory();
+ final int descriptionId = android.R.string.unknownName;
+ final boolean primary = true;
+ final boolean removable = primaryPhysical;
+ final boolean emulated = !primaryPhysical;
+ final long mtpReserveSize = 0L;
+ final boolean allowMassStorage = false;
+ final long maxFileSize = 0L;
+ final UserHandle owner = new UserHandle(userId);
+ final String uuid = null;
+ final String userLabel = null;
+ final String state = Environment.MEDIA_REMOVED;
+
+ res.add(0, new StorageVolume(id, MtpStorage.getStorageIdForIndex(0), path,
+ descriptionId, primary, removable, emulated, mtpReserveSize,
+ allowMassStorage, maxFileSize, owner, uuid, userLabel, state));
+ }
+
+ return res.toArray(new StorageVolume[res.size()]);
}
private void addObbStateLocked(ObbState obbState) throws RemoteException {
@@ -3073,21 +2552,13 @@
if (path.startsWith(obbPath)) {
path = path.substring(obbPath.length() + 1);
- if (forVold) {
- return new File(Environment.getEmulatedStorageObbSource(), path).getAbsolutePath();
- } else {
- final UserEnvironment ownerEnv = new UserEnvironment(UserHandle.USER_OWNER);
- return new File(ownerEnv.buildExternalStorageAndroidObbDirs()[0], path)
- .getAbsolutePath();
- }
+ final UserEnvironment ownerEnv = new UserEnvironment(UserHandle.USER_OWNER);
+ return new File(ownerEnv.buildExternalStorageAndroidObbDirs()[0], path)
+ .getAbsolutePath();
}
// Handle normal external storage paths
- if (forVold) {
- return new File(Environment.getEmulatedStorageSource(userId), path).getAbsolutePath();
- } else {
- return new File(userEnv.getExternalDirsForApp()[0], path).getAbsolutePath();
- }
+ return new File(userEnv.getExternalStorageDirectory(), path).getAbsolutePath();
}
@Override
@@ -3126,15 +2597,20 @@
pw.decreaseIndent();
}
- synchronized (mVolumesLock) {
+ synchronized (mLock) {
pw.println();
- pw.println("mVolumes:");
+ pw.println("Disks:");
pw.increaseIndent();
- for (StorageVolume volume : mVolumes) {
- pw.println(volume);
- pw.increaseIndent();
- pw.println("Current state: " + mVolumeStates.get(volume.getPath()));
- pw.decreaseIndent();
+ for (Disk disk : mDisks.values()) {
+ disk.dump(pw);
+ }
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println("Volumes:");
+ pw.increaseIndent();
+ for (Volume vol : mVolumes.values()) {
+ vol.dump(pw);
}
pw.decreaseIndent();
}
@@ -3153,6 +2629,7 @@
}
/** {@inheritDoc} */
+ @Override
public void monitor() {
if (mConnector != null) {
mConnector.monitor();
diff --git a/services/core/java/com/android/server/NativeDaemonConnector.java b/services/core/java/com/android/server/NativeDaemonConnector.java
index d2dfc7b..78c7f38 100644
--- a/services/core/java/com/android/server/NativeDaemonConnector.java
+++ b/services/core/java/com/android/server/NativeDaemonConnector.java
@@ -48,8 +48,6 @@
* {@code libsysutils} FrameworkListener protocol.
*/
final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
- private static final boolean LOGD = false;
-
private final static boolean VDBG = false;
private final String TAG;
@@ -58,6 +56,8 @@
private OutputStream mOutputStream;
private LocalLog mLocalLog;
+ private volatile boolean mDebug = false;
+
private final ResponseQueue mResponseQueue;
private final PowerManager.WakeLock mWakeLock;
@@ -99,6 +99,14 @@
mLocalLog = new LocalLog(maxLogSize);
}
+ /**
+ * Enable Set debugging mode, which causes messages to also be written to both
+ * {@link Slog} in addition to internal log.
+ */
+ public void setDebug(boolean debug) {
+ mDebug = debug;
+ }
+
@Override
public void run() {
mCallbackHandler = new Handler(mLooper, this);
@@ -513,7 +521,7 @@
}
private void log(String logstring) {
- if (LOGD) Slog.d(TAG, logstring);
+ if (mDebug) Slog.d(TAG, logstring);
mLocalLog.log(logstring);
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index a64e6c3..1b32f57 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -109,7 +109,7 @@
private static final int TIMEOUT_DELAY_MS = 1000 * 60;
private static final String DATABASE_NAME = "accounts.db";
- private static final int DATABASE_VERSION = 6;
+ private static final int DATABASE_VERSION = 7;
private final Context mContext;
@@ -131,6 +131,8 @@
private static final String ACCOUNTS_TYPE_COUNT = "count(type)";
private static final String ACCOUNTS_PASSWORD = "password";
private static final String ACCOUNTS_PREVIOUS_NAME = "previous_name";
+ private static final String ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS =
+ "last_password_entry_time_millis_epoch";
private static final String TABLE_AUTHTOKENS = "authtokens";
private static final String AUTHTOKENS_ID = "_id";
@@ -697,7 +699,8 @@
long identityToken = clearCallingIdentity();
try {
new Session(fromAccounts, response, account.type, false,
- false /* stripAuthTokenFromResult */) {
+ false /* stripAuthTokenFromResult */, account.name,
+ false /* authDetailsRequired */) {
@Override
protected String toDebugString(long now) {
return super.toDebugString(now) + ", getAccountCredentialsForClone"
@@ -725,12 +728,43 @@
}
}
+ @Override
+ public boolean accountAuthenticated(final Account account) {
+ if (account == null) {
+ throw new IllegalArgumentException("account is null");
+ }
+ checkAuthenticateAccountsPermission(account);
+
+ final UserAccounts accounts = getUserAccountsForCaller();
+ int userId = Binder.getCallingUserHandle().getIdentifier();
+ if (!canUserModifyAccounts(userId) || !canUserModifyAccountsForType(userId, account.type)) {
+ return false;
+ }
+ synchronized (accounts.cacheLock) {
+ final ContentValues values = new ContentValues();
+ values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, System.currentTimeMillis());
+ final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+ int i = db.update(
+ TABLE_ACCOUNTS,
+ values,
+ ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?",
+ new String[] {
+ account.name, account.type
+ });
+ if (i > 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private void completeCloningAccount(IAccountManagerResponse response,
final Bundle accountCredentials, final Account account, final UserAccounts targetUser) {
long id = clearCallingIdentity();
try {
new Session(targetUser, response, account.type, false,
- false /* stripAuthTokenFromResult */) {
+ false /* stripAuthTokenFromResult */, account.name,
+ false /* authDetailsRequired */) {
@Override
protected String toDebugString(long now) {
return super.toDebugString(now) + ", getAccountCredentialsForClone"
@@ -795,6 +829,7 @@
values.put(ACCOUNTS_NAME, account.name);
values.put(ACCOUNTS_TYPE, account.type);
values.put(ACCOUNTS_PASSWORD, password);
+ values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, System.currentTimeMillis());
long accountId = db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values);
if (accountId < 0) {
Log.w(TAG, "insertAccountIntoDatabase: " + account
@@ -885,7 +920,8 @@
public TestFeaturesSession(UserAccounts accounts, IAccountManagerResponse response,
Account account, String[] features) {
super(accounts, response, account.type, false /* expectActivityLaunch */,
- true /* stripAuthTokenFromResult */);
+ true /* stripAuthTokenFromResult */, account.name,
+ false /* authDetailsRequired */);
mFeatures = features;
mAccount = account;
}
@@ -1184,7 +1220,8 @@
public RemoveAccountSession(UserAccounts accounts, IAccountManagerResponse response,
Account account, boolean expectActivityLaunch) {
super(accounts, response, account.type, expectActivityLaunch,
- true /* stripAuthTokenFromResult */);
+ true /* stripAuthTokenFromResult */, account.name,
+ false /* authDetailsRequired */);
mAccount = account;
}
@@ -1419,6 +1456,13 @@
try {
final ContentValues values = new ContentValues();
values.put(ACCOUNTS_PASSWORD, password);
+ long time = 0;
+ // Only set current time, if it is a valid password. For clear password case, it
+ // should not be set.
+ if (password != null) {
+ time = System.currentTimeMillis();
+ }
+ values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, time);
final long accountId = getAccountIdLocked(db, account);
if (accountId >= 0) {
final String[] argsAccountId = {String.valueOf(accountId)};
@@ -1547,8 +1591,9 @@
UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid));
long identityToken = clearCallingIdentity();
try {
- new Session(accounts, response, accountType, false,
- false /* stripAuthTokenFromResult */) {
+ new Session(accounts, response, accountType, false /* expectActivityLaunch */,
+ false /* stripAuthTokenFromResult */, null /* accountName */,
+ false /* authDetailsRequired */) {
@Override
protected String toDebugString(long now) {
return super.toDebugString(now) + ", getAuthTokenLabel"
@@ -1648,7 +1693,8 @@
}
new Session(accounts, response, account.type, expectActivityLaunch,
- false /* stripAuthTokenFromResult */) {
+ false /* stripAuthTokenFromResult */, account.name,
+ false /* authDetailsRequired */) {
@Override
protected String toDebugString(long now) {
if (loginOptions != null) loginOptions.keySet();
@@ -1842,7 +1888,8 @@
long identityToken = clearCallingIdentity();
try {
new Session(accounts, response, accountType, expectActivityLaunch,
- true /* stripAuthTokenFromResult */) {
+ true /* stripAuthTokenFromResult */, null /* accountName */,
+ false /* authDetailsRequired */) {
@Override
public void run() throws RemoteException {
mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures,
@@ -1917,7 +1964,8 @@
long identityToken = clearCallingIdentity();
try {
new Session(accounts, response, accountType, expectActivityLaunch,
- true /* stripAuthTokenFromResult */) {
+ true /* stripAuthTokenFromResult */, null /* accountName */,
+ false /* authDetailsRequired */) {
@Override
public void run() throws RemoteException {
mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures,
@@ -1973,7 +2021,8 @@
long identityToken = clearCallingIdentity();
try {
new Session(accounts, response, account.type, expectActivityLaunch,
- true /* stripAuthTokenFromResult */) {
+ true /* stripAuthTokenFromResult */, account.name,
+ true /* authDetailsRequired */) {
@Override
public void run() throws RemoteException {
mAuthenticator.confirmCredentials(this, account, options);
@@ -2009,7 +2058,8 @@
long identityToken = clearCallingIdentity();
try {
new Session(accounts, response, account.type, expectActivityLaunch,
- true /* stripAuthTokenFromResult */) {
+ true /* stripAuthTokenFromResult */, account.name,
+ false /* authDetailsRequired */) {
@Override
public void run() throws RemoteException {
mAuthenticator.updateCredentials(this, account, authTokenType, loginOptions);
@@ -2045,7 +2095,8 @@
long identityToken = clearCallingIdentity();
try {
new Session(accounts, response, accountType, expectActivityLaunch,
- true /* stripAuthTokenFromResult */) {
+ true /* stripAuthTokenFromResult */, null /* accountName */,
+ false /* authDetailsRequired */) {
@Override
public void run() throws RemoteException {
mAuthenticator.editProperties(this, mAccountType);
@@ -2071,7 +2122,8 @@
public GetAccountsByTypeAndFeatureSession(UserAccounts accounts,
IAccountManagerResponse response, String type, String[] features, int callingUid) {
super(accounts, response, type, false /* expectActivityLaunch */,
- true /* stripAuthTokenFromResult */);
+ true /* stripAuthTokenFromResult */, null /* accountName */,
+ false /* authDetailsRequired */);
mCallingUid = callingUid;
mFeatures = features;
}
@@ -2437,6 +2489,9 @@
final String mAccountType;
final boolean mExpectActivityLaunch;
final long mCreationTime;
+ final String mAccountName;
+ // Indicates if we need to add auth details(like last credential time)
+ final boolean mAuthDetailsRequired;
public int mNumResults = 0;
private int mNumRequestContinued = 0;
@@ -2448,7 +2503,8 @@
protected final UserAccounts mAccounts;
public Session(UserAccounts accounts, IAccountManagerResponse response, String accountType,
- boolean expectActivityLaunch, boolean stripAuthTokenFromResult) {
+ boolean expectActivityLaunch, boolean stripAuthTokenFromResult, String accountName,
+ boolean authDetailsRequired) {
super();
//if (response == null) throw new IllegalArgumentException("response is null");
if (accountType == null) throw new IllegalArgumentException("accountType is null");
@@ -2458,6 +2514,9 @@
mAccountType = accountType;
mExpectActivityLaunch = expectActivityLaunch;
mCreationTime = SystemClock.elapsedRealtime();
+ mAccountName = accountName;
+ mAuthDetailsRequired = authDetailsRequired;
+
synchronized (mSessions) {
mSessions.put(toString(), this);
}
@@ -2592,6 +2651,16 @@
public void onResult(Bundle result) {
mNumResults++;
Intent intent = null;
+ if (result != null && mAuthDetailsRequired) {
+ long lastAuthenticatedTime = DatabaseUtils.longForQuery(
+ mAccounts.openHelper.getReadableDatabase(),
+ "select " + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " from " +
+ TABLE_ACCOUNTS + " WHERE " + ACCOUNTS_NAME + "=? AND "
+ + ACCOUNTS_TYPE + "=?",
+ new String[]{mAccountName, mAccountType});
+ result.putLong(AccountManager.KEY_LAST_AUTHENTICATE_TIME_MILLIS_EPOCH,
+ lastAuthenticatedTime);
+ }
if (result != null
&& (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
/*
@@ -2798,6 +2867,7 @@
+ ACCOUNTS_TYPE + " TEXT NOT NULL, "
+ ACCOUNTS_PASSWORD + " TEXT, "
+ ACCOUNTS_PREVIOUS_NAME + " TEXT, "
+ + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " INTEGER DEFAULT 0, "
+ "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " ( "
@@ -2833,6 +2903,11 @@
+ "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
}
+ private void addLastSuccessfullAuthenticatedTimeColumn(SQLiteDatabase db) {
+ db.execSQL("ALTER TABLE " + TABLE_ACCOUNTS + " ADD COLUMN "
+ + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " DEFAULT 0");
+ }
+
private void addOldAccountNameColumn(SQLiteDatabase db) {
db.execSQL("ALTER TABLE " + TABLE_ACCOUNTS + " ADD COLUMN " + ACCOUNTS_PREVIOUS_NAME);
}
@@ -2892,6 +2967,11 @@
oldVersion++;
}
+ if (oldVersion == 6) {
+ addLastSuccessfullAuthenticatedTimeColumn(db);
+ oldVersion++;
+ }
+
if (oldVersion != newVersion) {
Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b5e1de9..c318370 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3041,24 +3041,13 @@
int uid = app.uid;
int[] gids = null;
- int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
+ int mountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT;
if (!app.isolated) {
int[] permGids = null;
try {
checkTime(startTime, "startProcess: getting gids from package manager");
permGids = AppGlobals.getPackageManager().getPackageGids(app.info.packageName,
app.userId);
-
- if (Environment.isExternalStorageEmulated()) {
- checkTime(startTime, "startProcess: checking external storage perm");
- if (mContext.getPackageManager().checkPermission(
- android.Manifest.permission.ACCESS_ALL_EXTERNAL_STORAGE,
- app.info.packageName) == PERMISSION_GRANTED) {
- mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL;
- } else {
- mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER;
- }
- }
} catch (RemoteException e) {
Slog.w(TAG, "Unable to retrieve gids", e);
}
@@ -17505,8 +17494,12 @@
mFullPssPending = true;
mPendingPssProcesses.ensureCapacity(mLruProcesses.size());
mPendingPssProcesses.clear();
- for (int i=mLruProcesses.size()-1; i>=0; i--) {
+ for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
ProcessRecord app = mLruProcesses.get(i);
+ if (app.thread == null
+ || app.curProcState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
+ continue;
+ }
if (memLowered || now > (app.lastStateTime+ProcessList.PSS_ALL_INTERVAL)) {
app.pssProcState = app.setProcState;
app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, true,
@@ -17822,8 +17815,8 @@
}
}
}
- if (app.setProcState < 0 || ProcessList.procStatesDifferForMem(app.curProcState,
- app.setProcState)) {
+ if (app.setProcState == ActivityManager.PROCESS_STATE_NONEXISTENT
+ || ProcessList.procStatesDifferForMem(app.curProcState, app.setProcState)) {
if (false && mTestPssMode && app.setProcState >= 0 && app.lastStateTime <= (now-200)) {
// Experimental code to more aggressively collect pss while
// running test... the problem is that this tends to collect
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 0eb914b..8ba34e2 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1250,6 +1250,11 @@
TAG, "ensureActivitiesVisible behind " + top
+ " configChanges=0x" + Integer.toHexString(configChanges));
+ if (DEBUG_STATES && starting != null && starting.task.stack == this) {
+ Slog.d(TAG, "ensureActivitiesVisibleLocked: starting=" + starting + " state="
+ + starting.state + " fullscreen=" + starting.fullscreen + " top=" + top
+ + " state=" + top.state + " fullscreen=" + top.fullscreen);
+ }
if (mTranslucentActivityWaiting != top) {
mUndrawnActivitiesBelowTopTranslucent.clear();
if (mTranslucentActivityWaiting != null) {
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 7cf3b51..7c921ac 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -89,10 +90,10 @@
int curSchedGroup; // Currently desired scheduling class
int setSchedGroup; // Last set to background scheduling class
int trimMemoryLevel; // Last selected memory trimming level
- int curProcState = -1; // Currently computed process state: ActivityManager.PROCESS_STATE_*
- int repProcState = -1; // Last reported process state
- int setProcState = -1; // Last set process state in process tracker
- int pssProcState = -1; // The proc state we are currently requesting pss for
+ int curProcState = PROCESS_STATE_NONEXISTENT; // Currently computed process state
+ int repProcState = PROCESS_STATE_NONEXISTENT; // Last reported process state
+ int setProcState = PROCESS_STATE_NONEXISTENT; // Last set process state in process tracker
+ int pssProcState = PROCESS_STATE_NONEXISTENT; // Currently requesting pss for
boolean serviceb; // Process currently is on the service B list
boolean serviceHighRam; // We are forcing to service B list due to its RAM use
boolean setIsForeground; // Running foreground UI when last set?
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index d8d361b..3b34541 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -16,6 +16,8 @@
package com.android.server.am;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
@@ -346,8 +348,8 @@
if ((info.flags & ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS) != 0) {
// If the activity itself has requested auto-remove, then just always do it.
autoRemoveRecents = true;
- } else if ((intentFlags & (Intent.FLAG_ACTIVITY_NEW_DOCUMENT
- | Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS)) == Intent.FLAG_ACTIVITY_NEW_DOCUMENT) {
+ } else if ((intentFlags & (FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_RETAIN_IN_RECENTS))
+ == FLAG_ACTIVITY_NEW_DOCUMENT) {
// If the caller has not asked for the document to be retained, then we may
// want to turn on auto-remove, depending on whether the target has set its
// own document launch mode.
@@ -879,7 +881,8 @@
for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
final ActivityRecord r = activities.get(activityNdx);
if (r.info.persistableMode == ActivityInfo.PERSIST_ROOT_ONLY || !r.isPersistable() ||
- ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) &&
+ ((r.intent.getFlags() & FLAG_ACTIVITY_NEW_DOCUMENT
+ | FLAG_ACTIVITY_RETAIN_IN_RECENTS) == FLAG_ACTIVITY_NEW_DOCUMENT) &&
activityNdx > 0) {
// Stop at first non-persistable or first break in task (CLEAR_WHEN_TASK_RESET).
break;
diff --git a/services/core/java/com/android/server/camera/CameraService.java b/services/core/java/com/android/server/camera/CameraService.java
new file mode 100644
index 0000000..f9b17ed
--- /dev/null
+++ b/services/core/java/com/android/server/camera/CameraService.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 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.camera;
+
+import android.content.Context;
+import android.hardware.ICameraService;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.server.SystemService;
+
+/**
+ * CameraService is the system_server analog to the camera service running in mediaserver.
+ *
+ * @hide
+ */
+public class CameraService extends SystemService {
+
+ /**
+ * This must match the ICameraService.aidl definition
+ */
+ private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera";
+
+ // Event arguments to use with the camera service notifySystemEvent call:
+ public static final int NO_EVENT = 0; // NOOP
+ public static final int USER_SWITCHED = 1; // User changed, argument is the new user handle
+
+ public CameraService(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {}
+
+ @Override
+ public void onSwitchUser(int userHandle) {
+ super.onSwitchUser(userHandle);
+
+ /**
+ * Forward the user switch event to the native camera service running in mediaserver.
+ */
+ IBinder cameraServiceBinder = getBinderService(CAMERA_SERVICE_BINDER_NAME);
+ if (cameraServiceBinder == null) {
+ return; // Camera service not active, there is no need to evict user clients.
+ }
+ ICameraService cameraServiceRaw = ICameraService.Stub.asInterface(cameraServiceBinder);
+ try {
+ cameraServiceRaw.notifySystemEvent(USER_SWITCHED, userHandle);
+ } catch (RemoteException e) {
+ // Do nothing, if camera service is dead, there is no need to evict user clients.
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/policy/WindowOrientationListener.java b/services/core/java/com/android/server/policy/WindowOrientationListener.java
index a33ee4c..c8fd82e 100644
--- a/services/core/java/com/android/server/policy/WindowOrientationListener.java
+++ b/services/core/java/com/android/server/policy/WindowOrientationListener.java
@@ -136,7 +136,9 @@
public void onTouchStart() {
synchronized (mLock) {
- mSensorEventListener.onTouchStartLocked();
+ if (mSensorEventListener != null) {
+ mSensorEventListener.onTouchStartLocked();
+ }
}
}
@@ -144,7 +146,9 @@
long whenElapsedNanos = SystemClock.elapsedRealtimeNanos();
synchronized (mLock) {
- mSensorEventListener.onTouchEndLocked(whenElapsedNanos);
+ if (mSensorEventListener != null) {
+ mSensorEventListener.onTouchEndLocked(whenElapsedNanos);
+ }
}
}
diff --git a/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java b/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java
deleted file mode 100644
index 2fe68f8..0000000
--- a/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2013 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.updates;
-
-import android.util.Base64;
-
-import java.io.IOException;
-
-public class TZInfoInstallReceiver extends ConfigUpdateInstallReceiver {
-
- public TZInfoInstallReceiver() {
- super("/data/misc/zoneinfo/", "tzdata", "metadata/", "version");
- }
-
- @Override
- protected void install(byte[] encodedContent, int version) throws IOException {
- super.install(Base64.decode(encodedContent, Base64.DEFAULT), version);
- }
-}
diff --git a/services/core/java/com/android/server/updates/TzDataInstallReceiver.java b/services/core/java/com/android/server/updates/TzDataInstallReceiver.java
new file mode 100644
index 0000000..b260e4e
--- /dev/null
+++ b/services/core/java/com/android/server/updates/TzDataInstallReceiver.java
@@ -0,0 +1,54 @@
+/*
+ * 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.updates;
+
+import android.util.Slog;
+
+import java.io.File;
+import java.io.IOException;
+import libcore.tzdata.update.TzDataBundleInstaller;
+
+/**
+ * An install receiver responsible for installing timezone data updates.
+ */
+public class TzDataInstallReceiver extends ConfigUpdateInstallReceiver {
+
+ private static final String TAG = "TZDataInstallReceiver";
+
+ private static final File TZ_DATA_DIR = new File("/data/misc/zoneinfo");
+ private static final String UPDATE_DIR_NAME = TZ_DATA_DIR.getPath() + "/updates/";
+ private static final String UPDATE_METADATA_DIR_NAME = "metadata/";
+ private static final String UPDATE_VERSION_FILE_NAME = "version";
+ private static final String UPDATE_CONTENT_FILE_NAME = "tzdata_bundle.zip";
+
+ private final TzDataBundleInstaller installer;
+
+ public TzDataInstallReceiver() {
+ super(UPDATE_DIR_NAME, UPDATE_CONTENT_FILE_NAME, UPDATE_METADATA_DIR_NAME,
+ UPDATE_VERSION_FILE_NAME);
+ installer = new TzDataBundleInstaller(TAG, TZ_DATA_DIR);
+ }
+
+ @Override
+ protected void install(byte[] content, int version) throws IOException {
+ boolean valid = installer.install(content);
+ Slog.i(TAG, "Timezone data install valid for this device: " + valid);
+ // Even if !valid, we call super.install(). Only in the event of an exception should we
+ // not. If we didn't do this we could attempt to install repeatedly.
+ super.install(content, version);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 32bb78e..dcd233f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -9059,39 +9059,41 @@
goodToGo = false;
}
}
- if (goodToGo && isWallpaperVisible(mWallpaperTarget)) {
- boolean wallpaperGoodToGo = true;
- for (int curTokenIndex = mWallpaperTokens.size() - 1;
- curTokenIndex >= 0 && wallpaperGoodToGo; curTokenIndex--) {
- WindowToken token = mWallpaperTokens.get(curTokenIndex);
- for (int curWallpaperIndex = token.windows.size() - 1; curWallpaperIndex >= 0;
- curWallpaperIndex--) {
- WindowState wallpaper = token.windows.get(curWallpaperIndex);
- if (wallpaper.mWallpaperVisible && !wallpaper.isDrawnLw()) {
- // We've told this wallpaper to be visible, but it is not drawn yet
- wallpaperGoodToGo = false;
- if (mWallpaperDrawState != WALLPAPER_DRAW_TIMEOUT) {
- // wait for this wallpaper until it is drawn or timeout
- goodToGo = false;
- }
- if (mWallpaperDrawState == WALLPAPER_DRAW_NORMAL) {
- mWallpaperDrawState = WALLPAPER_DRAW_PENDING;
- mH.removeMessages(H.WALLPAPER_DRAW_PENDING_TIMEOUT);
- mH.sendEmptyMessageDelayed(H.WALLPAPER_DRAW_PENDING_TIMEOUT,
- WALLPAPER_DRAW_PENDING_TIMEOUT_DURATION);
- }
- if (DEBUG_APP_TRANSITIONS || DEBUG_WALLPAPER) Slog.v(TAG,
- "Wallpaper should be visible but has not been drawn yet. " +
- "mWallpaperDrawState=" + mWallpaperDrawState);
- break;
- }
- }
- }
- if (wallpaperGoodToGo) {
- mWallpaperDrawState = WALLPAPER_DRAW_NORMAL;
- mH.removeMessages(H.WALLPAPER_DRAW_PENDING_TIMEOUT);
- }
- }
+// Stuck in a state with mWallpaperDrawState == WALLPAPER_DRAW_PENDING without a timeout. Leave
+// commented out until that is understood.
+// if (goodToGo && isWallpaperVisible(mWallpaperTarget)) {
+// boolean wallpaperGoodToGo = true;
+// for (int curTokenIndex = mWallpaperTokens.size() - 1;
+// curTokenIndex >= 0 && wallpaperGoodToGo; curTokenIndex--) {
+// WindowToken token = mWallpaperTokens.get(curTokenIndex);
+// for (int curWallpaperIndex = token.windows.size() - 1; curWallpaperIndex >= 0;
+// curWallpaperIndex--) {
+// WindowState wallpaper = token.windows.get(curWallpaperIndex);
+// if (wallpaper.mWallpaperVisible && !wallpaper.isDrawnLw()) {
+// // We've told this wallpaper to be visible, but it is not drawn yet
+// wallpaperGoodToGo = false;
+// if (mWallpaperDrawState != WALLPAPER_DRAW_TIMEOUT) {
+// // wait for this wallpaper until it is drawn or timeout
+// goodToGo = false;
+// }
+// if (mWallpaperDrawState == WALLPAPER_DRAW_NORMAL) {
+// mWallpaperDrawState = WALLPAPER_DRAW_PENDING;
+// mH.removeMessages(H.WALLPAPER_DRAW_PENDING_TIMEOUT);
+// mH.sendEmptyMessageDelayed(H.WALLPAPER_DRAW_PENDING_TIMEOUT,
+// WALLPAPER_DRAW_PENDING_TIMEOUT_DURATION);
+// }
+// if (DEBUG_APP_TRANSITIONS || DEBUG_WALLPAPER) Slog.v(TAG,
+// "Wallpaper should be visible but has not been drawn yet. " +
+// "mWallpaperDrawState=" + mWallpaperDrawState);
+// break;
+// }
+// }
+// }
+// if (wallpaperGoodToGo) {
+// mWallpaperDrawState = WALLPAPER_DRAW_NORMAL;
+// mH.removeMessages(H.WALLPAPER_DRAW_PENDING_TIMEOUT);
+// }
+// }
}
if (goodToGo) {
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO");
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 090c0f8..53da75b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -53,6 +53,7 @@
import com.android.server.accounts.AccountManagerService;
import com.android.server.am.ActivityManagerService;
import com.android.server.audio.AudioService;
+import com.android.server.camera.CameraService;
import com.android.server.clipboard.ClipboardService;
import com.android.server.content.ContentService;
import com.android.server.devicepolicy.DevicePolicyManagerService;
@@ -408,6 +409,7 @@
AudioService audioService = null;
MmsServiceBroker mmsService = null;
EntropyMixer entropyMixer = null;
+ CameraService cameraService = null;
boolean disableStorage = SystemProperties.getBoolean("config.disable_storage", false);
boolean disableBluetooth = SystemProperties.getBoolean("config.disable_bluetooth", false);
@@ -436,6 +438,9 @@
mContentResolver = context.getContentResolver();
+ Slog.i(TAG, "Camera Service");
+ mSystemServiceManager.startService(CameraService.class);
+
// The AccountManager must come before the ContentService
try {
// TODO: seems like this should be disable-able, but req'd by ContentService
diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
index 7c101a40f..6ece888 100644
--- a/services/usb/java/com/android/server/usb/UsbMidiDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
@@ -29,6 +29,9 @@
import android.system.StructPollfd;
import android.util.Log;
+import com.android.internal.midi.MidiEventScheduler;
+import com.android.internal.midi.MidiEventScheduler.MidiEvent;
+
import libcore.io.IoUtils;
import java.io.Closeable;
@@ -42,7 +45,7 @@
private MidiDeviceServer mServer;
- private final MidiReceiver[] mInputPortReceivers;
+ private final MidiEventScheduler mEventScheduler;
private static final int BUFFER_SIZE = 512;
@@ -99,19 +102,7 @@
for (int i = 0; i < outputCount; i++) {
mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]);
}
-
- mInputPortReceivers = new MidiReceiver[inputCount];
- for (int port = 0; port < inputCount; port++) {
- final int portF = port;
- mInputPortReceivers[port] = new MidiReceiver() {
- @Override
- public void onReceive(byte[] data, int offset, int count, long timestamp)
- throws IOException {
- // FIXME - timestamps are ignored, future posting not supported yet.
- mOutputStreams[portF].write(data, offset, count);
- }
- };
- }
+ mEventScheduler = new MidiEventScheduler(inputCount);
}
private boolean register(Context context, Bundle properties) {
@@ -121,16 +112,22 @@
return false;
}
+ int inputCount = mInputStreams.length;
int outputCount = mOutputStreams.length;
- mServer = midiManager.createDeviceServer(mInputPortReceivers, outputCount,
+ MidiReceiver[] inputPortReceivers = new MidiReceiver[inputCount];
+ for (int port = 0; port < inputCount; port++) {
+ inputPortReceivers[port] = mEventScheduler.getReceiver(port);
+ }
+
+ mServer = midiManager.createDeviceServer(inputPortReceivers, outputCount,
null, null, properties, MidiDeviceInfo.TYPE_USB, null);
if (mServer == null) {
return false;
}
final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers();
- // FIXME can we only run this when we have a dispatcher that has listeners?
- new Thread() {
+ // Create input thread
+ new Thread("UsbMidiDevice input thread") {
@Override
public void run() {
byte[] buffer = new byte[BUFFER_SIZE];
@@ -160,6 +157,33 @@
} catch (ErrnoException e) {
Log.d(TAG, "reader thread exiting");
}
+ Log.d(TAG, "input thread exit");
+ }
+ }.start();
+
+ // Create output thread
+ new Thread("UsbMidiDevice output thread") {
+ @Override
+ public void run() {
+ while (true) {
+ MidiEvent event;
+ try {
+ event = (MidiEvent)mEventScheduler.waitNextEvent();
+ } catch (InterruptedException e) {
+ // try again
+ continue;
+ }
+ if (event == null) {
+ break;
+ }
+ try {
+ mOutputStreams[event.portNumber].write(event.data, 0, event.count);
+ } catch (IOException e) {
+ Log.e(TAG, "write failed for port " + event.portNumber);
+ }
+ mEventScheduler.addEventToPool(event);
+ }
+ Log.d(TAG, "output thread exit");
}
}.start();
@@ -168,6 +192,8 @@
@Override
public void close() throws IOException {
+ mEventScheduler.close();
+
if (mServer != null) {
mServer.close();
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index fbc70db..6bc6de63 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3316,11 +3316,11 @@
* @return the preferred network type, defined in RILConstants.java.
* @hide
*/
- public int getPreferredNetworkType() {
+ public int getPreferredNetworkType(int subId) {
try {
ITelephony telephony = getITelephony();
if (telephony != null)
- return telephony.getPreferredNetworkType();
+ return telephony.getPreferredNetworkType(subId);
} catch (RemoteException ex) {
Rlog.e(TAG, "getPreferredNetworkType RemoteException", ex);
} catch (NullPointerException ex) {
@@ -3330,6 +3330,27 @@
}
/**
+ * Sets the network selection mode to automatic.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+ * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
+ *
+ * @hide
+ */
+ public void setNetworkSelectionModeAutomatic(int subId) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null)
+ telephony.setNetworkSelectionModeAutomatic(subId);
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "setNetworkSelectionModeAutomatic RemoteException", ex);
+ } catch (NullPointerException ex) {
+ Rlog.e(TAG, "setNetworkSelectionModeAutomatic NPE", ex);
+ }
+ }
+
+ /**
* Set the preferred network type.
* Used for device configuration by some CDMA operators.
* <p>
@@ -3337,15 +3358,16 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
* Or the calling app has carrier privileges. @see #hasCarrierPrivileges
*
+ * @param subId the id of the subscription to set the preferred network type for.
* @param networkType the preferred network type, defined in RILConstants.java.
* @return true on success; false on any failure.
* @hide
*/
- public boolean setPreferredNetworkType(int networkType) {
+ public boolean setPreferredNetworkType(int subId, int networkType) {
try {
ITelephony telephony = getITelephony();
if (telephony != null)
- return telephony.setPreferredNetworkType(networkType);
+ return telephony.setPreferredNetworkType(subId, networkType);
} catch (RemoteException ex) {
Rlog.e(TAG, "setPreferredNetworkType RemoteException", ex);
} catch (NullPointerException ex) {
@@ -3364,7 +3386,8 @@
* @return true on success; false on any failure.
*/
public boolean setPreferredNetworkTypeToGlobal() {
- return setPreferredNetworkType(RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA);
+ return setPreferredNetworkType(getDefaultSubscription(),
+ RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA);
}
/**
@@ -4448,4 +4471,22 @@
return retval;
}
+
+ /**
+ * Resets telephony manager settings back to factory defaults.
+ *
+ * @hide
+ */
+ public void factoryReset(int subId) {
+ if (SubscriptionManager.isUsableSubIdValue(subId)) {
+ // Enable data
+ setDataEnabled(subId, true);
+ // Set network selection mode to automatic
+ setNetworkSelectionModeAutomatic(subId);
+ // Set preferred mobile network type to the best available
+ setPreferredNetworkType(subId, RILConstants.PREFERRED_NETWORK_MODE);
+ // Turn off roaming
+ SubscriptionManager.from(mContext).setDataRoaming(0, subId);
+ }
+ }
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index c18e3b6..a24859b 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -653,9 +653,10 @@
* Get the preferred network type.
* Used for device configuration by some CDMA operators.
*
+ * @param subId the id of the subscription to query.
* @return the preferred network type, defined in RILConstants.java.
*/
- int getPreferredNetworkType();
+ int getPreferredNetworkType(int subId);
/**
* Check TETHER_DUN_REQUIRED and TETHER_DUN_APN settings, net.tethering.noprovisioning
@@ -667,13 +668,21 @@
int getTetherApnRequired();
/**
+ * Set the network selection mode to automatic.
+ *
+ * @param subId the id of the subscription to update.
+ */
+ void setNetworkSelectionModeAutomatic(int subId);
+
+ /**
* Set the preferred network type.
* Used for device configuration by some CDMA operators.
*
+ * @param subId the id of the subscription to update.
* @param networkType the preferred network type, defined in RILConstants.java.
* @return true on success; false on any failure.
*/
- boolean setPreferredNetworkType(int networkType);
+ boolean setPreferredNetworkType(int subId, int networkType);
/**
* User enable/disable Mobile Data.
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index e1460ef..6371891 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -2621,4 +2621,25 @@
}
return false;
}
+
+ /**
+ * Resets all wifi manager settings back to factory defaults.
+ *
+ * @hide
+ */
+ public void factoryReset() {
+ // Enable wifi
+ setWifiEnabled(true);
+ // Delete all Wifi SSIDs
+ List<WifiConfiguration> networks = getConfiguredNetworks();
+ if (networks != null) {
+ for (WifiConfiguration config : networks) {
+ removeNetwork(config.networkId);
+ }
+ saveConfiguration();
+ }
+
+ // Turn mobile hotspot off
+ setWifiApEnabled(null, false);
+ }
}