Merge "Make JavascriptInterface annotation public." into jb-mr1-dev
diff --git a/api/current.txt b/api/current.txt
index 54a03cd..9c127b7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -27070,7 +27070,7 @@
}
public abstract class WebSettings {
- method public boolean enableSmoothTransition();
+ method public deprecated boolean enableSmoothTransition();
method public boolean getAllowContentAccess();
method public boolean getAllowFileAccess();
method public abstract boolean getAllowFileAccessFromFileURLs();
@@ -27136,7 +27136,7 @@
method public void setDefaultZoom(android.webkit.WebSettings.ZoomDensity);
method public void setDisplayZoomControls(boolean);
method public synchronized void setDomStorageEnabled(boolean);
- method public void setEnableSmoothTransition(boolean);
+ method public deprecated void setEnableSmoothTransition(boolean);
method public synchronized void setFantasyFontFamily(java.lang.String);
method public synchronized void setFixedFontFamily(java.lang.String);
method public synchronized void setGeolocationDatabasePath(java.lang.String);
@@ -27175,7 +27175,7 @@
field public static final int LOAD_CACHE_ELSE_NETWORK = 1; // 0x1
field public static final int LOAD_CACHE_ONLY = 3; // 0x3
field public static final int LOAD_DEFAULT = -1; // 0xffffffff
- field public static final int LOAD_NORMAL = 0; // 0x0
+ field public static final deprecated int LOAD_NORMAL = 0; // 0x0
field public static final int LOAD_NO_CACHE = 2; // 0x2
}
@@ -27258,13 +27258,13 @@
ctor public WebView(android.content.Context);
ctor public WebView(android.content.Context, android.util.AttributeSet);
ctor public WebView(android.content.Context, android.util.AttributeSet, int);
- ctor public WebView(android.content.Context, android.util.AttributeSet, int, boolean);
+ ctor public deprecated WebView(android.content.Context, android.util.AttributeSet, int, boolean);
method public void addJavascriptInterface(java.lang.Object, java.lang.String);
method public boolean canGoBack();
method public boolean canGoBackOrForward(int);
method public boolean canGoForward();
- method public boolean canZoomIn();
- method public boolean canZoomOut();
+ method public deprecated boolean canZoomIn();
+ method public deprecated boolean canZoomOut();
method public android.graphics.Picture capturePicture();
method public void clearCache(boolean);
method public void clearFormData();
@@ -27292,7 +27292,7 @@
method public java.lang.String[] getHttpAuthUsernamePassword(java.lang.String, java.lang.String);
method public java.lang.String getOriginalUrl();
method public int getProgress();
- method public float getScale();
+ method public deprecated float getScale();
method public android.webkit.WebSettings getSettings();
method public java.lang.String getTitle();
method public java.lang.String getUrl();
@@ -27329,13 +27329,13 @@
method public android.webkit.WebBackForwardList saveState(android.os.Bundle);
method public void saveWebArchive(java.lang.String);
method public void saveWebArchive(java.lang.String, boolean, android.webkit.ValueCallback<java.lang.String>);
- method public void setCertificate(android.net.http.SslCertificate);
+ method public deprecated void setCertificate(android.net.http.SslCertificate);
method public void setDownloadListener(android.webkit.DownloadListener);
method public void setFindListener(android.webkit.WebView.FindListener);
method public void setHorizontalScrollbarOverlay(boolean);
method public void setHttpAuthUsernamePassword(java.lang.String, java.lang.String, java.lang.String, java.lang.String);
method public void setInitialScale(int);
- method public void setMapTrackballToArrowKeys(boolean);
+ method public deprecated void setMapTrackballToArrowKeys(boolean);
method public void setNetworkAvailable(boolean);
method public deprecated void setPictureListener(android.webkit.WebView.PictureListener);
method public void setVerticalScrollbarOverlay(boolean);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 09fa99c..812ac9e 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -52,6 +52,7 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.Debug;
+import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -4877,6 +4878,8 @@
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
+ Environment.initForCurrentUser();
+
Security.addProvider(new AndroidKeyStoreProvider());
Process.setArgV0("<pre-initialized>");
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 060a235..6bc9a1f 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -18,7 +18,7 @@
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.Parcelable.Creator;
+import android.os.UserHandle;
/**
* Per-user information.
@@ -92,6 +92,10 @@
serialNumber = orig.serialNumber;
}
+ public UserHandle getUserHandle() {
+ return new UserHandle(id);
+ }
+
@Override
public String toString() {
return "UserInfo{" + id + ":" + name + ":" + Integer.toHexString(flags) + "}";
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 591cd0e..c08bfeb 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -95,7 +95,7 @@
* Default trace file path and file
*/
private static final String DEFAULT_TRACE_PATH_PREFIX =
- Environment.getExternalStorageDirectory().getPath() + "/";
+ Environment.getLegacyExternalStorageDirectory().getPath() + "/";
private static final String DEFAULT_TRACE_BODY = "dmtrace";
private static final String DEFAULT_TRACE_EXTENSION = ".trace";
private static final String DEFAULT_TRACE_FILE_PATH =
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 2fbcf3f..6667a41f 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -16,9 +16,10 @@
package android.os;
-import android.content.res.Resources;
import android.os.storage.IMountService;
+import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
+import android.text.TextUtils;
import android.util.Log;
import java.io.File;
@@ -29,31 +30,125 @@
public class Environment {
private static final String TAG = "Environment";
+ private static final String ENV_EXTERNAL_STORAGE = "EXTERNAL_STORAGE";
+ private static final String ENV_EMULATED_STORAGE_TARGET = "EMULATED_STORAGE_TARGET";
+
private static final File ROOT_DIRECTORY
= getDirectory("ANDROID_ROOT", "/system");
private static final String SYSTEM_PROPERTY_EFS_ENABLED = "persist.security.efs.enabled";
- private static final Object mLock = new Object();
+ private static UserEnvironment sCurrentUser;
- private volatile static StorageVolume mPrimaryVolume = null;
+ private static final Object sLock = new Object();
+
+ // @GuardedBy("sLock")
+ private static volatile StorageVolume sPrimaryVolume;
private static StorageVolume getPrimaryVolume() {
- if (mPrimaryVolume == null) {
- synchronized (mLock) {
- if (mPrimaryVolume == null) {
+ if (sPrimaryVolume == null) {
+ synchronized (sLock) {
+ if (sPrimaryVolume == null) {
try {
IMountService mountService = IMountService.Stub.asInterface(ServiceManager
.getService("mount"));
- Parcelable[] volumes = mountService.getVolumeList();
- mPrimaryVolume = (StorageVolume)volumes[0];
+ final StorageVolume[] volumes = mountService.getVolumeList();
+ sPrimaryVolume = StorageManager.getPrimaryVolume(volumes);
} catch (Exception e) {
Log.e(TAG, "couldn't talk to MountService", e);
}
}
}
}
- return mPrimaryVolume;
+ return sPrimaryVolume;
+ }
+
+ static {
+ initForCurrentUser();
+ }
+
+ /** {@hide} */
+ public static void initForCurrentUser() {
+ final int userId = UserHandle.myUserId();
+ sCurrentUser = new UserEnvironment(userId);
+
+ synchronized (sLock) {
+ sPrimaryVolume = null;
+ }
+ }
+
+ /** {@hide} */
+ public static class UserEnvironment {
+ // TODO: generalize further to create package-specific environment
+
+ private final File mExternalStorage;
+ private final File mExternalStorageAndroidData;
+ private final File mExternalStorageAndroidMedia;
+ private final File mExternalStorageAndroidObb;
+
+ public UserEnvironment(int userId) {
+ // See storage config details at http://source.android.com/tech/storage/
+ String rawExternalStorage = System.getenv(ENV_EXTERNAL_STORAGE);
+ String rawEmulatedStorageTarget = System.getenv(ENV_EMULATED_STORAGE_TARGET);
+
+ if (!TextUtils.isEmpty(rawEmulatedStorageTarget)) {
+ // Device has emulated storage; external storage paths should have
+ // userId burned into them.
+ final File emulatedBase = new File(rawEmulatedStorageTarget);
+
+ // /storage/emulated/0
+ mExternalStorage = buildPath(emulatedBase, Integer.toString(userId));
+ // /storage/emulated/obb
+ mExternalStorageAndroidObb = buildPath(emulatedBase, "obb");
+
+ } 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
+ mExternalStorage = new File(rawExternalStorage);
+ // /storage/sdcard0/Android/obb
+ mExternalStorageAndroidObb = buildPath(mExternalStorage, "Android", "obb");
+ }
+
+ mExternalStorageAndroidData = buildPath(mExternalStorage, "Android", "data");
+ mExternalStorageAndroidMedia = buildPath(mExternalStorage, "Android", "media");
+ }
+
+ public File getExternalStorageDirectory() {
+ return mExternalStorage;
+ }
+
+ public File getExternalStoragePublicDirectory(String type) {
+ return new File(mExternalStorage, type);
+ }
+
+ public File getExternalStorageAndroidDataDir() {
+ return mExternalStorageAndroidData;
+ }
+
+ public File getExternalStorageAppDataDirectory(String packageName) {
+ return new File(mExternalStorageAndroidData, packageName);
+ }
+
+ public File getExternalStorageAppMediaDirectory(String packageName) {
+ return new File(mExternalStorageAndroidMedia, packageName);
+ }
+
+ public File getExternalStorageAppObbDirectory(String packageName) {
+ return new File(mExternalStorageAndroidObb, packageName);
+ }
+
+ public File getExternalStorageAppFilesDirectory(String packageName) {
+ return new File(new File(mExternalStorageAndroidData, packageName), "files");
+ }
+
+ public File getExternalStorageAppCacheDirectory(String packageName) {
+ return new File(new File(mExternalStorageAndroidData, packageName), "cache");
+ }
}
/**
@@ -137,20 +232,7 @@
private static final File MEDIA_STORAGE_DIRECTORY
= getDirectory("MEDIA_STORAGE", "/data/media");
- private static final File EXTERNAL_STORAGE_DIRECTORY
- = getDirectory("EXTERNAL_STORAGE", "/storage/sdcard0");
-
- private static final File EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY = new File(new File(
- getDirectory("EXTERNAL_STORAGE", "/storage/sdcard0"), "Android"), "data");
-
- private static final File EXTERNAL_STORAGE_ANDROID_MEDIA_DIRECTORY = new File(new File(
- getDirectory("EXTERNAL_STORAGE", "/storage/sdcard0"), "Android"), "media");
-
- private static final File EXTERNAL_STORAGE_ANDROID_OBB_DIRECTORY = new File(new File(
- getDirectory("EXTERNAL_STORAGE", "/storage/sdcard0"), "Android"), "obb");
-
- private static final File DOWNLOAD_CACHE_DIRECTORY
- = getDirectory("DOWNLOAD_CACHE", "/cache");
+ private static final File DOWNLOAD_CACHE_DIRECTORY = getDirectory("DOWNLOAD_CACHE", "/cache");
/**
* Gets the Android data directory.
@@ -198,7 +280,13 @@
* @see #isExternalStorageRemovable()
*/
public static File getExternalStorageDirectory() {
- return EXTERNAL_STORAGE_DIRECTORY;
+ throwIfSystem();
+ return sCurrentUser.getExternalStorageDirectory();
+ }
+
+ /** {@hide} */
+ public static File getLegacyExternalStorageDirectory() {
+ return new File(System.getenv(ENV_EXTERNAL_STORAGE));
}
/**
@@ -318,7 +406,8 @@
* using it such as with {@link File#mkdirs File.mkdirs()}.
*/
public static File getExternalStoragePublicDirectory(String type) {
- return new File(getExternalStorageDirectory(), type);
+ throwIfSystem();
+ return sCurrentUser.getExternalStoragePublicDirectory(type);
}
/**
@@ -326,7 +415,8 @@
* @hide
*/
public static File getExternalStorageAndroidDataDir() {
- return EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY;
+ throwIfSystem();
+ return sCurrentUser.getExternalStorageAndroidDataDir();
}
/**
@@ -334,7 +424,8 @@
* @hide
*/
public static File getExternalStorageAppDataDirectory(String packageName) {
- return new File(EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY, packageName);
+ throwIfSystem();
+ return sCurrentUser.getExternalStorageAppDataDirectory(packageName);
}
/**
@@ -342,7 +433,8 @@
* @hide
*/
public static File getExternalStorageAppMediaDirectory(String packageName) {
- return new File(EXTERNAL_STORAGE_ANDROID_MEDIA_DIRECTORY, packageName);
+ throwIfSystem();
+ return sCurrentUser.getExternalStorageAppMediaDirectory(packageName);
}
/**
@@ -350,7 +442,8 @@
* @hide
*/
public static File getExternalStorageAppObbDirectory(String packageName) {
- return new File(EXTERNAL_STORAGE_ANDROID_OBB_DIRECTORY, packageName);
+ throwIfSystem();
+ return sCurrentUser.getExternalStorageAppObbDirectory(packageName);
}
/**
@@ -358,17 +451,17 @@
* @hide
*/
public static File getExternalStorageAppFilesDirectory(String packageName) {
- return new File(new File(EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY,
- packageName), "files");
+ throwIfSystem();
+ return sCurrentUser.getExternalStorageAppFilesDirectory(packageName);
}
-
+
/**
* Generates the path to an application's cache.
* @hide
*/
public static File getExternalStorageAppCacheDirectory(String packageName) {
- return new File(new File(EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY,
- packageName), "cache");
+ throwIfSystem();
+ return sCurrentUser.getExternalStorageAppCacheDirectory(packageName);
}
/**
@@ -441,9 +534,10 @@
try {
IMountService mountService = IMountService.Stub.asInterface(ServiceManager
.getService("mount"));
- return mountService.getVolumeState(getExternalStorageDirectory()
- .toString());
- } catch (Exception rex) {
+ final StorageVolume primary = getPrimaryVolume();
+ return mountService.getVolumeState(primary.getPath());
+ } catch (RemoteException rex) {
+ Log.w(TAG, "Failed to read external storage state; assuming REMOVED: " + rex);
return Environment.MEDIA_REMOVED;
}
}
@@ -457,8 +551,8 @@
* <p>See {@link #getExternalStorageDirectory()} for more information.
*/
public static boolean isExternalStorageRemovable() {
- StorageVolume volume = getPrimaryVolume();
- return (volume != null && volume.isRemovable());
+ final StorageVolume primary = getPrimaryVolume();
+ return (primary != null && primary.isRemovable());
}
/**
@@ -475,12 +569,30 @@
* android.content.ComponentName, boolean)} for additional details.
*/
public static boolean isExternalStorageEmulated() {
- StorageVolume volume = getPrimaryVolume();
- return (volume != null && volume.isEmulated());
+ final StorageVolume primary = getPrimaryVolume();
+ return (primary != null && primary.isEmulated());
}
static File getDirectory(String variableName, String defaultPath) {
String path = System.getenv(variableName);
return path == null ? new File(defaultPath) : new File(path);
}
+
+ private static void throwIfSystem() {
+ if (Process.myUid() == Process.SYSTEM_UID) {
+ Log.wtf(TAG, "Static storage paths aren't available from AID_SYSTEM", new Throwable());
+ }
+ }
+
+ private static File buildPath(File base, String... segments) {
+ File cur = base;
+ for (String segment : segments) {
+ if (cur == null) {
+ cur = new File(segment);
+ } else {
+ cur = new File(cur, segment);
+ }
+ }
+ return cur;
+ }
}
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index ab64866..0b16316 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -677,15 +677,15 @@
return _result;
}
- public Parcelable[] getVolumeList() throws RemoteException {
+ public StorageVolume[] getVolumeList() throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
- Parcelable[] _result;
+ StorageVolume[] _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getVolumeList, _data, _reply, 0);
_reply.readException();
- _result = _reply.readParcelableArray(StorageVolume.class.getClassLoader());
+ _result = _reply.createTypedArray(StorageVolume.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
@@ -1119,9 +1119,9 @@
}
case TRANSACTION_getVolumeList: {
data.enforceInterface(DESCRIPTOR);
- Parcelable[] result = getVolumeList();
+ StorageVolume[] result = getVolumeList();
reply.writeNoException();
- reply.writeParcelableArray(result, 0);
+ reply.writeTypedArray(result, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
return true;
}
case TRANSACTION_getSecureContainerFilesystemPath: {
@@ -1358,7 +1358,7 @@
/**
* Returns list of all mountable volumes.
*/
- public Parcelable[] getVolumeList() throws RemoteException;
+ public StorageVolume[] getVolumeList() 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 8a20a6e..54c8709d 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -16,6 +16,8 @@
package android.os.storage;
+import android.app.NotificationManager;
+import android.content.Context;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
@@ -285,6 +287,11 @@
}
}
+ /** {@hide} */
+ public static StorageManager from(Context context) {
+ return (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
+ }
+
/**
* Constructs a StorageManager object through which an application can
* can communicate with the systems mount service.
@@ -594,4 +601,20 @@
}
return paths;
}
+
+ /** {@hide} */
+ public StorageVolume getPrimaryVolume() {
+ return getPrimaryVolume(getVolumeList());
+ }
+
+ /** {@hide} */
+ public static StorageVolume getPrimaryVolume(StorageVolume[] volumes) {
+ for (StorageVolume volume : volumes) {
+ if (volume.isPrimary()) {
+ return volume;
+ }
+ }
+ Log.w(TAG, "No primary storage defined");
+ return null;
+ }
}
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index b5983d1..177a955 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -19,16 +19,22 @@
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
+
+import java.io.File;
/**
- * A class representing a storage volume
+ * Description of a storage volume and its capabilities, including the
+ * filesystem path where it may be mounted.
+ *
* @hide
*/
public class StorageVolume implements Parcelable {
+ // TODO: switch to more durable token
private int mStorageId;
- private final String mPath;
+ private final File mPath;
private final int mDescriptionId;
private final boolean mPrimary;
private final boolean mRemovable;
@@ -37,14 +43,17 @@
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;
// 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(String path, int descriptionId, boolean primary, boolean removable,
- boolean emulated, int mtpReserveSpace, boolean allowMassStorage, long maxFileSize) {
+ public StorageVolume(File path, int descriptionId, boolean primary, boolean removable,
+ boolean emulated, int mtpReserveSpace, boolean allowMassStorage, long maxFileSize,
+ UserHandle owner) {
mPath = path;
mDescriptionId = descriptionId;
mPrimary = primary;
@@ -53,18 +62,26 @@
mMtpReserveSpace = mtpReserveSpace;
mAllowMassStorage = allowMassStorage;
mMaxFileSize = maxFileSize;
+ mOwner = owner;
}
private StorageVolume(Parcel in) {
mStorageId = in.readInt();
- mPath = in.readString();
+ mPath = new File(in.readString());
mDescriptionId = in.readInt();
- mPrimary = in.readByte() != 0;
- mRemovable = in.readByte() != 0;
- mEmulated = in.readByte() != 0;
+ mPrimary = in.readInt() != 0;
+ mRemovable = in.readInt() != 0;
+ mEmulated = in.readInt() != 0;
mMtpReserveSpace = in.readInt();
- mAllowMassStorage = in.readByte() != 0;
+ mAllowMassStorage = in.readInt() != 0;
mMaxFileSize = in.readLong();
+ mOwner = in.readParcelable(null);
+ }
+
+ 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);
}
/**
@@ -73,6 +90,10 @@
* @return the mount path
*/
public String getPath() {
+ return mPath.toString();
+ }
+
+ public File getPathFile() {
return mPath;
}
@@ -164,6 +185,10 @@
return mMaxFileSize;
}
+ public UserHandle getOwner() {
+ return mOwner;
+ }
+
@Override
public boolean equals(Object obj) {
if (obj instanceof StorageVolume && mPath != null) {
@@ -180,10 +205,19 @@
@Override
public String toString() {
- return "StorageVolume [mAllowMassStorage=" + mAllowMassStorage + ", mDescriptionId="
- + mDescriptionId + ", mEmulated=" + mEmulated + ", mMaxFileSize=" + mMaxFileSize
- + ", mMtpReserveSpace=" + mMtpReserveSpace + ", mPath=" + mPath + ", mRemovable="
- + mRemovable + ", mStorageId=" + mStorageId + "]";
+ final StringBuilder builder = new StringBuilder("StorageVolume [");
+ builder.append("mStorageId=").append(mStorageId);
+ builder.append(" mPath=").append(mPath);
+ builder.append(" mDescriptionId=").append(mDescriptionId);
+ builder.append(" mPrimary=").append(mPrimary);
+ builder.append(" mRemovable=").append(mRemovable);
+ builder.append(" mEmulated=").append(mEmulated);
+ builder.append(" mMtpReserveSpace=").append(mMtpReserveSpace);
+ builder.append(" mAllowMassStorage=").append(mAllowMassStorage);
+ builder.append(" mMaxFileSize=").append(mMaxFileSize);
+ builder.append(" mOwner=").append(mOwner);
+ builder.append("]");
+ return builder.toString();
}
public static final Creator<StorageVolume> CREATOR = new Creator<StorageVolume>() {
@@ -206,7 +240,7 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(mStorageId);
- parcel.writeString(mPath);
+ parcel.writeString(mPath.toString());
parcel.writeInt(mDescriptionId);
parcel.writeInt(mPrimary ? 1 : 0);
parcel.writeInt(mRemovable ? 1 : 0);
@@ -214,5 +248,6 @@
parcel.writeInt(mMtpReserveSpace);
parcel.writeInt(mAllowMassStorage ? 1 : 0);
parcel.writeLong(mMaxFileSize);
+ parcel.writeParcelable(mOwner, flags);
}
}
diff --git a/core/java/android/webkit/CertTool.java b/core/java/android/webkit/CertTool.java
index a2325c3..e4d09a9 100644
--- a/core/java/android/webkit/CertTool.java
+++ b/core/java/android/webkit/CertTool.java
@@ -16,6 +16,7 @@
package android.webkit;
+import com.android.org.bouncycastle.asn1.ASN1Encoding;
import com.android.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import com.android.org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import com.android.org.bouncycastle.jce.netscape.NetscapeCertRequest;
@@ -57,7 +58,7 @@
NetscapeCertRequest request = new NetscapeCertRequest(challenge,
MD5_WITH_RSA, pair.getPublic());
request.sign(pair.getPrivate());
- byte[] signed = request.toASN1Object().getDEREncoded();
+ byte[] signed = request.toASN1Primitive().getEncoded(ASN1Encoding.DER);
Credentials.getInstance().install(context, pair);
return new String(Base64.encode(signed));
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index e8ff01f..a29ff37 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -102,7 +102,12 @@
/**
* Normal cache usage mode. Use with {@link #setCacheMode}.
+ *
+ * @deprecated This value is obsolete, as from API level
+ * {@link android.os.Build.VERSION_CODES#HONEYCOMB} and onwards it has the
+ * same effect as {@link #LOAD_DEFAULT}.
*/
+ @Deprecated
public static final int LOAD_NORMAL = 0;
/**
@@ -333,7 +338,10 @@
* If it is true, WebView will choose a solution to maximize the performance.
* e.g. the WebView's content may not be updated during the transition.
* If it is false, WebView will keep its fidelity. The default value is false.
+ *
+ * @deprecated This method is now obsolete, and will become a no-op in future.
*/
+ @Deprecated
public void setEnableSmoothTransition(boolean enable) {
throw new MustOverrideException();
}
@@ -343,7 +351,10 @@
* zooming.
*
* @see #setEnableSmoothTransition
+ *
+ * @deprecated This method is now obsolete, and will become a no-op in future.
*/
+ @Deprecated
public boolean enableSmoothTransition() {
throw new MustOverrideException();
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 72afcc8..05b86a3 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -469,7 +469,13 @@
* @param defStyle the default style resource ID
* @param privateBrowsing whether this WebView will be initialized in
* private mode
+ *
+ * @deprecated Private browsing is no longer supported directly via
+ * WebView and will be removed in a future release. Prefer using
+ * {@link WebSettings}, {@link WebViewDatabase}, {@link CookieManager}
+ * and {@link WebStorage} for fine-grained control of privacy data.
*/
+ @Deprecated
public WebView(Context context, AttributeSet attrs, int defStyle,
boolean privateBrowsing) {
this(context, attrs, defStyle, null, privateBrowsing);
@@ -568,7 +574,11 @@
/**
* Sets the SSL certificate for the main top-level page.
+ *
+ * @deprecated Calling this function has no useful effect, and will be
+ * ignored in future releases.
*/
+ @Deprecated
public void setCertificate(SslCertificate certificate) {
checkThread();
mProvider.setCertificate(certificate);
@@ -1017,7 +1027,12 @@
* Gets the current scale of this WebView.
*
* @return the current scale
+ *
+ * @deprecated This method is prone to inaccuracy due to race conditions
+ * between the web rendering and UI threads; prefer
+ * {@link WebViewClient#onScaleChanged}.
*/
+ @Deprecated
@ViewDebug.ExportedProperty(category = "webview")
public float getScale() {
checkThread();
@@ -1607,6 +1622,10 @@
public void onGlobalFocusChanged(View oldFocus, View newFocus) {
}
+ /**
+ * @deprecated Only the default case, true, will be supported in a future version.
+ */
+ @Deprecated
public void setMapTrackballToArrowKeys(boolean setMap) {
checkThread();
mProvider.setMapTrackballToArrowKeys(setMap);
@@ -1640,7 +1659,12 @@
* Gets whether this WebView can be zoomed in.
*
* @return true if this WebView can be zoomed in
+ *
+ * @deprecated This method is prone to inaccuracy due to race conditions
+ * between the web rendering and UI threads; prefer
+ * {@link WebViewClient#onScaleChanged}.
*/
+ @Deprecated
public boolean canZoomIn() {
checkThread();
return mProvider.canZoomIn();
@@ -1650,7 +1674,12 @@
* Gets whether this WebView can be zoomed out.
*
* @return true if this WebView can be zoomed out
+ *
+ * @deprecated This method is prone to inaccuracy due to race conditions
+ * between the web rendering and UI threads; prefer
+ * {@link WebViewClient#onScaleChanged}.
*/
+ @Deprecated
public boolean canZoomOut() {
checkThread();
return mProvider.canZoomOut();
diff --git a/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java b/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java
index 3905c88..fb7f215 100644
--- a/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java
+++ b/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java
@@ -122,7 +122,7 @@
public void onCancel(DialogInterface dialog) {
IMountService mountService = getMountService();
String extStoragePath = mStorageVolume == null ?
- Environment.getExternalStorageDirectory().toString() :
+ Environment.getLegacyExternalStorageDirectory().toString() :
mStorageVolume.getPath();
try {
mountService.mountVolume(extStoragePath);
@@ -149,7 +149,7 @@
updateProgressDialog(R.string.progress_unmounting);
IMountService mountService = getMountService();
final String extStoragePath = mStorageVolume == null ?
- Environment.getExternalStorageDirectory().toString() :
+ Environment.getLegacyExternalStorageDirectory().toString() :
mStorageVolume.getPath();
try {
// Remove encryption mapping if this is an unmount for a factory reset.
@@ -163,7 +163,7 @@
updateProgressDialog(R.string.progress_erasing);
final IMountService mountService = getMountService();
final String extStoragePath = mStorageVolume == null ?
- Environment.getExternalStorageDirectory().toString() :
+ Environment.getLegacyExternalStorageDirectory().toString() :
mStorageVolume.getPath();
if (mountService != null) {
new Thread() {
diff --git a/docs/html/guide/topics/ui/themes.jd b/docs/html/guide/topics/ui/themes.jd
index d787492..bc1c4f0 100644
--- a/docs/html/guide/topics/ui/themes.jd
+++ b/docs/html/guide/topics/ui/themes.jd
@@ -413,8 +413,8 @@
themes will give you a better understanding of what style properties each one provides.
For a better reference to the Android styles and themes, see the following source code:</p>
<ul>
- <li><a href="http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob;f=core/res/res/values/styles.xml;h=d7b654e49809cb97a35682754b1394af5c8bc88b;hb=HEAD">Android Styles (styles.xml)</a></li>
- <li><a href="http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob;f=core/res/res/values/themes.xml;h=6b3d7407d1c895a3c297e60d5beac98e2d34c271;hb=HEAD">Android Themes (themes.xml)</a></li>
+ <li><a href="https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/res/res/values/styles.xml">Android Styles (styles.xml)</a></li>
+ <li><a href="https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/res/res/values/themes.xml">Android Themes (themes.xml)</a></li>
</ul>
<p>These files will help you learn through example. For instance, in the Android themes source code,
@@ -422,9 +422,8 @@
you'll see all of the properties that are used to style dialogs that are used by the Android
framework.</p>
-<p>For more information about the syntax used to create styles in XML, see
-<a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">Available Resource Types:
-Style and Themes</a>.</p>
+<p>For more information about the syntax for styles and themes in XML, see the
+<a href="{@docRoot}guide/topics/resources/style-resource.html">Style Resource</a> document.</p>
<p>For a reference of available style attributes that you can use to define a style or theme
(e.g., "windowBackground" or "textAppearance"), see {@link android.R.attr} or the respective
diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java
index b233ff6..d8109ce 100644
--- a/keystore/java/android/security/Credentials.java
+++ b/keystore/java/android/security/Credentials.java
@@ -20,8 +20,9 @@
import android.content.Context;
import android.content.Intent;
import android.util.Log;
-import com.android.org.bouncycastle.openssl.PEMReader;
-import com.android.org.bouncycastle.openssl.PEMWriter;
+import com.android.org.bouncycastle.util.io.pem.PemObject;
+import com.android.org.bouncycastle.util.io.pem.PemReader;
+import com.android.org.bouncycastle.util.io.pem.PemWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -32,6 +33,10 @@
import java.io.Writer;
import java.nio.charset.Charsets;
import java.security.KeyPair;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
@@ -108,34 +113,41 @@
public static final String EXTRA_CA_CERTIFICATES_DATA = "ca_certificates_data";
/**
- * Convert objects to a PEM format, which is used for
- * CA_CERTIFICATE, USER_CERTIFICATE, and USER_PRIVATE_KEY
- * entries.
+ * Convert objects to a PEM format which is used for
+ * CA_CERTIFICATE and USER_CERTIFICATE entries.
*/
- public static byte[] convertToPem(Object... objects) throws IOException {
+ public static byte[] convertToPem(Certificate... objects)
+ throws IOException, CertificateEncodingException {
ByteArrayOutputStream bao = new ByteArrayOutputStream();
Writer writer = new OutputStreamWriter(bao, Charsets.US_ASCII);
- PEMWriter pw = new PEMWriter(writer);
- for (Object o : objects) {
- pw.writeObject(o);
+ PemWriter pw = new PemWriter(writer);
+ for (Certificate o : objects) {
+ pw.writeObject(new PemObject("CERTIFICATE", o.getEncoded()));
}
pw.close();
return bao.toByteArray();
}
/**
* Convert objects from PEM format, which is used for
- * CA_CERTIFICATE, USER_CERTIFICATE, and USER_PRIVATE_KEY
- * entries.
+ * CA_CERTIFICATE and USER_CERTIFICATE entries.
*/
- public static List<Object> convertFromPem(byte[] bytes) throws IOException {
+ public static List<X509Certificate> convertFromPem(byte[] bytes)
+ throws IOException, CertificateException {
ByteArrayInputStream bai = new ByteArrayInputStream(bytes);
Reader reader = new InputStreamReader(bai, Charsets.US_ASCII);
- PEMReader pr = new PEMReader(reader);
+ PemReader pr = new PemReader(reader);
- List<Object> result = new ArrayList<Object>();
- Object o;
- while ((o = pr.readObject()) != null) {
- result.add(o);
+ CertificateFactory cf = CertificateFactory.getInstance("X509");
+
+ List<X509Certificate> result = new ArrayList<X509Certificate>();
+ PemObject o;
+ while ((o = pr.readPemObject()) != null) {
+ if (o.getType().equals("CERTIFICATE")) {
+ Certificate c = cf.generateCertificate(new ByteArrayInputStream(o.getContent()));
+ result.add((X509Certificate) c);
+ } else {
+ throw new IllegalArgumentException("Unknown type " + o.getType());
+ }
}
pr.close();
return result;
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index 755170f..c3444e6 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -1412,8 +1412,12 @@
mSnapshot = new Snapshot(mFirstSnapshot,
SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
mSaveCount = 1;
+
mSnapshot->setClip(0.0f, 0.0f, mWidth, mHeight);
+ mDirtyClip = opaque;
+
mRestoreSaveCount = -1;
+
return DrawGlInfo::kStatusDone; // No invalidate needed at record-time
}
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index 5a8f2b7..02af5e2 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -332,10 +332,10 @@
if (Caches::getInstance().extensions.hasDiscardFramebuffer()) {
GLuint previousFbo;
glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &previousFbo);
-
- GLenum attachments = GL_COLOR_ATTACHMENT0;
if (fbo != previousFbo) glBindFramebuffer(GL_FRAMEBUFFER, fbo);
- glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, &attachments);
+
+ const GLenum attachments[] = { GL_COLOR_ATTACHMENT0 };
+ glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, attachments);
if (fbo != previousFbo) glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
}
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index f4c2675..02448e8 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -173,6 +173,15 @@
mSnapshot->setClip(left, top, right, bottom);
mDirtyClip = opaque;
+ // If we know that we are going to redraw the entire framebuffer,
+ // perform a discard to let the driver know we don't need to preserve
+ // the back buffer for this frame.
+ if (mCaches.extensions.hasDiscardFramebuffer() &&
+ left <= 0.0f && top <= 0.0f && right >= mWidth && bottom >= mHeight) {
+ const GLenum attachments[] = { getTargetFbo() == 0 ? GL_COLOR_EXT : GL_COLOR_ATTACHMENT0 };
+ glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, attachments);
+ }
+
syncState();
if (!opaque) {
diff --git a/media/tests/EffectsTest/res/layout/bassboosttest.xml b/media/tests/EffectsTest/res/layout/bassboosttest.xml
index 0888e98..ac912c8 100755
--- a/media/tests/EffectsTest/res/layout/bassboosttest.xml
+++ b/media/tests/EffectsTest/res/layout/bassboosttest.xml
@@ -105,6 +105,8 @@
style="@android:style/TextAppearance.Medium" />
<EditText android:id="@+id/sessionEdit"
+ android:singleLine="true"
+ android:numeric="integer"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0.5"
diff --git a/media/tests/EffectsTest/res/layout/equalizertest.xml b/media/tests/EffectsTest/res/layout/equalizertest.xml
index 2223c48..5ef035d 100755
--- a/media/tests/EffectsTest/res/layout/equalizertest.xml
+++ b/media/tests/EffectsTest/res/layout/equalizertest.xml
@@ -105,6 +105,8 @@
style="@android:style/TextAppearance.Medium" />
<EditText android:id="@+id/sessionEdit"
+ android:singleLine="true"
+ android:numeric="integer"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0.5"
diff --git a/media/tests/EffectsTest/res/layout/presetreverbtest.xml b/media/tests/EffectsTest/res/layout/presetreverbtest.xml
index b648899..cd7fbd3a 100755
--- a/media/tests/EffectsTest/res/layout/presetreverbtest.xml
+++ b/media/tests/EffectsTest/res/layout/presetreverbtest.xml
@@ -105,6 +105,8 @@
style="@android:style/TextAppearance.Medium" />
<EditText android:id="@+id/sessionEdit"
+ android:singleLine="true"
+ android:numeric="integer"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0.5"
diff --git a/media/tests/EffectsTest/res/layout/virtualizertest.xml b/media/tests/EffectsTest/res/layout/virtualizertest.xml
index c9203de..1fafeab 100755
--- a/media/tests/EffectsTest/res/layout/virtualizertest.xml
+++ b/media/tests/EffectsTest/res/layout/virtualizertest.xml
@@ -105,6 +105,8 @@
style="@android:style/TextAppearance.Medium" />
<EditText android:id="@+id/sessionEdit"
+ android:singleLine="true"
+ android:numeric="integer"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0.5"
diff --git a/media/tests/EffectsTest/res/layout/visualizertest.xml b/media/tests/EffectsTest/res/layout/visualizertest.xml
index 8611e8c..50ac7bb 100755
--- a/media/tests/EffectsTest/res/layout/visualizertest.xml
+++ b/media/tests/EffectsTest/res/layout/visualizertest.xml
@@ -105,6 +105,8 @@
style="@android:style/TextAppearance.Medium" />
<EditText android:id="@+id/sessionEdit"
+ android:singleLine="true"
+ android:numeric="integer"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0.5"
diff --git a/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java b/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java
index 6612766..7020246 100755
--- a/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java
+++ b/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java
@@ -32,6 +32,7 @@
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.media.audiofx.AudioEffect;
+
import java.util.UUID;
public class EffectsTest extends Activity {
@@ -154,13 +155,35 @@
this.setOrientation(VERTICAL);
}
+ public String effectUuidToString(UUID effectType) {
+ if (effectType.equals(AudioEffect.EFFECT_TYPE_VIRTUALIZER)) {
+ return "Virtualizer";
+ } else if (effectType.equals(AudioEffect.EFFECT_TYPE_ENV_REVERB)){
+ return "Reverb";
+ } else if (effectType.equals(AudioEffect.EFFECT_TYPE_PRESET_REVERB)){
+ return "Preset Reverb";
+ } else if (effectType.equals(AudioEffect.EFFECT_TYPE_EQUALIZER)){
+ return "Equalizer";
+ } else if (effectType.equals(AudioEffect.EFFECT_TYPE_BASS_BOOST)){
+ return "Bass Boost";
+ } else if (effectType.equals(AudioEffect.EFFECT_TYPE_AGC)){
+ return "Automatic Gain Control";
+ } else if (effectType.equals(AudioEffect.EFFECT_TYPE_AEC)){
+ return "Acoustic Echo Canceler";
+ } else if (effectType.equals(AudioEffect.EFFECT_TYPE_NS)){
+ return "Noise Suppressor";
+ }
+
+ return effectType.toString();
+ }
+
public void set(int position) {
TextView tv = new TextView(mContext);
tv.setText("Effect "+ position);
addView(tv, new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
tv = new TextView(mContext);
- tv.setText(" type: "+ mDescriptors[position].type.toString());
+ tv.setText(" type: "+ effectUuidToString(mDescriptors[position].type));
addView(tv, new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
tv = new TextView(mContext);
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8fb703f..6aed1aa 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -155,13 +155,13 @@
<string name="always_use_accessory">Use by default for this USB accessory</string>
<!-- Title of confirmation dialog for USB debugging -->
- <string name="usb_debugging_title">Allow USB Debugging?</string>
+ <string name="usb_debugging_title">Allow USB debugging?</string>
<!-- Message of confirmation dialog for USB debugging -->
- <string name="usb_debugging_message">Allow USB Debugging from this computer?\nYour RSA key fingerprint is\n<xliff:g id="fingerprint">%1$s</xliff:g></string>
+ <string name="usb_debugging_message">The computer\'s RSA key fingerprint is:\n<xliff:g id="fingerprint">%1$s</xliff:g></string>
<!-- Option to always allow USB debugging from the attached computer -->
- <string name="usb_debugging_always">Always allow this computer</string>
+ <string name="usb_debugging_always">Always allow from this computer</string>
<!-- Checkbox label for application compatibility mode ON (zooming app to look like it's running
on a phone). [CHAR LIMIT=25] -->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
index 7056e03..1c3cb2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
@@ -167,10 +167,9 @@
startSettingsActivity(intent);
}
private void startSettingsActivity(Intent intent) {
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
mBar.collapseAllPanels(true);
mContext.startActivity(intent);
-
}
private void addUserTiles(ViewGroup parent, LayoutInflater inflater) {
@@ -310,23 +309,25 @@
parent.addView(airplaneTile);
// Bluetooth
- QuickSettingsTileView bluetoothTile = (QuickSettingsTileView)
- inflater.inflate(R.layout.quick_settings_tile, parent, false);
- bluetoothTile.setContent(R.layout.quick_settings_tile_bluetooth, inflater);
- bluetoothTile.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- startSettingsActivity(android.provider.Settings.ACTION_BLUETOOTH_SETTINGS);
- }
- });
- mModel.addBluetoothTile(bluetoothTile, new QuickSettingsModel.RefreshCallback() {
- @Override
- public void refreshView(QuickSettingsTileView view, State state) {
- TextView tv = (TextView) view.findViewById(R.id.bluetooth_textview);
- tv.setCompoundDrawablesRelativeWithIntrinsicBounds(0, state.iconId, 0, 0);
- }
- });
- parent.addView(bluetoothTile);
+ if (mModel.deviceSupportsBluetooth()) {
+ QuickSettingsTileView bluetoothTile = (QuickSettingsTileView)
+ inflater.inflate(R.layout.quick_settings_tile, parent, false);
+ bluetoothTile.setContent(R.layout.quick_settings_tile_bluetooth, inflater);
+ bluetoothTile.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ startSettingsActivity(android.provider.Settings.ACTION_BLUETOOTH_SETTINGS);
+ }
+ });
+ mModel.addBluetoothTile(bluetoothTile, new QuickSettingsModel.RefreshCallback() {
+ @Override
+ public void refreshView(QuickSettingsTileView view, State state) {
+ TextView tv = (TextView) view.findViewById(R.id.bluetooth_textview);
+ tv.setCompoundDrawablesRelativeWithIntrinsicBounds(0, state.iconId, 0, 0);
+ }
+ });
+ parent.addView(bluetoothTile);
+ }
// Brightness
QuickSettingsTileView brightnessTile = (QuickSettingsTileView)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
index 2318921..aa40f3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
@@ -258,6 +258,9 @@
final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
onBluetoothStateChange(adapter.isEnabled());
}
+ boolean deviceSupportsBluetooth() {
+ return (BluetoothAdapter.getDefaultAdapter() != null);
+ }
// BluetoothController callback
@Override
public void onBluetoothStateChange(boolean on) {
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java
index 61003bf..d9088e0 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java
@@ -214,9 +214,9 @@
*/
public synchronized void reset() {
if (DEBUG) Log.d(TAG, "reset()");
- if (mKeyguardView != null) {
- mKeyguardView.reset();
- }
+ // User might have switched, check if we need to go back to keyguard
+ // TODO: It's preferable to stay and show the correct lockscreen or unlock if none
+ maybeCreateKeyguardLocked(shouldEnableScreenRotation());
}
public synchronized void onScreenTurnedOff() {
diff --git a/services/common_time/common_clock_service.h b/services/common_time/common_clock_service.h
index a65e398..bd663f0 100644
--- a/services/common_time/common_clock_service.h
+++ b/services/common_time/common_clock_service.h
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-#include <common_time/ICommonClock.h>
-
#ifndef ANDROID_COMMON_CLOCK_SERVICE_H
#define ANDROID_COMMON_CLOCK_SERVICE_H
+#include <sys/socket.h>
+#include <common_time/ICommonClock.h>
+
namespace android {
class CommonTimeServer;
diff --git a/services/common_time/common_time_config_service.h b/services/common_time/common_time_config_service.h
index 8283c24..89806dd 100644
--- a/services/common_time/common_time_config_service.h
+++ b/services/common_time/common_time_config_service.h
@@ -13,11 +13,12 @@
* limitations under the License.
*/
-#include <common_time/ICommonTimeConfig.h>
-
#ifndef ANDROID_COMMON_TIME_CONFIG_SERVICE_H
#define ANDROID_COMMON_TIME_CONFIG_SERVICE_H
+#include <sys/socket.h>
+#include <common_time/ICommonTimeConfig.h>
+
namespace android {
class String16;
diff --git a/services/common_time/common_time_server.h b/services/common_time/common_time_server.h
index f6a2419..6e18050 100644
--- a/services/common_time/common_time_server.h
+++ b/services/common_time/common_time_server.h
@@ -19,7 +19,7 @@
#include <arpa/inet.h>
#include <stdint.h>
-#include <linux/socket.h>
+#include <sys/socket.h>
#include <common_time/ICommonClock.h>
#include <common_time/local_clock.h>
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 1d40f4f..5e2b425 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -67,6 +67,7 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.WorkSource;
+import android.os.Environment.UserEnvironment;
import android.os.storage.IMountService;
import android.provider.Settings;
import android.util.EventLog;
@@ -2720,9 +2721,13 @@
FullBackup.backupToTar(pkg.packageName, FullBackup.APK_TREE_TOKEN, null,
apkDir, appSourceDir, output);
+ // TODO: migrate this to SharedStorageBackup, since AID_SYSTEM
+ // doesn't have access to external storage.
+
// Save associated .obb content if it exists and we did save the apk
// check for .obb and save those too
- final File obbDir = Environment.getExternalStorageAppObbDirectory(pkg.packageName);
+ final UserEnvironment userEnv = new UserEnvironment(UserHandle.USER_OWNER);
+ final File obbDir = userEnv.getExternalStorageAppObbDirectory(pkg.packageName);
if (obbDir != null) {
if (MORE_DEBUG) Log.i(TAG, "obb dir: " + obbDir.getAbsolutePath());
File[] obbFiles = obbDir.listFiles();
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index f40333d..32ab154 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -16,11 +16,7 @@
package com.android.server;
-import com.android.internal.app.IMediaContainerService;
-import com.android.internal.util.XmlUtils;
-import com.android.server.am.ActivityManagerService;
-import com.android.server.pm.PackageManagerService;
-import com.android.server.NativeDaemonConnector.Command;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import android.Manifest;
import android.content.BroadcastReceiver;
@@ -30,6 +26,7 @@
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
import android.content.res.ObbInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -38,15 +35,14 @@
import android.net.Uri;
import android.os.Binder;
import android.os.Environment;
+import android.os.Environment.UserEnvironment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
-import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.storage.IMountService;
@@ -61,9 +57,18 @@
import android.util.Slog;
import android.util.Xml;
-import org.xmlpull.v1.XmlPullParser;
+import com.android.internal.app.IMediaContainerService;
+import com.android.internal.util.XmlUtils;
+import com.android.server.NativeDaemonConnector.Command;
+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 org.xmlpull.v1.XmlPullParserException;
+import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
@@ -81,7 +86,6 @@
import java.util.Map.Entry;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import java.util.Set;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
@@ -96,9 +100,11 @@
class MountService extends IMountService.Stub
implements INativeDaemonConnectorCallbacks, Watchdog.Monitor {
- private static final boolean LOCAL_LOGD = false;
- private static final boolean DEBUG_UNMOUNT = false;
- private static final boolean DEBUG_EVENTS = false;
+ // TODO: listen for user creation/deletion
+
+ private static final boolean LOCAL_LOGD = true;
+ private static final boolean DEBUG_UNMOUNT = true;
+ private static final boolean DEBUG_EVENTS = true;
private static final boolean DEBUG_OBB = false;
// Disable this since it messes up long-running cryptfs operations.
@@ -166,25 +172,34 @@
public static final int VolumeBadRemoval = 632;
}
- private Context mContext;
- private NativeDaemonConnector mConnector;
- private final ArrayList<StorageVolume> mVolumes = new ArrayList<StorageVolume>();
- private StorageVolume mPrimaryVolume;
- private final HashMap<String, String> mVolumeStates = new HashMap<String, String>();
- private final HashMap<String, StorageVolume> mVolumeMap = new HashMap<String, StorageVolume>();
- private String mExternalStoragePath;
+ private Context mContext;
+ private NativeDaemonConnector mConnector;
+
+ private final Object mVolumesLock = new Object();
+
+ /** When defined, base template for user-specific {@link StorageVolume}. */
+ private StorageVolume mEmulatedTemplate;
+
+ // @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 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 boolean mBooted = false;
private CountDownLatch mConnectedSignal = new CountDownLatch(1);
private CountDownLatch mAsecsScanned = new CountDownLatch(1);
private boolean mSendUmsConnectedOnBoot = false;
- // true if we should fake MEDIA_MOUNTED state for external storage
- private boolean mEmulateExternalStorage = false;
/**
* Private hash of currently mounted secure containers.
@@ -303,6 +318,8 @@
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 RETRY_UNMOUNT_DELAY = 30; // in ms
private static final int MAX_UNMOUNT_RETRIES = 4;
@@ -437,17 +454,26 @@
}
break;
}
- case H_UNMOUNT_MS : {
+ 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);
+ }
+ break;
+ }
}
}
};
- final private HandlerThread mHandlerThread;
- final private Handler mHandler;
+
+ private final HandlerThread mHandlerThread;
+ private final Handler mHandler;
void waitForAsecScan() {
waitForLatch(mAsecsScanned);
@@ -476,90 +502,119 @@
}
}
- private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ 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);
+ }
+
+ 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;
+ }
+ }
+
+ private final BroadcastReceiver mBootReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ if (userId == -1) return;
+ final UserHandle user = new UserHandle(userId);
- if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
- mBooted = true;
+ Slog.d(TAG, "BOOT_COMPLETED for " + user);
- /*
- * In the simulator, we need to broadcast a volume mounted event
- * to make the media scanner run.
- */
- if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
- notifyVolumeStateChange(null, "/sdcard", VolumeState.NoMedia,
- VolumeState.Mounted);
- return;
- }
- new Thread() {
- @Override
- public void run() {
- try {
- // it is not safe to call vold with mVolumeStates locked
- // so we make a copy of the paths and states and process them
- // outside the lock
- String[] paths;
- String[] states;
- int count;
- synchronized (mVolumeStates) {
- Set<String> keys = mVolumeStates.keySet();
- count = keys.size();
- paths = keys.toArray(new String[count]);
- states = new String[count];
- for (int i = 0; i < count; i++) {
- states[i] = mVolumeStates.get(paths[i]);
- }
- }
+ // Broadcast mounted volumes to newly booted user. This kicks off
+ // media scanner when a user becomes active.
+ synchronized (mVolumesLock) {
+ for (StorageVolume volume : mVolumes) {
+ final UserHandle owner = volume.getOwner();
+ final boolean ownerMatch = owner == null
+ || owner.getIdentifier() == user.getIdentifier();
- for (int i = 0; i < count; i++) {
- String path = paths[i];
- String state = states[i];
+ final String state = mVolumeStates.get(volume.getPath());
- 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);
- }
- }
-
- /* notify external storage has mounted to trigger media scanner */
- if (mEmulateExternalStorage) {
- notifyVolumeStateChange(null,
- Environment.getExternalStorageDirectory().getPath(),
- VolumeState.NoMedia, VolumeState.Mounted);
- }
-
- /*
- * If UMS was connected on boot, send the connected event
- * now that we're up.
- */
- if (mSendUmsConnectedOnBoot) {
- sendUmsIntent(true);
- mSendUmsConnectedOnBoot = false;
- }
- } catch (Exception ex) {
- Slog.e(TAG, "Boot-time mount exception", ex);
- }
+ if (ownerMatch && (Environment.MEDIA_MOUNTED.equals(state)
+ || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state))) {
+ sendStorageIntent(Intent.ACTION_MEDIA_MOUNTED, volume, user);
}
- }.start();
- } else if (action.equals(UsbManager.ACTION_USB_STATE)) {
- boolean available = (intent.getBooleanExtra(UsbManager.USB_CONNECTED, false) &&
- intent.getBooleanExtra(UsbManager.USB_FUNCTION_MASS_STORAGE, false));
- notifyShareAvailabilityChange(available);
+ }
}
}
};
+
+ 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);
+
+ 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);
+ }
+ }
+ }
+ }
+ };
+
+ 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 final class MountServiceBinderListener implements IBinder.DeathRecipient {
final IMountServiceListener mListener;
@@ -590,11 +645,13 @@
}
}
- private void updatePublicVolumeState(String path, String state) {
- String oldState;
- synchronized(mVolumeStates) {
+ private void updatePublicVolumeState(StorageVolume volume, String state) {
+ final String path = volume.getPath();
+ final String oldState;
+ synchronized (mVolumesLock) {
oldState = mVolumeStates.put(path, state);
}
+
if (state.equals(oldState)) {
Slog.w(TAG, String.format("Duplicate state transition (%s -> %s) for %s",
state, state, path));
@@ -603,24 +660,24 @@
Slog.d(TAG, "volume state changed for " + path + " (" + oldState + " -> " + state + ")");
- if (path.equals(mExternalStoragePath)) {
- // Update state on PackageManager, but only of real events
- if (!mEmulateExternalStorage) {
- if (Environment.MEDIA_UNMOUNTED.equals(state)) {
- mPms.updateExternalMediaStatus(false, false);
+ // 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);
- }
+ /*
+ * 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);
@@ -637,7 +694,6 @@
}
/**
- *
* Callback from NativeDaemonConnector
*/
public void onDaemonConnected() {
@@ -661,6 +717,11 @@
String path = tok[1];
String state = Environment.MEDIA_REMOVED;
+ final StorageVolume volume;
+ synchronized (mVolumesLock) {
+ volume = mVolumesByPath.get(path);
+ }
+
int st = Integer.parseInt(tok[2]);
if (st == VolumeState.NoMedia) {
state = Environment.MEDIA_REMOVED;
@@ -678,12 +739,15 @@
if (state != null) {
if (DEBUG_EVENTS) Slog.i(TAG, "Updating valid state " + state);
- updatePublicVolumeState(path, state);
+ updatePublicVolumeState(volume, state);
}
}
} catch (Exception e) {
Slog.e(TAG, "Error processing initial volume state", e);
- updatePublicVolumeState(mExternalStoragePath, Environment.MEDIA_REMOVED);
+ final StorageVolume primary = getPrimaryPhysicalVolume();
+ if (primary != null) {
+ updatePublicVolumeState(primary, Environment.MEDIA_REMOVED);
+ }
}
/*
@@ -749,6 +813,13 @@
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() {
@Override
@@ -772,27 +843,27 @@
}
/* Send the media unmounted event first */
if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first");
- updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
- sendStorageIntent(Environment.MEDIA_UNMOUNTED, path);
+ updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
+ sendStorageIntent(Environment.MEDIA_UNMOUNTED, volume, UserHandle.ALL);
if (DEBUG_EVENTS) Slog.i(TAG, "Sending media removed");
- updatePublicVolumeState(path, Environment.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(path, Environment.MEDIA_UNMOUNTED);
+ updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
action = Intent.ACTION_MEDIA_UNMOUNTED;
if (DEBUG_EVENTS) Slog.i(TAG, "Sending media bad removal");
- updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL);
+ updatePublicVolumeState(volume, Environment.MEDIA_BAD_REMOVAL);
action = Intent.ACTION_MEDIA_BAD_REMOVAL;
} else {
Slog.e(TAG, String.format("Unknown code {%d}", code));
}
if (action != null) {
- sendStorageIntent(action, path);
+ sendStorageIntent(action, volume, UserHandle.ALL);
}
} else {
return false;
@@ -802,14 +873,20 @@
}
private void notifyVolumeStateChange(String label, String path, int oldState, int newState) {
- String vs = getVolumeState(path);
- if (DEBUG_EVENTS) Slog.i(TAG, "notifyVolumeStateChanged::" + vs);
+ final StorageVolume volume;
+ final String state;
+ synchronized (mVolumesLock) {
+ volume = mVolumesByPath.get(path);
+ state = getVolumeState(path);
+ }
+
+ 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, path);
+ sendStorageIntent(Intent.ACTION_MEDIA_UNSHARED, volume, UserHandle.ALL);
}
if (newState == VolumeState.Init) {
@@ -820,22 +897,22 @@
* Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or
* if we're in the process of enabling UMS
*/
- if (!vs.equals(
- Environment.MEDIA_BAD_REMOVAL) && !vs.equals(
- Environment.MEDIA_NOFS) && !vs.equals(
+ 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(path, Environment.MEDIA_UNMOUNTED);
+ updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
action = Intent.ACTION_MEDIA_UNMOUNTED;
}
} else if (newState == VolumeState.Pending) {
} else if (newState == VolumeState.Checking) {
if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state checking");
- updatePublicVolumeState(path, Environment.MEDIA_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(path, Environment.MEDIA_MOUNTED);
+ updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED);
action = Intent.ACTION_MEDIA_MOUNTED;
} else if (newState == VolumeState.Unmounting) {
action = Intent.ACTION_MEDIA_EJECT;
@@ -843,11 +920,11 @@
} else if (newState == VolumeState.Shared) {
if (DEBUG_EVENTS) Slog.i(TAG, "Updating volume state media mounted");
/* Send the media unmounted event first */
- updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
- sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, path);
+ updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
+ sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL);
if (DEBUG_EVENTS) Slog.i(TAG, "Updating media shared");
- updatePublicVolumeState(path, Environment.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) {
@@ -858,13 +935,18 @@
}
if (action != null) {
- sendStorageIntent(action, path);
+ sendStorageIntent(action, volume, UserHandle.ALL);
}
}
private int doMountVolume(String path) {
int rc = StorageResultCode.OperationSucceeded;
+ final StorageVolume volume;
+ synchronized (mVolumesLock) {
+ volume = mVolumesByPath.get(path);
+ }
+
if (DEBUG_EVENTS) Slog.i(TAG, "doMountVolume: Mouting " + path);
try {
mConnector.execute("volume", "mount", path);
@@ -884,7 +966,7 @@
/*
* Media is blank or does not contain a supported filesystem
*/
- updatePublicVolumeState(path, Environment.MEDIA_NOFS);
+ updatePublicVolumeState(volume, Environment.MEDIA_NOFS);
action = Intent.ACTION_MEDIA_NOFS;
rc = StorageResultCode.OperationFailedMediaBlank;
} else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
@@ -892,7 +974,7 @@
/*
* Volume consistency check failed
*/
- updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE);
+ updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTABLE);
action = Intent.ACTION_MEDIA_UNMOUNTABLE;
rc = StorageResultCode.OperationFailedMediaCorrupt;
} else {
@@ -903,7 +985,7 @@
* Send broadcast intent (if required for the failure)
*/
if (action != null) {
- sendStorageIntent(action, path);
+ sendStorageIntent(action, volume, UserHandle.ALL);
}
}
@@ -1011,14 +1093,16 @@
}
}
- if (mBooted == true) {
+ if (mSystemReady == true) {
sendUmsIntent(avail);
} else {
mSendUmsConnectedOnBoot = avail;
}
- final String path = Environment.getExternalStorageDirectory().getPath();
- if (avail == false && getVolumeState(path).equals(Environment.MEDIA_SHARED)) {
+ 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
*/
@@ -1042,12 +1126,11 @@
}
}
- private void sendStorageIntent(String action, String path) {
- Intent intent = new Intent(action, Uri.parse("file://" + path));
- // add StorageVolume extra
- intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mVolumeMap.get(path));
- Slog.d(TAG, "sendStorageIntent " + intent);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ 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);
+ Slog.d(TAG, "sendStorageIntent " + intent + " to " + user);
+ mContext.sendBroadcastAsUser(intent, user);
}
private void sendUmsIntent(boolean c) {
@@ -1066,7 +1149,10 @@
private static final String TAG_STORAGE_LIST = "StorageList";
private static final String TAG_STORAGE = "storage";
- private void readStorageList() {
+ private void readStorageListLocked() {
+ mVolumes.clear();
+ mVolumeStates.clear();
+
Resources resources = mContext.getResources();
int id = com.android.internal.R.xml.storage_list;
@@ -1085,7 +1171,7 @@
TypedArray a = resources.obtainAttributes(attrs,
com.android.internal.R.styleable.Storage);
- CharSequence path = a.getText(
+ String path = a.getString(
com.android.internal.R.styleable.Storage_mountPoint);
int descriptionId = a.getResourceId(
com.android.internal.R.styleable.Storage_storageDescription, -1);
@@ -1110,27 +1196,29 @@
" emulated: " + emulated + " mtpReserve: " + mtpReserve +
" allowMassStorage: " + allowMassStorage +
" maxFileSize: " + maxFileSize);
- if (path == null || description == null) {
- Slog.e(TAG, "path or description is null in readStorageList");
+
+ 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()) {
+ createEmulatedVolumeForUserLocked(user.getUserHandle());
+ }
+
} else {
- String pathString = path.toString();
- StorageVolume volume = new StorageVolume(pathString, descriptionId, primary,
- removable, emulated, mtpReserve, allowMassStorage, maxFileSize);
- if (primary) {
- if (mPrimaryVolume == null) {
- mPrimaryVolume = volume;
- } else {
- Slog.e(TAG, "multiple primary volumes in storage list");
- }
- }
- if (mPrimaryVolume == volume) {
- // primay volume must be first
- mVolumes.add(0, volume);
+ if (path == null || description == null) {
+ Slog.e(TAG, "Missing storage path or description in readStorageList");
} else {
- mVolumes.add(volume);
+ final StorageVolume volume = new StorageVolume(new File(path),
+ descriptionId, primary, removable, emulated, mtpReserve,
+ allowMassStorage, maxFileSize, null);
+ addVolumeLocked(volume);
}
- mVolumeMap.put(pathString, volume);
}
+
a.recycle();
}
}
@@ -1139,48 +1227,105 @@
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
- // compute storage ID for each volume
- int length = mVolumes.size();
- for (int i = 0; i < length; i++) {
- mVolumes.get(i).setStorageId(i);
+ // 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);
+ }
+ }
+
+ 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
*
* @param context Binder context for this service
*/
public MountService(Context context) {
mContext = context;
- readStorageList();
- if (mPrimaryVolume != null) {
- mExternalStoragePath = mPrimaryVolume.getPath();
- mEmulateExternalStorage = mPrimaryVolume.isEmulated();
- if (mEmulateExternalStorage) {
- Slog.d(TAG, "using emulated external storage");
- mVolumeStates.put(mExternalStoragePath, Environment.MEDIA_MOUNTED);
- }
+ synchronized (mVolumesLock) {
+ readStorageListLocked();
}
// XXX: This will go away soon in favor of IMountServiceObserver
mPms = (PackageManagerService) ServiceManager.getService("package");
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_BOOT_COMPLETED);
- // don't bother monitoring USB if mass storage is not supported on our primary volume
- if (mPrimaryVolume != null && mPrimaryVolume.allowMassStorage()) {
- filter.addAction(UsbManager.ACTION_USB_STATE);
- }
- mContext.registerReceiver(mBroadcastReceiver, filter, null, null);
-
mHandlerThread = new HandlerThread("MountService");
mHandlerThread.start();
mHandler = new MountServiceHandler(mHandlerThread.getLooper());
+ // Watch for user boot completion
+ mContext.registerReceiverAsUser(mBootReceiver, UserHandle.ALL,
+ new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, mHandler);
+
+ // 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(mHandlerThread.getLooper());
@@ -1200,6 +1345,11 @@
}
}
+ public void systemReady() {
+ mSystemReady = true;
+ mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget();
+ }
+
/**
* Exposed API calls below here
*/
@@ -1232,7 +1382,7 @@
validatePermission(android.Manifest.permission.SHUTDOWN);
Slog.i(TAG, "Shutting down");
- synchronized (mVolumeStates) {
+ synchronized (mVolumesLock) {
for (String path : mVolumeStates.keySet()) {
String state = mVolumeStates.get(path);
@@ -1313,12 +1463,15 @@
waitForReady();
validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+ 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 = Environment.getExternalStorageDirectory().getPath();
+ String path = primary.getPath();
String vs = getVolumeState(path);
String method = "ums";
if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
@@ -1348,14 +1501,20 @@
public boolean isUsbMassStorageEnabled() {
waitForReady();
- return doGetVolumeShared(Environment.getExternalStorageDirectory().getPath(), "ums");
+
+ final StorageVolume primary = getPrimaryPhysicalVolume();
+ if (primary != null) {
+ return doGetVolumeShared(primary.getPath(), "ums");
+ } else {
+ return false;
+ }
}
/**
* @return state of the volume at the specified mount point
*/
public String getVolumeState(String mountPoint) {
- synchronized (mVolumeStates) {
+ synchronized (mVolumesLock) {
String state = mVolumeStates.get(mountPoint);
if (state == null) {
Slog.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
@@ -1370,8 +1529,9 @@
}
}
+ @Override
public boolean isExternalStorageEmulated() {
- return mEmulateExternalStorage;
+ return mEmulatedTemplate != null;
}
public int mountVolume(String path) {
@@ -1437,7 +1597,9 @@
}
private void warnOnNotMounted() {
- if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ final StorageVolume primary = getPrimaryPhysicalVolume();
+ if (primary != null
+ && Environment.MEDIA_MOUNTED.equals(getVolumeState(primary.getPath()))) {
Slog.w(TAG, "getSecureContainerList() called when storage not mounted");
}
}
@@ -1935,14 +2097,23 @@
}
}
- public Parcelable[] getVolumeList() {
- synchronized(mVolumes) {
- int size = mVolumes.size();
- Parcelable[] result = new Parcelable[size];
- for (int i = 0; i < size; i++) {
- result[i] = mVolumes.get(i);
+ @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);
+
+ 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);
+ }
}
- return result;
+ return filtered.toArray(new StorageVolume[filtered.size()]);
}
}
@@ -2458,7 +2629,7 @@
pw.println("");
- synchronized (mVolumes) {
+ synchronized (mVolumesLock) {
pw.println(" mVolumes:");
final int N = mVolumes.size();
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 73e82ab..4398441 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -125,6 +125,7 @@
BatteryService battery = null;
VibratorService vibrator = null;
AlarmManagerService alarm = null;
+ MountService mountService = null;
NetworkManagementService networkManagement = null;
NetworkStatsService networkStats = null;
NetworkPolicyManagerService networkPolicy = null;
@@ -374,7 +375,6 @@
}
if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
- MountService mountService = null;
if (!"0".equals(SystemProperties.get("system_init.startmountservice"))) {
try {
/*
@@ -813,6 +813,7 @@
// These are needed to propagate to the runnable below.
final Context contextF = context;
+ final MountService mountServiceF = mountService;
final BatteryService batteryF = battery;
final NetworkManagementService networkManagementF = networkManagement;
final NetworkStatsService networkStatsF = networkStats;
@@ -847,6 +848,11 @@
if (!headless) startSystemUi(contextF);
try {
+ if (mountServiceF != null) mountServiceF.systemReady();
+ } catch (Throwable e) {
+ reportWtf("making Mount Service ready", e);
+ }
+ try {
if (batteryF != null) batteryF.systemReady();
} catch (Throwable e) {
reportWtf("making Battery Service ready", e);
diff --git a/services/java/com/android/server/accessibility/ScreenMagnifier.java b/services/java/com/android/server/accessibility/ScreenMagnifier.java
index d6482fa..48781ac 100644
--- a/services/java/com/android/server/accessibility/ScreenMagnifier.java
+++ b/services/java/com/android/server/accessibility/ScreenMagnifier.java
@@ -28,7 +28,6 @@
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PixelFormat;
-import android.graphics.PointF;
import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -40,18 +39,20 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.Settings;
-import android.util.MathUtils;
import android.util.Property;
import android.util.Slog;
import android.view.Display;
import android.view.DisplayInfo;
+import android.view.GestureDetector;
+import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.Gravity;
import android.view.IDisplayContentChangeListener;
import android.view.IWindowManager;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
-import android.view.ScaleGestureDetector.SimpleOnScaleGestureListener;
+import android.view.ScaleGestureDetector;
+import android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.view.Surface;
import android.view.View;
import android.view.ViewConfiguration;
@@ -119,7 +120,6 @@
private static final boolean DEBUG_VIEWPORT_WINDOW = false;
private static final boolean DEBUG_WINDOW_TRANSITIONS = false;
private static final boolean DEBUG_ROTATION = false;
- private static final boolean DEBUG_GESTURE_DETECTOR = false;
private static final boolean DEBUG_MAGNIFICATION_CONTROLLER = false;
private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName();
@@ -141,7 +141,7 @@
private final DisplayProvider mDisplayProvider;
private final DetectingStateHandler mDetectingStateHandler = new DetectingStateHandler();
- private final GestureDetector mGestureDetector;
+ private final MagnifiedContentInteractonStateHandler mMagnifiedContentInteractonStateHandler;
private final StateViewportDraggingHandler mStateViewportDraggingHandler =
new StateViewportDraggingHandler();
@@ -196,14 +196,15 @@
mScreenStateObserver = new ScreenStateObserver(mContext, mViewport,
mMagnificationController);
- mGestureDetector = new GestureDetector(context);
+ mMagnifiedContentInteractonStateHandler = new MagnifiedContentInteractonStateHandler(
+ context);
transitionToState(STATE_DETECTING);
}
@Override
public void onMotionEvent(MotionEvent event, int policyFlags) {
- mGestureDetector.onMotionEvent(event);
+ mMagnifiedContentInteractonStateHandler.onMotionEvent(event);
switch (mCurrentState) {
case STATE_DELEGATING: {
handleMotionEventStateDelegating(event, policyFlags);
@@ -215,9 +216,9 @@
mStateViewportDraggingHandler.onMotionEvent(event, policyFlags);
} break;
case STATE_MAGNIFIED_INTERACTION: {
- // Handled by the gesture detector. Since the detector
- // needs all touch events to work properly we cannot
- // call it only for this state.
+ // mMagnifiedContentInteractonStateHandler handles events only
+ // if this is the current state since it uses ScaleGestureDetecotr
+ // and a GestureDetector which need well formed event stream.
} break;
default: {
throw new IllegalStateException("Unknown state: " + mCurrentState);
@@ -242,7 +243,7 @@
mCurrentState = STATE_DETECTING;
mDetectingStateHandler.clear();
mStateViewportDraggingHandler.clear();
- mGestureDetector.clear();
+ mMagnifiedContentInteractonStateHandler.clear();
if (mNext != null) {
mNext.clear();
}
@@ -347,46 +348,29 @@
mCurrentState = state;
}
- private final class GestureDetector implements OnScaleGestureListener {
+ private final class MagnifiedContentInteractonStateHandler
+ extends SimpleOnGestureListener implements OnScaleGestureListener {
private static final float MIN_SCALE = 1.3f;
private static final float MAX_SCALE = 5.0f;
- private static final float DETECT_SCALING_THRESHOLD = 0.30f;
- private static final int DETECT_PANNING_THRESHOLD_DIP = 30;
-
- private final float mScaledDetectPanningThreshold;
+ private static final float SCALING_THRESHOLD = 0.3f;
private final ScaleGestureDetector mScaleGestureDetector;
+ private final GestureDetector mGestureDetector;
- private final PointF mPrevFocus = new PointF(Float.NaN, Float.NaN);
- private final PointF mInitialFocus = new PointF(Float.NaN, Float.NaN);
-
- private float mCurrScale = Float.NaN;
- private float mCurrScaleFactor = 1.0f;
- private float mPrevScaleFactor = 1.0f;
- private float mCurrPan;
- private float mPrevPan;
-
- private float mScaleFocusX = Float.NaN;
- private float mScaleFocusY = Float.NaN;
-
+ private float mInitialScaleFactor = -1;
private boolean mScaling;
- private boolean mPanning;
- public GestureDetector(Context context) {
- final float density = context.getResources().getDisplayMetrics().density;
- mScaledDetectPanningThreshold = DETECT_PANNING_THRESHOLD_DIP * density;
- mScaleGestureDetector = new ScaleGestureDetector(this);
+ public MagnifiedContentInteractonStateHandler(Context context) {
+ mScaleGestureDetector = new ScaleGestureDetector(context, this);
+ mGestureDetector = new GestureDetector(context, this);
}
public void onMotionEvent(MotionEvent event) {
mScaleGestureDetector.onTouchEvent(event);
- switch (mCurrentState) {
- case STATE_DETECTING:
- case STATE_DELEGATING:
- case STATE_VIEWPORT_DRAGGING: {
- return;
- }
+ mGestureDetector.onTouchEvent(event);
+ if (mCurrentState != STATE_MAGNIFIED_INTERACTION) {
+ return;
}
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
clear();
@@ -403,121 +387,62 @@
}
@Override
- public boolean onScale(ScaleGestureDetector detector) {
- switch (mCurrentState) {
- case STATE_DETECTING:
- case STATE_DELEGATING:
- case STATE_VIEWPORT_DRAGGING: {
- return true;
- }
- case STATE_MAGNIFIED_INTERACTION: {
- mCurrScaleFactor = mScaleGestureDetector.getScaleFactor();
- final float scaleDelta = Math.abs(1.0f - mCurrScaleFactor * mPrevScaleFactor);
- if (DEBUG_GESTURE_DETECTOR) {
- Slog.i(LOG_TAG, "scaleDelta: " + scaleDelta);
- }
- if (!mScaling && scaleDelta > DETECT_SCALING_THRESHOLD) {
- mScaling = true;
- clearContextualState();
- return true;
- }
- if (mScaling) {
- performScale(detector);
- }
- mCurrPan = (float) MathUtils.dist(
- mScaleGestureDetector.getFocusX(),
- mScaleGestureDetector.getFocusY(),
- mInitialFocus.x, mInitialFocus.y);
- final float panDelta = mCurrPan + mPrevPan;
- if (DEBUG_GESTURE_DETECTOR) {
- Slog.i(LOG_TAG, "panDelta: " + panDelta);
- }
- if (!mPanning && panDelta > mScaledDetectPanningThreshold) {
- mPanning = true;
- clearContextualState();
- return true;
- }
- if (mPanning) {
- performPan(detector);
- }
- } break;
- }
- return false;
- }
-
- @Override
- public boolean onScaleBegin(ScaleGestureDetector detector) {
- mPrevScaleFactor *= mCurrScaleFactor;
- mCurrScale = Float.NaN;
- mPrevPan += mCurrPan;
- mPrevFocus.x = mInitialFocus.x = detector.getFocusX();
- mPrevFocus.y = mInitialFocus.y = detector.getFocusY();
- return true;
- }
-
- @Override
- public void onScaleEnd(ScaleGestureDetector detector) {
- clearContextualState();
- }
-
- public void clear() {
- clearContextualState();
- mScaling = false;
- mPanning = false;
- }
-
- private void clearContextualState() {
- mCurrScaleFactor = 1.0f;
- mPrevScaleFactor = 1.0f;
- mPrevPan = 0;
- mCurrPan = 0;
- mInitialFocus.set(Float.NaN, Float.NaN);
- mPrevFocus.set(Float.NaN, Float.NaN);
- mCurrScale = Float.NaN;
- mScaleFocusX = Float.NaN;
- mScaleFocusY = Float.NaN;
- }
-
- private void performPan(ScaleGestureDetector detector) {
- if (Float.compare(mPrevFocus.x, Float.NaN) == 0
- && Float.compare(mPrevFocus.y, Float.NaN) == 0) {
- mPrevFocus.set(detector.getFocusX(), detector.getFocusY());
- return;
+ public boolean onScroll(MotionEvent first, MotionEvent second, float distanceX,
+ float distanceY) {
+ if (mCurrentState != STATE_MAGNIFIED_INTERACTION) {
+ return true;
}
final float scale = mMagnificationController.getScale();
- final float scrollX = (detector.getFocusX() - mPrevFocus.x) / scale;
- final float scrollY = (detector.getFocusY() - mPrevFocus.y) / scale;
- final float centerX = mMagnificationController.getMagnifiedRegionCenterX()
- - scrollX;
- final float centerY = mMagnificationController.getMagnifiedRegionCenterY()
- - scrollY;
+ final float scrollX = distanceX / scale;
+ final float scrollY = distanceY / scale;
+ final float centerX = mMagnificationController.getMagnifiedRegionCenterX() + scrollX;
+ final float centerY = mMagnificationController.getMagnifiedRegionCenterY() + scrollY;
if (DEBUG_PANNING) {
Slog.i(LOG_TAG, "Panned content by scrollX: " + scrollX
+ " scrollY: " + scrollY);
}
mMagnificationController.setMagnifiedRegionCenter(centerX, centerY, false);
- mPrevFocus.set(detector.getFocusX(), detector.getFocusY());
+ return true;
}
- private void performScale(ScaleGestureDetector detector) {
- if (Float.compare(mCurrScale, Float.NaN) == 0) {
- mCurrScale = mMagnificationController.getScale();
- return;
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ if (!mScaling) {
+ if (mInitialScaleFactor < 0) {
+ mInitialScaleFactor = detector.getScaleFactor();
+ } else {
+ final float deltaScale = detector.getScaleFactor() - mInitialScaleFactor;
+ if (Math.abs(deltaScale) > SCALING_THRESHOLD) {
+ mScaling = true;
+ return true;
+ }
+ }
+ return false;
}
- final float totalScaleFactor = mPrevScaleFactor * detector.getScaleFactor();
- final float newScale = mCurrScale * totalScaleFactor;
- final float normalizedNewScale = Math.min(Math.max(newScale, MIN_SCALE),
- MAX_SCALE);
+ final float newScale = mMagnificationController.getScale()
+ * detector.getScaleFactor();
+ final float normalizedNewScale = Math.min(Math.max(newScale, MIN_SCALE), MAX_SCALE);
if (DEBUG_SCALING) {
Slog.i(LOG_TAG, "normalizedNewScale: " + normalizedNewScale);
}
- if (Float.compare(mScaleFocusX, Float.NaN) == 0
- && Float.compare(mScaleFocusY, Float.NaN) == 0) {
- mScaleFocusX = detector.getFocusX();
- mScaleFocusY = detector.getFocusY();
- }
- mMagnificationController.setScale(normalizedNewScale, mScaleFocusX,
- mScaleFocusY, false);
+ mMagnificationController.setScale(normalizedNewScale, detector.getFocusX(),
+ detector.getFocusY(), false);
+ return true;
+ }
+
+ @Override
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ return (mCurrentState == STATE_MAGNIFIED_INTERACTION);
+ }
+
+ @Override
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ clear();
+ }
+
+ private void clear() {
+ mInitialScaleFactor = -1;
+ mScaling = false;
}
}
@@ -1870,482 +1795,4 @@
updateDisplayInfo();
}
}
-
- /**
- * The listener for receiving notifications when gestures occur.
- * If you want to listen for all the different gestures then implement
- * this interface. If you only want to listen for a subset it might
- * be easier to extend {@link SimpleOnScaleGestureListener}.
- *
- * An application will receive events in the following order:
- * <ul>
- * <li>One {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)}
- * <li>Zero or more {@link OnScaleGestureListener#onScale(ScaleGestureDetector)}
- * <li>One {@link OnScaleGestureListener#onScaleEnd(ScaleGestureDetector)}
- * </ul>
- */
- interface OnScaleGestureListener {
- /**
- * Responds to scaling events for a gesture in progress.
- * Reported by pointer motion.
- *
- * @param detector The detector reporting the event - use this to
- * retrieve extended info about event state.
- * @return Whether or not the detector should consider this event
- * as handled. If an event was not handled, the detector
- * will continue to accumulate movement until an event is
- * handled. This can be useful if an application, for example,
- * only wants to update scaling factors if the change is
- * greater than 0.01.
- */
- public boolean onScale(ScaleGestureDetector detector);
-
- /**
- * Responds to the beginning of a scaling gesture. Reported by
- * new pointers going down.
- *
- * @param detector The detector reporting the event - use this to
- * retrieve extended info about event state.
- * @return Whether or not the detector should continue recognizing
- * this gesture. For example, if a gesture is beginning
- * with a focal point outside of a region where it makes
- * sense, onScaleBegin() may return false to ignore the
- * rest of the gesture.
- */
- public boolean onScaleBegin(ScaleGestureDetector detector);
-
- /**
- * Responds to the end of a scale gesture. Reported by existing
- * pointers going up.
- *
- * Once a scale has ended, {@link ScaleGestureDetector#getFocusX()}
- * and {@link ScaleGestureDetector#getFocusY()} will return the location
- * of the pointer remaining on the screen.
- *
- * @param detector The detector reporting the event - use this to
- * retrieve extended info about event state.
- */
- public void onScaleEnd(ScaleGestureDetector detector);
- }
-
- class ScaleGestureDetector {
-
- private final MinCircleFinder mMinCircleFinder = new MinCircleFinder();
-
- private final OnScaleGestureListener mListener;
-
- private float mFocusX;
- private float mFocusY;
-
- private float mCurrSpan;
- private float mPrevSpan;
- private float mCurrSpanX;
- private float mCurrSpanY;
- private float mPrevSpanX;
- private float mPrevSpanY;
- private long mCurrTime;
- private long mPrevTime;
- private boolean mInProgress;
-
- public ScaleGestureDetector(OnScaleGestureListener listener) {
- mListener = listener;
- }
-
- /**
- * Accepts MotionEvents and dispatches events to a {@link OnScaleGestureListener}
- * when appropriate.
- *
- * <p>Applications should pass a complete and consistent event stream to this method.
- * A complete and consistent event stream involves all MotionEvents from the initial
- * ACTION_DOWN to the final ACTION_UP or ACTION_CANCEL.</p>
- *
- * @param event The event to process
- * @return true if the event was processed and the detector wants to receive the
- * rest of the MotionEvents in this event stream.
- */
- public boolean onTouchEvent(MotionEvent event) {
- boolean streamEnded = false;
- boolean contextChanged = false;
- int excludedPtrIdx = -1;
- final int action = event.getActionMasked();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- case MotionEvent.ACTION_POINTER_DOWN: {
- contextChanged = true;
- } break;
- case MotionEvent.ACTION_POINTER_UP: {
- contextChanged = true;
- excludedPtrIdx = event.getActionIndex();
- } break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL: {
- streamEnded = true;
- } break;
- }
-
- if (mInProgress && (contextChanged || streamEnded)) {
- mListener.onScaleEnd(this);
- mInProgress = false;
- mPrevSpan = 0;
- mPrevSpanX = 0;
- mPrevSpanY = 0;
- return true;
- }
-
- final long currTime = mCurrTime;
-
- mFocusX = 0;
- mFocusY = 0;
- mCurrSpan = 0;
- mCurrSpanX = 0;
- mCurrSpanY = 0;
- mCurrTime = 0;
- mPrevTime = 0;
-
- if (!streamEnded) {
- MinCircleFinder.Circle circle =
- mMinCircleFinder.computeMinCircleAroundPointers(event);
- mFocusX = circle.centerX;
- mFocusY = circle.centerY;
-
- double sumSlope = 0;
- final int pointerCount = event.getPointerCount();
- for (int i = 0; i < pointerCount; i++) {
- if (i == excludedPtrIdx) {
- continue;
- }
- float x = event.getX(i) - mFocusX;
- float y = event.getY(i) - mFocusY;
- if (x == 0) {
- x += 0.1f;
- }
- sumSlope += y / x;
- }
- final double avgSlope = sumSlope
- / ((excludedPtrIdx < 0) ? pointerCount : pointerCount - 1);
-
- double angle = Math.atan(avgSlope);
- mCurrSpan = 2 * circle.radius;
- mCurrSpanX = (float) Math.abs((Math.cos(angle) * mCurrSpan));
- mCurrSpanY = (float) Math.abs((Math.sin(angle) * mCurrSpan));
- }
-
- if (contextChanged || mPrevSpan == 0 || mPrevSpanX == 0 || mPrevSpanY == 0) {
- mPrevSpan = mCurrSpan;
- mPrevSpanX = mCurrSpanX;
- mPrevSpanY = mCurrSpanY;
- }
-
- if (!mInProgress && mCurrSpan != 0 && !streamEnded) {
- mInProgress = mListener.onScaleBegin(this);
- }
-
- if (mInProgress) {
- mPrevTime = (currTime != 0) ? currTime : event.getEventTime();
- mCurrTime = event.getEventTime();
- if (mCurrSpan == 0) {
- mListener.onScaleEnd(this);
- mInProgress = false;
- } else {
- if (mListener.onScale(this)) {
- mPrevSpanX = mCurrSpanX;
- mPrevSpanY = mCurrSpanY;
- mPrevSpan = mCurrSpan;
- }
- }
- }
-
- return true;
- }
-
- /**
- * Returns {@code true} if a scale gesture is in progress.
- */
- public boolean isInProgress() {
- return mInProgress;
- }
-
- /**
- * Get the X coordinate of the current gesture's focal point.
- * If a gesture is in progress, the focal point is between
- * each of the pointers forming the gesture.
- *
- * If {@link #isInProgress()} would return false, the result of this
- * function is undefined.
- *
- * @return X coordinate of the focal point in pixels.
- */
- public float getFocusX() {
- return mFocusX;
- }
-
- /**
- * Get the Y coordinate of the current gesture's focal point.
- * If a gesture is in progress, the focal point is between
- * each of the pointers forming the gesture.
- *
- * If {@link #isInProgress()} would return false, the result of this
- * function is undefined.
- *
- * @return Y coordinate of the focal point in pixels.
- */
- public float getFocusY() {
- return mFocusY;
- }
-
- /**
- * Return the average distance between each of the pointers forming the
- * gesture in progress through the focal point.
- *
- * @return Distance between pointers in pixels.
- */
- public float getCurrentSpan() {
- return mCurrSpan;
- }
-
- /**
- * Return the average X distance between each of the pointers forming the
- * gesture in progress through the focal point.
- *
- * @return Distance between pointers in pixels.
- */
- public float getCurrentSpanX() {
- return mCurrSpanX;
- }
-
- /**
- * Return the average Y distance between each of the pointers forming the
- * gesture in progress through the focal point.
- *
- * @return Distance between pointers in pixels.
- */
- public float getCurrentSpanY() {
- return mCurrSpanY;
- }
-
- /**
- * Return the previous average distance between each of the pointers forming the
- * gesture in progress through the focal point.
- *
- * @return Previous distance between pointers in pixels.
- */
- public float getPreviousSpan() {
- return mPrevSpan;
- }
-
- /**
- * Return the previous average X distance between each of the pointers forming the
- * gesture in progress through the focal point.
- *
- * @return Previous distance between pointers in pixels.
- */
- public float getPreviousSpanX() {
- return mPrevSpanX;
- }
-
- /**
- * Return the previous average Y distance between each of the pointers forming the
- * gesture in progress through the focal point.
- *
- * @return Previous distance between pointers in pixels.
- */
- public float getPreviousSpanY() {
- return mPrevSpanY;
- }
-
- /**
- * Return the scaling factor from the previous scale event to the current
- * event. This value is defined as
- * ({@link #getCurrentSpan()} / {@link #getPreviousSpan()}).
- *
- * @return The current scaling factor.
- */
- public float getScaleFactor() {
- return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1;
- }
-
- /**
- * Return the time difference in milliseconds between the previous
- * accepted scaling event and the current scaling event.
- *
- * @return Time difference since the last scaling event in milliseconds.
- */
- public long getTimeDelta() {
- return mCurrTime - mPrevTime;
- }
-
- /**
- * Return the event time of the current event being processed.
- *
- * @return Current event time in milliseconds.
- */
- public long getEventTime() {
- return mCurrTime;
- }
- }
-
- private static final class MinCircleFinder {
- private final ArrayList<PointHolder> mPoints = new ArrayList<PointHolder>();
- private final ArrayList<PointHolder> sBoundary = new ArrayList<PointHolder>();
- private final Circle mMinCircle = new Circle();
-
- /**
- * Finds the minimal circle that contains all pointers of a motion event.
- *
- * @param event A motion event.
- * @return The minimal circle.
- */
- public Circle computeMinCircleAroundPointers(MotionEvent event) {
- ArrayList<PointHolder> points = mPoints;
- points.clear();
- final int pointerCount = event.getPointerCount();
- for (int i = 0; i < pointerCount; i++) {
- PointHolder point = PointHolder.obtain(event.getX(i), event.getY(i));
- points.add(point);
- }
- ArrayList<PointHolder> boundary = sBoundary;
- boundary.clear();
- computeMinCircleAroundPointsRecursive(points, boundary, mMinCircle);
- for (int i = points.size() - 1; i >= 0; i--) {
- points.remove(i).recycle();
- }
- boundary.clear();
- return mMinCircle;
- }
-
- private static void computeMinCircleAroundPointsRecursive(ArrayList<PointHolder> points,
- ArrayList<PointHolder> boundary, Circle outCircle) {
- if (points.isEmpty()) {
- if (boundary.size() == 0) {
- outCircle.initialize();
- } else if (boundary.size() == 1) {
- outCircle.initialize(boundary.get(0).mData, boundary.get(0).mData);
- } else if (boundary.size() == 2) {
- outCircle.initialize(boundary.get(0).mData, boundary.get(1).mData);
- } else if (boundary.size() == 3) {
- outCircle.initialize(boundary.get(0).mData, boundary.get(1).mData,
- boundary.get(2).mData);
- }
- return;
- }
- PointHolder point = points.remove(points.size() - 1);
- computeMinCircleAroundPointsRecursive(points, boundary, outCircle);
- if (!outCircle.contains(point.mData)) {
- boundary.add(point);
- computeMinCircleAroundPointsRecursive(points, boundary, outCircle);
- boundary.remove(point);
- }
- points.add(point);
- }
-
- private static final class PointHolder {
- private static final int MAX_POOL_SIZE = 20;
- private static PointHolder sPool;
- private static int sPoolSize;
-
- private PointHolder mNext;
- private boolean mIsInPool;
-
- private final PointF mData = new PointF();
-
- public static PointHolder obtain(float x, float y) {
- PointHolder holder;
- if (sPoolSize > 0) {
- sPoolSize--;
- holder = sPool;
- sPool = sPool.mNext;
- holder.mNext = null;
- holder.mIsInPool = false;
- } else {
- holder = new PointHolder();
- }
- holder.mData.set(x, y);
- return holder;
- }
-
- public void recycle() {
- if (mIsInPool) {
- throw new IllegalStateException("Already recycled.");
- }
- clear();
- if (sPoolSize < MAX_POOL_SIZE) {
- sPoolSize++;
- mNext = sPool;
- sPool = this;
- mIsInPool = true;
- }
- }
-
- private void clear() {
- mData.set(0, 0);
- }
- }
-
- public static final class Circle {
- public float centerX;
- public float centerY;
- public float radius;
-
- private void initialize() {
- centerX = 0;
- centerY = 0;
- radius = 0;
- }
-
- private void initialize(PointF first, PointF second, PointF third) {
- if (!hasLineWithInfiniteSlope(first, second, third)) {
- initializeInternal(first, second, third);
- } else if (!hasLineWithInfiniteSlope(first, third, second)) {
- initializeInternal(first, third, second);
- } else if (!hasLineWithInfiniteSlope(second, first, third)) {
- initializeInternal(second, first, third);
- } else if (!hasLineWithInfiniteSlope(second, third, first)) {
- initializeInternal(second, third, first);
- } else if (!hasLineWithInfiniteSlope(third, first, second)) {
- initializeInternal(third, first, second);
- } else if (!hasLineWithInfiniteSlope(third, second, first)) {
- initializeInternal(third, second, first);
- } else {
- initialize();
- }
- }
-
- private void initialize(PointF first, PointF second) {
- radius = (float) (Math.hypot(second.x - first.x, second.y - first.y) / 2);
- centerX = (float) (second.x + first.x) / 2;
- centerY = (float) (second.y + first.y) / 2;
- }
-
- public boolean contains(PointF point) {
- return (int) (Math.hypot(point.x - centerX, point.y - centerY)) <= radius;
- }
-
- private void initializeInternal(PointF first, PointF second, PointF third) {
- final float x1 = first.x;
- final float y1 = first.y;
- final float x2 = second.x;
- final float y2 = second.y;
- final float x3 = third.x;
- final float y3 = third.y;
-
- final float sl1 = (y2 - y1) / (x2 - x1);
- final float sl2 = (y3 - y2) / (x3 - x2);
-
- centerX = (int) ((sl1 * sl2 * (y1 - y3) + sl2 * (x1 + x2) - sl1 * (x2 + x3))
- / (2 * (sl2 - sl1)));
- centerY = (int) (-1 / sl1 * (centerX - (x1 + x2) / 2) + (y1 + y2) / 2);
- radius = (int) Math.hypot(x1 - centerX, y1 - centerY);
- }
-
- private boolean hasLineWithInfiniteSlope(PointF first, PointF second, PointF third) {
- return (second.x - first.x == 0 || third.x - second.x == 0
- || second.y - first.y == 0 || third.y - second.y == 0);
- }
-
- @Override
- public String toString() {
- return "cetner: [" + centerX + ", " + centerY + "] radius: " + radius;
- }
- }
- }
}
diff --git a/services/java/com/android/server/display/WifiDisplayAdapter.java b/services/java/com/android/server/display/WifiDisplayAdapter.java
index ee82050..b75940e 100644
--- a/services/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/java/com/android/server/display/WifiDisplayAdapter.java
@@ -27,7 +27,6 @@
import android.media.RemoteDisplay;
import android.os.Handler;
import android.os.IBinder;
-import android.util.Slog;
import android.view.Surface;
import java.io.PrintWriter;
@@ -50,8 +49,8 @@
final class WifiDisplayAdapter extends DisplayAdapter {
private static final String TAG = "WifiDisplayAdapter";
- private WifiDisplayHandle mDisplayHandle;
private WifiDisplayController mDisplayController;
+ private WifiDisplayDevice mDisplayDevice;
private WifiDisplayStatus mCurrentStatus;
private boolean mEnabled;
@@ -71,13 +70,6 @@
public void dumpLocked(PrintWriter pw) {
super.dumpLocked(pw);
- if (mDisplayHandle == null) {
- pw.println("mDisplayHandle=null");
- } else {
- pw.println("mDisplayHandle:");
- mDisplayHandle.dumpLocked(pw);
- }
-
pw.println("mCurrentStatus=" + getWifiDisplayStatusLocked());
pw.println("mEnabled=" + mEnabled);
pw.println("mScanState=" + mScanState);
@@ -151,16 +143,29 @@
return mCurrentStatus;
}
- private void handleConnectLocked(WifiDisplay display, String iface) {
+ private void handleConnectLocked(WifiDisplay display,
+ Surface surface, int width, int height, int flags) {
handleDisconnectLocked();
- mDisplayHandle = new WifiDisplayHandle(display.getDeviceName(), iface);
+ int deviceFlags = 0;
+ if ((flags & RemoteDisplay.DISPLAY_FLAG_SECURE) != 0) {
+ deviceFlags |= DisplayDeviceInfo.FLAG_SECURE;
+ }
+
+ float refreshRate = 60.0f; // TODO: get this for real
+
+ String name = display.getDeviceName();
+ IBinder displayToken = Surface.createDisplay(name);
+ mDisplayDevice = new WifiDisplayDevice(displayToken, name, width, height,
+ refreshRate, deviceFlags, surface);
+ sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_ADDED);
}
private void handleDisconnectLocked() {
- if (mDisplayHandle != null) {
- mDisplayHandle.disposeLocked();
- mDisplayHandle = null;
+ if (mDisplayDevice != null) {
+ mDisplayDevice.clearSurfaceLocked();
+ sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_REMOVED);
+ mDisplayDevice = null;
}
}
@@ -258,9 +263,10 @@
}
@Override
- public void onDisplayConnected(WifiDisplay display, String iface) {
+ public void onDisplayConnected(WifiDisplay display, Surface surface,
+ int width, int height, int flags) {
synchronized (getSyncRoot()) {
- handleConnectLocked(display, iface);
+ handleConnectLocked(display, surface, width, height, flags);
if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_CONNECTED
|| mActiveDisplay == null
@@ -337,92 +343,4 @@
return mInfo;
}
}
-
- private final class WifiDisplayHandle implements RemoteDisplay.Listener {
- private final String mName;
- private final String mIface;
- private final RemoteDisplay mRemoteDisplay;
-
- private WifiDisplayDevice mDevice;
- private int mLastError;
-
- public WifiDisplayHandle(String name, String iface) {
- mName = name;
- mIface = iface;
- mRemoteDisplay = RemoteDisplay.listen(iface, this, getHandler());
-
- Slog.i(TAG, "Listening for Wifi display connections on " + iface
- + " from " + mName);
- }
-
- public void disposeLocked() {
- Slog.i(TAG, "Stopped listening for Wifi display connections on " + mIface
- + " from " + mName);
-
- removeDisplayLocked();
- mRemoteDisplay.dispose();
- }
-
- public void dumpLocked(PrintWriter pw) {
- pw.println(" " + mName + ": " + (mDevice != null ? "connected" : "disconnected"));
- pw.println(" mIface=" + mIface);
- pw.println(" mLastError=" + mLastError);
- }
-
- // Called on the handler thread.
- @Override
- public void onDisplayConnected(Surface surface, int width, int height, int flags) {
- synchronized (getSyncRoot()) {
- mLastError = 0;
- removeDisplayLocked();
- addDisplayLocked(surface, width, height, flags);
-
- Slog.i(TAG, "Wifi display connected: " + mName);
- }
- }
-
- // Called on the handler thread.
- @Override
- public void onDisplayDisconnected() {
- synchronized (getSyncRoot()) {
- mLastError = 0;
- removeDisplayLocked();
-
- Slog.i(TAG, "Wifi display disconnected: " + mName);
- }
- }
-
- // Called on the handler thread.
- @Override
- public void onDisplayError(int error) {
- synchronized (getSyncRoot()) {
- mLastError = error;
- removeDisplayLocked();
-
- Slog.i(TAG, "Wifi display disconnected due to error " + error + ": " + mName);
- }
- }
-
- private void addDisplayLocked(Surface surface, int width, int height, int flags) {
- int deviceFlags = 0;
- if ((flags & RemoteDisplay.DISPLAY_FLAG_SECURE) != 0) {
- deviceFlags |= DisplayDeviceInfo.FLAG_SECURE;
- }
-
- float refreshRate = 60.0f; // TODO: get this for real
-
- IBinder displayToken = Surface.createDisplay(mName);
- mDevice = new WifiDisplayDevice(displayToken, mName, width, height,
- refreshRate, deviceFlags, surface);
- sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_ADDED);
- }
-
- private void removeDisplayLocked() {
- if (mDevice != null) {
- mDevice.clearSurfaceLocked();
- sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_REMOVED);
- mDevice = null;
- }
- }
- }
}
diff --git a/services/java/com/android/server/display/WifiDisplayController.java b/services/java/com/android/server/display/WifiDisplayController.java
index 144b391..6e0be55 100644
--- a/services/java/com/android/server/display/WifiDisplayController.java
+++ b/services/java/com/android/server/display/WifiDisplayController.java
@@ -23,6 +23,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.display.WifiDisplay;
+import android.media.RemoteDisplay;
import android.net.NetworkInfo;
import android.net.wifi.p2p.WifiP2pConfig;
import android.net.wifi.p2p.WifiP2pDevice;
@@ -36,6 +37,7 @@
import android.net.wifi.p2p.WifiP2pManager.PeerListListener;
import android.os.Handler;
import android.util.Slog;
+import android.view.Surface;
import java.io.PrintWriter;
import java.net.Inet4Address;
@@ -64,6 +66,7 @@
private static final int DEFAULT_CONTROL_PORT = 7236;
private static final int MAX_THROUGHPUT = 50;
private static final int CONNECTION_TIMEOUT_SECONDS = 30;
+ private static final int RTSP_TIMEOUT_SECONDS = 15;
private static final int DISCOVER_PEERS_MAX_RETRIES = 10;
private static final int DISCOVER_PEERS_RETRY_DELAY_MILLIS = 500;
@@ -104,12 +107,19 @@
// The group info obtained after connecting.
private WifiP2pGroup mConnectedDeviceGroupInfo;
- // The device that we announced to the rest of the system.
- private WifiP2pDevice mPublishedDevice;
-
// Number of connection retries remaining.
private int mConnectionRetriesLeft;
+ // The remote display that is listening on the connection.
+ // Created after the Wifi P2P network is connected.
+ private RemoteDisplay mRemoteDisplay;
+
+ // The remote display interface.
+ private String mRemoteDisplayInterface;
+
+ // True if RTSP has connected.
+ private boolean mRemoteDisplayConnected;
+
public WifiDisplayController(Context context, Handler handler, Listener listener) {
mContext = context;
mHandler = handler;
@@ -135,8 +145,10 @@
pw.println("mDesiredDevice=" + describeWifiP2pDevice(mDesiredDevice));
pw.println("mConnectingDisplay=" + describeWifiP2pDevice(mConnectingDevice));
pw.println("mConnectedDevice=" + describeWifiP2pDevice(mConnectedDevice));
- pw.println("mPublishedDevice=" + describeWifiP2pDevice(mPublishedDevice));
pw.println("mConnectionRetriesLeft=" + mConnectionRetriesLeft);
+ pw.println("mRemoteDisplay=" + mRemoteDisplay);
+ pw.println("mRemoteDisplayInterface=" + mRemoteDisplayInterface);
+ pw.println("mRemoteDisplayConnected=" + mRemoteDisplayConnected);
pw.println("mKnownWifiDisplayPeers: size=" + mKnownWifiDisplayPeers.size());
for (WifiP2pDevice device : mKnownWifiDisplayPeers) {
@@ -341,7 +353,7 @@
}
private void retryConnection() {
- if (mDesiredDevice != null && mPublishedDevice != mDesiredDevice
+ if (mDesiredDevice != null && mConnectedDevice != mDesiredDevice
&& mConnectionRetriesLeft > 0) {
mConnectionRetriesLeft -= 1;
Slog.i(TAG, "Retrying Wifi display connection. Retries left: "
@@ -363,14 +375,22 @@
private void updateConnection() {
// Step 1. Before we try to connect to a new device, tell the system we
// have disconnected from the old one.
- if (mPublishedDevice != null && mPublishedDevice != mDesiredDevice) {
+ if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) {
+ Slog.i(TAG, "Stopped listening for RTSP connection on " + mRemoteDisplayInterface
+ + " from Wifi display: " + mConnectedDevice.deviceName);
+
+ mRemoteDisplay.dispose();
+ mRemoteDisplay = null;
+ mRemoteDisplayInterface = null;
+ mRemoteDisplayConnected = false;
+ mHandler.removeCallbacks(mRtspTimeout);
+
mHandler.post(new Runnable() {
@Override
public void run() {
mListener.onDisplayDisconnected();
}
});
- mPublishedDevice = null;
// continue to next step
}
@@ -471,9 +491,9 @@
@Override
public void onFailure(int reason) {
- Slog.i(TAG, "Failed to initiate connection to Wifi display: "
- + newDevice.deviceName + ", reason=" + reason);
if (mConnectingDevice == newDevice) {
+ Slog.i(TAG, "Failed to initiate connection to Wifi display: "
+ + newDevice.deviceName + ", reason=" + reason);
mConnectingDevice = null;
handleConnectionFailure(false);
}
@@ -482,8 +502,8 @@
return; // wait for asynchronous callback
}
- // Step 6. Publish the new connection.
- if (mConnectedDevice != null && mPublishedDevice == null) {
+ // Step 6. Listen for incoming connections.
+ if (mConnectedDevice != null && mRemoteDisplay == null) {
Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo);
if (addr == null) {
Slog.i(TAG, "Failed to get local interface address for communicating "
@@ -492,17 +512,57 @@
return; // done
}
- final WifiDisplay display = createWifiDisplay(mConnectedDevice);
+ final WifiP2pDevice oldDevice = mConnectedDevice;
final int port = getPortNumber(mConnectedDevice);
final String iface = addr.getHostAddress() + ":" + port;
+ mRemoteDisplayInterface = iface;
- mPublishedDevice = mConnectedDevice;
- mHandler.post(new Runnable() {
+ Slog.i(TAG, "Listening for RTSP connection on " + iface
+ + " from Wifi display: " + mConnectedDevice.deviceName);
+
+ mRemoteDisplay = RemoteDisplay.listen(iface, new RemoteDisplay.Listener() {
@Override
- public void run() {
- mListener.onDisplayConnected(display, iface);
+ public void onDisplayConnected(final Surface surface,
+ final int width, final int height, final int flags) {
+ if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) {
+ Slog.i(TAG, "Opened RTSP connection with Wifi display: "
+ + mConnectedDevice.deviceName);
+ mRemoteDisplayConnected = true;
+ mHandler.removeCallbacks(mRtspTimeout);
+
+ final WifiDisplay display = createWifiDisplay(mConnectedDevice);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onDisplayConnected(display,
+ surface, width, height, flags);
+ }
+ });
+ }
}
- });
+
+ @Override
+ public void onDisplayDisconnected() {
+ if (mConnectedDevice == oldDevice) {
+ Slog.i(TAG, "Closed RTSP connection with Wifi display: "
+ + mConnectedDevice.deviceName);
+ mHandler.removeCallbacks(mRtspTimeout);
+ disconnect();
+ }
+ }
+
+ @Override
+ public void onDisplayError(int error) {
+ if (mConnectedDevice == oldDevice) {
+ Slog.i(TAG, "Lost RTSP connection with Wifi display due to error "
+ + error + ": " + mConnectedDevice.deviceName);
+ mHandler.removeCallbacks(mRtspTimeout);
+ handleConnectionFailure(false);
+ }
+ }
+ }, mHandler);
+
+ mHandler.postDelayed(mRtspTimeout, RTSP_TIMEOUT_SECONDS * 1000);
}
}
@@ -591,17 +651,30 @@
}
};
+ private final Runnable mRtspTimeout = new Runnable() {
+ @Override
+ public void run() {
+ if (mConnectedDevice != null
+ && mRemoteDisplay != null && !mRemoteDisplayConnected) {
+ Slog.i(TAG, "Timed out waiting for Wifi display RTSP connection after "
+ + RTSP_TIMEOUT_SECONDS + " seconds: "
+ + mConnectedDevice.deviceName);
+ handleConnectionFailure(true);
+ }
+ }
+ };
+
private void handleConnectionFailure(boolean timeoutOccurred) {
+ Slog.i(TAG, "Wifi display connection failed!");
+
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onDisplayConnectionFailed();
+ }
+ });
+
if (mDesiredDevice != null) {
- Slog.i(TAG, "Wifi display connection failed!");
-
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mListener.onDisplayConnectionFailed();
- }
- });
-
if (mConnectionRetriesLeft > 0) {
mHandler.postDelayed(new Runnable() {
@Override
@@ -714,7 +787,8 @@
void onDisplayConnecting(WifiDisplay display);
void onDisplayConnectionFailed();
- void onDisplayConnected(WifiDisplay display, String iface);
+ void onDisplayConnected(WifiDisplay display,
+ Surface surface, int width, int height, int flags);
void onDisplayDisconnected();
}
}
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index e19a803..8ce474a 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -109,6 +109,7 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.Environment.UserEnvironment;
import android.provider.Settings.Secure;
import android.security.SystemKeyStore;
import android.util.DisplayMetrics;
@@ -6135,19 +6136,20 @@
mounted = true;
} else {
final String status = Environment.getExternalStorageState();
-
- mounted = status.equals(Environment.MEDIA_MOUNTED)
- || status.equals(Environment.MEDIA_MOUNTED_READ_ONLY);
+ mounted = (Environment.MEDIA_MOUNTED.equals(status)
+ || Environment.MEDIA_MOUNTED_READ_ONLY.equals(status));
}
if (mounted) {
- final File externalCacheDir = Environment
+ final UserEnvironment userEnv = new UserEnvironment(mStats.userHandle);
+
+ final File externalCacheDir = userEnv
.getExternalStorageAppCacheDirectory(mStats.packageName);
final long externalCacheSize = mContainerService
.calculateDirectorySize(externalCacheDir.getPath());
mStats.externalCacheSize = externalCacheSize;
- final File externalDataDir = Environment
+ final File externalDataDir = userEnv
.getExternalStorageAppDataDirectory(mStats.packageName);
long externalDataSize = mContainerService.calculateDirectorySize(externalDataDir
.getPath());
@@ -6157,12 +6159,12 @@
}
mStats.externalDataSize = externalDataSize;
- final File externalMediaDir = Environment
+ final File externalMediaDir = userEnv
.getExternalStorageAppMediaDirectory(mStats.packageName);
mStats.externalMediaSize = mContainerService
.calculateDirectorySize(externalMediaDir.getPath());
- final File externalObbDir = Environment
+ final File externalObbDir = userEnv
.getExternalStorageAppObbDirectory(mStats.packageName);
mStats.externalObbSize = mContainerService.calculateDirectorySize(externalObbDir
.getPath());
@@ -8361,20 +8363,22 @@
if (conn.mContainerService == null) {
return;
}
- final File externalCacheDir = Environment
+
+ final UserEnvironment userEnv = new UserEnvironment(curUser);
+ final File externalCacheDir = userEnv
.getExternalStorageAppCacheDirectory(packageName);
try {
conn.mContainerService.clearDirectory(externalCacheDir.toString());
} catch (RemoteException e) {
}
if (allData) {
- final File externalDataDir = Environment
+ final File externalDataDir = userEnv
.getExternalStorageAppDataDirectory(packageName);
try {
conn.mContainerService.clearDirectory(externalDataDir.toString());
} catch (RemoteException e) {
}
- final File externalMediaDir = Environment
+ final File externalMediaDir = userEnv
.getExternalStorageAppMediaDirectory(packageName);
try {
conn.mContainerService.clearDirectory(externalMediaDir.toString());
diff --git a/services/java/com/android/server/usb/UsbDeviceManager.java b/services/java/com/android/server/usb/UsbDeviceManager.java
index 607ff39..3ef6d4c 100644
--- a/services/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/java/com/android/server/usb/UsbDeviceManager.java
@@ -183,12 +183,9 @@
// We do not show the USB notification if the primary volume supports mass storage.
// The legacy mass storage UI will be used instead.
boolean massStorageSupported = false;
- StorageManager storageManager = (StorageManager)
- mContext.getSystemService(Context.STORAGE_SERVICE);
- StorageVolume[] volumes = storageManager.getVolumeList();
- if (volumes.length > 0) {
- massStorageSupported = volumes[0].allowMassStorage();
- }
+ final StorageManager storageManager = StorageManager.from(mContext);
+ final StorageVolume primary = storageManager.getPrimaryVolume();
+ massStorageSupported = primary != null && primary.allowMassStorage();
mUseUsbNotification = !massStorageSupported;
// make sure the ADB_ENABLED setting value matches the current state