Merge "Fix issue #7238170: Edit picture option is not available for the secondary user." into jb-mr1-dev
diff --git a/api/current.txt b/api/current.txt
index c732c3e..a123620 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -20332,7 +20332,6 @@
method public boolean isInteractive();
method public boolean isLowProfile();
method public boolean isScreenBright();
- method protected deprecated void lightsOut();
method public void onActionModeFinished(android.view.ActionMode);
method public void onActionModeStarted(android.view.ActionMode);
method public void onAttachedToWindow();
@@ -20360,7 +20359,7 @@
field public static final java.lang.String ACTION_DREAMING_STARTED = "android.intent.action.DREAMING_STARTED";
field public static final java.lang.String ACTION_DREAMING_STOPPED = "android.intent.action.DREAMING_STOPPED";
field public static final java.lang.String CATEGORY_DREAM = "android.intent.category.DREAM";
- field public static final java.lang.String METADATA_NAME_CONFIG_ACTIVITY = "android.service.dreams.config_activity";
+ field public static final java.lang.String DREAM_META_DATA = "android.service.dream";
}
}
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index bc9e74e..396b32f 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -804,8 +804,9 @@
ParcelFileDescriptor fd = null;
try {
- fd = ParcelFileDescriptor.open(
- new File(heapFile),
+ File file = new File(heapFile);
+ file.delete();
+ fd = ParcelFileDescriptor.open(file,
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_TRUNCATE |
ParcelFileDescriptor.MODE_READ_WRITE);
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 9b08493..9874b0b 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1509,9 +1509,9 @@
case DUMP_HEAP_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
String process = data.readString();
+ int userId = data.readInt();
boolean managed = data.readInt() != 0;
String path = data.readString();
- int userId = data.readInt();
ParcelFileDescriptor fd = data.readInt() != 0
? data.readFileDescriptor() : null;
boolean res = dumpHeap(process, userId, managed, path, fd);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index d4b204f..6638433 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2711,7 +2711,7 @@
r.activity.performResume();
EventLog.writeEvent(LOG_ON_RESUME_CALLED,
- r.activity.getComponentName().getClassName());
+ UserHandle.myUserId(), r.activity.getComponentName().getClassName());
r.paused = false;
r.stopped = false;
@@ -2979,7 +2979,8 @@
// Now we are idle.
r.activity.mCalled = false;
mInstrumentation.callActivityOnPause(r.activity);
- EventLog.writeEvent(LOG_ON_PAUSE_CALLED, r.activity.getComponentName().getClassName());
+ EventLog.writeEvent(LOG_ON_PAUSE_CALLED, UserHandle.myUserId(),
+ r.activity.getComponentName().getClassName());
if (!r.activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
@@ -3364,7 +3365,7 @@
try {
r.activity.mCalled = false;
mInstrumentation.callActivityOnPause(r.activity);
- EventLog.writeEvent(LOG_ON_PAUSE_CALLED,
+ EventLog.writeEvent(LOG_ON_PAUSE_CALLED, UserHandle.myUserId(),
r.activity.getComponentName().getClassName());
if (!r.activity.mCalled) {
throw new SuperNotCalledException(
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index c095280..0acad75 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -124,6 +124,9 @@
int[] idOut = new int[1];
INotificationManager service = getService();
String pkg = mContext.getPackageName();
+ if (notification.sound != null) {
+ notification.sound = notification.sound.getCanonicalUri();
+ }
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
try {
service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut,
@@ -143,6 +146,9 @@
int[] idOut = new int[1];
INotificationManager service = getService();
String pkg = mContext.getPackageName();
+ if (notification.sound != null) {
+ notification.sound = notification.sound.getCanonicalUri();
+ }
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
try {
service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut,
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index ac36cf7..201b43f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -178,7 +178,7 @@
* Flag for {@link #bindService}: indicates that the client application
* binding to this service considers the service to be more important than
* the app itself. When set, the platform will try to have the out of
- * memory kill the app before it kills the service it is bound to, though
+ * memory killer kill the app before it kills the service it is bound to, though
* this is not guaranteed to be the case.
*/
public static final int BIND_ABOVE_CLIENT = 0x0008;
@@ -219,6 +219,19 @@
public static final int BIND_ADJUST_WITH_ACTIVITY = 0x0080;
/**
+ * @hide An idea that is not yet implemented.
+ * Flag for {@link #bindService}: If binding from an activity, consider
+ * this service to be visible like the binding activity is. That is,
+ * it will be treated as something more important to keep around than
+ * invisible background activities. This will impact the number of
+ * recent activities the user can switch between without having them
+ * restart. There is no guarantee this will be respected, as the system
+ * tries to balance such requests from one app vs. the importantance of
+ * keeping other apps around.
+ */
+ public static final int BIND_VISIBLE = 0x0100;
+
+ /**
* Flag for {@link #bindService}: Don't consider the bound service to be
* visible, even if the caller is visible.
* @hide
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index 3b990e3..cc6903d 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -16,10 +16,13 @@
package android.net;
+import android.os.Environment;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.Environment.UserEnvironment;
import android.util.Log;
import java.io.File;
+import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charsets;
@@ -2288,4 +2291,39 @@
builder = builder.appendEncodedPath(pathSegment);
return builder.build();
}
+
+ /**
+ * If this {@link Uri} is {@code file://}, then resolve and return its
+ * canonical path. Also fixes legacy emulated storage paths so they are
+ * usable across user boundaries. Should always be called from the app
+ * process before sending elsewhere.
+ *
+ * @hide
+ */
+ public Uri getCanonicalUri() {
+ if ("file".equals(getScheme())) {
+ final String canonicalPath;
+ try {
+ canonicalPath = new File(getPath()).getCanonicalPath();
+ } catch (IOException e) {
+ return this;
+ }
+
+ if (Environment.isExternalStorageEmulated()) {
+ final String legacyPath = Environment.getLegacyExternalStorageDirectory()
+ .toString();
+
+ // Splice in user-specific path when legacy path is found
+ if (canonicalPath.startsWith(legacyPath)) {
+ return Uri.fromFile(new File(
+ Environment.getExternalStorageDirectory().toString(),
+ canonicalPath.substring(legacyPath.length() + 1)));
+ }
+ }
+
+ return Uri.fromFile(new File(canonicalPath));
+ } else {
+ return this;
+ }
+ }
}
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 5c4c036..3315566 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -31,6 +31,7 @@
private static final String TAG = "Environment";
private static final String ENV_EXTERNAL_STORAGE = "EXTERNAL_STORAGE";
+ private static final String ENV_EMULATED_STORAGE_SOURCE = "EMULATED_STORAGE_SOURCE";
private static final String ENV_EMULATED_STORAGE_TARGET = "EMULATED_STORAGE_TARGET";
private static final String ENV_MEDIA_STORAGE = "MEDIA_STORAGE";
@@ -134,6 +135,10 @@
return mExternalStorage;
}
+ public File getExternalStorageObbDirectory() {
+ return mExternalStorageAndroidObb;
+ }
+
public File getExternalStoragePublicDirectory(String type) {
return new File(mExternalStorage, type);
}
@@ -302,6 +307,23 @@
return new File(System.getenv(ENV_EXTERNAL_STORAGE));
}
+ /** {@hide} */
+ public static File getLegacyExternalStorageObbDirectory() {
+ return buildPath(getLegacyExternalStorageDirectory(), DIRECTORY_ANDROID, "obb");
+ }
+
+ /** {@hide} */
+ public static File getEmulatedStorageSource(int userId) {
+ // /mnt/shell/emulated/0
+ return new File(System.getenv(ENV_EMULATED_STORAGE_SOURCE), String.valueOf(userId));
+ }
+
+ /** {@hide} */
+ public static File getEmulatedStorageObbSource() {
+ // /mnt/shell/emulated/obb
+ return new File(System.getenv(ENV_EMULATED_STORAGE_SOURCE), "obb");
+ }
+
/**
* Standard directory in which to place any audio files that should be
* in the regular list of music for the user.
diff --git a/core/java/android/os/UserHandle.aidl b/core/java/android/os/UserHandle.aidl
new file mode 100644
index 0000000..4892d32
--- /dev/null
+++ b/core/java/android/os/UserHandle.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2012, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+parcelable UserHandle;
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index 0b16316..fc18617 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -489,13 +489,14 @@
* IObbActionListener to inform it of the terminal state of the
* call.
*/
- public void mountObb(String filename, String key, IObbActionListener token, int nonce)
- throws RemoteException {
+ public void mountObb(String rawPath, String canonicalPath, String key,
+ IObbActionListener token, int nonce) throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
- _data.writeString(filename);
+ _data.writeString(rawPath);
+ _data.writeString(canonicalPath);
_data.writeString(key);
_data.writeStrongBinder((token != null ? token.asBinder() : null));
_data.writeInt(nonce);
@@ -514,13 +515,14 @@
* IObbActionListener to inform it of the terminal state of the
* call.
*/
- public void unmountObb(String filename, boolean force, IObbActionListener token,
- int nonce) throws RemoteException {
+ public void unmountObb(
+ String rawPath, boolean force, IObbActionListener token, int nonce)
+ throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
- _data.writeString(filename);
+ _data.writeString(rawPath);
_data.writeInt((force ? 1 : 0));
_data.writeStrongBinder((token != null ? token.asBinder() : null));
_data.writeInt(nonce);
@@ -536,13 +538,13 @@
* Checks whether the specified Opaque Binary Blob (OBB) is mounted
* somewhere.
*/
- public boolean isObbMounted(String filename) throws RemoteException {
+ public boolean isObbMounted(String rawPath) throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
boolean _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
- _data.writeString(filename);
+ _data.writeString(rawPath);
mRemote.transact(Stub.TRANSACTION_isObbMounted, _data, _reply, 0);
_reply.readException();
_result = 0 != _reply.readInt();
@@ -556,13 +558,13 @@
/**
* Gets the path to the mounted Opaque Binary Blob (OBB).
*/
- public String getMountedObbPath(String filename) throws RemoteException {
+ public String getMountedObbPath(String rawPath) throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
- _data.writeString(filename);
+ _data.writeString(rawPath);
mRemote.transact(Stub.TRANSACTION_getMountedObbPath, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
@@ -1042,15 +1044,14 @@
}
case TRANSACTION_mountObb: {
data.enforceInterface(DESCRIPTOR);
- String filename;
- filename = data.readString();
- String key;
- key = data.readString();
+ final String rawPath = data.readString();
+ final String canonicalPath = data.readString();
+ final String key = data.readString();
IObbActionListener observer;
observer = IObbActionListener.Stub.asInterface(data.readStrongBinder());
int nonce;
nonce = data.readInt();
- mountObb(filename, key, observer, nonce);
+ mountObb(rawPath, canonicalPath, key, observer, nonce);
reply.writeNoException();
return true;
}
@@ -1194,7 +1195,7 @@
/**
* Gets the path to the mounted Opaque Binary Blob (OBB).
*/
- public String getMountedObbPath(String filename) throws RemoteException;
+ public String getMountedObbPath(String rawPath) throws RemoteException;
/**
* Gets an Array of currently known secure container IDs
@@ -1220,7 +1221,7 @@
* Checks whether the specified Opaque Binary Blob (OBB) is mounted
* somewhere.
*/
- public boolean isObbMounted(String filename) throws RemoteException;
+ public boolean isObbMounted(String rawPath) throws RemoteException;
/*
* Returns true if the specified container is mounted
@@ -1243,8 +1244,8 @@
* MountService will call back to the supplied IObbActionListener to inform
* it of the terminal state of the call.
*/
- public void mountObb(String filename, String key, IObbActionListener token, int nonce)
- throws RemoteException;
+ public void mountObb(String rawPath, String canonicalPath, String key,
+ IObbActionListener token, int nonce) throws RemoteException;
/*
* Mount a secure container with the specified key and owner UID. Returns an
@@ -1287,7 +1288,7 @@
* MountService will call back to the supplied IObbActionListener to inform
* it of the terminal state of the call.
*/
- public void unmountObb(String filename, boolean force, IObbActionListener token, int nonce)
+ public void unmountObb(String rawPath, boolean force, IObbActionListener token, int nonce)
throws RemoteException;
/*
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 54c8709d..862a95c 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -28,6 +28,10 @@
import android.util.Log;
import android.util.SparseArray;
+import com.android.internal.util.Preconditions;
+
+import java.io.File;
+import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
@@ -443,25 +447,23 @@
* That is, shared UID applications can attempt to mount any other
* application's OBB that shares its UID.
*
- * @param filename the path to the OBB file
+ * @param rawPath the path to the OBB file
* @param key secret used to encrypt the OBB; may be <code>null</code> if no
* encryption was used on the OBB.
* @param listener will receive the success or failure of the operation
* @return whether the mount call was successfully queued or not
*/
- public boolean mountObb(String filename, String key, OnObbStateChangeListener listener) {
- if (filename == null) {
- throw new IllegalArgumentException("filename cannot be null");
- }
-
- if (listener == null) {
- throw new IllegalArgumentException("listener cannot be null");
- }
+ public boolean mountObb(String rawPath, String key, OnObbStateChangeListener listener) {
+ Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
+ Preconditions.checkNotNull(listener, "listener cannot be null");
try {
+ final String canonicalPath = new File(rawPath).getCanonicalPath();
final int nonce = mObbActionListener.addListener(listener);
- mMountService.mountObb(filename, key, mObbActionListener, nonce);
+ mMountService.mountObb(rawPath, canonicalPath, key, mObbActionListener, nonce);
return true;
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Failed to resolve path: " + rawPath, e);
} catch (RemoteException e) {
Log.e(TAG, "Failed to mount OBB", e);
}
@@ -483,24 +485,19 @@
* application's OBB that shares its UID.
* <p>
*
- * @param filename path to the OBB file
+ * @param rawPath path to the OBB file
* @param force whether to kill any programs using this in order to unmount
* it
* @param listener will receive the success or failure of the operation
* @return whether the unmount call was successfully queued or not
*/
- public boolean unmountObb(String filename, boolean force, OnObbStateChangeListener listener) {
- if (filename == null) {
- throw new IllegalArgumentException("filename cannot be null");
- }
-
- if (listener == null) {
- throw new IllegalArgumentException("listener cannot be null");
- }
+ public boolean unmountObb(String rawPath, boolean force, OnObbStateChangeListener listener) {
+ Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
+ Preconditions.checkNotNull(listener, "listener cannot be null");
try {
final int nonce = mObbActionListener.addListener(listener);
- mMountService.unmountObb(filename, force, mObbActionListener, nonce);
+ mMountService.unmountObb(rawPath, force, mObbActionListener, nonce);
return true;
} catch (RemoteException e) {
Log.e(TAG, "Failed to mount OBB", e);
@@ -512,16 +509,14 @@
/**
* Check whether an Opaque Binary Blob (OBB) is mounted or not.
*
- * @param filename path to OBB image
+ * @param rawPath path to OBB image
* @return true if OBB is mounted; false if not mounted or on error
*/
- public boolean isObbMounted(String filename) {
- if (filename == null) {
- throw new IllegalArgumentException("filename cannot be null");
- }
+ public boolean isObbMounted(String rawPath) {
+ Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
try {
- return mMountService.isObbMounted(filename);
+ return mMountService.isObbMounted(rawPath);
} catch (RemoteException e) {
Log.e(TAG, "Failed to check if OBB is mounted", e);
}
@@ -534,17 +529,15 @@
* give you the path to where you can obtain access to the internals of the
* OBB.
*
- * @param filename path to OBB image
+ * @param rawPath path to OBB image
* @return absolute path to mounted OBB image data or <code>null</code> if
* not mounted or exception encountered trying to read status
*/
- public String getMountedObbPath(String filename) {
- if (filename == null) {
- throw new IllegalArgumentException("filename cannot be null");
- }
+ public String getMountedObbPath(String rawPath) {
+ Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
try {
- return mMountService.getMountedObbPath(filename);
+ return mMountService.getMountedObbPath(rawPath);
} catch (RemoteException e) {
Log.e(TAG, "Failed to find mounted path for OBB", e);
}
diff --git a/core/java/android/service/dreams/Dream.java b/core/java/android/service/dreams/Dream.java
index dedfb0c..473414c 100644
--- a/core/java/android/service/dreams/Dream.java
+++ b/core/java/android/service/dreams/Dream.java
@@ -59,10 +59,10 @@
* <category android:name="android.intent.category.DREAM" />
* </intent-filter>
*
- * <!-- Point to configuration activity for this dream (optional) -->
+ * <!-- Point to additional information for this dream (optional) -->
* <meta-data
- * android:name="android.service.dreams.config_activity"
- * android:value="com.example.mypackage/com.example.mypackage.MyDreamSettingsActivity" />
+ * android:name="android.service.dream"
+ * android:resource="@xml/my_dream" />
* </service>
* }
* </pre>
@@ -81,12 +81,12 @@
"android.intent.category.DREAM";
/**
- * Service meta-data key for declaring an optional configuration activity.
- *
- * @see Dream
- * */
- public static final String METADATA_NAME_CONFIG_ACTIVITY =
- "android.service.dreams.config_activity";
+ * Name under which a Dream publishes information about itself.
+ * This meta-data must reference an XML resource containing
+ * a <code><{@link android.R.styleable#Dream dream}></code>
+ * tag.
+ */
+ public static final String DREAM_META_DATA = "android.service.dream";
/**
* Broadcast Action: Sent after the system starts dreaming.
@@ -361,13 +361,6 @@
return getWindow().findViewById(id);
}
- /** FIXME remove once platform dreams are updated */
- @Deprecated
- protected void lightsOut() {
- setLowProfile(true);
- setFullscreen(true);
- }
-
/**
* Marks this dream as interactive to receive input events.
*
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index da925c7..28763b3 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -21,6 +21,7 @@
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
+import android.opengl.EGL14;
import android.opengl.GLUtils;
import android.opengl.ManagedEGLContext;
import android.os.Handler;
@@ -608,12 +609,6 @@
@SuppressWarnings({"deprecation"})
static abstract class GlRenderer extends HardwareRenderer {
- // These values are not exposed in our EGL APIs
- static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
- static final int EGL_OPENGL_ES2_BIT = 4;
- static final int EGL_SURFACE_TYPE = 0x3033;
- static final int EGL_SWAP_BEHAVIOR_PRESERVED_BIT = 0x0400;
-
static final int SURFACE_STATE_ERROR = 0;
static final int SURFACE_STATE_SUCCESS = 1;
static final int SURFACE_STATE_UPDATED = 2;
@@ -953,19 +948,8 @@
return null;
}
- /*
- * Before we can issue GL commands, we need to make sure
- * the context is current and bound to a surface.
- */
- if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
- throw new Surface.OutOfResourcesException("eglMakeCurrent failed "
- + GLUtils.getEGLErrorString(sEgl.eglGetError()));
- }
-
initCaches();
- enableDirtyRegions();
-
return mEglContext.getGL();
}
@@ -990,7 +974,7 @@
abstract void initCaches();
EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
- int[] attribs = { EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL_NONE };
+ int[] attribs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL_NONE };
EGLContext context = egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT,
mGlVersion != 0 ? attribs : null);
@@ -1066,6 +1050,14 @@
throw new RuntimeException("createWindowSurface failed "
+ GLUtils.getEGLErrorString(error));
}
+
+ if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+ throw new IllegalStateException("eglMakeCurrent failed " +
+ GLUtils.getEGLErrorString(sEgl.eglGetError()));
+ }
+
+ enableDirtyRegions();
+
return true;
}
@@ -1430,7 +1422,7 @@
@Override
int[] getConfig(boolean dirtyRegions) {
return new int[] {
- EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
@@ -1439,7 +1431,7 @@
// TODO: Find a better way to choose the stencil size
EGL_STENCIL_SIZE, mShowOverdraw ? GLES20Canvas.getStencilSize() : 0,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT |
- (dirtyRegions ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0),
+ (dirtyRegions ? EGL14.EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0),
EGL_NONE
};
}
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index fa03139..123ce2a 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -17,8 +17,11 @@
package android.view;
import android.content.Context;
+import android.os.SystemClock;
import android.util.FloatMath;
+import java.util.Arrays;
+
/**
* Detects scaling transformation gestures using the supplied {@link MotionEvent}s.
* The {@link OnScaleGestureListener} callback will notify users when a particular
@@ -139,6 +142,12 @@
private int mSpanSlop;
private int mMinSpan;
+ private float[] mTouchHistoryLastAccepted;
+ private int[] mTouchHistoryDirection;
+ private long[] mTouchHistoryLastAcceptedTime;
+
+ private static final long TOUCH_STABILIZE_TIME = 128; // ms
+
/**
* Consistency verifier for debugging purposes.
*/
@@ -155,6 +164,120 @@
}
/**
+ * The touchMajor/touchMinor elements of a MotionEvent can flutter/jitter on
+ * some hardware/driver combos. Smooth it out to get kinder, gentler behavior.
+ * @param ev MotionEvent to add to the ongoing history
+ */
+ private void addTouchHistory(MotionEvent ev) {
+ final long currentTime = SystemClock.uptimeMillis();
+ final int count = ev.getPointerCount();
+ for (int i = 0; i < count; i++) {
+ final int id = ev.getPointerId(i);
+ ensureTouchHistorySize(id);
+
+ final boolean hasLastAccepted = !Float.isNaN(mTouchHistoryLastAccepted[id]);
+ boolean accept = true;
+ final int historySize = ev.getHistorySize();
+ for (int h = 0; h < historySize + 1; h++) {
+ final float major;
+ final float minor;
+ if (h < historySize) {
+ major = ev.getHistoricalTouchMajor(i, h);
+ minor = ev.getHistoricalTouchMinor(i, h);
+ } else {
+ major = ev.getTouchMajor(i);
+ minor = ev.getTouchMinor(i);
+ }
+ final float avg = (major + minor) / 2;
+
+ if (hasLastAccepted) {
+ final int directionSig = (int) Math.signum(avg - mTouchHistoryLastAccepted[id]);
+ if (directionSig != mTouchHistoryDirection[id] ||
+ (directionSig == 0 && mTouchHistoryDirection[id] == 0)) {
+ mTouchHistoryDirection[id] = directionSig;
+ final long time = h < historySize ? ev.getHistoricalEventTime(h)
+ : ev.getEventTime();
+ mTouchHistoryLastAcceptedTime[id] = time;
+ accept = false;
+ }
+ if (currentTime - mTouchHistoryLastAcceptedTime[id] < TOUCH_STABILIZE_TIME) {
+ accept = false;
+ }
+ }
+ }
+
+ if (accept) {
+ float newAccepted = (ev.getTouchMajor(i) + ev.getTouchMinor(i)) / 2;
+ if (hasLastAccepted) {
+ newAccepted = (mTouchHistoryLastAccepted[id] + newAccepted) / 2;
+ }
+ mTouchHistoryLastAccepted[id] = newAccepted;
+ mTouchHistoryDirection[id] = 0;
+ mTouchHistoryLastAcceptedTime[id] = ev.getEventTime();
+ }
+ }
+ }
+
+ /**
+ * Clear out the touch history for a given pointer id.
+ * @param id pointer id to clear
+ * @see #addTouchHistory(MotionEvent)
+ */
+ private void removeTouchHistoryForId(int id) {
+ mTouchHistoryLastAccepted[id] = Float.NaN;
+ mTouchHistoryDirection[id] = 0;
+ mTouchHistoryLastAcceptedTime[id] = 0;
+ }
+
+ /**
+ * Get the adjusted combined touchMajor/touchMinor value for a given pointer id
+ * @param id the pointer id of the data to obtain
+ * @return the adjusted major/minor value for the point at id
+ * @see #addTouchHistory(MotionEvent)
+ */
+ private float getAdjustedTouchHistory(int id) {
+ return mTouchHistoryLastAccepted[id];
+ }
+
+ /**
+ * Clear all touch history tracking. Useful in ACTION_CANCEL or ACTION_UP.
+ * @see #addTouchHistory(MotionEvent)
+ */
+ private void clearTouchHistory() {
+ Arrays.fill(mTouchHistoryLastAccepted, Float.NaN);
+ Arrays.fill(mTouchHistoryDirection, 0);
+ Arrays.fill(mTouchHistoryLastAcceptedTime, 0);
+ }
+
+ private void ensureTouchHistorySize(int id) {
+ final int requiredSize = id + 1;
+ if (mTouchHistoryLastAccepted == null || mTouchHistoryLastAccepted.length < requiredSize) {
+ final float[] newLastAccepted = new float[requiredSize];
+ final int[] newDirection = new int[requiredSize];
+ final long[] newLastAcceptedTime = new long[requiredSize];
+
+ int oldLength = 0;
+ if (mTouchHistoryLastAccepted != null) {
+ System.arraycopy(mTouchHistoryLastAccepted, 0, newLastAccepted, 0,
+ mTouchHistoryLastAccepted.length);
+ System.arraycopy(mTouchHistoryDirection, 0, newDirection, 0,
+ mTouchHistoryDirection.length);
+ System.arraycopy(mTouchHistoryLastAcceptedTime, 0, newLastAcceptedTime, 0,
+ mTouchHistoryLastAcceptedTime.length);
+ oldLength = mTouchHistoryLastAccepted.length;
+ }
+
+ Arrays.fill(newLastAccepted, oldLength, newLastAccepted.length, Float.NaN);
+ Arrays.fill(newDirection, oldLength, newDirection.length, 0);
+ Arrays.fill(newLastAcceptedTime, oldLength, newLastAcceptedTime.length, 0);
+
+ mTouchHistoryLastAccepted = newLastAccepted;
+ mTouchHistoryDirection = newDirection;
+ mTouchHistoryLastAcceptedTime = newLastAcceptedTime;
+ }
+ }
+
+ /**
* Accepts MotionEvents and dispatches events to a {@link OnScaleGestureListener}
* when appropriate.
*
@@ -186,6 +309,7 @@
}
if (streamComplete) {
+ clearTouchHistory();
return true;
}
}
@@ -208,13 +332,19 @@
final float focusX = sumX / div;
final float focusY = sumY / div;
+ if (pointerUp) {
+ removeTouchHistoryForId(event.getPointerId(event.getActionIndex()));
+ } else {
+ addTouchHistory(event);
+ }
+
// Determine average deviation from focal point
float devSumX = 0, devSumY = 0;
for (int i = 0; i < count; i++) {
if (skipIndex == i) continue;
// Average touch major and touch minor and convert the resulting diameter into a radius.
- final float touchSize = (event.getTouchMajor(i) + event.getTouchMinor(i)) / 4;
+ final float touchSize = getAdjustedTouchHistory(event.getPointerId(i));
devSumX += Math.abs(event.getX(i) - focusX) + touchSize;
devSumY += Math.abs(event.getY(i) - focusY) + touchSize;
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 3be63d5..0d16dd3 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -46,13 +46,18 @@
*
* <p>The surface is Z ordered so that it is behind the window holding its
* SurfaceView; the SurfaceView punches a hole in its window to allow its
- * surface to be displayed. The view hierarchy will take care of correctly
+ * surface to be displayed. The view hierarchy will take care of correctly
* compositing with the Surface any siblings of the SurfaceView that would
- * normally appear on top of it. This can be used to place overlays such as
+ * normally appear on top of it. This can be used to place overlays such as
* buttons on top of the Surface, though note however that it can have an
* impact on performance since a full alpha-blended composite will be performed
* each time the Surface changes.
*
+ * <p> The transparent region that makes the surface visible is based on the
+ * layout positions in the view hierarchy. If the post-layout transform
+ * properties are used to draw a sibling view on top of the SurfaceView, the
+ * view may not be properly composited with the surface.
+ *
* <p>Access to the underlying surface is provided via the SurfaceHolder interface,
* which can be retrieved by calling {@link #getHolder}.
*
@@ -62,14 +67,14 @@
* Surface is created and destroyed as the window is shown and hidden.
*
* <p>One of the purposes of this class is to provide a surface in which a
- * secondary thread can render into the screen. If you are going to use it
+ * secondary thread can render into the screen. If you are going to use it
* this way, you need to be aware of some threading semantics:
*
* <ul>
* <li> All SurfaceView and
* {@link SurfaceHolder.Callback SurfaceHolder.Callback} methods will be called
* from the thread running the SurfaceView's window (typically the main thread
- * of the application). They thus need to correctly synchronize with any
+ * of the application). They thus need to correctly synchronize with any
* state that is also touched by the drawing thread.
* <li> You must ensure that the drawing thread only touches the underlying
* Surface while it is valid -- between
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ecacaca..12eb800 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2141,6 +2141,17 @@
*/
static final int PFLAG2_PADDING_RESOLVED = 0x20000000;
+ /**
+ * Flag indicating that the start/end drawables has been resolved into left/right ones.
+ */
+ static final int PFLAG2_DRAWABLE_RESOLVED = 0x40000000;
+
+ /**
+ * Group of bits indicating that RTL properties resolution is done.
+ */
+ static final int ALL_RTL_PROPERTIES_RESOLVED = PFLAG2_LAYOUT_DIRECTION_RESOLVED |
+ PFLAG2_TEXT_DIRECTION_RESOLVED | PFLAG2_TEXT_ALIGNMENT_RESOLVED;
+
// There are a couple of flags left in mPrivateFlags2
/* End of masks for mPrivateFlags2 */
@@ -3199,9 +3210,12 @@
mResources = context != null ? context.getResources() : null;
mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED;
// Set layout and text direction defaults
- mPrivateFlags2 = (LAYOUT_DIRECTION_DEFAULT << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) |
+ mPrivateFlags2 =
+ (LAYOUT_DIRECTION_DEFAULT << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) |
(TEXT_DIRECTION_DEFAULT << PFLAG2_TEXT_DIRECTION_MASK_SHIFT) |
+ (PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT) |
(TEXT_ALIGNMENT_DEFAULT << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) |
+ (PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT) |
(IMPORTANT_FOR_ACCESSIBILITY_DEFAULT << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);
@@ -3419,7 +3433,8 @@
break;
case com.android.internal.R.styleable.View_layoutDirection:
// Clear any layout direction flags (included resolved bits) already set
- mPrivateFlags2 &= ~(PFLAG2_LAYOUT_DIRECTION_MASK | PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK);
+ mPrivateFlags2 &=
+ ~(PFLAG2_LAYOUT_DIRECTION_MASK | PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK);
// Set the layout direction flags depending on the value of the attribute
final int layoutDirection = a.getInt(attr, -1);
final int value = (layoutDirection != -1) ?
@@ -5772,6 +5787,8 @@
* {@link #LAYOUT_DIRECTION_INHERIT} or
* {@link #LAYOUT_DIRECTION_LOCALE}.
* @attr ref android.R.styleable#View_layoutDirection
+ *
+ * @hide
*/
@ViewDebug.ExportedProperty(category = "layout", mapping = {
@ViewDebug.IntToString(from = LAYOUT_DIRECTION_LTR, to = "LTR"),
@@ -5779,7 +5796,7 @@
@ViewDebug.IntToString(from = LAYOUT_DIRECTION_INHERIT, to = "INHERIT"),
@ViewDebug.IntToString(from = LAYOUT_DIRECTION_LOCALE, to = "LOCALE")
})
- private int getRawLayoutDirection() {
+ public int getRawLayoutDirection() {
return (mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_MASK) >> PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT;
}
@@ -5787,10 +5804,16 @@
* Set the layout direction for this view. This will propagate a reset of layout direction
* resolution to the view's children and resolve layout direction for this view.
*
- * @param layoutDirection One of {@link #LAYOUT_DIRECTION_LTR},
- * {@link #LAYOUT_DIRECTION_RTL},
- * {@link #LAYOUT_DIRECTION_INHERIT} or
- * {@link #LAYOUT_DIRECTION_LOCALE}.
+ * @param layoutDirection the layout direction to set. Should be one of:
+ *
+ * {@link #LAYOUT_DIRECTION_LTR},
+ * {@link #LAYOUT_DIRECTION_RTL},
+ * {@link #LAYOUT_DIRECTION_INHERIT},
+ * {@link #LAYOUT_DIRECTION_LOCALE}.
+ *
+ * Resolution will be done if the value is set to LAYOUT_DIRECTION_INHERIT. The resolution
+ * proceeds up the parent chain of the view to get the value. If there is no parent, then it
+ * will return the default {@link #LAYOUT_DIRECTION_LTR}.
*
* @attr ref android.R.styleable#View_layoutDirection
*/
@@ -5803,11 +5826,8 @@
// Set the new layout direction (filtered)
mPrivateFlags2 |=
((layoutDirection << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) & PFLAG2_LAYOUT_DIRECTION_MASK);
- resolveRtlProperties();
- // Notify changes
- onRtlPropertiesChanged();
- // ... and ask for a layout pass
- requestLayout();
+ // We need to resolve all RTL properties as they all depend on layout direction
+ resolveRtlPropertiesIfNeeded();
}
}
@@ -5816,6 +5836,9 @@
*
* @return {@link #LAYOUT_DIRECTION_RTL} if the layout direction is RTL or returns
* {@link #LAYOUT_DIRECTION_LTR} if the layout direction is not RTL.
+ *
+ * For compatibility, this will return {@link #LAYOUT_DIRECTION_LTR} if API version
+ * is lower than {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}.
*/
@ViewDebug.ExportedProperty(category = "layout", mapping = {
@ViewDebug.IntToString(from = LAYOUT_DIRECTION_LTR, to = "RESOLVED_DIRECTION_LTR"),
@@ -5827,12 +5850,8 @@
mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED;
return LAYOUT_DIRECTION_LTR;
}
- // The layout direction will be resolved only if needed
- if ((mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED) != PFLAG2_LAYOUT_DIRECTION_RESOLVED) {
- resolveLayoutDirection();
- }
- return ((mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) == PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ?
- LAYOUT_DIRECTION_RTL : LAYOUT_DIRECTION_LTR;
+ return ((mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ==
+ PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ? LAYOUT_DIRECTION_RTL : LAYOUT_DIRECTION_LTR;
}
/**
@@ -11474,10 +11493,6 @@
jumpDrawablesToCurrentState();
- resolveRtlProperties();
- // Notify changes
- onRtlPropertiesChanged();
-
clearAccessibilityFocus();
if (isFocused()) {
InputMethodManager imm = InputMethodManager.peekInstance();
@@ -11490,25 +11505,41 @@
}
/**
- * Resolve all RTL related properties
+ * Resolve all RTL related properties.
*/
- void resolveRtlProperties() {
- // Order is important here: LayoutDirection MUST be resolved first...
- resolveLayoutDirection();
+ void resolveRtlPropertiesIfNeeded() {
+ if (!needRtlPropertiesResolution()) return;
+
+ // Order is important here: LayoutDirection MUST be resolved first
+ if (!isLayoutDirectionResolved()) {
+ resolveLayoutDirection();
+ resolveLayoutParams();
+ }
// ... then we can resolve the others properties depending on the resolved LayoutDirection.
- resolveTextDirection();
- resolveTextAlignment();
- resolvePadding();
- resolveLayoutParams();
- resolveDrawables();
+ if (!isTextDirectionResolved()) {
+ resolveTextDirection();
+ }
+ if (!isTextAlignmentResolved()) {
+ resolveTextAlignment();
+ }
+ if (!isPaddingResolved()) {
+ resolvePadding();
+ }
+ if (!isDrawablesResolved()) {
+ resolveDrawables();
+ }
+ requestLayout();
+ invalidate(true);
+ onRtlPropertiesChanged();
}
- // Reset resolution of all RTL related properties
+ // Reset resolution of all RTL related properties.
void resetRtlProperties() {
resetResolvedLayoutDirection();
resetResolvedTextDirection();
resetResolvedTextAlignment();
resetResolvedPadding();
+ resetResolvedDrawables();
}
/**
@@ -11538,6 +11569,13 @@
}
/**
+ * @return true if RTL properties need resolution.
+ */
+ private boolean needRtlPropertiesResolution() {
+ return (mPrivateFlags2 & ALL_RTL_PROPERTIES_RESOLVED) != ALL_RTL_PROPERTIES_RESOLVED;
+ }
+
+ /**
* Called when any RTL property (layout direction or text direction or text alignment) has
* been changed.
*
@@ -11614,7 +11652,8 @@
}
/**
- * Reset the resolved layout direction.
+ * Reset the resolved layout direction. Layout direction will be resolved during a call to
+ * {@link #onMeasure(int, int)}.
*
* @hide
*/
@@ -11624,6 +11663,8 @@
}
/**
+ * @return true if the layout direction is inherited.
+ *
* @hide
*/
public boolean isLayoutDirectionInherited() {
@@ -11631,12 +11672,19 @@
}
/**
+ * @return true if layout direction has been resolved.
+ */
+ private boolean isLayoutDirectionResolved() {
+ return (mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED) == PFLAG2_LAYOUT_DIRECTION_RESOLVED;
+ }
+
+ /**
* Return if padding has been resolved
*
* @hide
*/
boolean isPaddingResolved() {
- return (mPrivateFlags2 & PFLAG2_PADDING_RESOLVED) != 0;
+ return (mPrivateFlags2 & PFLAG2_PADDING_RESOLVED) == PFLAG2_PADDING_RESOLVED;
}
/**
@@ -14116,6 +14164,7 @@
if (mBackground != null) {
mBackground.setLayoutDirection(getLayoutDirection());
}
+ mPrivateFlags2 |= PFLAG2_DRAWABLE_RESOLVED;
onResolveDrawables(getLayoutDirection());
}
@@ -14134,6 +14183,14 @@
public void onResolveDrawables(int layoutDirection) {
}
+ private void resetResolvedDrawables() {
+ mPrivateFlags2 &= ~PFLAG2_DRAWABLE_RESOLVED;
+ }
+
+ private boolean isDrawablesResolved() {
+ return (mPrivateFlags2 & PFLAG2_DRAWABLE_RESOLVED) == PFLAG2_DRAWABLE_RESOLVED;
+ }
+
/**
* If your view subclass is displaying its own Drawable objects, it should
* override this function and return true for any Drawable it is
@@ -14403,6 +14460,7 @@
padding = new Rect();
sThreadLocal.set(padding);
}
+ resetResolvedDrawables();
background.setLayoutDirection(getLayoutDirection());
if (background.getPadding(padding)) {
resetResolvedPadding();
@@ -15379,9 +15437,7 @@
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
- if (!isPaddingResolved()) {
- resolvePadding();
- }
+ resolveRtlPropertiesIfNeeded();
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
@@ -16526,6 +16582,10 @@
* {@link #TEXT_DIRECTION_LTR},
* {@link #TEXT_DIRECTION_RTL},
* {@link #TEXT_DIRECTION_LOCALE}
+ *
+ * Resolution will be done if the value is set to TEXT_DIRECTION_INHERIT. The resolution
+ * proceeds up the parent chain of the view to get the value. If there is no parent, then it will
+ * return the default {@link #TEXT_DIRECTION_FIRST_STRONG}.
*/
public void setTextDirection(int textDirection) {
if (getRawTextDirection() != textDirection) {
@@ -16534,6 +16594,8 @@
resetResolvedTextDirection();
// Set the new text direction
mPrivateFlags2 |= ((textDirection << PFLAG2_TEXT_DIRECTION_MASK_SHIFT) & PFLAG2_TEXT_DIRECTION_MASK);
+ // Do resolution
+ resolveTextDirection();
// Notify change
onRtlPropertiesChanged();
// Refresh
@@ -16545,11 +16607,6 @@
/**
* Return the resolved text direction.
*
- * This needs resolution if the value is TEXT_DIRECTION_INHERIT. The resolution matches what has
- * been set by {@link #setTextDirection(int)} if it is not TEXT_DIRECTION_INHERIT, otherwise the
- * resolution proceeds up the parent chain of the view. If there is no parent, then it will
- * return the default {@link #TEXT_DIRECTION_FIRST_STRONG}.
- *
* @return the resolved text direction. Returns one of:
*
* {@link #TEXT_DIRECTION_FIRST_STRONG}
@@ -16559,10 +16616,6 @@
* {@link #TEXT_DIRECTION_LOCALE}
*/
public int getTextDirection() {
- // The text direction will be resolved only if needed
- if ((mPrivateFlags2 & PFLAG2_TEXT_DIRECTION_RESOLVED) != PFLAG2_TEXT_DIRECTION_RESOLVED) {
- resolveTextDirection();
- }
return (mPrivateFlags2 & PFLAG2_TEXT_DIRECTION_RESOLVED_MASK) >> PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT;
}
@@ -16601,6 +16654,8 @@
} else {
// We cannot do the resolution if there is no parent, so use the default one
mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
+ // Resolution will need to happen again later
+ return;
}
break;
case TEXT_DIRECTION_FIRST_STRONG:
@@ -16639,16 +16694,21 @@
}
/**
- * Reset resolved text direction. Text direction can be resolved with a call to
- * getTextDirection().
+ * Reset resolved text direction. Text direction will be resolved during a call to
+ * {@link #onMeasure(int, int)}.
*
* @hide
*/
public void resetResolvedTextDirection() {
+ // Reset any previous text direction resolution
mPrivateFlags2 &= ~(PFLAG2_TEXT_DIRECTION_RESOLVED | PFLAG2_TEXT_DIRECTION_RESOLVED_MASK);
+ // Set to default value
+ mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
}
/**
+ * @return true if text direction is inherited.
+ *
* @hide
*/
public boolean isTextDirectionInherited() {
@@ -16656,6 +16716,13 @@
}
/**
+ * @return true if text direction is resolved.
+ */
+ private boolean isTextDirectionResolved() {
+ return (mPrivateFlags2 & PFLAG2_TEXT_DIRECTION_RESOLVED) == PFLAG2_TEXT_DIRECTION_RESOLVED;
+ }
+
+ /**
* Return the value specifying the text alignment or policy that was set with
* {@link #setTextAlignment(int)}.
*
@@ -16697,6 +16764,10 @@
* {@link #TEXT_ALIGNMENT_VIEW_START},
* {@link #TEXT_ALIGNMENT_VIEW_END}
*
+ * Resolution will be done if the value is set to TEXT_ALIGNMENT_INHERIT. The resolution
+ * proceeds up the parent chain of the view to get the value. If there is no parent, then it
+ * will return the default {@link #TEXT_ALIGNMENT_GRAVITY}.
+ *
* @attr ref android.R.styleable#View_textAlignment
*/
public void setTextAlignment(int textAlignment) {
@@ -16705,7 +16776,10 @@
mPrivateFlags2 &= ~PFLAG2_TEXT_ALIGNMENT_MASK;
resetResolvedTextAlignment();
// Set the new text alignment
- mPrivateFlags2 |= ((textAlignment << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) & PFLAG2_TEXT_ALIGNMENT_MASK);
+ mPrivateFlags2 |=
+ ((textAlignment << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) & PFLAG2_TEXT_ALIGNMENT_MASK);
+ // Do resolution
+ resolveTextAlignment();
// Notify change
onRtlPropertiesChanged();
// Refresh
@@ -16717,10 +16791,6 @@
/**
* Return the resolved text alignment.
*
- * The resolved text alignment. This needs resolution if the value is
- * TEXT_ALIGNMENT_INHERIT. The resolution matches {@link #setTextAlignment(int)} if it is
- * not TEXT_ALIGNMENT_INHERIT, otherwise resolution proceeds up the parent chain of the view.
- *
* @return the resolved text alignment. Returns one of:
*
* {@link #TEXT_ALIGNMENT_GRAVITY},
@@ -16740,11 +16810,8 @@
@ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_END, to = "VIEW_END")
})
public int getTextAlignment() {
- // If text alignment is not resolved, then resolve it
- if ((mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_RESOLVED) != PFLAG2_TEXT_ALIGNMENT_RESOLVED) {
- resolveTextAlignment();
- }
- return (mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK) >> PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT;
+ return (mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK) >>
+ PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT;
}
/**
@@ -16786,6 +16853,8 @@
else {
// We cannot do the resolution if there is no parent so use the default
mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+ // Resolution will need to happen again later
+ return;
}
break;
case TEXT_ALIGNMENT_GRAVITY:
@@ -16825,16 +16894,21 @@
}
/**
- * Reset resolved text alignment.
+ * Reset resolved text alignment. Text alignment will be resolved during a call to
+ * {@link #onMeasure(int, int)}.
*
* @hide
*/
public void resetResolvedTextAlignment() {
// Reset any previous text alignment resolution
mPrivateFlags2 &= ~(PFLAG2_TEXT_ALIGNMENT_RESOLVED | PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK);
+ // Set to default
+ mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
}
/**
+ * @return true if text alignment is inherited.
+ *
* @hide
*/
public boolean isTextAlignmentInherited() {
@@ -16842,6 +16916,13 @@
}
/**
+ * @return true if text alignment is resolved.
+ */
+ private boolean isTextAlignmentResolved() {
+ return (mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_RESOLVED) == PFLAG2_TEXT_ALIGNMENT_RESOLVED;
+ }
+
+ /**
* Generate a value suitable for use in {@link #setId(int)}.
* This value will not collide with ID values generated at build time by aapt for R.id.
*
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 34411ea..41890d6 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3391,11 +3391,6 @@
if (child.hasTransientState()) {
childHasTransientStateChanged(child, true);
}
-
- if (child.isLayoutDirectionInherited()) {
- child.resetResolvedLayoutDirection();
- child.resolveRtlProperties();
- }
}
private void addInArray(View child, int index) {
@@ -3621,7 +3616,7 @@
childHasTransientStateChanged(view, false);
}
- view.resetResolvedLayoutDirection();
+ view.resetRtlProperties();
onViewRemoved(view);
@@ -5261,19 +5256,92 @@
* @hide
*/
@Override
+ public void resolveLayoutDirection() {
+ super.resolveLayoutDirection();
+
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.isLayoutDirectionInherited()) {
+ child.resolveLayoutDirection();
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void resolveTextDirection() {
+ super.resolveTextDirection();
+
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.isTextDirectionInherited()) {
+ child.resolveTextDirection();
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void resolveTextAlignment() {
+ super.resolveTextAlignment();
+
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.isTextAlignmentInherited()) {
+ child.resolveTextAlignment();
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
public void resetResolvedLayoutDirection() {
super.resetResolvedLayoutDirection();
- // Take care of resetting the children resolution too
int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.isLayoutDirectionInherited()) {
child.resetResolvedLayoutDirection();
}
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void resetResolvedTextDirection() {
+ super.resetResolvedTextDirection();
+
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
if (child.isTextDirectionInherited()) {
child.resetResolvedTextDirection();
}
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void resetResolvedTextAlignment() {
+ super.resetResolvedTextAlignment();
+
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
if (child.isTextAlignmentInherited()) {
child.resetResolvedTextAlignment();
}
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index 63a0870..c7da818 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -36,6 +36,10 @@
/**
* This widget display an analogic clock with two hands for hours and
* minutes.
+ *
+ * @attr ref android.R.styleable#AnalogClock_dial
+ * @attr ref android.R.styleable#AnalogClock_hand_hour
+ * @attr ref android.R.styleable#AnalogClock_hand_minute
*/
@RemoteView
public class AnalogClock extends View {
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index 87396fb..7ca8322 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -789,6 +789,7 @@
if (resizeWidth) {
int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) +
pleft + pright;
+ widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec);
if (newWidth <= widthSize) {
widthSize = newWidth;
done = true;
@@ -799,6 +800,7 @@
if (!done && resizeHeight) {
int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) +
ptop + pbottom;
+ heightSize = resolveAdjustedSize(newHeight, mMaxHeight, heightMeasureSpec);
if (newHeight <= heightSize) {
heightSize = newHeight;
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 90f55bf..4b5dfb8 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -2166,6 +2166,8 @@
* @param value The value to pass to the method.
*/
public void setUri(int viewId, String methodName, Uri value) {
+ // Resolve any filesystem path before sending remotely
+ value = value.getCanonicalUri();
addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value));
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 09457cc..84e1d95 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -256,9 +256,13 @@
* @return Whether the pattern matches the stored one.
*/
public boolean checkPattern(List<LockPatternView.Cell> pattern) {
- int userId = getCurrentOrCallingUserId();
+ final int userId = getCurrentOrCallingUserId();
try {
- return getLockSettings().checkPattern(patternToHash(pattern), userId);
+ final boolean matched = getLockSettings().checkPattern(patternToHash(pattern), userId);
+ if (matched && (userId == UserHandle.USER_OWNER)) {
+ KeyStore.getInstance().password(patternToString(pattern));
+ }
+ return matched;
} catch (RemoteException re) {
return true;
}
@@ -271,9 +275,14 @@
* @return Whether the password matches the stored one.
*/
public boolean checkPassword(String password) {
- int userId = getCurrentOrCallingUserId();
+ final int userId = getCurrentOrCallingUserId();
try {
- return getLockSettings().checkPassword(passwordToHash(password), userId);
+ final boolean matched = getLockSettings().checkPassword(passwordToHash(password),
+ userId);
+ if (matched && (userId == UserHandle.USER_OWNER)) {
+ KeyStore.getInstance().password(password);
+ }
+ return matched;
} catch (RemoteException re) {
return true;
}
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index f522a9a..56db116 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -242,6 +242,18 @@
return (jint) AudioSystem::getDevicesForStream(static_cast <audio_stream_type_t>(stream));
}
+static jint
+android_media_AudioSystem_getPrimaryOutputSamplingRate(JNIEnv *env, jobject clazz)
+{
+ return (jint) AudioSystem::getPrimaryOutputSamplingRate();
+}
+
+static jint
+android_media_AudioSystem_getPrimaryOutputFrameCount(JNIEnv *env, jobject clazz)
+{
+ return (jint) AudioSystem::getPrimaryOutputFrameCount();
+}
+
// ----------------------------------------------------------------------------
static JNINativeMethod gMethods[] = {
@@ -263,6 +275,8 @@
{"setMasterMute", "(Z)I", (void *)android_media_AudioSystem_setMasterMute},
{"getMasterMute", "()Z", (void *)android_media_AudioSystem_getMasterMute},
{"getDevicesForStream", "(I)I", (void *)android_media_AudioSystem_getDevicesForStream},
+ {"getPrimaryOutputSamplingRate", "()I", (void *)android_media_AudioSystem_getPrimaryOutputSamplingRate},
+ {"getPrimaryOutputFrameCount", "()I", (void *)android_media_AudioSystem_getPrimaryOutputFrameCount},
};
int register_android_media_AudioSystem(JNIEnv *env)
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 0755038..9759bdc 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5305,6 +5305,19 @@
<attr name="description" />
</declare-styleable>
+ <!-- Use <code>dream</code> as the root tag of the XML resource that
+ describes an
+ {@link android.service.dreams.Dream}, which is
+ referenced from its
+ {@link android.service.dreams.Dream#DREAM_META_DATA}
+ meta-data entry. Described here are the attributes that can be
+ included in that tag. -->
+ <declare-styleable name="Dream">
+ <!-- Component name of an activity that allows the user to modify
+ the settings for this dream. -->
+ <attr name="settingsActivity" />
+ </declare-styleable>
+
<!-- =============================== -->
<!-- Accounts package class attributes -->
<!-- =============================== -->
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java
index 39e2cf2..79d928c 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java
@@ -219,15 +219,16 @@
// Stress Wifi reconnection to secure net after sleep
@LargeTest
public void testWifiReconnectionAfterSleep() {
- int value = Settings.System.getInt(mRunner.getContext().getContentResolver(),
- Settings.System.WIFI_SLEEP_POLICY, -1);
- if (value < 0) {
- Settings.System.putInt(mRunner.getContext().getContentResolver(),
- Settings.System.WIFI_SLEEP_POLICY, Settings.System.WIFI_SLEEP_POLICY_DEFAULT);
+ int value = Settings.Global.getInt(mRunner.getContext().getContentResolver(),
+ Settings.Global.WIFI_SLEEP_POLICY, -1);
+ log("wifi sleep policy is: " + value);
+ if (value != Settings.Global.WIFI_SLEEP_POLICY_DEFAULT) {
+ Settings.Global.putInt(mRunner.getContext().getContentResolver(),
+ Settings.Global.WIFI_SLEEP_POLICY, Settings.Global.WIFI_SLEEP_POLICY_DEFAULT);
log("set wifi sleep policy to default value");
}
- Settings.Secure.putLong(mRunner.getContext().getContentResolver(),
- Settings.Secure.WIFI_IDLE_MS, WIFI_IDLE_MS);
+ Settings.Global.putLong(mRunner.getContext().getContentResolver(),
+ Settings.Global.WIFI_IDLE_MS, WIFI_IDLE_MS);
// Connect to a Wi-Fi network
WifiConfiguration config = new WifiConfiguration();
diff --git a/docs/html/about/versions/jelly-bean.jd b/docs/html/about/versions/jelly-bean.jd
index 485a1bb..0583e12 100644
--- a/docs/html/about/versions/jelly-bean.jd
+++ b/docs/html/about/versions/jelly-bean.jd
@@ -303,12 +303,12 @@
<p>Smart app updates is a new feature of Google Play that introduces a better way of delivering <strong>app updates</strong> to devices. When developers publish an update, Google Play now delivers only the <strong>bits that have changed</strong> to devices, rather than the entire APK. This makes the updates much lighter-weight in most cases, so they are faster to download, save the device’s battery, and conserve bandwidth usage on users’ mobile data plan. On average, a smart app update is about <strong>1/3 the size</strong> of a full APK update.</p>
-<h3 id="gps">Google Play services (coming soon)</h3>
+<h3 id="gps">Google Play services</h3>
<p>Google Play services helps developers to <strong>integrate Google services</strong> such as authentication and Google+ into their apps delivered through Google Play.</p>
-<p>Google Play services will be automatically provisioned to end user devices by Google Play, so all you need is a <strong>thin client library</strong> in your apps.</p>
+<p>Google Play services is automatically provisioned to end user devices by Google Play, so all you need is a <strong>thin client library</strong> in your apps.</p>
<p>Because your app only contains the small client library, you can take advantage of these services without a big increase in download size and storage footprint. Also, Google Play will <strong>deliver regular updates</strong> to the services, without developers needing to publish app updates to take advantage of them.</p>
-<p>For more information about the APIs included in Google Play Services, see the <a href="http://developers.google.com/android/google-play-services/index.html">Google Play Services</a> developer page.</p>
+<p>For more information about the APIs included in Google Play Services, see the <a href="http://developers.google.com/android/google-play-services/index.html">Google Play services</a> developer page.</p>
diff --git a/docs/html/guide/google/play/services.jd b/docs/html/guide/google/play/services.jd
index 519d78b..092642c 100644
--- a/docs/html/guide/google/play/services.jd
+++ b/docs/html/guide/google/play/services.jd
@@ -1,24 +1,25 @@
page.title=Google Play Services
-
@jd:body
-
-<p>Google Play services is a platform that is delivered through the Google Play Store that
+<p>
+ Google Play services is a platform that is delivered through the Google Play Store that
offers integration with Google products, such as Google+, into Android apps.
The Google Play services framework consists of a services component
- that runs on the device and a thin client library that you package with your app. </p>
-
-
+ that runs on the device and a thin client library that you package with your app.
+</p>
<div class="distribute-features col-13">
- <ul>
- <li style="border-top: 1px solid #F80;"><h5>Easy Authentication</h5> Your app can leverage the user's
- existing Google account on the device without having to go through
- tedious authentication flows. A few clicks from the user and you're set!
- <br /> <a href="https://developers.google.com/android/google-play-services">Learn more »</a>
+<ul>
+ <li style="border-top: 1px solid #F80;"><h5>Easy Authentication</h5>
+ Your app can leverage the user's existing Google account on the device without having to go
+ through tedious authentication flows. A few clicks from the user and you're set!
+ <br/>
+ <a href="https://developers.google.com/android/google-play-services">Learn more »</a>
</li>
- <li style="border-top: 1px solid #F80;"><h5>Google+ Integration</h5> Google Play services makes it
- easy for your app to integrate with Sign in with Google+, +1 button, and Google+ history.
- <br /> <a href="https://developers.google.com/android/google-play-services">Learn more »</a>
- </li>
- </ul>
-
-</div>
\ No newline at end of file
+ <li style="border-top: 1px solid #F80;"><h5>Google+ Integration</h5>
+ Google Play services makes it easy for your app to integrate with Google+ sign-in and the +1
+ button.
+ <br/>
+ <a href="https://developers.google.com/android/google-play-services">Learn more »</a>
+ </li>
+</ul>
+
+</div>
diff --git a/docs/html/guide/topics/appwidgets/index.jd b/docs/html/guide/topics/appwidgets/index.jd
index 5a4e03a..7e031d9 100644
--- a/docs/html/guide/topics/appwidgets/index.jd
+++ b/docs/html/guide/topics/appwidgets/index.jd
@@ -117,7 +117,7 @@
application's
<code>AndroidManifest.xml</code> file. For example:</p>
-<pre>
+<pre style="clear:right">
<receiver android:name="ExampleAppWidgetProvider" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@@ -815,8 +815,7 @@
sample</a>:</p>
<p>
-<img src="{@docRoot}resources/samples/images/StackWidget.png" alt="StackView
-Widget" />
+<img src="{@docRoot}resources/images/StackWidget.png" alt="" />
</p>
<p>This sample consists of a stack of 10 views, which display the values
diff --git a/docs/html/training/basics/activity-lifecycle/recreating.jd b/docs/html/training/basics/activity-lifecycle/recreating.jd
index 8c7126a..1b88e19 100644
--- a/docs/html/training/basics/activity-lifecycle/recreating.jd
+++ b/docs/html/training/basics/activity-lifecycle/recreating.jd
@@ -54,20 +54,25 @@
<p>By default, the system uses the {@link android.os.Bundle} instance state to save information
about each {@link android.view.View} object in your activity layout (such as the text value entered
into an {@link android.widget.EditText} object). So, if your activity instance is destroyed and
-recreated, the state of the layout is automatically restored to its previous state. However, your
+recreated, the state of the layout is restored to its previous state with no
+code required by you. However, your
activity might have more state information that you'd like to restore, such as member variables that
track the user's progress in the activity.</p>
-<p>In order for you to add additional data to the saved instance state for your activity, there's an
-additional callback method in the activity lifecycle that's not shown in the illustration from
-previous lessons. The method is {@link android.app.Activity#onSaveInstanceState
-onSaveInstanceState()} and the system calls it when the user is leaving your activity. When the
-system calls this method, it passes the {@link android.os.Bundle} object that will be saved in the
-event that your activity is destroyed unexpectedly so you can add additional information to it. Then
-if the system must recreate the activity instance after it was destroyed, it passes the same {@link
-android.os.Bundle} object to your activity's {@link android.app.Activity#onRestoreInstanceState
-onRestoreInstanceState()} method and also to your {@link android.app.Activity#onCreate onCreate()}
-method.</p>
+<p class="note"><strong>Note:</strong> In order for the Android system to restore the state of
+the views in your activity, <strong>each view must have a unique ID</strong>, supplied by the
+<a href="{@docRoot}reference/android/view/View.html#attr_android:id">{@code
+android:id}</a> attribute.</p>
+
+<p>To save additional data about the activity state, you must override
+the {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} callback method.
+The system calls this method when the user is leaving your activity
+and passes it the {@link android.os.Bundle} object that will be saved in the
+event that your activity is destroyed unexpectedly. If
+the system must recreate the activity instance later, it passes the same {@link
+android.os.Bundle} object to both the {@link android.app.Activity#onRestoreInstanceState
+onRestoreInstanceState()} and {@link android.app.Activity#onCreate onCreate()}
+methods.</p>
<img src="{@docRoot}images/training/basics/basic-lifecycle-savestate.png" />
<p class="img-caption"><strong>Figure 2.</strong> As the system begins to stop your activity, it
diff --git a/include/storage/IMountService.h b/include/storage/IMountService.h
index 43df7f0..c3d34d8 100644
--- a/include/storage/IMountService.h
+++ b/include/storage/IMountService.h
@@ -21,6 +21,8 @@
#include <storage/IMountShutdownObserver.h>
#include <storage/IObbActionListener.h>
+#include <utils/String8.h>
+
#include <binder/IInterface.h>
#include <binder/Parcel.h>
@@ -60,8 +62,9 @@
String16*& containers) = 0;
virtual void shutdown(const sp<IMountShutdownObserver>& observer) = 0;
virtual void finishMediaUpdate() = 0;
- virtual void mountObb(const String16& filename, const String16& key,
- const sp<IObbActionListener>& token, const int32_t nonce) = 0;
+ virtual void mountObb(const String16& rawPath, const String16& canonicalPath,
+ const String16& key, const sp<IObbActionListener>& token,
+ const int32_t nonce) = 0;
virtual void unmountObb(const String16& filename, const bool force,
const sp<IObbActionListener>& token, const int32_t nonce) = 0;
virtual bool isObbMounted(const String16& filename) = 0;
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index 898962a..f0f72f9 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -227,21 +227,29 @@
textureCache.clearGarbage();
pathCache.clearGarbage();
- Mutex::Autolock _l(mGarbageLock);
+ Vector<DisplayList*> displayLists;
+ Vector<Layer*> layers;
- size_t count = mLayerGarbage.size();
- for (size_t i = 0; i < count; i++) {
- Layer* layer = mLayerGarbage.itemAt(i);
- LayerRenderer::destroyLayer(layer);
+ { // scope for the lock
+ Mutex::Autolock _l(mGarbageLock);
+ displayLists = mDisplayListGarbage;
+ layers = mLayerGarbage;
+ mDisplayListGarbage.clear();
+ mLayerGarbage.clear();
}
- mLayerGarbage.clear();
- count = mDisplayListGarbage.size();
+ size_t count = displayLists.size();
for (size_t i = 0; i < count; i++) {
- DisplayList* displayList = mDisplayListGarbage.itemAt(i);
+ DisplayList* displayList = displayLists.itemAt(i);
delete displayList;
}
- mDisplayListGarbage.clear();
+
+ count = layers.size();
+ for (size_t i = 0; i < count; i++) {
+ Layer* layer = layers.itemAt(i);
+ delete layer;
+ }
+ layers.clear();
}
void Caches::deleteLayerDeferred(Layer* layer) {
diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
index cd2e571..882e4bb 100644
--- a/libs/hwui/Layer.cpp
+++ b/libs/hwui/Layer.cpp
@@ -47,12 +47,16 @@
if (mesh) delete mesh;
if (meshIndices) delete meshIndices;
if (colorFilter) Caches::getInstance().resourceCache.decrementRefcount(colorFilter);
+ removeFbo();
+ deleteTexture();
+}
+
+void Layer::removeFbo() {
if (fbo) {
LayerRenderer::flushLayer(this);
Caches::getInstance().fboCache.put(fbo);
fbo = 0;
}
- deleteTexture();
}
void Layer::setPaint(SkPaint* paint) {
@@ -69,5 +73,7 @@
}
}
+
+
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index 59c66d7..69be317 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -48,6 +48,8 @@
Layer(const uint32_t layerWidth, const uint32_t layerHeight);
~Layer();
+ void removeFbo();
+
/**
* Sets this layer's region to a rectangle. Computes the appropriate
* texture coordinates.
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index c581041..f2e7f66 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -307,6 +307,7 @@
#if DEBUG_LAYER_RENDERER
Caches::getInstance().layerCache.dump();
#endif
+ layer->removeFbo();
layer->region.clear();
}
}
@@ -315,6 +316,7 @@
void LayerRenderer::destroyLayerDeferred(Layer* layer) {
if (layer) {
LAYER_RENDERER_LOGD("Deferring layer destruction, fbo = %d", layer->getFbo());
+
Caches::getInstance().deleteLayerDeferred(layer);
}
}
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index d0d1d93..87c3a47 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -2729,13 +2729,13 @@
mCaches.activeTexture(0);
if (CC_LIKELY(!layer->region.isEmpty())) {
+ SkiaColorFilter* oldFilter = mColorFilter;
+ mColorFilter = layer->getColorFilter();
+
if (layer->region.isRect()) {
composeLayerRect(layer, layer->regionRect);
} else if (layer->mesh) {
const float a = layer->getAlpha() / 255.0f;
- SkiaColorFilter *oldFilter = mColorFilter;
- mColorFilter = layer->getColorFilter();
-
setupDraw();
setupDrawWithTexture();
setupDrawColor(a, a, a, a);
@@ -2764,13 +2764,13 @@
finishDrawTexture();
- mColorFilter = oldFilter;
-
#if DEBUG_LAYERS_AS_REGIONS
drawRegionRects(layer->region);
#endif
}
+ mColorFilter = oldFilter;
+
if (debugLayerUpdate) {
drawColorRect(x, y, x + layer->layer.getWidth(), y + layer->layer.getHeight(),
0x7f00ff00, SkXfermode::kSrcOver_Mode);
diff --git a/libs/hwui/ResourceCache.cpp b/libs/hwui/ResourceCache.cpp
index 39e64bc..81f7b94 100644
--- a/libs/hwui/ResourceCache.cpp
+++ b/libs/hwui/ResourceCache.cpp
@@ -325,9 +325,8 @@
}
break;
case kLayer: {
- // No need to check for hasInstance, layers only exist
- // when we have a Caches instance
- Caches::getInstance().deleteLayerDeferred((Layer*) resource);
+ Layer* layer = (Layer*) resource;
+ Caches::getInstance().deleteLayerDeferred(layer);
}
break;
}
diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h
index 1adf2c7..03e2172 100644
--- a/libs/hwui/Texture.h
+++ b/libs/hwui/Texture.h
@@ -38,6 +38,8 @@
firstFilter = true;
firstWrap = true;
+
+ id = 0;
}
void setWrap(GLenum wrap, bool bindTexture = false, bool force = false,
diff --git a/libs/storage/IMountService.cpp b/libs/storage/IMountService.cpp
index 4ec8b25..5701678 100644
--- a/libs/storage/IMountService.cpp
+++ b/libs/storage/IMountService.cpp
@@ -433,12 +433,13 @@
reply.readExceptionCode();
}
- void mountObb(const String16& filename, const String16& key,
+ void mountObb(const String16& rawPath, const String16& canonicalPath, const String16& key,
const sp<IObbActionListener>& token, int32_t nonce)
{
Parcel data, reply;
data.writeInterfaceToken(IMountService::getInterfaceDescriptor());
- data.writeString16(filename);
+ data.writeString16(rawPath);
+ data.writeString16(canonicalPath);
data.writeString16(key);
data.writeStrongBinder(token->asBinder());
data.writeInt32(nonce);
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 270c88f..ee17bd3 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2474,8 +2474,16 @@
* or null if there is no value for that key.
*/
public String getProperty(String key) {
- // implementation to be written
- return null;
+ if (PROPERTY_OUTPUT_SAMPLE_RATE.equals(key)) {
+ int outputSampleRate = AudioSystem.getPrimaryOutputSamplingRate();
+ return outputSampleRate > 0 ? Integer.toString(outputSampleRate) : null;
+ } else if (PROPERTY_OUTPUT_FRAMES_PER_BUFFER.equals(key)) {
+ int outputFramesPerBuffer = AudioSystem.getPrimaryOutputFrameCount();
+ return outputFramesPerBuffer > 0 ? Integer.toString(outputFramesPerBuffer) : null;
+ } else {
+ // null or unknown key
+ return null;
+ }
}
}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 129e84f..2cff4ff 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -381,4 +381,9 @@
public static native int setMasterMute(boolean mute);
public static native boolean getMasterMute();
public static native int getDevicesForStream(int stream);
+
+ // helpers for android.media.AudioManager.getProperty(), see description there for meaning
+ public static native int getPrimaryOutputSamplingRate();
+ public static native int getPrimaryOutputFrameCount();
+
}
diff --git a/media/java/android/media/IRingtonePlayer.aidl b/media/java/android/media/IRingtonePlayer.aidl
index 44a0333..0872f1d 100644
--- a/media/java/android/media/IRingtonePlayer.aidl
+++ b/media/java/android/media/IRingtonePlayer.aidl
@@ -17,6 +17,7 @@
package android.media;
import android.net.Uri;
+import android.os.UserHandle;
/**
* @hide
@@ -28,6 +29,6 @@
boolean isPlaying(IBinder token);
/** Used for Notification sound playback. */
- void playAsync(in Uri uri, boolean looping, int streamType);
+ void playAsync(in Uri uri, in UserHandle user, boolean looping, int streamType);
void stopAsync();
}
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index 23f7b55..f190eb9 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -225,8 +225,9 @@
mLocalPlayer.start();
}
} else if (mAllowRemote) {
+ final Uri canonicalUri = mUri.getCanonicalUri();
try {
- mRemotePlayer.play(mRemoteToken, mUri, mStreamType);
+ mRemotePlayer.play(mRemoteToken, canonicalUri, mStreamType);
} catch (RemoteException e) {
Log.w(TAG, "Problem playing ringtone: " + e);
}
diff --git a/native/android/storage_manager.cpp b/native/android/storage_manager.cpp
index f2f36b62..399f1ff 100644
--- a/native/android/storage_manager.cpp
+++ b/native/android/storage_manager.cpp
@@ -125,11 +125,20 @@
}
}
- void mountObb(const char* filename, const char* key, AStorageManager_obbCallbackFunc func, void* data) {
+ void mountObb(const char* rawPath, const char* key, AStorageManager_obbCallbackFunc func,
+ void* data) {
+ // Resolve path before sending to MountService
+ char canonicalPath[PATH_MAX];
+ if (realpath(rawPath, canonicalPath) == NULL) {
+ ALOGE("mountObb failed to resolve path %s: %s", rawPath, strerror(errno));
+ return;
+ }
+
ObbCallback* cb = registerObbCallback(func, data);
- String16 filename16(filename);
+ String16 rawPath16(rawPath);
+ String16 canonicalPath16(canonicalPath);
String16 key16(key);
- mMountService->mountObb(filename16, key16, mObbActionListener, cb->nonce);
+ mMountService->mountObb(rawPath16, canonicalPath16, key16, mObbActionListener, cb->nonce);
}
void unmountObb(const char* filename, const bool force, AStorageManager_obbCallbackFunc func, void* data) {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4d241ed..a7294ec 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -5,6 +5,7 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.ACCESS_ALL_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INJECT_EVENTS" />
diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index 3502b62..0c6e59c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
@@ -17,6 +17,7 @@
package com.android.systemui.media;
import android.content.Context;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.media.IAudioService;
import android.media.IRingtonePlayer;
import android.media.Ringtone;
@@ -26,6 +27,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.util.Slog;
import com.android.systemui.SystemUI;
@@ -70,9 +72,10 @@
private final IBinder mToken;
private final Ringtone mRingtone;
- public Client(IBinder token, Uri uri, int streamType) {
+ public Client(IBinder token, Uri uri, UserHandle user, int streamType) {
mToken = token;
- mRingtone = new Ringtone(mContext, false);
+
+ mRingtone = new Ringtone(getContextForUser(user), false);
mRingtone.setStreamType(streamType);
mRingtone.setUri(uri);
}
@@ -90,12 +93,16 @@
private IRingtonePlayer mCallback = new IRingtonePlayer.Stub() {
@Override
public void play(IBinder token, Uri uri, int streamType) throws RemoteException {
- if (LOGD) Slog.d(TAG, "play(token=" + token + ", uri=" + uri + ")");
+ if (LOGD) {
+ Slog.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid="
+ + Binder.getCallingUid() + ")");
+ }
Client client;
synchronized (mClients) {
client = mClients.get(token);
if (client == null) {
- client = new Client(token, uri, streamType);
+ final UserHandle user = Binder.getCallingUserHandle();
+ client = new Client(token, uri, user, streamType);
token.linkToDeath(client, 0);
mClients.put(token, client);
}
@@ -131,12 +138,13 @@
}
@Override
- public void playAsync(Uri uri, boolean looping, int streamType) {
- if (LOGD) Slog.d(TAG, "playAsync(uri=" + uri + ")");
+ public void playAsync(Uri uri, UserHandle user, boolean looping, int streamType) {
+ if (LOGD) Slog.d(TAG, "playAsync(uri=" + uri + ", user=" + user + ")");
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("Async playback only available from system UID.");
}
- mAsyncPlayer.play(mContext, uri, looping, streamType);
+
+ mAsyncPlayer.play(getContextForUser(user), uri, looping, streamType);
}
@Override
@@ -149,6 +157,14 @@
}
};
+ private Context getContextForUser(UserHandle user) {
+ try {
+ return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user);
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("Clients:");
diff --git a/services/java/com/android/server/BluetoothManagerService.java b/services/java/com/android/server/BluetoothManagerService.java
index aa5ae3d..ce75e35 100755
--- a/services/java/com/android/server/BluetoothManagerService.java
+++ b/services/java/com/android/server/BluetoothManagerService.java
@@ -218,8 +218,6 @@
}
public IBluetooth registerAdapter(IBluetoothManagerCallback callback){
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
- "Need BLUETOOTH permission");
Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_ADAPTER);
msg.obj = callback;
mHandler.sendMessage(msg);
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index c685473..6952d72 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -34,7 +34,9 @@
import org.xmlpull.v1.XmlSerializer;
import android.app.ActivityManagerNative;
+import android.app.AppGlobals;
import android.app.AlertDialog;
+import android.app.IUserSwitchObserver;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
@@ -49,6 +51,7 @@
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
@@ -63,12 +66,15 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.IInterface;
+import android.os.IRemoteCallback;
import android.os.Message;
+import android.os.Process;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.provider.Settings.SettingNotFoundException;
@@ -378,6 +384,8 @@
private InputMethodInfo[] mIms;
private int[] mSubtypeIds;
private Locale mLastSystemLocale;
+ private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
+ private final IPackageManager mIPackageManager;
class SettingsObserver extends ContentObserver {
SettingsObserver(Handler handler) {
@@ -398,37 +406,55 @@
}
}
- class ScreenOnOffReceiver extends android.content.BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
- mScreenOn = true;
- refreshImeWindowVisibilityLocked();
- } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
- mScreenOn = false;
- setImeWindowVisibilityStatusHiddenLocked();
- } else if (intent.getAction().equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
- hideInputMethodMenu();
- return;
- } else {
- Slog.w(TAG, "Unexpected intent " + intent);
- }
-
+ class ImmsBroadcastReceiver extends android.content.BroadcastReceiver {
+ private void updateActive() {
// Inform the current client of the change in active status
if (mCurClient != null && mCurClient.client != null) {
executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
MSG_SET_ACTIVE, mScreenOn ? 1 : 0, mCurClient));
}
}
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (Intent.ACTION_SCREEN_ON.equals(action)) {
+ mScreenOn = true;
+ refreshImeWindowVisibilityLocked();
+ updateActive();
+ return;
+ } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+ mScreenOn = false;
+ setImeWindowVisibilityStatusHiddenLocked();
+ updateActive();
+ return;
+ } else if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
+ hideInputMethodMenu();
+ // No need to updateActive
+ return;
+ } else {
+ Slog.w(TAG, "Unexpected intent " + intent);
+ }
+ }
}
class MyPackageMonitor extends PackageMonitor {
-
+ private boolean isChangingPackagesOfCurrentUser() {
+ final int userId = getChangingUserId();
+ final boolean retval = userId == mSettings.getCurrentUserId();
+ if (DEBUG) {
+ Slog.d(TAG, "--- ignore this call back from a background user: " + userId);
+ }
+ return retval;
+ }
+
@Override
public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
+ if (!isChangingPackagesOfCurrentUser()) {
+ return false;
+ }
synchronized (mMethodMap) {
- String curInputMethodId = Settings.Secure.getString(mContext
- .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+ String curInputMethodId = mSettings.getSelectedInputMethod();
final int N = mMethodList.size();
if (curInputMethodId != null) {
for (int i=0; i<N; i++) {
@@ -453,10 +479,12 @@
@Override
public void onSomePackagesChanged() {
+ if (!isChangingPackagesOfCurrentUser()) {
+ return;
+ }
synchronized (mMethodMap) {
InputMethodInfo curIm = null;
- String curInputMethodId = Settings.Secure.getString(mContext
- .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+ String curInputMethodId = mSettings.getSelectedInputMethod();
final int N = mMethodList.size();
if (curInputMethodId != null) {
for (int i=0; i<N; i++) {
@@ -489,9 +517,9 @@
|| change == PACKAGE_PERMANENT_CHANGE) {
ServiceInfo si = null;
try {
- si = mContext.getPackageManager().getServiceInfo(
- curIm.getComponent(), 0);
- } catch (PackageManager.NameNotFoundException ex) {
+ si = mIPackageManager.getServiceInfo(
+ curIm.getComponent(), 0, mSettings.getCurrentUserId());
+ } catch (RemoteException ex) {
}
if (si == null) {
// Uh oh, current input method is no longer around!
@@ -565,6 +593,7 @@
}
public InputMethodManagerService(Context context, WindowManagerService windowManager) {
+ mIPackageManager = AppGlobals.getPackageManager();
mContext = context;
mRes = context.getResources();
mHandler = new Handler(this);
@@ -601,23 +630,44 @@
}
mImListManager = new InputMethodAndSubtypeListManager(context, this);
- (new MyPackageMonitor()).register(mContext, null, true);
-
- IntentFilter screenOnOffFilt = new IntentFilter();
- screenOnOffFilt.addAction(Intent.ACTION_SCREEN_ON);
- screenOnOffFilt.addAction(Intent.ACTION_SCREEN_OFF);
- screenOnOffFilt.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- mContext.registerReceiver(new ScreenOnOffReceiver(), screenOnOffFilt);
+ final IntentFilter broadcastFilter = new IntentFilter();
+ broadcastFilter.addAction(Intent.ACTION_SCREEN_ON);
+ broadcastFilter.addAction(Intent.ACTION_SCREEN_OFF);
+ broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter);
mNotificationShown = false;
+ int userId = 0;
+ try {
+ ActivityManagerNative.getDefault().registerUserSwitchObserver(
+ new IUserSwitchObserver.Stub() {
+ @Override
+ public void onUserSwitching(int newUserId, IRemoteCallback reply) {
+ switchUser(newUserId);
+ if (reply != null) {
+ try {
+ reply.sendResult(null);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ @Override
+ public void onUserSwitchComplete(int newUserId) throws RemoteException {
+ }
+ });
+ userId = ActivityManagerNative.getDefault().getCurrentUser().id;
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e);
+ }
+ mMyPackageMonitor.register(mContext, null, true);
// mSettings should be created before buildInputMethodListLocked
mSettings = new InputMethodSettings(
- mRes, context.getContentResolver(), mMethodMap, mMethodList);
+ mRes, context.getContentResolver(), mMethodMap, mMethodList, userId);
// Just checking if defaultImiId is empty or not
- final String defaultImiId = Settings.Secure.getString(
- mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+ final String defaultImiId = mSettings.getSelectedInputMethod();
mImeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
buildInputMethodListLocked(mMethodList, mMethodMap);
@@ -646,24 +696,6 @@
}, filter);
}
- private void checkCurrentLocaleChangedLocked() {
- if (!mSystemReady) {
- // not system ready
- return;
- }
- final Locale newLocale = mRes.getConfiguration().locale;
- if (newLocale != null && !newLocale.equals(mLastSystemLocale)) {
- if (DEBUG) {
- Slog.i(TAG, "Locale has been changed to " + newLocale);
- }
- buildInputMethodListLocked(mMethodList, mMethodMap);
- // Reset the current ime to the proper one
- resetDefaultImeLocked(mContext);
- updateFromSettingsLocked();
- mLastSystemLocale = newLocale;
- }
- }
-
private void resetDefaultImeLocked(Context context) {
// Do not reset the default (current) IME when it is a 3rd-party IME
if (mCurMethodId != null && !isSystemIme(mMethodMap.get(mCurMethodId))) {
@@ -688,6 +720,52 @@
}
}
+ private void resetAllInternalStateLocked(boolean updateOnlyWhenLocaleChanged) {
+ if (!mSystemReady) {
+ // not system ready
+ return;
+ }
+ final Locale newLocale = mRes.getConfiguration().locale;
+ if (!updateOnlyWhenLocaleChanged
+ || (newLocale != null && !newLocale.equals(mLastSystemLocale))) {
+ if (!updateOnlyWhenLocaleChanged) {
+ hideCurrentInputLocked(0, null);
+ mCurMethodId = null;
+ unbindCurrentMethodLocked(true);
+ }
+ if (DEBUG) {
+ Slog.i(TAG, "Locale has been changed to " + newLocale);
+ }
+ buildInputMethodListLocked(mMethodList, mMethodMap);
+ if (!updateOnlyWhenLocaleChanged) {
+ final String selectedImiId = mSettings.getSelectedInputMethod();
+ if (TextUtils.isEmpty(selectedImiId)) {
+ // This is the first time of the user switch and
+ // set the current ime to the proper one.
+ resetDefaultImeLocked(mContext);
+ }
+ }
+ updateFromSettingsLocked();
+ mLastSystemLocale = newLocale;
+ if (!updateOnlyWhenLocaleChanged) {
+ try {
+ startInputInnerLocked();
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "Unexpected exception", e);
+ }
+ }
+ }
+ }
+
+ private void checkCurrentLocaleChangedLocked() {
+ resetAllInternalStateLocked(true);
+ }
+
+ private void switchUser(int newUserId) {
+ mSettings.setCurrentUserId(newUserId);
+ resetAllInternalStateLocked(false);
+ }
+
private boolean isValidSystemDefaultIme(InputMethodInfo imi, Context context) {
if (!mSystemReady) {
return false;
@@ -748,10 +826,13 @@
public void systemReady(StatusBarManagerService statusBar) {
synchronized (mMethodMap) {
+ if (DEBUG) {
+ Slog.d(TAG, "--- systemReady");
+ }
if (!mSystemReady) {
mSystemReady = true;
- mKeyguardManager = (KeyguardManager)
- mContext.getSystemService(Context.KEYGUARD_SERVICE);
+ mKeyguardManager =
+ (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
mNotificationManager = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
mStatusBar = statusBar;
@@ -802,8 +883,42 @@
setImeWindowStatus(mCurToken, mImeWindowVis, mBackDisposition);
}
+ // ---------------------------------------------------------------------------------------
+ // Check whether or not this is a valid IPC. Assumes an IPC is valid when either
+ // 1) it comes from the system process
+ // 2) the calling process' user id is identical to the current user id IMMS thinks.
+ private boolean calledFromValidUser() {
+ final int uid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserId(uid);
+ if (DEBUG) {
+ Slog.d(TAG, "--- calledFromForegroundUserOrSystemProcess ? "
+ + "calling uid = " + uid + " system uid = " + Process.SYSTEM_UID
+ + " calling userId = " + userId + ", foreground user id = "
+ + mSettings.getCurrentUserId());
+ }
+ if (uid == Process.SYSTEM_UID || userId == mSettings.getCurrentUserId()) {
+ return true;
+ } else {
+ Slog.w(TAG, "--- IPC called from background users. Ignore. \n" + getStackTrace());
+ return false;
+ }
+ }
+
+ private boolean bindCurrentInputMethodService(
+ Intent service, ServiceConnection conn, int flags) {
+ if (service == null || conn == null) {
+ Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn);
+ return false;
+ }
+ return mContext.bindService(service, conn, flags, mSettings.getCurrentUserId());
+ }
+
@Override
public List<InputMethodInfo> getInputMethodList() {
+ // TODO: Make this work even for non-current users?
+ if (!calledFromValidUser()) {
+ return Collections.emptyList();
+ }
synchronized (mMethodMap) {
return new ArrayList<InputMethodInfo>(mMethodList);
}
@@ -811,6 +926,10 @@
@Override
public List<InputMethodInfo> getEnabledInputMethodList() {
+ // TODO: Make this work even for non-current users?
+ if (!calledFromValidUser()) {
+ return Collections.emptyList();
+ }
synchronized (mMethodMap) {
return mSettings.getEnabledInputMethodListLocked();
}
@@ -820,7 +939,7 @@
getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked() {
HashMap<InputMethodInfo, List<InputMethodSubtype>> enabledInputMethodAndSubtypes =
new HashMap<InputMethodInfo, List<InputMethodSubtype>>();
- for (InputMethodInfo imi: getEnabledInputMethodList()) {
+ for (InputMethodInfo imi: mSettings.getEnabledInputMethodListLocked()) {
enabledInputMethodAndSubtypes.put(
imi, getEnabledInputMethodSubtypeListLocked(imi, true));
}
@@ -843,6 +962,10 @@
@Override
public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi,
boolean allowsImplicitlySelectedSubtypes) {
+ // TODO: Make this work even for non-current users?
+ if (!calledFromValidUser()) {
+ return Collections.emptyList();
+ }
synchronized (mMethodMap) {
return getEnabledInputMethodSubtypeListLocked(imi, allowsImplicitlySelectedSubtypes);
}
@@ -851,6 +974,9 @@
@Override
public void addClient(IInputMethodClient client,
IInputContext inputContext, int uid, int pid) {
+ if (!calledFromValidUser()) {
+ return;
+ }
synchronized (mMethodMap) {
mClients.put(client.asBinder(), new ClientState(client,
inputContext, uid, pid));
@@ -859,6 +985,9 @@
@Override
public void removeClient(IInputMethodClient client) {
+ if (!calledFromValidUser()) {
+ return;
+ }
synchronized (mMethodMap) {
mClients.remove(client.asBinder());
}
@@ -1060,7 +1189,7 @@
com.android.internal.R.string.input_method_binding_label);
mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
- if (mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE
+ if (bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
| Context.BIND_NOT_VISIBLE)) {
mLastBindTime = SystemClock.uptimeMillis();
mHaveConnection = true;
@@ -1084,6 +1213,9 @@
@Override
public InputBindResult startInput(IInputMethodClient client,
IInputContext inputContext, EditorInfo attribute, int controlFlags) {
+ if (!calledFromValidUser()) {
+ return null;
+ }
synchronized (mMethodMap) {
final long ident = Binder.clearCallingIdentity();
try {
@@ -1242,10 +1374,12 @@
if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
CharSequence contentDescription = null;
try {
- PackageManager packageManager = mContext.getPackageManager();
+ // Use PackageManager to load label
+ final PackageManager packageManager = mContext.getPackageManager();
contentDescription = packageManager.getApplicationLabel(
- packageManager.getApplicationInfo(packageName, 0));
- } catch (NameNotFoundException nnfe) {
+ mIPackageManager.getApplicationInfo(packageName, 0,
+ mSettings.getCurrentUserId()));
+ } catch (RemoteException e) {
/* ignore */
}
if (mStatusBar != null) {
@@ -1309,13 +1443,14 @@
}
}
+ // Caution! This method is called in this class. Handle multi-user carefully
@SuppressWarnings("deprecation")
@Override
public void setImeWindowStatus(IBinder token, int vis, int backDisposition) {
- int uid = Binder.getCallingUid();
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
if (token == null || mCurToken != token) {
+ int uid = Binder.getCallingUid();
Slog.w(TAG, "Ignoring setImeWindowStatus of uid " + uid + " token: " + token);
return;
}
@@ -1329,10 +1464,14 @@
final boolean iconVisibility = (vis & InputMethodService.IME_ACTIVE) != 0;
final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
if (imi != null && iconVisibility && needsToShowImeSwitchOngoingNotification()) {
+ // Used to load label
final PackageManager pm = mContext.getPackageManager();
final CharSequence title = mRes.getText(
com.android.internal.R.string.select_input_method);
final CharSequence imiLabel = imi.loadLabel(pm);
+ if (DEBUG) {
+ Slog.d(TAG, "--- imiLabel = " + imiLabel);
+ }
final CharSequence summary = mCurrentSubtype != null
? TextUtils.concat(mCurrentSubtype.getDisplayName(mContext,
imi.getPackageName(), imi.getServiceInfo().applicationInfo),
@@ -1363,6 +1502,9 @@
@Override
public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
+ if (!calledFromValidUser()) {
+ return;
+ }
synchronized (mMethodMap) {
final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
for (int i = 0; i < spans.length; ++i) {
@@ -1377,6 +1519,9 @@
@Override
public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
+ if (!calledFromValidUser()) {
+ return false;
+ }
synchronized (mMethodMap) {
final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span);
// TODO: Do not send the intent if the process of the targetImi is already dead.
@@ -1404,12 +1549,10 @@
// ENABLED_INPUT_METHODS is taking care of keeping them correctly in
// sync, so we will never have a DEFAULT_INPUT_METHOD that is not
// enabled.
- String id = Settings.Secure.getString(mContext.getContentResolver(),
- Settings.Secure.DEFAULT_INPUT_METHOD);
+ String id = mSettings.getSelectedInputMethod();
// There is no input method selected, try to choose new applicable input method.
if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
- id = Settings.Secure.getString(mContext.getContentResolver(),
- Settings.Secure.DEFAULT_INPUT_METHOD);
+ id = mSettings.getSelectedInputMethod();
}
if (!TextUtils.isEmpty(id)) {
try {
@@ -1446,7 +1589,7 @@
} else {
// If subtype is null, try to find the most applicable one from
// getCurrentInputMethodSubtype.
- newSubtype = getCurrentInputMethodSubtype();
+ newSubtype = getCurrentInputMethodSubtypeLocked();
}
if (newSubtype == null || oldSubtype == null) {
Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype
@@ -1493,6 +1636,9 @@
@Override
public boolean showSoftInput(IInputMethodClient client, int flags,
ResultReceiver resultReceiver) {
+ if (!calledFromValidUser()) {
+ return false;
+ }
int uid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
try {
@@ -1541,7 +1687,8 @@
resultReceiver));
mInputShown = true;
if (mHaveConnection && !mVisibleBound) {
- mContext.bindService(mCurIntent, mVisibleConnection, Context.BIND_AUTO_CREATE);
+ bindCurrentInputMethodService(
+ mCurIntent, mVisibleConnection, Context.BIND_AUTO_CREATE);
mVisibleBound = true;
}
res = true;
@@ -1555,8 +1702,13 @@
SystemClock.uptimeMillis()-mLastBindTime,1);
Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()");
mContext.unbindService(this);
- mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE
+ bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
| Context.BIND_NOT_VISIBLE);
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, "Can't show input: connection = " + mHaveConnection + ", time = "
+ + ((mLastBindTime+TIME_TO_RECONNECT) - SystemClock.uptimeMillis()));
+ }
}
return res;
@@ -1565,6 +1717,9 @@
@Override
public boolean hideSoftInput(IInputMethodClient client, int flags,
ResultReceiver resultReceiver) {
+ if (!calledFromValidUser()) {
+ return false;
+ }
int uid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
try {
@@ -1630,6 +1785,9 @@
public InputBindResult windowGainedFocus(IInputMethodClient client, IBinder windowToken,
int controlFlags, int softInputMode, int windowFlags,
EditorInfo attribute, IInputContext inputContext) {
+ if (!calledFromValidUser()) {
+ return null;
+ }
InputBindResult res = null;
long ident = Binder.clearCallingIdentity();
try {
@@ -1770,6 +1928,9 @@
@Override
public void showInputMethodPickerFromClient(IInputMethodClient client) {
+ if (!calledFromValidUser()) {
+ return;
+ }
synchronized (mMethodMap) {
if (mCurClient == null || client == null
|| mCurClient.client.asBinder() != client.asBinder()) {
@@ -1785,11 +1946,17 @@
@Override
public void setInputMethod(IBinder token, String id) {
+ if (!calledFromValidUser()) {
+ return;
+ }
setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID);
}
@Override
public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) {
+ if (!calledFromValidUser()) {
+ return;
+ }
synchronized (mMethodMap) {
if (subtype != null) {
setInputMethodWithSubtypeId(token, id, getSubtypeIdFromHashCode(
@@ -1803,6 +1970,9 @@
@Override
public void showInputMethodAndSubtypeEnablerFromClient(
IInputMethodClient client, String inputMethodId) {
+ if (!calledFromValidUser()) {
+ return;
+ }
synchronized (mMethodMap) {
if (mCurClient == null || client == null
|| mCurClient.client.asBinder() != client.asBinder()) {
@@ -1815,6 +1985,9 @@
@Override
public boolean switchToLastInputMethod(IBinder token) {
+ if (!calledFromValidUser()) {
+ return false;
+ }
synchronized (mMethodMap) {
final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
final InputMethodInfo lastImi;
@@ -1882,6 +2055,9 @@
@Override
public boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) {
+ if (!calledFromValidUser()) {
+ return false;
+ }
synchronized (mMethodMap) {
final ImeSubtypeListItem nextSubtype = mImListManager.getNextInputMethod(
onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype);
@@ -1895,6 +2071,9 @@
@Override
public InputMethodSubtype getLastInputMethodSubtype() {
+ if (!calledFromValidUser()) {
+ return null;
+ }
synchronized (mMethodMap) {
final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
// TODO: Handle the case of the last IME with no subtypes
@@ -1917,14 +2096,22 @@
@Override
public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
+ if (!calledFromValidUser()) {
+ return;
+ }
// By this IPC call, only a process which shares the same uid with the IME can add
// additional input method subtypes to the IME.
if (TextUtils.isEmpty(imiId) || subtypes == null || subtypes.length == 0) return;
synchronized (mMethodMap) {
final InputMethodInfo imi = mMethodMap.get(imiId);
if (imi == null) return;
- final PackageManager pm = mContext.getPackageManager();
- final String[] packageInfos = pm.getPackagesForUid(Binder.getCallingUid());
+ final String[] packageInfos;
+ try {
+ packageInfos = mIPackageManager.getPackagesForUid(Binder.getCallingUid());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get package infos");
+ return;
+ }
if (packageInfos != null) {
final int packageNum = packageInfos.length;
for (int i = 0; i < packageNum; ++i) {
@@ -1971,6 +2158,9 @@
@Override
public void hideMySoftInput(IBinder token, int flags) {
+ if (!calledFromValidUser()) {
+ return;
+ }
synchronized (mMethodMap) {
if (token == null || mCurToken != token) {
if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid "
@@ -1988,6 +2178,9 @@
@Override
public void showMySoftInput(IBinder token, int flags) {
+ if (!calledFromValidUser()) {
+ return;
+ }
synchronized (mMethodMap) {
if (token == null || mCurToken != token) {
Slog.w(TAG, "Ignoring showMySoftInput of uid "
@@ -2224,19 +2417,22 @@
void buildInputMethodListLocked(ArrayList<InputMethodInfo> list,
HashMap<String, InputMethodInfo> map) {
+ if (DEBUG) {
+ Slog.d(TAG, "--- re-buildInputMethodList " + ", \n ------ \n" + getStackTrace());
+ }
list.clear();
map.clear();
- PackageManager pm = mContext.getPackageManager();
+ // Use for queryIntentServicesAsUser
+ final PackageManager pm = mContext.getPackageManager();
final Configuration config = mRes.getConfiguration();
final boolean haveHardKeyboard = config.keyboard == Configuration.KEYBOARD_QWERTY;
- String disabledSysImes = Settings.Secure.getString(mContext.getContentResolver(),
- Secure.DISABLED_SYSTEM_INPUT_METHODS);
+ String disabledSysImes = mSettings.getDisabledSystemInputMethods();
if (disabledSysImes == null) disabledSysImes = "";
- List<ResolveInfo> services = pm.queryIntentServices(
+ final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
new Intent(InputMethod.SERVICE_INTERFACE),
- PackageManager.GET_META_DATA);
+ PackageManager.GET_META_DATA, mSettings.getCurrentUserId());
final HashMap<String, List<InputMethodSubtype>> additionalSubtypes =
mFileManager.getAllAdditionalInputMethodSubtypes();
@@ -2279,8 +2475,7 @@
}
}
- final String defaultImiId = Settings.Secure.getString(mContext
- .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+ final String defaultImiId = mSettings.getSelectedInputMethod();
if (!TextUtils.isEmpty(defaultImiId)) {
if (!map.containsKey(defaultImiId)) {
Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
@@ -2331,11 +2526,9 @@
if (DEBUG) Slog.v(TAG, "Show switching menu");
final Context context = mContext;
- final PackageManager pm = context.getPackageManager();
final boolean isScreenLocked = isScreenLocked();
- final String lastInputMethodId = Settings.Secure.getString(context
- .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+ final String lastInputMethodId = mSettings.getSelectedInputMethod();
int lastInputMethodSubtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId);
if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
@@ -2353,7 +2546,7 @@
showSubtypes, mInputShown, isScreenLocked);
if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
- final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtype();
+ final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtypeLocked();
if (currentSubtype != null) {
final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
lastInputMethodSubtypeId =
@@ -2582,6 +2775,10 @@
@Override
public boolean setInputMethodEnabled(String id, boolean enabled) {
+ // TODO: Make this work even for non-current users?
+ if (!calledFromValidUser()) {
+ return false;
+ }
synchronized (mMethodMap) {
if (mContext.checkCallingOrSelfPermission(
android.Manifest.permission.WRITE_SECURE_SETTINGS)
@@ -2626,8 +2823,7 @@
if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
builder, enabledInputMethodsList, id)) {
// Disabled input method is currently selected, switch to another one.
- String selId = Settings.Secure.getString(mContext.getContentResolver(),
- Settings.Secure.DEFAULT_INPUT_METHOD);
+ final String selId = mSettings.getSelectedInputMethod();
if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
resetSelectedInputMethodAndSubtypeLocked("");
@@ -2674,7 +2870,7 @@
} else {
mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
// If the subtype is not specified, choose the most applicable one
- mCurrentSubtype = getCurrentInputMethodSubtype();
+ mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
}
}
@@ -2716,14 +2912,8 @@
if (imi == null) {
return NOT_A_SUBTYPE_ID;
}
- int subtypeId;
- try {
- subtypeId = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE);
- } catch (SettingNotFoundException e) {
- return NOT_A_SUBTYPE_ID;
- }
- return getSubtypeIdFromHashCode(imi, subtypeId);
+ final int subtypeHashCode = mSettings.getSelectedInputMethodSubtypeHashCode();
+ return getSubtypeIdFromHashCode(imi, subtypeHashCode);
}
private static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) {
@@ -2886,7 +3076,7 @@
}
InputMethodSubtype subtype = null;
final List<InputMethodSubtype> enabledSubtypes =
- getEnabledInputMethodSubtypeList(imi, true);
+ getEnabledInputMethodSubtypeListLocked(imi, true);
// 1. Search by the current subtype's locale from enabledSubtypes.
if (mCurrentSubtype != null) {
subtype = findLastResortApplicableSubtypeLocked(
@@ -2955,49 +3145,53 @@
*/
@Override
public InputMethodSubtype getCurrentInputMethodSubtype() {
+ // TODO: Make this work even for non-current users?
+ if (!calledFromValidUser()) {
+ return null;
+ }
+ synchronized (mMethodMap) {
+ return getCurrentInputMethodSubtypeLocked();
+ }
+ }
+
+ private InputMethodSubtype getCurrentInputMethodSubtypeLocked() {
if (mCurMethodId == null) {
return null;
}
- boolean subtypeIsSelected = false;
- try {
- subtypeIsSelected = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE) != NOT_A_SUBTYPE_ID;
- } catch (SettingNotFoundException e) {
+ final boolean subtypeIsSelected =
+ mSettings.getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
+ final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
+ if (imi == null || imi.getSubtypeCount() == 0) {
+ return null;
}
- synchronized (mMethodMap) {
- final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
- if (imi == null || imi.getSubtypeCount() == 0) {
- return null;
- }
- if (!subtypeIsSelected || mCurrentSubtype == null
- || !isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
- int subtypeId = getSelectedInputMethodSubtypeId(mCurMethodId);
- if (subtypeId == NOT_A_SUBTYPE_ID) {
- // If there are no selected subtypes, the framework will try to find
- // the most applicable subtype from explicitly or implicitly enabled
- // subtypes.
- List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
- getEnabledInputMethodSubtypeList(imi, true);
- // If there is only one explicitly or implicitly enabled subtype,
- // just returns it.
- if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
- mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
- } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
+ if (!subtypeIsSelected || mCurrentSubtype == null
+ || !isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
+ int subtypeId = getSelectedInputMethodSubtypeId(mCurMethodId);
+ if (subtypeId == NOT_A_SUBTYPE_ID) {
+ // If there are no selected subtypes, the framework will try to find
+ // the most applicable subtype from explicitly or implicitly enabled
+ // subtypes.
+ List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
+ getEnabledInputMethodSubtypeListLocked(imi, true);
+ // If there is only one explicitly or implicitly enabled subtype,
+ // just returns it.
+ if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
+ mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
+ } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
+ mCurrentSubtype = findLastResortApplicableSubtypeLocked(
+ mRes, explicitlyOrImplicitlyEnabledSubtypes,
+ SUBTYPE_MODE_KEYBOARD, null, true);
+ if (mCurrentSubtype == null) {
mCurrentSubtype = findLastResortApplicableSubtypeLocked(
- mRes, explicitlyOrImplicitlyEnabledSubtypes,
- SUBTYPE_MODE_KEYBOARD, null, true);
- if (mCurrentSubtype == null) {
- mCurrentSubtype = findLastResortApplicableSubtypeLocked(
- mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null,
- true);
- }
+ mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null,
+ true);
}
- } else {
- mCurrentSubtype = getSubtypes(imi).get(subtypeId);
}
+ } else {
+ mCurrentSubtype = getSubtypes(imi).get(subtypeId);
}
- return mCurrentSubtype;
}
+ return mCurrentSubtype;
}
private void addShortcutInputMethodAndSubtypes(InputMethodInfo imi,
@@ -3042,6 +3236,10 @@
@Override
public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
+ // TODO: Make this work even for non-current users?
+ if (!calledFromValidUser()) {
+ return false;
+ }
synchronized (mMethodMap) {
if (subtype != null && mCurMethodId != null) {
InputMethodInfo imi = mMethodMap.get(mCurMethodId);
@@ -3057,6 +3255,7 @@
private static class InputMethodAndSubtypeListManager {
private final Context mContext;
+ // Used to load label
private final PackageManager mPm;
private final InputMethodManagerService mImms;
private final String mSystemLocaleStr;
@@ -3193,6 +3392,7 @@
private final ArrayList<InputMethodInfo> mMethodList;
private String mEnabledInputMethodsStrCache;
+ private int mCurrentUserId;
private static void buildEnabledInputMethodsSettingString(
StringBuilder builder, Pair<String, ArrayList<String>> pair) {
@@ -3208,13 +3408,24 @@
public InputMethodSettings(
Resources res, ContentResolver resolver,
- HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList) {
+ HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
+ int userId) {
+ setCurrentUserId(userId);
mRes = res;
mResolver = resolver;
mMethodMap = methodMap;
mMethodList = methodList;
}
+ public void setCurrentUserId(int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "--- Swtich the current user from " + mCurrentUserId + " to "
+ + userId + ", new ime = " + getSelectedInputMethod());
+ }
+ // IMMS settings are kept per user, so keep track of current user
+ mCurrentUserId = userId;
+ }
+
public List<InputMethodInfo> getEnabledInputMethodListLocked() {
return createEnabledInputMethodListLocked(
getEnabledInputMethodsAndSubtypeListLocked());
@@ -3363,15 +3574,20 @@
}
private void putEnabledInputMethodsStr(String str) {
- Settings.Secure.putString(mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str);
+ Settings.Secure.putStringForUser(
+ mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str, mCurrentUserId);
mEnabledInputMethodsStrCache = str;
+ if (DEBUG) {
+ Slog.d(TAG, "putEnabledInputMethodStr: " + str);
+ }
}
private String getEnabledInputMethodsStr() {
- mEnabledInputMethodsStrCache = Settings.Secure.getString(
- mResolver, Settings.Secure.ENABLED_INPUT_METHODS);
+ mEnabledInputMethodsStrCache = Settings.Secure.getStringForUser(
+ mResolver, Settings.Secure.ENABLED_INPUT_METHODS, mCurrentUserId);
if (DEBUG) {
- Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache);
+ Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache
+ + ", " + mCurrentUserId);
}
return mEnabledInputMethodsStrCache;
}
@@ -3426,8 +3642,8 @@
if (DEBUG) {
Slog.d(TAG, "putSubtypeHistoryStr: " + str);
}
- Settings.Secure.putString(
- mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str);
+ Settings.Secure.putStringForUser(
+ mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str, mCurrentUserId);
}
public Pair<String, String> getLastInputMethodAndSubtypeLocked() {
@@ -3546,20 +3762,57 @@
private String getSubtypeHistoryStr() {
if (DEBUG) {
- Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getString(
- mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY));
+ Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getStringForUser(
+ mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId));
}
- return Settings.Secure.getString(
- mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY);
+ return Settings.Secure.getStringForUser(
+ mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId);
}
public void putSelectedInputMethod(String imeId) {
- Settings.Secure.putString(mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
+ if (DEBUG) {
+ Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
+ + mCurrentUserId);
+ }
+ Settings.Secure.putStringForUser(
+ mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId, mCurrentUserId);
}
public void putSelectedSubtype(int subtypeId) {
- Settings.Secure.putInt(
- mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
+ if (DEBUG) {
+ Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
+ + mCurrentUserId);
+ }
+ Settings.Secure.putIntForUser(mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
+ subtypeId, mCurrentUserId);
+ }
+
+ public String getDisabledSystemInputMethods() {
+ return Settings.Secure.getStringForUser(
+ mResolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS, mCurrentUserId);
+ }
+
+ public String getSelectedInputMethod() {
+ if (DEBUG) {
+ Slog.d(TAG, "getSelectedInputMethodStr: " + Settings.Secure.getStringForUser(
+ mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId)
+ + ", " + mCurrentUserId);
+ }
+ return Settings.Secure.getStringForUser(
+ mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId);
+ }
+
+ public int getSelectedInputMethodSubtypeHashCode() {
+ try {
+ return Settings.Secure.getIntForUser(
+ mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, mCurrentUserId);
+ } catch (SettingNotFoundException e) {
+ return NOT_A_SUBTYPE_ID;
+ }
+ }
+
+ public int getCurrentUserId() {
+ return mCurrentUserId;
}
}
@@ -3762,6 +4015,20 @@
}
// ----------------------------------------------------------------------
+ // Utilities for debug
+ private static String getStackTrace() {
+ final StringBuilder sb = new StringBuilder();
+ try {
+ throw new RuntimeException();
+ } catch (RuntimeException e) {
+ final StackTraceElement[] frames = e.getStackTrace();
+ // Start at 1 because the first frame is here and we don't care about it
+ for (int j = 1; j < frames.length; ++j) {
+ sb.append(frames[j].toString() + "\n");
+ }
+ }
+ return sb.toString();
+ }
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index b834a84..578e602 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -703,10 +703,10 @@
}
private String pickBest(List<String> providers) {
- if (providers.contains(LocationManager.NETWORK_PROVIDER)) {
- return LocationManager.NETWORK_PROVIDER;
- } else if (providers.contains(LocationManager.GPS_PROVIDER)) {
+ if (providers.contains(LocationManager.GPS_PROVIDER)) {
return LocationManager.GPS_PROVIDER;
+ } else if (providers.contains(LocationManager.NETWORK_PROVIDER)) {
+ return LocationManager.NETWORK_PROVIDER;
} else {
return providers.get(0);
}
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index fe2f8d8..0312705 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -58,6 +58,7 @@
import android.util.Xml;
import com.android.internal.app.IMediaContainerService;
+import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.server.NativeDaemonConnector.Command;
import com.android.server.am.ActivityManagerService;
@@ -224,22 +225,31 @@
* OBBs.
*/
final private Map<IBinder, List<ObbState>> mObbMounts = new HashMap<IBinder, List<ObbState>>();
+
+ /** Map from raw paths to {@link ObbState}. */
final private Map<String, ObbState> mObbPathToStateMap = new HashMap<String, ObbState>();
class ObbState implements IBinder.DeathRecipient {
- public ObbState(String filename, int callerUid, IObbActionListener token, int nonce)
- throws RemoteException {
- this.filename = filename;
- this.callerUid = callerUid;
+ public ObbState(String rawPath, String canonicalPath, int callingUid,
+ IObbActionListener token, int nonce) {
+ this.rawPath = rawPath;
+ this.canonicalPath = canonicalPath.toString();
+
+ final int userId = UserHandle.getUserId(callingUid);
+ this.ownerPath = buildObbPath(canonicalPath, userId, false);
+ this.voldPath = buildObbPath(canonicalPath, userId, true);
+
+ this.ownerGid = UserHandle.getSharedAppGid(callingUid);
this.token = token;
this.nonce = nonce;
}
- // OBB source filename
- String filename;
+ final String rawPath;
+ final String canonicalPath;
+ final String ownerPath;
+ final String voldPath;
- // Binder.callingUid()
- final public int callerUid;
+ final int ownerGid;
// Token of remote Binder caller
final IObbActionListener token;
@@ -268,12 +278,13 @@
@Override
public String toString() {
StringBuilder sb = new StringBuilder("ObbState{");
- sb.append("filename=");
- sb.append(filename);
- sb.append(",token=");
- sb.append(token.toString());
- sb.append(",callerUid=");
- sb.append(callerUid);
+ sb.append("rawPath=").append(rawPath);
+ sb.append(",canonicalPath=").append(canonicalPath);
+ sb.append(",ownerPath=").append(ownerPath);
+ sb.append(",voldPath=").append(voldPath);
+ sb.append(",ownerGid=").append(ownerGid);
+ sb.append(",token=").append(token);
+ sb.append(",binder=").append(getBinder());
sb.append('}');
return sb.toString();
}
@@ -1853,17 +1864,24 @@
return callerUid == packageUid;
}
- public String getMountedObbPath(String filename) {
- if (filename == null) {
- throw new IllegalArgumentException("filename cannot be null");
- }
+ public String getMountedObbPath(String rawPath) {
+ Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
waitForReady();
warnOnNotMounted();
+ final ObbState state;
+ synchronized (mObbPathToStateMap) {
+ state = mObbPathToStateMap.get(rawPath);
+ }
+ if (state == null) {
+ Slog.w(TAG, "Failed to find OBB mounted at " + rawPath);
+ return null;
+ }
+
final NativeDaemonEvent event;
try {
- event = mConnector.execute("obb", "path", filename);
+ event = mConnector.execute("obb", "path", state.voldPath);
event.checkCode(VoldResponseCode.AsecPathResult);
return event.getMessage();
} catch (NativeDaemonConnectorException e) {
@@ -1876,48 +1894,52 @@
}
}
- public boolean isObbMounted(String filename) {
- if (filename == null) {
- throw new IllegalArgumentException("filename cannot be null");
- }
-
+ @Override
+ public boolean isObbMounted(String rawPath) {
+ Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
synchronized (mObbMounts) {
- return mObbPathToStateMap.containsKey(filename);
+ return mObbPathToStateMap.containsKey(rawPath);
}
}
- public void mountObb(String filename, String key, IObbActionListener token, int nonce)
- throws RemoteException {
- if (filename == null) {
- throw new IllegalArgumentException("filename cannot be null");
- }
+ @Override
+ public void mountObb(
+ String rawPath, String canonicalPath, String key, IObbActionListener token, int nonce) {
+ Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
+ Preconditions.checkNotNull(canonicalPath, "canonicalPath cannot be null");
+ Preconditions.checkNotNull(token, "token cannot be null");
- if (token == null) {
- throw new IllegalArgumentException("token cannot be null");
- }
-
- final int callerUid = Binder.getCallingUid();
- final ObbState obbState = new ObbState(filename, callerUid, token, nonce);
- final ObbAction action = new MountObbAction(obbState, key);
+ final int callingUid = Binder.getCallingUid();
+ final ObbState obbState = new ObbState(rawPath, canonicalPath, callingUid, token, nonce);
+ final ObbAction action = new MountObbAction(obbState, key, callingUid);
mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
if (DEBUG_OBB)
Slog.i(TAG, "Send to OBB handler: " + action.toString());
}
- public void unmountObb(String filename, boolean force, IObbActionListener token, int nonce)
- throws RemoteException {
- if (filename == null) {
- throw new IllegalArgumentException("filename cannot be null");
+ @Override
+ public void unmountObb(String rawPath, boolean force, IObbActionListener token, int nonce) {
+ Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
+
+ final ObbState existingState;
+ synchronized (mObbPathToStateMap) {
+ existingState = mObbPathToStateMap.get(rawPath);
}
- final int callerUid = Binder.getCallingUid();
- final ObbState obbState = new ObbState(filename, callerUid, token, nonce);
- final ObbAction action = new UnmountObbAction(obbState, force);
- mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
+ if (existingState != null) {
+ // TODO: separate state object from request data
+ final int callingUid = Binder.getCallingUid();
+ final ObbState newState = new ObbState(
+ rawPath, existingState.canonicalPath, callingUid, token, nonce);
+ final ObbAction action = new UnmountObbAction(newState, force);
+ mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
- if (DEBUG_OBB)
- Slog.i(TAG, "Send to OBB handler: " + action.toString());
+ if (DEBUG_OBB)
+ Slog.i(TAG, "Send to OBB handler: " + action.toString());
+ } else {
+ Slog.w(TAG, "Unknown OBB mount at " + rawPath);
+ }
}
@Override
@@ -2094,7 +2116,7 @@
mObbMounts.put(binder, obbStates);
} else {
for (final ObbState o : obbStates) {
- if (o.filename.equals(obbState.filename)) {
+ if (o.rawPath.equals(obbState.rawPath)) {
throw new IllegalStateException("Attempt to add ObbState twice. "
+ "This indicates an error in the MountService logic.");
}
@@ -2118,7 +2140,7 @@
throw e;
}
- mObbPathToStateMap.put(obbState.filename, obbState);
+ mObbPathToStateMap.put(obbState.rawPath, obbState);
}
private void removeObbStateLocked(ObbState obbState) {
@@ -2133,7 +2155,7 @@
}
}
- mObbPathToStateMap.remove(obbState.filename);
+ mObbPathToStateMap.remove(obbState.rawPath);
}
private class ObbActionHandler extends Handler {
@@ -2241,33 +2263,32 @@
synchronized (mObbMounts) {
final List<ObbState> obbStatesToRemove = new LinkedList<ObbState>();
- final Iterator<Entry<String, ObbState>> i =
- mObbPathToStateMap.entrySet().iterator();
+ final Iterator<ObbState> i = mObbPathToStateMap.values().iterator();
while (i.hasNext()) {
- final Entry<String, ObbState> obbEntry = i.next();
+ final ObbState state = i.next();
/*
* If this entry's source file is in the volume path
* that got unmounted, remove it because it's no
* longer valid.
*/
- if (obbEntry.getKey().startsWith(path)) {
- obbStatesToRemove.add(obbEntry.getValue());
+ if (state.canonicalPath.startsWith(path)) {
+ obbStatesToRemove.add(state);
}
}
for (final ObbState obbState : obbStatesToRemove) {
if (DEBUG_OBB)
- Slog.i(TAG, "Removing state for " + obbState.filename);
+ Slog.i(TAG, "Removing state for " + obbState.rawPath);
removeObbStateLocked(obbState);
try {
- obbState.token.onObbResult(obbState.filename, obbState.nonce,
+ obbState.token.onObbResult(obbState.rawPath, obbState.nonce,
OnObbStateChangeListener.UNMOUNTED);
} catch (RemoteException e) {
Slog.i(TAG, "Couldn't send unmount notification for OBB: "
- + obbState.filename);
+ + obbState.rawPath);
}
}
}
@@ -2339,14 +2360,14 @@
protected ObbInfo getObbInfo() throws IOException {
ObbInfo obbInfo;
try {
- obbInfo = mContainerService.getObbInfo(mObbState.filename);
+ obbInfo = mContainerService.getObbInfo(mObbState.ownerPath);
} catch (RemoteException e) {
Slog.d(TAG, "Couldn't call DefaultContainerService to fetch OBB info for "
- + mObbState.filename);
+ + mObbState.ownerPath);
obbInfo = null;
}
if (obbInfo == null) {
- throw new IOException("Couldn't read OBB file: " + mObbState.filename);
+ throw new IOException("Couldn't read OBB file: " + mObbState.ownerPath);
}
return obbInfo;
}
@@ -2357,7 +2378,7 @@
}
try {
- mObbState.token.onObbResult(mObbState.filename, mObbState.nonce, status);
+ mObbState.token.onObbResult(mObbState.rawPath, mObbState.nonce, status);
} catch (RemoteException e) {
Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
}
@@ -2366,10 +2387,12 @@
class MountObbAction extends ObbAction {
private final String mKey;
+ private final int mCallingUid;
- MountObbAction(ObbState obbState, String key) {
+ MountObbAction(ObbState obbState, String key, int callingUid) {
super(obbState);
mKey = key;
+ mCallingUid = callingUid;
}
@Override
@@ -2379,7 +2402,7 @@
final ObbInfo obbInfo = getObbInfo();
- if (!isUidOwnerOfPackageOrSystem(obbInfo.packageName, mObbState.callerUid)) {
+ if (!isUidOwnerOfPackageOrSystem(obbInfo.packageName, mCallingUid)) {
Slog.w(TAG, "Denied attempt to mount OBB " + obbInfo.filename
+ " which is owned by " + obbInfo.packageName);
sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED);
@@ -2388,7 +2411,7 @@
final boolean isMounted;
synchronized (mObbMounts) {
- isMounted = mObbPathToStateMap.containsKey(obbInfo.filename);
+ isMounted = mObbPathToStateMap.containsKey(mObbState.rawPath);
}
if (isMounted) {
Slog.w(TAG, "Attempt to mount OBB which is already mounted: " + obbInfo.filename);
@@ -2396,12 +2419,6 @@
return;
}
- /*
- * The filename passed in might not be the canonical name, so just
- * set the filename to the canonicalized version.
- */
- mObbState.filename = obbInfo.filename;
-
final String hashedKey;
if (mKey == null) {
hashedKey = "none";
@@ -2428,7 +2445,7 @@
int rc = StorageResultCode.OperationSucceeded;
try {
mConnector.execute(
- "obb", "mount", mObbState.filename, hashedKey, mObbState.callerUid);
+ "obb", "mount", mObbState.voldPath, hashedKey, mObbState.ownerGid);
} catch (NativeDaemonConnectorException e) {
int code = e.getCode();
if (code != VoldResponseCode.OpFailedStorageBusy) {
@@ -2438,7 +2455,7 @@
if (rc == StorageResultCode.OperationSucceeded) {
if (DEBUG_OBB)
- Slog.d(TAG, "Successfully mounted OBB " + mObbState.filename);
+ Slog.d(TAG, "Successfully mounted OBB " + mObbState.voldPath);
synchronized (mObbMounts) {
addObbStateLocked(mObbState);
@@ -2461,14 +2478,7 @@
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("MountObbAction{");
- sb.append("filename=");
- sb.append(mObbState.filename);
- sb.append(",callerUid=");
- sb.append(mObbState.callerUid);
- sb.append(",token=");
- sb.append(mObbState.token != null ? mObbState.token.toString() : "NULL");
- sb.append(",binder=");
- sb.append(mObbState.token != null ? mObbState.getBinder().toString() : "null");
+ sb.append(mObbState);
sb.append('}');
return sb.toString();
}
@@ -2489,28 +2499,26 @@
final ObbInfo obbInfo = getObbInfo();
- final ObbState obbState;
+ final ObbState existingState;
synchronized (mObbMounts) {
- obbState = mObbPathToStateMap.get(obbInfo.filename);
+ existingState = mObbPathToStateMap.get(mObbState.rawPath);
}
- if (obbState == null) {
+ if (existingState == null) {
sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_NOT_MOUNTED);
return;
}
- if (obbState.callerUid != mObbState.callerUid) {
- Slog.w(TAG, "Permission denied attempting to unmount OBB " + obbInfo.filename
- + " (owned by " + obbInfo.packageName + ")");
+ if (existingState.ownerGid != mObbState.ownerGid) {
+ Slog.w(TAG, "Permission denied attempting to unmount OBB " + existingState.rawPath
+ + " (owned by GID " + existingState.ownerGid + ")");
sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED);
return;
}
- mObbState.filename = obbInfo.filename;
-
int rc = StorageResultCode.OperationSucceeded;
try {
- final Command cmd = new Command("obb", "unmount", mObbState.filename);
+ final Command cmd = new Command("obb", "unmount", mObbState.voldPath);
if (mForceUnmount) {
cmd.appendArg("force");
}
@@ -2529,12 +2537,12 @@
if (rc == StorageResultCode.OperationSucceeded) {
synchronized (mObbMounts) {
- removeObbStateLocked(obbState);
+ removeObbStateLocked(existingState);
}
sendNewStatusOrIgnore(OnObbStateChangeListener.UNMOUNTED);
} else {
- Slog.w(TAG, "Could not mount OBB: " + mObbState.filename);
+ Slog.w(TAG, "Could not unmount OBB: " + existingState);
sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_UNMOUNT);
}
}
@@ -2548,21 +2556,63 @@
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("UnmountObbAction{");
- sb.append("filename=");
- sb.append(mObbState.filename != null ? mObbState.filename : "null");
+ sb.append(mObbState);
sb.append(",force=");
sb.append(mForceUnmount);
- sb.append(",callerUid=");
- sb.append(mObbState.callerUid);
- sb.append(",token=");
- sb.append(mObbState.token != null ? mObbState.token.toString() : "null");
- sb.append(",binder=");
- sb.append(mObbState.token != null ? mObbState.getBinder().toString() : "null");
sb.append('}');
return sb.toString();
}
}
+ // @VisibleForTesting
+ public static String buildObbPath(final String canonicalPath, int userId, boolean forVold) {
+ // TODO: allow caller to provide Environment for full testing
+
+ // Only adjust paths when storage is emulated
+ if (!Environment.isExternalStorageEmulated()) {
+ return canonicalPath;
+ }
+
+ String path = canonicalPath.toString();
+
+ // First trim off any external storage prefix
+ final UserEnvironment userEnv = new UserEnvironment(userId);
+
+ // /storage/emulated/0
+ final String externalPath = userEnv.getExternalStorageDirectory().toString();
+ // /storage/emulated_legacy
+ final String legacyExternalPath = Environment.getLegacyExternalStorageDirectory()
+ .toString();
+
+ if (path.startsWith(externalPath)) {
+ path = path.substring(externalPath.length() + 1);
+ } else if (path.startsWith(legacyExternalPath)) {
+ path = path.substring(legacyExternalPath.length() + 1);
+ } else {
+ return canonicalPath;
+ }
+
+ // Handle special OBB paths on emulated storage
+ final String obbPath = "Android/obb";
+ if (path.startsWith(obbPath)) {
+ path = path.substring(obbPath.length() + 1);
+
+ if (forVold) {
+ return new File(Environment.getEmulatedStorageObbSource(), path).toString();
+ } else {
+ final UserEnvironment ownerEnv = new UserEnvironment(UserHandle.USER_OWNER);
+ return new File(ownerEnv.getExternalStorageObbDirectory(), path).toString();
+ }
+ }
+
+ // Handle normal external storage paths
+ if (forVold) {
+ return new File(Environment.getEmulatedStorageSource(userId), path).toString();
+ } else {
+ return new File(userEnv.getExternalStorageDirectory(), path).toString();
+ }
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) {
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index 76194ae..5d5f8d3 100755
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -890,6 +890,7 @@
userId = ActivityManager.handleIncomingUser(callingPid,
callingUid, userId, true, true, "enqueueNotification", pkg);
+ final UserHandle user = new UserHandle(userId);
// Limit the number of notifications that any given package except the android
// package can enqueue. Prevents DOS attacks and deals with leaks.
@@ -991,7 +992,6 @@
}
if (notification.icon != 0) {
- final UserHandle user = new UserHandle(userId);
final StatusBarNotification n = new StatusBarNotification(
pkg, id, tag, r.uid, r.initialPid, score, notification, user);
if (old != null && old.statusBarKey != null) {
@@ -1063,7 +1063,7 @@
try {
final IRingtonePlayer player = mAudioService.getRingtonePlayer();
if (player != null) {
- player.playAsync(uri, looping, audioStreamType);
+ player.playAsync(uri, user, looping, audioStreamType);
}
} catch (RemoteException e) {
} finally {
diff --git a/services/java/com/android/server/am/ActiveServices.java b/services/java/com/android/server/am/ActiveServices.java
index 0c0f00c..1269433 100644
--- a/services/java/com/android/server/am/ActiveServices.java
+++ b/services/java/com/android/server/am/ActiveServices.java
@@ -44,7 +44,6 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
-import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.IBinder;
import android.os.Message;
@@ -391,7 +390,7 @@
if (r.isForeground) {
r.isForeground = false;
if (r.app != null) {
- mAm.updateLruProcessLocked(r.app, false, true);
+ mAm.updateLruProcessLocked(r.app, false);
updateServiceForegroundLocked(r.app, true);
}
}
@@ -760,7 +759,8 @@
int N = mPendingServices.size();
for (int i=0; i<N; i++) {
ServiceRecord pr = mPendingServices.get(i);
- if (pr.name.equals(name)) {
+ if (pr.serviceInfo.applicationInfo.uid == sInfo.applicationInfo.uid
+ && pr.name.equals(name)) {
mPendingServices.remove(i);
i--;
N--;
@@ -942,7 +942,7 @@
Slog.w(TAG, "Scheduling restart of crashed service "
+ r.shortName + " in " + r.restartDelay + "ms");
EventLog.writeEvent(EventLogTags.AM_SCHEDULE_SERVICE_RESTART,
- r.shortName, r.restartDelay);
+ r.userId, r.shortName, r.restartDelay);
return canceled;
}
@@ -1083,14 +1083,14 @@
app.services.add(r);
bumpServiceExecutingLocked(r, "create");
- mAm.updateLruProcessLocked(app, true, true);
+ mAm.updateLruProcessLocked(app, true);
boolean created = false;
try {
mAm.mStringBuilder.setLength(0);
r.intent.getIntent().toShortString(mAm.mStringBuilder, true, false, true, false);
EventLog.writeEvent(EventLogTags.AM_CREATE_SERVICE,
- System.identityHashCode(r), r.shortName,
+ r.userId, System.identityHashCode(r), r.shortName,
mAm.mStringBuilder.toString(), r.app.pid);
synchronized (r.stats.getBatteryStats()) {
r.stats.startLaunchedLocked();
@@ -1240,7 +1240,7 @@
if (DEBUG_SERVICE) Slog.v(TAG, "Bringing down " + r + " " + r.intent);
EventLog.writeEvent(EventLogTags.AM_DESTROY_SERVICE,
- System.identityHashCode(r), r.shortName,
+ r.userId, System.identityHashCode(r), r.shortName,
(r.app != null) ? r.app.pid : -1);
mServiceMap.removeServiceByName(r.name, r.userId);
@@ -1664,7 +1664,7 @@
Slog.w(TAG, "Service crashed " + sr.crashCount
+ " times, stopping: " + sr);
EventLog.writeEvent(EventLogTags.AM_SERVICE_CRASHED_TOO_MUCH,
- sr.crashCount, sr.shortName, app.pid);
+ sr.userId, sr.crashCount, sr.shortName, app.pid);
bringDownServiceLocked(sr, true);
} else if (!allowRestart) {
bringDownServiceLocked(sr, true);
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index b266bd4..370d427 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -1369,7 +1369,7 @@
synchronized (mSelf.mPidsSelfLocked) {
mSelf.mPidsSelfLocked.put(app.pid, app);
}
- mSelf.updateLruProcessLocked(app, true, true);
+ mSelf.updateLruProcessLocked(app, true);
}
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(
@@ -1805,8 +1805,7 @@
}
}
- private final void updateLruProcessInternalLocked(ProcessRecord app,
- boolean updateActivityTime, int bestPos) {
+ private final void updateLruProcessInternalLocked(ProcessRecord app, int bestPos) {
// put it on the LRU to keep track of when it should be exited.
int lrui = mLruProcesses.indexOf(app);
if (lrui >= 0) mLruProcesses.remove(lrui);
@@ -1817,9 +1816,7 @@
app.lruSeq = mLruSeq;
// compute the new weight for this process.
- if (updateActivityTime) {
- app.lastActivityTime = SystemClock.uptimeMillis();
- }
+ app.lastActivityTime = SystemClock.uptimeMillis();
if (app.activities.size() > 0) {
// If this process has activities, we more strongly want to keep
// it around.
@@ -1863,24 +1860,22 @@
if (cr.binding != null && cr.binding.service != null
&& cr.binding.service.app != null
&& cr.binding.service.app.lruSeq != mLruSeq) {
- updateLruProcessInternalLocked(cr.binding.service.app,
- updateActivityTime, i+1);
+ updateLruProcessInternalLocked(cr.binding.service.app, i+1);
}
}
}
for (int j=app.conProviders.size()-1; j>=0; j--) {
ContentProviderRecord cpr = app.conProviders.get(j).provider;
if (cpr.proc != null && cpr.proc.lruSeq != mLruSeq) {
- updateLruProcessInternalLocked(cpr.proc,
- updateActivityTime, i+1);
+ updateLruProcessInternalLocked(cpr.proc, i+1);
}
}
}
final void updateLruProcessLocked(ProcessRecord app,
- boolean oomAdj, boolean updateActivityTime) {
+ boolean oomAdj) {
mLruSeq++;
- updateLruProcessInternalLocked(app, updateActivityTime, 0);
+ updateLruProcessInternalLocked(app, 0);
//Slog.i(TAG, "Putting proc to front: " + app.processName);
if (oomAdj) {
@@ -1981,7 +1976,8 @@
+ "/" + info.processName);
mProcessCrashTimes.remove(info.processName, info.uid);
if (mBadProcesses.get(info.processName, info.uid) != null) {
- EventLog.writeEvent(EventLogTags.AM_PROC_GOOD, info.uid,
+ EventLog.writeEvent(EventLogTags.AM_PROC_GOOD,
+ UserHandle.getUserId(info.uid), info.uid,
info.processName);
mBadProcesses.remove(info.processName, info.uid);
if (app != null) {
@@ -2129,7 +2125,8 @@
}
}
- EventLog.writeEvent(EventLogTags.AM_PROC_START, startResult.pid, uid,
+ EventLog.writeEvent(EventLogTags.AM_PROC_START,
+ UserHandle.getUserId(uid), startResult.pid, uid,
app.processName, hostingType,
hostingNameStr != null ? hostingNameStr : "");
@@ -2946,7 +2943,7 @@
if (!r.finishing) {
Slog.w(TAG, "Force removing " + r + ": app died, no saved state");
EventLog.writeEvent(EventLogTags.AM_FINISH_ACTIVITY,
- System.identityHashCode(r),
+ r.userId, System.identityHashCode(r),
r.task.taskId, r.shortComponentName,
"proc died without state saved");
}
@@ -3037,7 +3034,7 @@
Slog.i(TAG, "Process " + app.processName + " (pid " + pid
+ ") has died.");
}
- EventLog.writeEvent(EventLogTags.AM_PROC_DIED, app.pid, app.processName);
+ EventLog.writeEvent(EventLogTags.AM_PROC_DIED, app.userId, app.pid, app.processName);
if (localLOGV) Slog.v(
TAG, "Dying app: " + app + ", pid: " + pid
+ ", thread: " + thread.asBinder());
@@ -3086,7 +3083,7 @@
// A new process has already been started.
Slog.i(TAG, "Process " + app.processName + " (pid " + pid
+ ") has died and restarted (pid " + app.pid + ").");
- EventLog.writeEvent(EventLogTags.AM_PROC_DIED, app.pid, app.processName);
+ EventLog.writeEvent(EventLogTags.AM_PROC_DIED, app.userId, app.pid, app.processName);
} else if (DEBUG_PROCESSES) {
Slog.d(TAG, "Received spurious death notification for thread "
+ thread.asBinder());
@@ -3321,8 +3318,8 @@
app.notResponding = true;
// Log the ANR to the event log.
- EventLog.writeEvent(EventLogTags.AM_ANR, app.pid, app.processName, app.info.flags,
- annotation);
+ EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid,
+ app.processName, app.info.flags, annotation);
// Dump thread traces as quickly as we can, starting with "interesting" processes.
firstPids.add(app.pid);
@@ -3408,7 +3405,7 @@
synchronized (this) {
if (!showBackground && !app.isInterestingToUserLocked() && app.pid != MY_PID) {
Slog.w(TAG, "Killing " + app + ": background ANR");
- EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
+ EventLog.writeEvent(EventLogTags.AM_KILL, app.userId, app.pid,
app.processName, app.setAdj, "background ANR");
Process.killProcessQuiet(app.pid);
return;
@@ -4077,8 +4074,8 @@
if (gone) {
Slog.w(TAG, "Process " + app + " failed to attach");
- EventLog.writeEvent(EventLogTags.AM_PROCESS_START_TIMEOUT, pid, app.uid,
- app.processName);
+ EventLog.writeEvent(EventLogTags.AM_PROCESS_START_TIMEOUT, app.userId,
+ pid, app.uid, app.processName);
mProcessNames.remove(app.processName, app.uid);
mIsolatedProcesses.remove(app.uid);
if (mHeavyWeightProcess == app) {
@@ -4090,7 +4087,7 @@
checkAppInLaunchingProvidersLocked(app, true);
// Take care of any services that are waiting for the process.
mServices.processStartTimedOutLocked(app);
- EventLog.writeEvent(EventLogTags.AM_KILL, pid,
+ EventLog.writeEvent(EventLogTags.AM_KILL, app.userId, pid,
app.processName, app.setAdj, "start timeout");
Process.killProcessQuiet(pid);
if (mBackupTarget != null && mBackupTarget.app.pid == pid) {
@@ -4166,7 +4163,7 @@
return false;
}
- EventLog.writeEvent(EventLogTags.AM_PROC_BOUND, app.pid, app.processName);
+ EventLog.writeEvent(EventLogTags.AM_PROC_BOUND, app.userId, app.pid, app.processName);
app.thread = thread;
app.curAdj = app.setAdj = -100;
@@ -4244,7 +4241,7 @@
enableOpenGlTrace, isRestrictedBackupMode || !normalMode, app.persistent,
new Configuration(mConfiguration), app.compat, getCommonServicesLocked(),
mCoreSettingsObserver.getCoreSettingsLocked());
- updateLruProcessLocked(app, false, true);
+ updateLruProcessLocked(app, false);
app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis();
} catch (Exception e) {
// todo: Yikes! What should we do? For now we will try to
@@ -5914,7 +5911,7 @@
ProcessRecord pr = procs.get(i);
if (pr.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) {
Slog.i(TAG, "Killing " + pr.toShortString() + ": remove task");
- EventLog.writeEvent(EventLogTags.AM_KILL, pr.pid,
+ EventLog.writeEvent(EventLogTags.AM_KILL, pr.userId, pr.pid,
pr.processName, pr.setAdj, "remove task");
pr.killedBackground = true;
Process.killProcessQuiet(pr.pid);
@@ -6442,7 +6439,7 @@
// make sure to count it as being accessed and thus
// back up on the LRU list. This is good because
// content providers are often expensive to start.
- updateLruProcessLocked(cpr.proc, false, true);
+ updateLruProcessLocked(cpr.proc, false);
}
}
@@ -6630,6 +6627,7 @@
+ cpi.applicationInfo.uid + " for provider "
+ name + ": launching app became null");
EventLog.writeEvent(EventLogTags.AM_PROVIDER_LOST_PROCESS,
+ UserHandle.getUserId(cpi.applicationInfo.uid),
cpi.applicationInfo.packageName,
cpi.applicationInfo.uid, name);
return null;
@@ -7013,7 +7011,7 @@
if (isolated) {
mIsolatedProcesses.put(app.uid, app);
}
- updateLruProcessLocked(app, true, true);
+ updateLruProcessLocked(app, true);
}
// This package really, really can not be stopped.
@@ -7499,7 +7497,7 @@
int adj = proc.setAdj;
if (adj >= worstType && !proc.killedBackground) {
Slog.w(TAG, "Killing " + proc + " (adj " + adj + "): " + reason);
- EventLog.writeEvent(EventLogTags.AM_KILL, proc.pid,
+ EventLog.writeEvent(EventLogTags.AM_KILL, proc.userId, proc.pid,
proc.processName, adj, reason);
killed = true;
proc.killedBackground = true;
@@ -7535,8 +7533,8 @@
final int adj = proc.setAdj;
if (adj > belowAdj && !proc.killedBackground) {
Slog.w(TAG, "Killing " + proc + " (adj " + adj + "): " + reason);
- EventLog.writeEvent(
- EventLogTags.AM_KILL, proc.pid, proc.processName, adj, reason);
+ EventLog.writeEvent(EventLogTags.AM_KILL, proc.userId,
+ proc.pid, proc.processName, adj, reason);
killed = true;
proc.killedBackground = true;
Process.killProcessQuiet(pid);
@@ -7953,7 +7951,7 @@
if (app.pid > 0 && app.pid != MY_PID) {
handleAppCrashLocked(app);
Slog.i(ActivityManagerService.TAG, "Killing " + app + ": user's request");
- EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
+ EventLog.writeEvent(EventLogTags.AM_KILL, app.userId, app.pid,
app.processName, app.setAdj, "user's request after error");
Process.killProcessQuiet(app.pid);
}
@@ -7978,7 +7976,7 @@
Slog.w(TAG, "Process " + app.info.processName
+ " has crashed too many times: killing!");
EventLog.writeEvent(EventLogTags.AM_PROCESS_CRASHED_TOO_MUCH,
- app.info.processName, app.uid);
+ app.userId, app.info.processName, app.uid);
for (int i=mMainStack.mHistory.size()-1; i>=0; i--) {
ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i);
if (r.app == app) {
@@ -7993,7 +7991,7 @@
// explicitly does so... but for persistent process, we really
// need to keep it running. If a persistent process is actually
// repeatedly crashing, then badness for everyone.
- EventLog.writeEvent(EventLogTags.AM_PROC_BAD, app.uid,
+ EventLog.writeEvent(EventLogTags.AM_PROC_BAD, app.userId, app.uid,
app.info.processName);
if (!app.isolated) {
// XXX We don't have a way to mark isolated processes
@@ -8106,7 +8104,7 @@
: (r == null ? "unknown" : r.processName);
EventLog.writeEvent(EventLogTags.AM_CRASH, Binder.getCallingPid(),
- processName,
+ UserHandle.getUserId(Binder.getCallingUid()), processName,
r == null ? -1 : r.info.flags,
crashInfo.exceptionClassName,
crashInfo.exceptionMessage,
@@ -8304,7 +8302,8 @@
final String processName = app == null ? "system_server"
: (r == null ? "unknown" : r.processName);
- EventLog.writeEvent(EventLogTags.AM_WTF, Binder.getCallingPid(),
+ EventLog.writeEvent(EventLogTags.AM_WTF,
+ UserHandle.getUserId(Binder.getCallingUid()), Binder.getCallingPid(),
processName,
r == null ? -1 : r.info.flags,
tag, crashInfo.exceptionMessage);
@@ -10067,6 +10066,7 @@
pw.print(" ");
pw.print("oom: max="); pw.print(r.maxAdj);
pw.print(" hidden="); pw.print(r.hiddenAdj);
+ pw.print(" client="); pw.print(r.clientHiddenAdj);
pw.print(" empty="); pw.print(r.emptyAdj);
pw.print(" curRaw="); pw.print(r.curRawAdj);
pw.print(" setRaw="); pw.print(r.setRawAdj);
@@ -10591,7 +10591,7 @@
Slog.i(TAG, "Kill " + capp.processName
+ " (pid " + capp.pid + "): provider " + cpr.info.name
+ " in dying process " + (proc != null ? proc.processName : "??"));
- EventLog.writeEvent(EventLogTags.AM_KILL, capp.pid,
+ EventLog.writeEvent(EventLogTags.AM_KILL, capp.userId, capp.pid,
capp.processName, capp.setAdj, "dying provider "
+ cpr.name.toShortString());
Process.killProcessQuiet(capp.pid);
@@ -12466,7 +12466,7 @@
return null;
}
- private final int computeOomAdjLocked(ProcessRecord app, int hiddenAdj,
+ private final int computeOomAdjLocked(ProcessRecord app, int hiddenAdj, int clientHiddenAdj,
int emptyAdj, ProcessRecord TOP_APP, boolean recursed, boolean doingAll) {
if (mAdjSeq == app.adjSeq) {
// This adjustment has already been computed. If we are calling
@@ -12474,8 +12474,13 @@
// an earlier hidden adjustment that isn't really for us... if
// so, use the new hidden adjustment.
if (!recursed && app.hidden) {
- app.curAdj = app.curRawAdj = app.nonStoppingAdj =
- app.hasActivities ? hiddenAdj : emptyAdj;
+ if (app.hasActivities) {
+ app.curAdj = app.curRawAdj = app.nonStoppingAdj = hiddenAdj;
+ } else if (app.hasClientActivities) {
+ app.curAdj = app.curRawAdj = app.nonStoppingAdj = clientHiddenAdj;
+ } else {
+ app.curAdj = app.curRawAdj = app.nonStoppingAdj = emptyAdj;
+ }
}
return app.curRawAdj;
}
@@ -12491,6 +12496,7 @@
app.adjTarget = null;
app.empty = false;
app.hidden = false;
+ app.hasClientActivities = false;
final int activitiesSize = app.activities.size();
@@ -12572,7 +12578,7 @@
adj = hiddenAdj;
schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
app.hidden = true;
- app.adjType = "bg-activities";
+ app.adjType = "bg-act";
}
boolean hasStoppingActivities = false;
@@ -12614,11 +12620,16 @@
}
if (adj == hiddenAdj && !app.hasActivities) {
- // Whoops, this process is completely empty as far as we know
- // at this point.
- adj = emptyAdj;
- app.empty = true;
- app.adjType = "bg-empty";
+ if (app.hasClientActivities) {
+ adj = clientHiddenAdj;
+ app.adjType = "bg-client-act";
+ } else {
+ // Whoops, this process is completely empty as far as we know
+ // at this point.
+ adj = emptyAdj;
+ app.empty = true;
+ app.adjType = "bg-empty";
+ }
}
if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
@@ -12626,13 +12637,13 @@
// The user is aware of this app, so make it visible.
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
app.hidden = false;
- app.adjType = "foreground-service";
+ app.adjType = "fg-service";
schedGroup = Process.THREAD_GROUP_DEFAULT;
} else if (app.forcingToForeground != null) {
// The user is aware of this app, so make it visible.
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
app.hidden = false;
- app.adjType = "force-foreground";
+ app.adjType = "force-fg";
app.adjSource = app.forcingToForeground;
schedGroup = Process.THREAD_GROUP_DEFAULT;
}
@@ -12754,6 +12765,14 @@
myHiddenAdj = ProcessList.VISIBLE_APP_ADJ;
}
}
+ int myClientHiddenAdj = clientHiddenAdj;
+ if (myClientHiddenAdj > client.clientHiddenAdj) {
+ if (client.clientHiddenAdj >= ProcessList.VISIBLE_APP_ADJ) {
+ myClientHiddenAdj = client.clientHiddenAdj;
+ } else {
+ myClientHiddenAdj = ProcessList.VISIBLE_APP_ADJ;
+ }
+ }
int myEmptyAdj = emptyAdj;
if (myEmptyAdj > client.emptyAdj) {
if (client.emptyAdj >= ProcessList.VISIBLE_APP_ADJ) {
@@ -12763,7 +12782,7 @@
}
}
clientAdj = computeOomAdjLocked(client, myHiddenAdj,
- myEmptyAdj, TOP_APP, true, doingAll);
+ myClientHiddenAdj, myEmptyAdj, TOP_APP, true, doingAll);
String adjType = null;
if ((cr.flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) {
// Not doing bind OOM management, so treat
@@ -12792,6 +12811,19 @@
clientAdj = adj;
}
}
+ } else if ((cr.flags&Context.BIND_AUTO_CREATE) != 0) {
+ if ((cr.flags&Context.BIND_NOT_VISIBLE) == 0) {
+ // If this connection is keeping the service
+ // created, then we want to try to better follow
+ // its memory management semantics for activities.
+ // That is, if it is sitting in the background
+ // LRU list as a hidden process (with activities),
+ // we don't want the service it is connected to
+ // to go into the empty LRU and quickly get killed,
+ // because I'll we'll do is just end up restarting
+ // the service.
+ app.hasClientActivities |= client.hasActivities;
+ }
}
if (adj > clientAdj) {
// If this process has recently shown UI, and
@@ -12843,8 +12875,8 @@
}
}
}
+ final ActivityRecord a = cr.activity;
if ((cr.flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) {
- ActivityRecord a = cr.activity;
if (a != null && adj > ProcessList.FOREGROUND_APP_ADJ &&
(a.visible || a.state == ActivityState.RESUMED
|| a.state == ActivityState.PAUSING)) {
@@ -12902,6 +12934,14 @@
myHiddenAdj = ProcessList.FOREGROUND_APP_ADJ;
}
}
+ int myClientHiddenAdj = clientHiddenAdj;
+ if (myClientHiddenAdj > client.clientHiddenAdj) {
+ if (client.clientHiddenAdj >= ProcessList.FOREGROUND_APP_ADJ) {
+ myClientHiddenAdj = client.clientHiddenAdj;
+ } else {
+ myClientHiddenAdj = ProcessList.FOREGROUND_APP_ADJ;
+ }
+ }
int myEmptyAdj = emptyAdj;
if (myEmptyAdj > client.emptyAdj) {
if (client.emptyAdj > ProcessList.FOREGROUND_APP_ADJ) {
@@ -12911,7 +12951,7 @@
}
}
int clientAdj = computeOomAdjLocked(client, myHiddenAdj,
- myEmptyAdj, TOP_APP, true, doingAll);
+ myClientHiddenAdj, myEmptyAdj, TOP_APP, true, doingAll);
if (adj > clientAdj) {
if (app.hasShownUi && app != mHomeProcess
&& clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
@@ -13301,7 +13341,7 @@
Slog.w(TAG, "Excessive wake lock in " + app.processName
+ " (pid " + app.pid + "): held " + wtimeUsed
+ " during " + realtimeSince);
- EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
+ EventLog.writeEvent(EventLogTags.AM_KILL, app.userId, app.pid,
app.processName, app.setAdj, "excessive wake lock");
Process.killProcessQuiet(app.pid);
} else if (doCpuKills && uptimeSince > 0
@@ -13313,7 +13353,7 @@
Slog.w(TAG, "Excessive CPU in " + app.processName
+ " (pid " + app.pid + "): used " + cputimeUsed
+ " during " + uptimeSince);
- EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
+ EventLog.writeEvent(EventLogTags.AM_KILL, app.userId, app.pid,
app.processName, app.setAdj, "excessive cpu");
Process.killProcessQuiet(app.pid);
} else {
@@ -13325,8 +13365,9 @@
}
private final boolean updateOomAdjLocked(ProcessRecord app, int hiddenAdj,
- int emptyAdj, ProcessRecord TOP_APP, boolean doingAll) {
+ int clientHiddenAdj, int emptyAdj, ProcessRecord TOP_APP, boolean doingAll) {
app.hiddenAdj = hiddenAdj;
+ app.clientHiddenAdj = clientHiddenAdj;
app.emptyAdj = emptyAdj;
if (app.thread == null) {
@@ -13337,7 +13378,7 @@
boolean success = true;
- computeOomAdjLocked(app, hiddenAdj, emptyAdj, TOP_APP, false, doingAll);
+ computeOomAdjLocked(app, hiddenAdj, clientHiddenAdj, emptyAdj, TOP_APP, false, doingAll);
if (app.curRawAdj != app.setRawAdj) {
if (wasKeeping && !app.keeping) {
@@ -13374,7 +13415,7 @@
if (app.waitingToKill != null &&
app.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) {
Slog.i(TAG, "Killing " + app.toShortString() + ": " + app.waitingToKill);
- EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
+ EventLog.writeEvent(EventLogTags.AM_KILL, app.userId, app.pid,
app.processName, app.setAdj, app.waitingToKill);
app.killedBackground = true;
Process.killProcessQuiet(app.pid);
@@ -13424,8 +13465,8 @@
mAdjSeq++;
- boolean success = updateOomAdjLocked(app, app.hiddenAdj, app.emptyAdj,
- TOP_APP, false);
+ boolean success = updateOomAdjLocked(app, app.hiddenAdj, app.clientHiddenAdj,
+ app.emptyAdj, TOP_APP, false);
final boolean nowHidden = app.curAdj >= ProcessList.HIDDEN_APP_MIN_ADJ
&& app.curAdj <= ProcessList.HIDDEN_APP_MAX_ADJ;
if (nowHidden != wasHidden) {
@@ -13439,6 +13480,7 @@
final void updateOomAdjLocked() {
final ActivityRecord TOP_ACT = resumedAppLocked();
final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null;
+ final long oldTime = SystemClock.uptimeMillis() - ProcessList.MAX_EMPTY_TIME;
if (false) {
RuntimeException e = new RuntimeException();
@@ -13449,20 +13491,40 @@
mAdjSeq++;
mNewNumServiceProcs = 0;
+ final int emptyProcessLimit;
+ final int hiddenProcessLimit;
+ if (mProcessLimit <= 0) {
+ emptyProcessLimit = hiddenProcessLimit = 0;
+ } else if (mProcessLimit == 1) {
+ emptyProcessLimit = 1;
+ hiddenProcessLimit = 0;
+ } else {
+ emptyProcessLimit = (mProcessLimit*2)/3;
+ hiddenProcessLimit = mProcessLimit - emptyProcessLimit;
+ }
+
// Let's determine how many processes we have running vs.
// how many slots we have for background processes; we may want
// to put multiple processes in a slot of there are enough of
// them.
int numSlots = (ProcessList.HIDDEN_APP_MAX_ADJ
- ProcessList.HIDDEN_APP_MIN_ADJ + 1) / 2;
- int emptyFactor = (mLruProcesses.size()-mNumNonHiddenProcs-mNumHiddenProcs)/numSlots;
+ int numEmptyProcs = mLruProcesses.size()-mNumNonHiddenProcs-mNumHiddenProcs;
+ if (numEmptyProcs > hiddenProcessLimit) {
+ // If there are more empty processes than our limit on hidden
+ // processes, then use the hidden process limit for the factor.
+ // This ensures that the really old empty processes get pushed
+ // down to the bottom, so if we are running low on memory we will
+ // have a better chance at keeping around more hidden processes
+ // instead of a gazillion empty processes.
+ numEmptyProcs = hiddenProcessLimit;
+ }
+ int emptyFactor = numEmptyProcs/numSlots;
if (emptyFactor < 1) emptyFactor = 1;
int hiddenFactor = (mNumHiddenProcs > 0 ? mNumHiddenProcs : 1)/numSlots;
if (hiddenFactor < 1) hiddenFactor = 1;
int stepHidden = 0;
int stepEmpty = 0;
- final int emptyProcessLimit = mProcessLimit > 1 ? mProcessLimit / 2 : mProcessLimit;
- final int hiddenProcessLimit = mProcessLimit > 1 ? mProcessLimit / 2 : mProcessLimit;
int numHidden = 0;
int numEmpty = 0;
int numTrimming = 0;
@@ -13477,11 +13539,12 @@
int nextHiddenAdj = curHiddenAdj+1;
int curEmptyAdj = ProcessList.HIDDEN_APP_MIN_ADJ;
int nextEmptyAdj = curEmptyAdj+2;
+ int curClientHiddenAdj = curEmptyAdj;
while (i > 0) {
i--;
ProcessRecord app = mLruProcesses.get(i);
//Slog.i(TAG, "OOM " + app + ": cur hidden=" + curHiddenAdj);
- updateOomAdjLocked(app, curHiddenAdj, curEmptyAdj, TOP_APP, true);
+ updateOomAdjLocked(app, curHiddenAdj, curClientHiddenAdj, curEmptyAdj, TOP_APP, true);
if (!app.killedBackground) {
if (app.curRawAdj == curHiddenAdj && app.hasActivities) {
// This process was assigned as a hidden process... step the
@@ -13496,17 +13559,31 @@
if (nextHiddenAdj > ProcessList.HIDDEN_APP_MAX_ADJ) {
nextHiddenAdj = ProcessList.HIDDEN_APP_MAX_ADJ;
}
+ if (curClientHiddenAdj <= curHiddenAdj) {
+ curClientHiddenAdj = curHiddenAdj + 1;
+ if (curClientHiddenAdj > ProcessList.HIDDEN_APP_MAX_ADJ) {
+ curClientHiddenAdj = ProcessList.HIDDEN_APP_MAX_ADJ;
+ }
+ }
}
}
numHidden++;
if (numHidden > hiddenProcessLimit) {
Slog.i(TAG, "No longer want " + app.processName
+ " (pid " + app.pid + "): hidden #" + numHidden);
- EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
+ EventLog.writeEvent(EventLogTags.AM_KILL, app.userId, app.pid,
app.processName, app.setAdj, "too many background");
app.killedBackground = true;
Process.killProcessQuiet(app.pid);
}
+ } else if (app.curRawAdj == curHiddenAdj && app.hasClientActivities) {
+ // This process has a client that has activities. We will have
+ // given it the current hidden adj; here we will just leave it
+ // without stepping the hidden adj.
+ curClientHiddenAdj++;
+ if (curClientHiddenAdj > ProcessList.HIDDEN_APP_MAX_ADJ) {
+ curClientHiddenAdj = ProcessList.HIDDEN_APP_MAX_ADJ;
+ }
} else {
if (app.curRawAdj == curEmptyAdj || app.curRawAdj == curHiddenAdj) {
// This process was assigned as an empty process... step the
@@ -13525,15 +13602,28 @@
} else if (app.curRawAdj < ProcessList.HIDDEN_APP_MIN_ADJ) {
mNumNonHiddenProcs++;
}
- if (app.curAdj >= ProcessList.HIDDEN_APP_MIN_ADJ) {
- numEmpty++;
- if (numEmpty > emptyProcessLimit) {
+ if (app.curAdj >= ProcessList.HIDDEN_APP_MIN_ADJ
+ && !app.hasClientActivities) {
+ if (numEmpty > ProcessList.TRIM_EMPTY_APPS
+ && app.lastActivityTime < oldTime) {
Slog.i(TAG, "No longer want " + app.processName
- + " (pid " + app.pid + "): empty #" + numEmpty);
- EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
- app.processName, app.setAdj, "too many background");
+ + " (pid " + app.pid + "): empty for "
+ + ((oldTime+ProcessList.MAX_EMPTY_TIME-app.lastActivityTime)
+ / 1000) + "s");
+ EventLog.writeEvent(EventLogTags.AM_KILL, app.userId, app.pid,
+ app.processName, app.setAdj, "old background process");
app.killedBackground = true;
Process.killProcessQuiet(app.pid);
+ } else {
+ numEmpty++;
+ if (numEmpty > emptyProcessLimit) {
+ Slog.i(TAG, "No longer want " + app.processName
+ + " (pid " + app.pid + "): empty #" + numEmpty);
+ EventLog.writeEvent(EventLogTags.AM_KILL, app.userId, app.pid,
+ app.processName, app.setAdj, "too many background");
+ app.killedBackground = true;
+ Process.killProcessQuiet(app.pid);
+ }
}
}
}
@@ -13546,7 +13636,7 @@
// left sitting around after no longer needed.
Slog.i(TAG, "Isolated process " + app.processName
+ " (pid " + app.pid + ") no longer needed");
- EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
+ EventLog.writeEvent(EventLogTags.AM_KILL, app.userId, app.pid,
app.processName, app.setAdj, "isolated not needed");
app.killedBackground = true;
Process.killProcessQuiet(app.pid);
@@ -13567,8 +13657,8 @@
// are managing to keep around is less than half the maximum we desire;
// if we are keeping a good number around, we'll let them use whatever
// memory they want.
- if (numHidden <= (ProcessList.MAX_HIDDEN_APPS/4)
- && numEmpty <= (ProcessList.MAX_HIDDEN_APPS/4)) {
+ if (numHidden <= ProcessList.TRIM_HIDDEN_APPS
+ && numEmpty <= ProcessList.TRIM_EMPTY_APPS) {
final int numHiddenAndEmpty = numHidden + numEmpty;
final int N = mLruProcesses.size();
int factor = numTrimming/3;
@@ -13578,9 +13668,9 @@
if (factor < minFactor) factor = minFactor;
int step = 0;
int fgTrimLevel;
- if (numHiddenAndEmpty <= (ProcessList.MAX_HIDDEN_APPS/5)) {
+ if (numHiddenAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) {
fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL;
- } else if (numHiddenAndEmpty <= (ProcessList.MAX_HIDDEN_APPS/3)) {
+ } else if (numHiddenAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) {
fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
} else {
fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE;
@@ -13700,7 +13790,7 @@
+ (app.thread != null ? app.thread.asBinder() : null)
+ ")\n");
if (app.pid > 0 && app.pid != MY_PID) {
- EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
+ EventLog.writeEvent(EventLogTags.AM_KILL, app.userId, app.pid,
app.processName, app.setAdj, "empty");
Process.killProcessQuiet(app.pid);
} else {
diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java
index 7ff5748..6cd86fd 100644
--- a/services/java/com/android/server/am/ActivityRecord.java
+++ b/services/java/com/android/server/am/ActivityRecord.java
@@ -746,8 +746,8 @@
final long totalTime = stack.mInitialStartTime != 0
? (curTime - stack.mInitialStartTime) : thisTime;
if (ActivityManagerService.SHOW_ACTIVITY_START_TIME) {
- EventLog.writeEvent(EventLogTags.ACTIVITY_LAUNCH_TIME,
- System.identityHashCode(this), shortComponentName,
+ EventLog.writeEvent(EventLogTags.AM_ACTIVITY_LAUNCH_TIME,
+ userId, System.identityHashCode(this), shortComponentName,
thisTime, totalTime);
StringBuilder sb = service.mStringBuilder;
sb.setLength(0);
@@ -923,6 +923,8 @@
StringBuilder sb = new StringBuilder(128);
sb.append("ActivityRecord{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(" u");
+ sb.append(userId);
sb.append(' ');
sb.append(intent.getComponent().flattenToShortString());
sb.append('}');
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index 1707ff0..2d445274 100755
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -638,7 +638,7 @@
if (idx < 0) {
app.activities.add(r);
}
- mService.updateLruProcessLocked(app, true, true);
+ mService.updateLruProcessLocked(app, true);
try {
if (app.thread == null) {
@@ -656,7 +656,7 @@
+ " andResume=" + andResume);
if (andResume) {
EventLog.writeEvent(EventLogTags.AM_RESTART_ACTIVITY,
- System.identityHashCode(r),
+ r.userId, System.identityHashCode(r),
r.task.taskId, r.shortComponentName);
}
if (r.isHomeActivity) {
@@ -951,7 +951,7 @@
if (DEBUG_PAUSE) Slog.v(TAG, "Enqueueing pending pause: " + prev);
try {
EventLog.writeEvent(EventLogTags.AM_PAUSE_ACTIVITY,
- System.identityHashCode(prev),
+ prev.userId, System.identityHashCode(prev),
prev.shortComponentName);
prev.app.thread.schedulePauseActivity(prev.appToken, prev.finishing,
userLeaving, prev.configChangeFlags);
@@ -1040,7 +1040,7 @@
completePauseLocked();
} else {
EventLog.writeEvent(EventLogTags.AM_FAILED_TO_PAUSE,
- System.identityHashCode(r), r.shortComponentName,
+ r.userId, System.identityHashCode(r), r.shortComponentName,
mPausingActivity != null
? mPausingActivity.shortComponentName : "(none)");
}
@@ -1505,7 +1505,7 @@
if (next.app != null && next.app.thread != null) {
// No reason to do full oom adj update here; we'll let that
// happen whenever it needs to later.
- mService.updateLruProcessLocked(next.app, false, true);
+ mService.updateLruProcessLocked(next.app, false);
}
startPausingLocked(userLeaving, false);
return true;
@@ -1641,7 +1641,7 @@
if (mMainStack) {
mService.addRecentTaskLocked(next.task);
}
- mService.updateLruProcessLocked(next.app, true, true);
+ mService.updateLruProcessLocked(next.app, true);
updateLRUListLocked(next);
// Have the window manager re-evaluate the orientation of
@@ -1699,7 +1699,7 @@
}
EventLog.writeEvent(EventLogTags.AM_RESUME_ACTIVITY,
- System.identityHashCode(next),
+ next.userId, System.identityHashCode(next),
next.task.taskId, next.shortComponentName);
next.sleeping = false;
@@ -2967,7 +2967,7 @@
intent, r.getUriPermissionsLocked());
if (newTask) {
- EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.task.taskId);
+ EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.userId, r.task.taskId);
}
logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task);
startActivityLocked(r, newTask, doResume, keepCurTransition, options);
@@ -3700,7 +3700,7 @@
r.makeFinishing();
EventLog.writeEvent(EventLogTags.AM_FINISH_ACTIVITY,
- System.identityHashCode(r),
+ r.userId, System.identityHashCode(r),
r.task.taskId, r.shortComponentName, reason);
if (index < (mHistory.size()-1)) {
ActivityRecord next = mHistory.get(index+1);
@@ -3996,7 +3996,7 @@
TAG, "Removing activity from " + reason + ": token=" + r
+ ", app=" + (r.app != null ? r.app.processName : "(null)"));
EventLog.writeEvent(EventLogTags.AM_DESTROY_ACTIVITY,
- System.identityHashCode(r),
+ r.userId, System.identityHashCode(r),
r.task.taskId, r.shortComponentName, reason);
boolean removedFromHistory = false;
@@ -4228,7 +4228,7 @@
}
finishTaskMoveLocked(task);
- EventLog.writeEvent(EventLogTags.AM_TASK_TO_FRONT, task);
+ EventLog.writeEvent(EventLogTags.AM_TASK_TO_FRONT, tr.userId, task);
}
private final void finishTaskMoveLocked(int task) {
@@ -4448,7 +4448,7 @@
private final void logStartActivity(int tag, ActivityRecord r,
TaskRecord task) {
EventLog.writeEvent(tag,
- System.identityHashCode(r), task.taskId,
+ r.userId, System.identityHashCode(r), task.taskId,
r.shortComponentName, r.intent.getAction(),
r.intent.getType(), r.intent.getDataString(),
r.intent.getFlags());
@@ -4590,7 +4590,7 @@
+ " with results=" + results + " newIntents=" + newIntents
+ " andResume=" + andResume);
EventLog.writeEvent(andResume ? EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY
- : EventLogTags.AM_RELAUNCH_ACTIVITY, System.identityHashCode(r),
+ : EventLogTags.AM_RELAUNCH_ACTIVITY, r.userId, System.identityHashCode(r),
r.task.taskId, r.shortComponentName);
r.startFreezingScreenLocked(r.app, 0);
diff --git a/services/java/com/android/server/am/BroadcastFilter.java b/services/java/com/android/server/am/BroadcastFilter.java
index 07440b5..c631b6e 100644
--- a/services/java/com/android/server/am/BroadcastFilter.java
+++ b/services/java/com/android/server/am/BroadcastFilter.java
@@ -64,6 +64,8 @@
StringBuilder sb = new StringBuilder();
sb.append("BroadcastFilter{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(" u");
+ sb.append(owningUserId);
sb.append(' ');
sb.append(receiverList);
sb.append('}');
diff --git a/services/java/com/android/server/am/BroadcastQueue.java b/services/java/com/android/server/am/BroadcastQueue.java
index b0af081..9f27994 100644
--- a/services/java/com/android/server/am/BroadcastQueue.java
+++ b/services/java/com/android/server/am/BroadcastQueue.java
@@ -209,7 +209,7 @@
r.receiver = app.thread.asBinder();
r.curApp = app;
app.curReceiver = r;
- mService.updateLruProcessLocked(app, true, true);
+ mService.updateLruProcessLocked(app, true);
// Tell the application to launch this receiver.
r.intent.setComponent(r.curComponent);
@@ -930,22 +930,22 @@
if (curReceiver instanceof BroadcastFilter) {
BroadcastFilter bf = (BroadcastFilter) curReceiver;
EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_FILTER,
- System.identityHashCode(r),
+ bf.owningUserId, System.identityHashCode(r),
r.intent.getAction(),
r.nextReceiver - 1,
System.identityHashCode(bf));
} else {
+ ResolveInfo ri = (ResolveInfo)curReceiver;
EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP,
- System.identityHashCode(r),
- r.intent.getAction(),
- r.nextReceiver - 1,
- ((ResolveInfo)curReceiver).toString());
+ UserHandle.getUserId(ri.activityInfo.applicationInfo.uid),
+ System.identityHashCode(r), r.intent.getAction(),
+ r.nextReceiver - 1, ri.toString());
}
} else {
Slog.w(TAG, "Discarding broadcast before first receiver is invoked: "
+ r);
EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP,
- System.identityHashCode(r),
+ -1, System.identityHashCode(r),
r.intent.getAction(),
r.nextReceiver,
"NONE");
diff --git a/services/java/com/android/server/am/BroadcastRecord.java b/services/java/com/android/server/am/BroadcastRecord.java
index ca6d5f7..85ec328 100644
--- a/services/java/com/android/server/am/BroadcastRecord.java
+++ b/services/java/com/android/server/am/BroadcastRecord.java
@@ -194,6 +194,6 @@
public String toString() {
return "BroadcastRecord{"
+ Integer.toHexString(System.identityHashCode(this))
- + " " + intent.getAction() + "}";
+ + " u" + userId + " " + intent.getAction() + "}";
}
}
diff --git a/services/java/com/android/server/am/ConnectionRecord.java b/services/java/com/android/server/am/ConnectionRecord.java
index 5b3ff8d..4ed3c31 100644
--- a/services/java/com/android/server/am/ConnectionRecord.java
+++ b/services/java/com/android/server/am/ConnectionRecord.java
@@ -62,6 +62,8 @@
StringBuilder sb = new StringBuilder(128);
sb.append("ConnectionRecord{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(" u");
+ sb.append(binding.client.userId);
sb.append(' ');
if ((flags&Context.BIND_AUTO_CREATE) != 0) {
sb.append("CR ");
@@ -70,7 +72,7 @@
sb.append("DBG ");
}
if ((flags&Context.BIND_NOT_FOREGROUND) != 0) {
- sb.append("NOTFG ");
+ sb.append("!FG ");
}
if ((flags&Context.BIND_ABOVE_CLIENT) != 0) {
sb.append("ABCLT ");
@@ -88,7 +90,10 @@
sb.append("ACT ");
}
if ((flags&Context.BIND_NOT_VISIBLE) != 0) {
- sb.append("NOTVIS ");
+ sb.append("!VIS ");
+ }
+ if ((flags&Context.BIND_VISIBLE) != 0) {
+ sb.append("VIS ");
}
if (serviceDead) {
sb.append("DEAD ");
diff --git a/services/java/com/android/server/am/ContentProviderRecord.java b/services/java/com/android/server/am/ContentProviderRecord.java
index de306b5..8fb6a93 100644
--- a/services/java/com/android/server/am/ContentProviderRecord.java
+++ b/services/java/com/android/server/am/ContentProviderRecord.java
@@ -25,6 +25,7 @@
import android.os.IBinder.DeathRecipient;
import android.os.Process;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.Slog;
import java.io.PrintWriter;
@@ -199,6 +200,8 @@
StringBuilder sb = new StringBuilder(128);
sb.append("ContentProviderRecord{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(" u");
+ sb.append(UserHandle.getUserId(uid));
sb.append(' ');
sb.append(name.flattenToShortString());
sb.append('}');
diff --git a/services/java/com/android/server/am/EventLogTags.logtags b/services/java/com/android/server/am/EventLogTags.logtags
index a579f44..6ee7507 100644
--- a/services/java/com/android/server/am/EventLogTags.logtags
+++ b/services/java/com/android/server/am/EventLogTags.logtags
@@ -14,72 +14,72 @@
# google3/googledata/wireless/android/provisioning/gservices.config !!
#
# An activity is being finished:
-30001 am_finish_activity (Token|1|5),(Task ID|1|5),(Component Name|3),(Reason|3)
+30001 am_finish_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Reason|3)
# A task is being brought to the front of the screen:
-30002 am_task_to_front (Task|1|5)
+30002 am_task_to_front (User|1|5),(Task|1|5)
# An existing activity is being given a new intent:
-30003 am_new_intent (Token|1|5),(Task ID|1|5),(Component Name|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5)
+30003 am_new_intent (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5)
# A new task is being created:
-30004 am_create_task (Task ID|1|5)
+30004 am_create_task (User|1|5),(Task ID|1|5)
# A new activity is being created in an existing task:
-30005 am_create_activity (Token|1|5),(Task ID|1|5),(Component Name|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5)
+30005 am_create_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5)
# An activity has been resumed into the foreground but was not already running:
-30006 am_restart_activity (Token|1|5),(Task ID|1|5),(Component Name|3)
+30006 am_restart_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3)
# An activity has been resumed and is now in the foreground:
-30007 am_resume_activity (Token|1|5),(Task ID|1|5),(Component Name|3)
+30007 am_resume_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3)
# Application Not Responding
-30008 am_anr (pid|1|5),(Package Name|3),(Flags|1|5),(reason|3)
+30008 am_anr (User|1|5),(pid|1|5),(Package Name|3),(Flags|1|5),(reason|3)
# Activity launch time
-30009 activity_launch_time (Token|1|5),(Component Name|3),(time|2|3)
+30009 am_activity_launch_time (User|1|5),(Token|1|5),(Component Name|3),(time|2|3)
# Application process bound to work
-30010 am_proc_bound (PID|1|5),(Process Name|3)
+30010 am_proc_bound (User|1|5),(PID|1|5),(Process Name|3)
# Application process died
-30011 am_proc_died (PID|1|5),(Process Name|3)
+30011 am_proc_died (User|1|5),(PID|1|5),(Process Name|3)
# The Activity Manager failed to pause the given activity.
-30012 am_failed_to_pause (Token|1|5),(Wanting to pause|3),(Currently pausing|3)
+30012 am_failed_to_pause (User|1|5),(Token|1|5),(Wanting to pause|3),(Currently pausing|3)
# Attempting to pause the current activity
-30013 am_pause_activity (Token|1|5),(Component Name|3)
+30013 am_pause_activity (User|1|5),(Token|1|5),(Component Name|3)
# Application process has been started
-30014 am_proc_start (PID|1|5),(UID|1|5),(Process Name|3),(Type|3),(Component|3)
+30014 am_proc_start (User|1|5),(PID|1|5),(UID|1|5),(Process Name|3),(Type|3),(Component|3)
# An application process has been marked as bad
-30015 am_proc_bad (UID|1|5),(Process Name|3)
+30015 am_proc_bad (User|1|5),(UID|1|5),(Process Name|3)
# An application process that was bad is now marked as good
-30016 am_proc_good (UID|1|5),(Process Name|3)
+30016 am_proc_good (User|1|5),(UID|1|5),(Process Name|3)
# Reporting to applications that memory is low
30017 am_low_memory (Num Processes|1|1)
# An activity is being destroyed:
-30018 am_destroy_activity (Token|1|5),(Task ID|1|5),(Component Name|3),(Reason|3)
+30018 am_destroy_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Reason|3)
# An activity has been relaunched, resumed, and is now in the foreground:
-30019 am_relaunch_resume_activity (Token|1|5),(Task ID|1|5),(Component Name|3)
+30019 am_relaunch_resume_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3)
# An activity has been relaunched:
-30020 am_relaunch_activity (Token|1|5),(Task ID|1|5),(Component Name|3)
+30020 am_relaunch_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3)
# The activity's onPause has been called.
-30021 am_on_paused_called (Component Name|3)
+30021 am_on_paused_called (User|1|5),(Component Name|3)
# The activity's onResume has been called.
-30022 am_on_resume_called (Component Name|3)
+30022 am_on_resume_called (User|1|5),(Component Name|3)
# Kill a process to reclaim memory.
-30023 am_kill (PID|1|5),(Process Name|3),(OomAdj|1|5),(Reason|3)
+30023 am_kill (User|1|5),(PID|1|5),(Process Name|3),(OomAdj|1|5),(Reason|3)
# Discard an undelivered serialized broadcast (timeout/ANR/crash)
-30024 am_broadcast_discard_filter (Broadcast|1|5),(Action|3),(Receiver Number|1|1),(BroadcastFilter|1|5)
-30025 am_broadcast_discard_app (Broadcast|1|5),(Action|3),(Receiver Number|1|1),(App|3)
+30024 am_broadcast_discard_filter (User|1|5),(Broadcast|1|5),(Action|3),(Receiver Number|1|1),(BroadcastFilter|1|5)
+30025 am_broadcast_discard_app (User|1|5),(Broadcast|1|5),(Action|3),(Receiver Number|1|1),(App|3)
# A service is being created
-30030 am_create_service (Service Record|1|5),(Name|3),(Intent|3),(PID|1|5)
+30030 am_create_service (User|1|5),(Service Record|1|5),(Name|3),(Intent|3),(PID|1|5)
# A service is being destroyed
-30031 am_destroy_service (Service Record|1|5),(Name|3),(PID|1|5)
+30031 am_destroy_service (User|1|5),(Service Record|1|5),(Name|3),(PID|1|5)
# A process has crashed too many times, it is being cleared
-30032 am_process_crashed_too_much (Name|3),(PID|1|5)
+30032 am_process_crashed_too_much (User|1|5),(Name|3),(PID|1|5)
# An unknown process is trying to attach to the activity manager
30033 am_drop_process (PID|1|5)
# A service has crashed too many times, it is being stopped
-30034 am_service_crashed_too_much (Crash Count|1|1),(Component Name|3),(PID|1|5)
+30034 am_service_crashed_too_much (User|1|5),(Crash Count|1|1),(Component Name|3),(PID|1|5)
# A service is going to be restarted after its process went away
-30035 am_schedule_service_restart (Component Name|3),(Time|2|3)
+30035 am_schedule_service_restart (User|1|5),(Component Name|3),(Time|2|3)
# A client was waiting for a content provider, but its process was lost
-30036 am_provider_lost_process (Package Name|3),(UID|1|5),(Name|3)
+30036 am_provider_lost_process (User|1|5),(Package Name|3),(UID|1|5),(Name|3)
# The activity manager gave up on a new process taking too long to start
-30037 am_process_start_timeout (PID|1|5),(UID|1|5),(Process Name|3)
+30037 am_process_start_timeout (User|1|5),(PID|1|5),(UID|1|5),(Process Name|3)
# Unhandled exception
-30039 am_crash (PID|1|5),(Process Name|3),(Flags|1|5),(Exception|3),(Message|3),(File|3),(Line|1|5)
+30039 am_crash (User|1|5),(PID|1|5),(Process Name|3),(Flags|1|5),(Exception|3),(Message|3),(File|3),(Line|1|5)
# Log.wtf() called
-30040 am_wtf (PID|1|5),(Process Name|3),(Flags|1|5),(Tag|3),(Message|3)
+30040 am_wtf (User|1|5),(PID|1|5),(Process Name|3),(Flags|1|5),(Tag|3),(Message|3)
diff --git a/services/java/com/android/server/am/ProcessList.java b/services/java/com/android/server/am/ProcessList.java
index afc060e..9e25e30 100644
--- a/services/java/com/android/server/am/ProcessList.java
+++ b/services/java/com/android/server/am/ProcessList.java
@@ -101,7 +101,24 @@
// The maximum number of hidden processes we will keep around before
// killing them; this is just a control to not let us go too crazy with
// keeping around processes on devices with large amounts of RAM.
- static final int MAX_HIDDEN_APPS = 15;
+ static final int MAX_HIDDEN_APPS = 24;
+
+ // We allow empty processes to stick around for at most 30 minutes.
+ static final long MAX_EMPTY_TIME = 30*60*1000;
+
+ // The number of hidden at which we don't consider it necessary to do
+ // memory trimming.
+ static final int TRIM_HIDDEN_APPS = 3;
+
+ // The number of empty apps at which we don't consider it necessary to do
+ // memory trimming.
+ static final int TRIM_EMPTY_APPS = 3;
+
+ // Threshold of number of hidden+empty where we consider memory critical.
+ static final int TRIM_CRITICAL_THRESHOLD = 3;
+
+ // Threshold of number of hidden+empty where we consider memory critical.
+ static final int TRIM_LOW_THRESHOLD = 5;
// We put empty content processes after any hidden processes that have
// been idle for less than 15 seconds.
diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java
index d372422..652fdb5 100644
--- a/services/java/com/android/server/am/ProcessRecord.java
+++ b/services/java/com/android/server/am/ProcessRecord.java
@@ -61,6 +61,7 @@
long lruWeight; // Weight for ordering in LRU list
int maxAdj; // Maximum OOM adjustment for this process
int hiddenAdj; // If hidden, this is the adjustment to use
+ int clientHiddenAdj; // If empty but hidden client, this is the adjustment to use
int emptyAdj; // If empty, this is the adjustment to use
int curRawAdj; // Current OOM unlimited adjustment for this process
int setRawAdj; // Last set OOM unlimited adjustment for this process
@@ -75,6 +76,7 @@
boolean keeping; // Actively running code so don't kill due to that?
boolean setIsForeground; // Running foreground UI when last set?
boolean hasActivities; // Are there any activities running in this process?
+ boolean hasClientActivities; // Are there any client services with activities?
boolean foregroundServices; // Running any services that are foreground?
boolean foregroundActivities; // Running any activities that are foreground?
boolean systemNoUi; // This is a system process, but not currently showing UI.
@@ -188,8 +190,7 @@
instrumentationInfo.dump(new PrintWriterPrinter(pw), prefix + " ");
}
}
- pw.print(prefix); pw.print("thread="); pw.print(thread);
- pw.print(" curReceiver="); pw.println(curReceiver);
+ pw.print(prefix); pw.print("thread="); pw.println(thread);
pw.print(prefix); pw.print("pid="); pw.print(pid); pw.print(" starting=");
pw.print(starting); pw.print(" lastPss="); pw.println(lastPss);
pw.print(prefix); pw.print("lastActivityTime=");
@@ -201,6 +202,7 @@
pw.print(" empty="); pw.println(empty);
pw.print(prefix); pw.print("oom: max="); pw.print(maxAdj);
pw.print(" hidden="); pw.print(hiddenAdj);
+ pw.print(" client="); pw.print(clientHiddenAdj);
pw.print(" empty="); pw.print(emptyAdj);
pw.print(" curRaw="); pw.print(curRawAdj);
pw.print(" setRaw="); pw.print(setRawAdj);
@@ -211,18 +213,27 @@
pw.print(" setSchedGroup="); pw.print(setSchedGroup);
pw.print(" systemNoUi="); pw.print(systemNoUi);
pw.print(" trimMemoryLevel="); pw.println(trimMemoryLevel);
- pw.print(prefix); pw.print("hasShownUi="); pw.print(hasShownUi);
- pw.print(" pendingUiClean="); pw.print(pendingUiClean);
- pw.print(" hasAboveClient="); pw.println(hasAboveClient);
- pw.print(prefix); pw.print("setIsForeground="); pw.print(setIsForeground);
- pw.print(" foregroundServices="); pw.print(foregroundServices);
- pw.print(" forcingToForeground="); pw.println(forcingToForeground);
- pw.print(prefix); pw.print("persistent="); pw.print(persistent);
- pw.print(" removed="); pw.print(removed);
- pw.print(" hasActivities="); pw.print(hasActivities);
- pw.print(" foregroundActivities="); pw.println(foregroundActivities);
pw.print(prefix); pw.print("adjSeq="); pw.print(adjSeq);
pw.print(" lruSeq="); pw.println(lruSeq);
+ if (hasShownUi || pendingUiClean || hasAboveClient) {
+ pw.print(prefix); pw.print("hasShownUi="); pw.print(hasShownUi);
+ pw.print(" pendingUiClean="); pw.print(pendingUiClean);
+ pw.print(" hasAboveClient="); pw.println(hasAboveClient);
+ }
+ if (setIsForeground || foregroundServices || forcingToForeground != null) {
+ pw.print(prefix); pw.print("setIsForeground="); pw.print(setIsForeground);
+ pw.print(" foregroundServices="); pw.print(foregroundServices);
+ pw.print(" forcingToForeground="); pw.println(forcingToForeground);
+ }
+ if (persistent || removed) {
+ pw.print(prefix); pw.print("persistent="); pw.print(persistent);
+ pw.print(" removed="); pw.println(removed);
+ }
+ if (hasActivities || hasClientActivities || foregroundActivities) {
+ pw.print(prefix); pw.print("hasActivities="); pw.print(hasActivities);
+ pw.print(" hasClientActivities="); pw.print(hasClientActivities);
+ pw.print(" foregroundActivities="); pw.println(foregroundActivities);
+ }
if (!keeping) {
long wtime;
synchronized (batteryStats.getBatteryStats()) {
@@ -231,10 +242,10 @@
}
long timeUsed = wtime - lastWakeTime;
pw.print(prefix); pw.print("lastWakeTime="); pw.print(lastWakeTime);
- pw.print(" time used=");
+ pw.print(" timeUsed=");
TimeUtils.formatDuration(timeUsed, pw); pw.println("");
pw.print(prefix); pw.print("lastCpuTime="); pw.print(lastCpuTime);
- pw.print(" time used=");
+ pw.print(" timeUsed=");
TimeUtils.formatDuration(curCpuTime-lastCpuTime, pw); pw.println("");
}
pw.print(prefix); pw.print("lastRequestedGc=");
@@ -299,6 +310,9 @@
pw.print(prefix); pw.print(" - "); pw.println(conProviders.get(i).toShortString());
}
}
+ if (curReceiver != null) {
+ pw.print(prefix); pw.print("curReceiver="); pw.println(curReceiver);
+ }
if (receivers.size() > 0) {
pw.print(prefix); pw.println("Receivers:");
for (ReceiverList rl : receivers) {
@@ -318,7 +332,7 @@
pkgList.add(_info.packageName);
thread = _thread;
maxAdj = ProcessList.HIDDEN_APP_MAX_ADJ;
- hiddenAdj = emptyAdj = ProcessList.HIDDEN_APP_MIN_ADJ;
+ hiddenAdj = clientHiddenAdj = emptyAdj = ProcessList.HIDDEN_APP_MIN_ADJ;
curRawAdj = setRawAdj = -100;
curAdj = setAdj = -100;
persistent = false;
diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java
index 7055fdc..84e824a 100644
--- a/services/java/com/android/server/am/ServiceRecord.java
+++ b/services/java/com/android/server/am/ServiceRecord.java
@@ -425,6 +425,7 @@
StringBuilder sb = new StringBuilder(128);
sb.append("ServiceRecord{")
.append(Integer.toHexString(System.identityHashCode(this)))
+ .append(" u").append(userId)
.append(' ').append(shortName).append('}');
return stringName = sb.toString();
}
diff --git a/services/jni/com_android_server_power_PowerManagerService.cpp b/services/jni/com_android_server_power_PowerManagerService.cpp
index 3f3970b..38af38d 100644
--- a/services/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/jni/com_android_server_power_PowerManagerService.cpp
@@ -174,32 +174,32 @@
sp<ISurfaceComposer> s(ComposerService::getComposerService());
if (on) {
{
- ALOGD_IF_SLOW(50, "Excessive delay in autosuspend_disable() while turning screen on");
+ ALOGD_IF_SLOW(100, "Excessive delay in autosuspend_disable() while turning screen on");
autosuspend_disable();
}
if (gPowerModule) {
- ALOGD_IF_SLOW(10, "Excessive delay in setInteractive(true) while turning screen on");
+ ALOGD_IF_SLOW(20, "Excessive delay in setInteractive(true) while turning screen on");
gPowerModule->setInteractive(gPowerModule, true);
}
{
- ALOGD_IF_SLOW(20, "Excessive delay in unblank() while turning screen on");
+ ALOGD_IF_SLOW(100, "Excessive delay in unblank() while turning screen on");
s->unblank();
}
} else {
{
- ALOGD_IF_SLOW(20, "Excessive delay in blank() while turning screen off");
+ ALOGD_IF_SLOW(100, "Excessive delay in blank() while turning screen off");
s->blank();
}
if (gPowerModule) {
- ALOGD_IF_SLOW(10, "Excessive delay in setInteractive(false) while turning screen off");
+ ALOGD_IF_SLOW(20, "Excessive delay in setInteractive(false) while turning screen off");
gPowerModule->setInteractive(gPowerModule, false);
}
{
- ALOGD_IF_SLOW(50, "Excessive delay in autosuspend_enable() while turning screen off");
+ ALOGD_IF_SLOW(100, "Excessive delay in autosuspend_enable() while turning screen off");
autosuspend_enable();
}
}
diff --git a/core/tests/coretests/res/raw/test1.obb b/services/tests/servicestests/res/raw/test1.obb
similarity index 99%
rename from core/tests/coretests/res/raw/test1.obb
rename to services/tests/servicestests/res/raw/test1.obb
index 8466588..7d2b4f6 100644
--- a/core/tests/coretests/res/raw/test1.obb
+++ b/services/tests/servicestests/res/raw/test1.obb
Binary files differ
diff --git a/core/tests/coretests/res/raw/test1_nosig.obb b/services/tests/servicestests/res/raw/test1_nosig.obb
similarity index 100%
rename from core/tests/coretests/res/raw/test1_nosig.obb
rename to services/tests/servicestests/res/raw/test1_nosig.obb
Binary files differ
diff --git a/core/tests/coretests/res/raw/test1_wrongpackage.obb b/services/tests/servicestests/res/raw/test1_wrongpackage.obb
similarity index 100%
rename from core/tests/coretests/res/raw/test1_wrongpackage.obb
rename to services/tests/servicestests/res/raw/test1_wrongpackage.obb
Binary files differ
diff --git a/core/tests/coretests/src/com/android/server/MountServiceTests.java b/services/tests/servicestests/src/com/android/server/MountServiceTests.java
similarity index 85%
rename from core/tests/coretests/src/com/android/server/MountServiceTests.java
rename to services/tests/servicestests/src/com/android/server/MountServiceTests.java
index 1f8c92e..9c88b40 100644
--- a/core/tests/coretests/src/com/android/server/MountServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/MountServiceTests.java
@@ -16,8 +16,6 @@
package com.android.server;
-import com.android.frameworks.coretests.R;
-
import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
@@ -29,6 +27,10 @@
import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
+import static com.android.server.MountService.buildObbPath;
+
+import com.android.frameworks.servicestests.R;
+
import java.io.File;
import java.io.InputStream;
@@ -282,4 +284,34 @@
unmountObb(sm, file1, OnObbStateChangeListener.UNMOUNTED);
unmountObb(sm, file2, OnObbStateChangeListener.UNMOUNTED);
}
+
+ public void testBuildObbPath() {
+ final int userId = 10;
+
+ // Paths outside external storage should remain untouched
+ assertEquals("/storage/random/foo",
+ buildObbPath("/storage/random/foo", userId, true));
+ assertEquals("/storage/random/foo",
+ buildObbPath("/storage/random/foo", userId, false));
+
+ // Paths on user-specific emulated storage
+ assertEquals("/mnt/shell/emulated/10/foo",
+ buildObbPath("/storage/emulated_legacy/foo", userId, true));
+ assertEquals("/storage/emulated/10/foo",
+ buildObbPath("/storage/emulated_legacy/foo", userId, false));
+ assertEquals("/mnt/shell/emulated/10/foo",
+ buildObbPath("/storage/emulated/10/foo", userId, true));
+ assertEquals("/storage/emulated/10/foo",
+ buildObbPath("/storage/emulated/10/foo", userId, false));
+
+ // Paths on shared OBB emulated storage
+ assertEquals("/mnt/shell/emulated/obb/foo",
+ buildObbPath("/storage/emulated_legacy/Android/obb/foo", userId, true));
+ assertEquals("/storage/emulated/0/Android/obb/foo",
+ buildObbPath("/storage/emulated_legacy/Android/obb/foo", userId, false));
+ assertEquals("/mnt/shell/emulated/obb/foo",
+ buildObbPath("/storage/emulated/10/Android/obb/foo", userId, true));
+ assertEquals("/storage/emulated/0/Android/obb/foo",
+ buildObbPath("/storage/emulated/10/Android/obb/foo", userId, false));
+ }
}
diff --git a/tests/ActivityTests/AndroidManifest.xml b/tests/ActivityTests/AndroidManifest.xml
index 9dfe4a1..15d075c 100644
--- a/tests/ActivityTests/AndroidManifest.xml
+++ b/tests/ActivityTests/AndroidManifest.xml
@@ -21,6 +21,8 @@
<uses-permission android:name="android.permission.REMOVE_TASKS" />
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <uses-permission android:name="android.permission.MANAGE_USERS" />
<application android:label="ActivityTest">
<activity android:name="ActivityTestMain">
<intent-filter>
@@ -31,6 +33,8 @@
<service android:name="SingleUserService"
android:singleUser="true" android:exported="true">
</service>
+ <service android:name="ServiceUserTarget">
+ </service>
<receiver android:name="UserTarget">
</receiver>
<receiver android:name="SingleUserReceiver"
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
index 2348e99..f0c3b22 100644
--- a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
+++ b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
@@ -16,6 +16,7 @@
package com.google.android.test.activity;
+import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
@@ -31,6 +32,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.UserManager;
import android.graphics.Bitmap;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -41,6 +43,7 @@
import android.view.MenuItem;
import android.view.View;
import android.content.Context;
+import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.util.Log;
@@ -51,6 +54,9 @@
ActivityManager mAm;
Configuration mOverrideConfig;
+ int mSecondUser;
+
+ ArrayList<ServiceConnection> mConnections = new ArrayList<ServiceConnection>();
class BroadcastResultReceiver extends BroadcastReceiver {
@Override
@@ -122,6 +128,15 @@
applyOverrideConfiguration(mOverrideConfig);
}
}
+
+ UserManager um = (UserManager)getSystemService(Context.USER_SERVICE);
+ List<UserInfo> users = um.getUsers();
+ mSecondUser = Integer.MAX_VALUE;
+ for (UserInfo ui : users) {
+ if (ui.id != 0 && mSecondUser > ui.id) {
+ mSecondUser = ui.id;
+ }
+ }
}
@Override
@@ -148,7 +163,12 @@
Log.i(TAG, "Service disconnected " + name);
}
};
- bindService(intent, conn, Context.BIND_AUTO_CREATE);
+ if (bindService(intent, conn, Context.BIND_AUTO_CREATE)) {
+ mConnections.add(conn);
+ } else {
+ Toast.makeText(ActivityTestMain.this, "Failed to bind",
+ Toast.LENGTH_LONG).show();
+ }
return true;
}
});
@@ -185,15 +205,70 @@
return true;
}
});
- menu.add("Send to user 1!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ menu.add("Send to user 0!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override public boolean onMenuItemClick(MenuItem item) {
Intent intent = new Intent(ActivityTestMain.this, UserTarget.class);
- sendOrderedBroadcastAsUser(intent, new UserHandle(1), null,
+ sendOrderedBroadcastAsUser(intent, new UserHandle(0), null,
+ new BroadcastResultReceiver(),
+ null, Activity.RESULT_OK, null, null);
+ return true;
+ }
+ });
+ menu.add("Send to user " + mSecondUser + "!").setOnMenuItemClickListener(
+ new MenuItem.OnMenuItemClickListener() {
+ @Override public boolean onMenuItemClick(MenuItem item) {
+ Intent intent = new Intent(ActivityTestMain.this, UserTarget.class);
+ sendOrderedBroadcastAsUser(intent, new UserHandle(mSecondUser), null,
new BroadcastResultReceiver(),
null, Activity.RESULT_OK, null, null);
return true;
}
});
+ menu.add("Bind to user 0!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override public boolean onMenuItemClick(MenuItem item) {
+ Intent intent = new Intent(ActivityTestMain.this, ServiceUserTarget.class);
+ ServiceConnection conn = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ Log.i(TAG, "Service connected " + name + " " + service);
+ }
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.i(TAG, "Service disconnected " + name);
+ }
+ };
+ if (bindService(intent, conn, Context.BIND_AUTO_CREATE, 0)) {
+ mConnections.add(conn);
+ } else {
+ Toast.makeText(ActivityTestMain.this, "Failed to bind",
+ Toast.LENGTH_LONG).show();
+ }
+ return true;
+ }
+ });
+ menu.add("Bind to user " + mSecondUser + "!").setOnMenuItemClickListener(
+ new MenuItem.OnMenuItemClickListener() {
+ @Override public boolean onMenuItemClick(MenuItem item) {
+ Intent intent = new Intent(ActivityTestMain.this, ServiceUserTarget.class);
+ ServiceConnection conn = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ Log.i(TAG, "Service connected " + name + " " + service);
+ }
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.i(TAG, "Service disconnected " + name);
+ }
+ };
+ if (bindService(intent, conn, Context.BIND_AUTO_CREATE, mSecondUser)) {
+ mConnections.add(conn);
+ } else {
+ Toast.makeText(ActivityTestMain.this, "Failed to bind",
+ Toast.LENGTH_LONG).show();
+ }
+ return true;
+ }
+ });
menu.add("Density!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override public boolean onMenuItemClick(MenuItem item) {
if (mOverrideConfig == null) {
@@ -226,6 +301,15 @@
}
}
+ @Override
+ protected void onStop() {
+ super.onStop();
+ for (ServiceConnection conn : mConnections) {
+ unbindService(conn);
+ }
+ mConnections.clear();
+ }
+
private View scrollWrap(View view) {
ScrollView scroller = new ScrollView(this);
scroller.addView(view, new ScrollView.LayoutParams(ScrollView.LayoutParams.MATCH_PARENT,
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/ServiceUserTarget.java b/tests/ActivityTests/src/com/google/android/test/activity/ServiceUserTarget.java
new file mode 100644
index 0000000..a7474ec
--- /dev/null
+++ b/tests/ActivityTests/src/com/google/android/test/activity/ServiceUserTarget.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.test.activity;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.widget.Toast;
+
+public class ServiceUserTarget extends Service {
+ Binder mBinder = new Binder();
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Toast.makeText(this,
+ "Service created as user " + UserHandle.myUserId(),
+ Toast.LENGTH_LONG).show();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+}
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/SingleUserService.java b/tests/ActivityTests/src/com/google/android/test/activity/SingleUserService.java
index c40582a..e9c340f 100644
--- a/tests/ActivityTests/src/com/google/android/test/activity/SingleUserService.java
+++ b/tests/ActivityTests/src/com/google/android/test/activity/SingleUserService.java
@@ -20,11 +20,21 @@
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
+import android.os.UserHandle;
+import android.widget.Toast;
public class SingleUserService extends Service {
Binder mBinder = new Binder();
@Override
+ public void onCreate() {
+ super.onCreate();
+ Toast.makeText(this,
+ "Service created as user " + UserHandle.myUserId(),
+ Toast.LENGTH_LONG).show();
+ }
+
+ @Override
public IBinder onBind(Intent intent) {
return mBinder;
}
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 9c2e1b9..77168f9 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -1946,7 +1946,7 @@
const bool pub = (typeSpecFlags&ResTable_typeSpec::SPEC_PUBLIC) != 0;
fprintf(fp,
- "int styleable.%s_%s %d\n",
+ "int styleable %s_%s %d\n",
nclassName.string(),
String8(name).string(), (int)pos);
}
diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java
index a5a2469..55ea34f 100644
--- a/wifi/java/android/net/wifi/WifiStateTracker.java
+++ b/wifi/java/android/net/wifi/WifiStateTracker.java
@@ -25,7 +25,6 @@
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkStateTracker;
-import android.net.wifi.p2p.WifiP2pManager;
import android.os.Handler;
import android.os.Message;
import android.util.Slog;
@@ -88,7 +87,6 @@
IntentFilter filter = new IntentFilter();
filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
filter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
- filter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
mWifiStateReceiver = new WifiStateReceiver();
mContext.registerReceiver(mWifiStateReceiver, filter);
@@ -217,20 +215,7 @@
@Override
public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) {
- mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
- WifiP2pManager.EXTRA_NETWORK_INFO);
- mLinkProperties = intent.getParcelableExtra(
- WifiP2pManager.EXTRA_LINK_PROPERTIES);
- if (mLinkProperties == null) {
- mLinkProperties = new LinkProperties();
- }
- mLinkCapabilities = intent.getParcelableExtra(
- WifiP2pManager.EXTRA_LINK_CAPABILITIES);
- if (mLinkCapabilities == null) {
- mLinkCapabilities = new LinkCapabilities();
- }
- } else if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+ if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
WifiManager.EXTRA_NETWORK_INFO);
mLinkProperties = intent.getParcelableExtra(