Merge "Add a hidden method to clear the capabilities of a NetworkRequest" into mnc-dev
diff --git a/Android.mk b/Android.mk
index eb6e7b5..146afe0 100644
--- a/Android.mk
+++ b/Android.mk
@@ -84,6 +84,7 @@
core/java/android/app/job/IJobScheduler.aidl \
core/java/android/app/job/IJobService.aidl \
core/java/android/app/ITransientNotification.aidl \
+ core/java/android/app/IUidObserver.aidl \
core/java/android/app/IUiAutomationConnection.aidl \
core/java/android/app/IUiModeManager.aidl \
core/java/android/app/IUserSwitchObserver.aidl \
diff --git a/api/current.txt b/api/current.txt
index 231be10..80c4dc8 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -7160,6 +7160,7 @@
public static final class ScanSettings.Builder {
ctor public ScanSettings.Builder();
method public android.bluetooth.le.ScanSettings build();
+ method public android.bluetooth.le.ScanSettings.Builder setCallbackType(int);
method public android.bluetooth.le.ScanSettings.Builder setMatchMode(int);
method public android.bluetooth.le.ScanSettings.Builder setNumOfMatches(int);
method public android.bluetooth.le.ScanSettings.Builder setReportDelay(long);
@@ -30323,6 +30324,7 @@
method public android.net.Uri getSubscriptionAddress();
method public java.util.List<java.lang.String> getSupportedUriSchemes();
method public boolean hasCapabilities(int);
+ method public boolean isEnabled();
method public boolean supportsUriScheme(java.lang.String);
method public android.telecom.PhoneAccount.Builder toBuilder();
method public void writeToParcel(android.os.Parcel, int);
@@ -34443,6 +34445,8 @@
method public abstract void setTitle(int);
method public void setTitleOptionalHint(boolean);
method public void setType(int);
+ method public void snooze(int);
+ field public static final int SNOOZE_TIME_DEFAULT;
field public static final int TYPE_FLOATING = 1; // 0x1
field public static final int TYPE_PRIMARY = 0; // 0x0
}
@@ -36507,6 +36511,7 @@
public class ViewConfiguration {
ctor public deprecated ViewConfiguration();
method public static android.view.ViewConfiguration get(android.content.Context);
+ method public static int getDefaultActionModeSnoozeTime();
method public static int getDoubleTapTimeout();
method public static deprecated int getEdgeSlop();
method public static deprecated int getFadingEdgeLength();
@@ -36910,6 +36915,7 @@
method public abstract void setText(java.lang.CharSequence);
method public abstract void setText(java.lang.CharSequence, int, int);
method public abstract void setTextPaint(android.text.TextPaint);
+ method public abstract void setTextStyle(int, int, int, int);
method public abstract void setVisibility(int);
}
diff --git a/api/system-current.txt b/api/system-current.txt
index afd82e3..6c4f278 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -32509,6 +32509,7 @@
method public android.net.Uri getSubscriptionAddress();
method public java.util.List<java.lang.String> getSupportedUriSchemes();
method public boolean hasCapabilities(int);
+ method public boolean isEnabled();
method public boolean supportsUriScheme(java.lang.String);
method public android.telecom.PhoneAccount.Builder toBuilder();
method public void writeToParcel(android.os.Parcel, int);
@@ -32650,6 +32651,7 @@
method public void cancelMissedCallsNotification();
method public deprecated void clearAccounts();
method public void clearPhoneAccounts();
+ method public void enablePhoneAccount(android.telecom.PhoneAccountHandle, boolean);
method public boolean endCall();
method public android.net.Uri getAdnUriForPhoneAccount(android.telecom.PhoneAccountHandle);
method public java.util.List<android.telecom.PhoneAccountHandle> getAllPhoneAccountHandles();
@@ -32665,7 +32667,6 @@
method public android.telecom.PhoneAccount getPhoneAccount(android.telecom.PhoneAccountHandle);
method public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsForPackage();
method public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsSupportingScheme(java.lang.String);
- method public java.util.List<android.telecom.PhoneAccountHandle> getRegisteredConnectionManagers();
method public android.telecom.PhoneAccountHandle getSimCallManager();
method public java.lang.String getVoiceMailNumber(android.telecom.PhoneAccountHandle);
method public boolean handleMmi(java.lang.String);
@@ -36706,6 +36707,8 @@
method public abstract void setTitle(int);
method public void setTitleOptionalHint(boolean);
method public void setType(int);
+ method public void snooze(int);
+ field public static final int SNOOZE_TIME_DEFAULT;
field public static final int TYPE_FLOATING = 1; // 0x1
field public static final int TYPE_PRIMARY = 0; // 0x0
}
@@ -38770,6 +38773,7 @@
public class ViewConfiguration {
ctor public deprecated ViewConfiguration();
method public static android.view.ViewConfiguration get(android.content.Context);
+ method public static int getDefaultActionModeSnoozeTime();
method public static int getDoubleTapTimeout();
method public static deprecated int getEdgeSlop();
method public static deprecated int getFadingEdgeLength();
@@ -39173,6 +39177,7 @@
method public abstract void setText(java.lang.CharSequence);
method public abstract void setText(java.lang.CharSequence, int, int);
method public abstract void setTextPaint(android.text.TextPaint);
+ method public abstract void setTextStyle(int, int, int, int);
method public abstract void setVisibility(int);
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 15cfbcd..cdf15e1 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1970,6 +1970,22 @@
return true;
}
+ case REGISTER_UID_OBSERVER_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IUidObserver observer = IUidObserver.Stub.asInterface(
+ data.readStrongBinder());
+ registerUidObserver(observer);
+ return true;
+ }
+
+ case UNREGISTER_UID_OBSERVER_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IUidObserver observer = IUidObserver.Stub.asInterface(
+ data.readStrongBinder());
+ unregisterUidObserver(observer);
+ return true;
+ }
+
case GET_PACKAGE_ASK_SCREEN_COMPAT_TRANSACTION:
{
data.enforceInterface(IActivityManager.descriptor);
@@ -5077,6 +5093,28 @@
reply.recycle();
}
+ public void registerUidObserver(IUidObserver observer) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(observer != null ? observer.asBinder() : null);
+ mRemote.transact(REGISTER_UID_OBSERVER_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ public void unregisterUidObserver(IUidObserver observer) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(observer != null ? observer.asBinder() : null);
+ mRemote.transact(UNREGISTER_UID_OBSERVER_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
public boolean isIntentSenderTargetedToPackage(IIntentSender sender) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 5dcbe37..cb436b5 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4585,7 +4585,7 @@
// crash if we can't get it.
IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
try {
- final ProxyInfo proxyInfo = service.getDefaultProxy();
+ final ProxyInfo proxyInfo = service.getProxyForNetwork(null);
Proxy.setHttpProxySystemProperty(proxyInfo);
} catch (RemoteException e) {}
}
diff --git a/core/java/android/app/AssistStructure.java b/core/java/android/app/AssistStructure.java
index a06bc31..b703b0e 100644
--- a/core/java/android/app/AssistStructure.java
+++ b/core/java/android/app/AssistStructure.java
@@ -635,6 +635,15 @@
}
@Override
+ public void setTextStyle(int size, int fgColor, int bgColor, int style) {
+ ViewNodeText t = getNodeText();
+ t.mTextColor = fgColor;
+ t.mTextBackgroundColor = bgColor;
+ t.mTextSize = size;
+ t.mTextStyle = style;
+ }
+
+ @Override
public void setHint(CharSequence hint) {
getNodeText().mHint = hint != null ? hint.toString() : null;
}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 4ba1055..310c5ef 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -387,6 +387,9 @@
public void registerProcessObserver(IProcessObserver observer) throws RemoteException;
public void unregisterProcessObserver(IProcessObserver observer) throws RemoteException;
+ public void registerUidObserver(IUidObserver observer) throws RemoteException;
+ public void unregisterUidObserver(IUidObserver observer) throws RemoteException;
+
public boolean isIntentSenderTargetedToPackage(IIntentSender sender) throws RemoteException;
public boolean isIntentSenderAnActivity(IIntentSender sender) throws RemoteException;
@@ -844,4 +847,6 @@
int UPDATE_DEVICE_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+295;
int UPDATE_PREFERRED_SETUP_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+296;
int KEYGUARD_GOING_AWAY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+297;
+ int REGISTER_UID_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+298;
+ int UNREGISTER_UID_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+299;
}
diff --git a/core/java/android/app/IUidObserver.aidl b/core/java/android/app/IUidObserver.aidl
new file mode 100644
index 0000000..308cb94
--- /dev/null
+++ b/core/java/android/app/IUidObserver.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+/** {@hide} */
+oneway interface IUidObserver {
+ void onUidStateChanged(int uid, int procState);
+ void onUidGone(int uid);
+}
diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
index 4f81f98..eb6166a 100644
--- a/core/java/android/bluetooth/BluetoothPan.java
+++ b/core/java/android/bluetooth/BluetoothPan.java
@@ -333,19 +333,25 @@
public void setBluetoothTethering(boolean value) {
if (DBG) log("setBluetoothTethering(" + value + ")");
- try {
- mPanService.setBluetoothTethering(value);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+
+ if (mPanService != null && isEnabled()) {
+ try {
+ mPanService.setBluetoothTethering(value);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ }
}
}
public boolean isTetheringOn() {
if (VDBG) log("isTetheringOn()");
- try {
- return mPanService.isTetheringOn();
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+
+ if (mPanService != null && isEnabled()) {
+ try {
+ return mPanService.isTetheringOn();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ }
}
return false;
}
diff --git a/core/java/android/bluetooth/le/ScanSettings.java b/core/java/android/bluetooth/le/ScanSettings.java
index 4eeb577..d616624 100644
--- a/core/java/android/bluetooth/le/ScanSettings.java
+++ b/core/java/android/bluetooth/le/ScanSettings.java
@@ -249,9 +249,7 @@
*
* @param callbackType The callback type flags for the scan.
* @throws IllegalArgumentException If the {@code callbackType} is invalid.
- * @hide
*/
- @SystemApi
public Builder setCallbackType(int callbackType) {
if (!isValidCallbackType(callbackType)) {
diff --git a/core/java/android/hardware/camera2/CameraAccessException.java b/core/java/android/hardware/camera2/CameraAccessException.java
index 84e9912..933ce0d 100644
--- a/core/java/android/hardware/camera2/CameraAccessException.java
+++ b/core/java/android/hardware/camera2/CameraAccessException.java
@@ -16,8 +16,13 @@
package android.hardware.camera2;
+import android.annotation.NonNull;
+import android.annotation.IntDef;
import android.util.AndroidException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* <p><code>CameraAccessException</code> is thrown if a camera device could not
* be queried or opened by the {@link CameraManager}, or if the connection to an
@@ -76,6 +81,16 @@
*/
public static final int CAMERA_DEPRECATED_HAL = 1000;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ {CAMERA_IN_USE,
+ MAX_CAMERAS_IN_USE,
+ CAMERA_DISABLED,
+ CAMERA_DISCONNECTED,
+ CAMERA_ERROR})
+ public @interface AccessError {};
+
// Make the eclipse warning about serializable exceptions go away
private static final long serialVersionUID = 5630338637471475675L; // randomly generated
@@ -88,26 +103,27 @@
* @see #CAMERA_DISCONNECTED
* @see #CAMERA_ERROR
*/
+ @AccessError
public final int getReason() {
return mReason;
}
- public CameraAccessException(int problem) {
+ public CameraAccessException(@AccessError int problem) {
super(getDefaultMessage(problem));
mReason = problem;
}
- public CameraAccessException(int problem, String message) {
+ public CameraAccessException(@AccessError int problem, String message) {
super(message);
mReason = problem;
}
- public CameraAccessException(int problem, String message, Throwable cause) {
+ public CameraAccessException(@AccessError int problem, String message, Throwable cause) {
super(message, cause);
mReason = problem;
}
- public CameraAccessException(int problem, Throwable cause) {
+ public CameraAccessException(@AccessError int problem, Throwable cause) {
super(getDefaultMessage(problem), cause);
mReason = problem;
}
@@ -115,7 +131,7 @@
/**
* @hide
*/
- public static String getDefaultMessage(int problem) {
+ public static String getDefaultMessage(@AccessError int problem) {
switch (problem) {
case CAMERA_IN_USE:
return "The camera device is in use already";
diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java
index c6f622c..b3e7cfc 100644
--- a/core/java/android/hardware/camera2/CameraCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraCaptureSession.java
@@ -16,6 +16,8 @@
package android.hardware.camera2;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Handler;
import android.view.Surface;
@@ -73,6 +75,7 @@
/**
* Get the camera device that this session is created for.
*/
+ @NonNull
public abstract CameraDevice getDevice();
/**
@@ -133,7 +136,7 @@
*
* @see StateCallback#onSurfacePrepared
*/
- public abstract void prepare(Surface surface) throws CameraAccessException;
+ public abstract void prepare(@NonNull Surface surface) throws CameraAccessException;
/**
* <p>Submit a request for an image to be captured by the camera device.</p>
@@ -194,7 +197,8 @@
* @see #abortCaptures
* @see CameraDevice#createReprocessableCaptureSession
*/
- public abstract int capture(CaptureRequest request, CaptureCallback listener, Handler handler)
+ public abstract int capture(@NonNull CaptureRequest request,
+ @Nullable CaptureCallback listener, @Nullable Handler handler)
throws CameraAccessException;
/**
@@ -252,8 +256,9 @@
* @see #setRepeatingBurst
* @see #abortCaptures
*/
- public abstract int captureBurst(List<CaptureRequest> requests, CaptureCallback listener,
- Handler handler) throws CameraAccessException;
+ public abstract int captureBurst(@NonNull List<CaptureRequest> requests,
+ @Nullable CaptureCallback listener, @Nullable Handler handler)
+ throws CameraAccessException;
/**
* Request endlessly repeating capture of images by this capture session.
@@ -318,8 +323,9 @@
* @see #stopRepeating
* @see #abortCaptures
*/
- public abstract int setRepeatingRequest(CaptureRequest request, CaptureCallback listener,
- Handler handler) throws CameraAccessException;
+ public abstract int setRepeatingRequest(@NonNull CaptureRequest request,
+ @Nullable CaptureCallback listener, @Nullable Handler handler)
+ throws CameraAccessException;
/**
* <p>Request endlessly repeating capture of a sequence of images by this
@@ -389,8 +395,9 @@
* @see #stopRepeating
* @see #abortCaptures
*/
- public abstract int setRepeatingBurst(List<CaptureRequest> requests, CaptureCallback listener,
- Handler handler) throws CameraAccessException;
+ public abstract int setRepeatingBurst(@NonNull List<CaptureRequest> requests,
+ @Nullable CaptureCallback listener, @Nullable Handler handler)
+ throws CameraAccessException;
/**
* <p>Cancel any ongoing repeating capture set by either
@@ -478,6 +485,7 @@
* @see android.media.ImageWriter
* @see android.media.ImageReader
*/
+ @Nullable
public abstract Surface getInputSurface();
/**
@@ -525,7 +533,7 @@
*
* @param session the session returned by {@link CameraDevice#createCaptureSession}
*/
- public abstract void onConfigured(CameraCaptureSession session);
+ public abstract void onConfigured(@NonNull CameraCaptureSession session);
/**
* This method is called if the session cannot be configured as requested.
@@ -540,7 +548,7 @@
*
* @param session the session returned by {@link CameraDevice#createCaptureSession}
*/
- public abstract void onConfigureFailed(CameraCaptureSession session);
+ public abstract void onConfigureFailed(@NonNull CameraCaptureSession session);
/**
* This method is called every time the session has no more capture requests to process.
@@ -555,7 +563,7 @@
* @param session the session returned by {@link CameraDevice#createCaptureSession}
*
*/
- public void onReady(CameraCaptureSession session) {
+ public void onReady(@NonNull CameraCaptureSession session) {
// default empty implementation
}
@@ -571,7 +579,7 @@
*
* @param session the session returned by {@link CameraDevice#createCaptureSession}
*/
- public void onActive(CameraCaptureSession session) {
+ public void onActive(@NonNull CameraCaptureSession session) {
// default empty implementation
}
@@ -589,7 +597,7 @@
*
* @param session the session returned by {@link CameraDevice#createCaptureSession}
*/
- public void onClosed(CameraCaptureSession session) {
+ public void onClosed(@NonNull CameraCaptureSession session) {
// default empty implementation
}
@@ -608,7 +616,8 @@
* @param session the session returned by {@link CameraDevice#createCaptureSession}
* @param surface the Surface that was used with the {@link #prepare} call.
*/
- public void onSurfacePrepared(CameraCaptureSession session, Surface surface) {
+ public void onSurfacePrepared(@NonNull CameraCaptureSession session,
+ @NonNull Surface surface) {
// default empty implementation
}
}
@@ -675,8 +684,8 @@
*
* @see android.media.MediaActionSound
*/
- public void onCaptureStarted(CameraCaptureSession session,
- CaptureRequest request, long timestamp, long frameNumber) {
+ public void onCaptureStarted(@NonNull CameraCaptureSession session,
+ @NonNull CaptureRequest request, long timestamp, long frameNumber) {
// Temporary trampoline for API change transition
onCaptureStarted(session, request, timestamp);
}
@@ -756,8 +765,8 @@
* @see #setRepeatingRequest
* @see #setRepeatingBurst
*/
- public void onCaptureProgressed(CameraCaptureSession session,
- CaptureRequest request, CaptureResult partialResult) {
+ public void onCaptureProgressed(@NonNull CameraCaptureSession session,
+ @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) {
// default empty implementation
}
@@ -785,8 +794,8 @@
* @see #setRepeatingRequest
* @see #setRepeatingBurst
*/
- public void onCaptureCompleted(CameraCaptureSession session,
- CaptureRequest request, TotalCaptureResult result) {
+ public void onCaptureCompleted(@NonNull CameraCaptureSession session,
+ @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
// default empty implementation
}
@@ -814,8 +823,8 @@
* @see #setRepeatingRequest
* @see #setRepeatingBurst
*/
- public void onCaptureFailed(CameraCaptureSession session,
- CaptureRequest request, CaptureFailure failure) {
+ public void onCaptureFailed(@NonNull CameraCaptureSession session,
+ @NonNull CaptureRequest request, @NonNull CaptureFailure failure) {
// default empty implementation
}
@@ -844,7 +853,7 @@
* @see CaptureFailure#getSequenceId()
* @see #onCaptureSequenceAborted
*/
- public void onCaptureSequenceCompleted(CameraCaptureSession session,
+ public void onCaptureSequenceCompleted(@NonNull CameraCaptureSession session,
int sequenceId, long frameNumber) {
// default empty implementation
}
@@ -873,7 +882,7 @@
* @see CaptureFailure#getSequenceId()
* @see #onCaptureSequenceCompleted
*/
- public void onCaptureSequenceAborted(CameraCaptureSession session,
+ public void onCaptureSequenceAborted(@NonNull CameraCaptureSession session,
int sequenceId) {
// default empty implementation
}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index d3b63f9..c2a6a44 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -16,6 +16,8 @@
package android.hardware.camera2;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.PublicKey;
import android.hardware.camera2.impl.SyntheticKey;
@@ -91,6 +93,7 @@
*
* @return String representation of the key name
*/
+ @NonNull
public String getName() {
return mKey.getName();
}
@@ -166,6 +169,7 @@
* @param key The characteristics field to read.
* @return The value of that key, or {@code null} if the field is not set.
*/
+ @Nullable
public <T> T get(Key<T> key) {
return mProperties.get(key);
}
@@ -194,6 +198,7 @@
/**
* {@inheritDoc}
*/
+ @NonNull
@Override
public List<Key<?>> getKeys() {
// List of keys is immutable; cache the results after we calculate them
@@ -227,6 +232,7 @@
* @return List of keys supported by this CameraDevice for CaptureRequests.
*/
@SuppressWarnings({"unchecked"})
+ @NonNull
public List<CaptureRequest.Key<?>> getAvailableCaptureRequestKeys() {
if (mAvailableRequestKeys == null) {
Object crKey = CaptureRequest.Key.class;
@@ -258,6 +264,7 @@
* @return List of keys supported by this CameraDevice for CaptureResults.
*/
@SuppressWarnings({"unchecked"})
+ @NonNull
public List<CaptureResult.Key<?>> getAvailableCaptureResultKeys() {
if (mAvailableResultKeys == null) {
Object crKey = CaptureResult.Key.class;
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index dad4fb6..d02f349 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -16,6 +16,9 @@
package android.hardware.camera2;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.IntDef;
import android.hardware.camera2.params.InputConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.hardware.camera2.params.OutputConfiguration;
@@ -23,6 +26,8 @@
import android.view.Surface;
import java.util.List;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* <p>The CameraDevice class is a representation of a single camera connected to an
@@ -124,6 +129,17 @@
*/
public static final int TEMPLATE_MANUAL = 6;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ {TEMPLATE_PREVIEW,
+ TEMPLATE_STILL_CAPTURE,
+ TEMPLATE_RECORD,
+ TEMPLATE_VIDEO_SNAPSHOT,
+ TEMPLATE_ZERO_SHUTTER_LAG,
+ TEMPLATE_MANUAL })
+ public @interface RequestTemplate {};
+
/**
* Get the ID of this camera device.
*
@@ -142,6 +158,7 @@
* @see CameraManager#getCameraCharacteristics
* @see CameraManager#getCameraIdList
*/
+ @NonNull
public abstract String getId();
/**
@@ -391,8 +408,8 @@
* @see StreamConfigurationMap#getOutputSizes(int)
* @see StreamConfigurationMap#getOutputSizes(Class)
*/
- public abstract void createCaptureSession(List<Surface> outputs,
- CameraCaptureSession.StateCallback callback, Handler handler)
+ public abstract void createCaptureSession(@NonNull List<Surface> outputs,
+ @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
throws CameraAccessException;
/**
@@ -560,8 +577,9 @@
* @see android.media.ImageWriter
* @see android.media.ImageReader
*/
- public abstract void createReprocessableCaptureSession(InputConfiguration inputConfig,
- List<Surface> outputs, CameraCaptureSession.StateCallback callback, Handler handler)
+ public abstract void createReprocessableCaptureSession(@NonNull InputConfiguration inputConfig,
+ @NonNull List<Surface> outputs, @NonNull CameraCaptureSession.StateCallback callback,
+ @Nullable Handler handler)
throws CameraAccessException;
/**
@@ -591,7 +609,8 @@
* @see #TEMPLATE_VIDEO_SNAPSHOT
* @see #TEMPLATE_MANUAL
*/
- public abstract CaptureRequest.Builder createCaptureRequest(int templateType)
+ @NonNull
+ public abstract CaptureRequest.Builder createCaptureRequest(@RequestTemplate int templateType)
throws CameraAccessException;
/**
@@ -620,8 +639,9 @@
* @see CameraDevice#createReprocessableCaptureSession
* @see android.media.ImageWriter
*/
+ @NonNull
public abstract CaptureRequest.Builder createReprocessCaptureRequest(
- TotalCaptureResult inputResult) throws CameraAccessException;
+ @NonNull TotalCaptureResult inputResult) throws CameraAccessException;
/**
* Close the connection to this camera device as quickly as possible.
@@ -727,6 +747,16 @@
*/
public static final int ERROR_CAMERA_SERVICE = 5;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ {ERROR_CAMERA_IN_USE,
+ ERROR_MAX_CAMERAS_IN_USE,
+ ERROR_CAMERA_DISABLED,
+ ERROR_CAMERA_DEVICE,
+ ERROR_CAMERA_SERVICE })
+ public @interface ErrorCode {};
+
/**
* The method called when a camera device has finished opening.
*
@@ -736,7 +766,7 @@
*
* @param camera the camera device that has become opened
*/
- public abstract void onOpened(CameraDevice camera); // Must implement
+ public abstract void onOpened(@NonNull CameraDevice camera); // Must implement
/**
* The method called when a camera device has been closed with
@@ -749,7 +779,7 @@
*
* @param camera the camera device that has become closed
*/
- public void onClosed(CameraDevice camera) {
+ public void onClosed(@NonNull CameraDevice camera) {
// Default empty implementation
}
@@ -781,7 +811,7 @@
*
* @param camera the device that has been disconnected
*/
- public abstract void onDisconnected(CameraDevice camera); // Must implement
+ public abstract void onDisconnected(@NonNull CameraDevice camera); // Must implement
/**
* The method called when a camera device has encountered a serious error.
@@ -805,12 +835,14 @@
* @param error The error code, one of the
* {@code StateCallback.ERROR_*} values.
*
+ * @see #ERROR_CAMERA_IN_USE
+ * @see #ERROR_MAX_CAMERAS_IN_USE
+ * @see #ERROR_CAMERA_DISABLED
* @see #ERROR_CAMERA_DEVICE
* @see #ERROR_CAMERA_SERVICE
- * @see #ERROR_CAMERA_DISABLED
- * @see #ERROR_CAMERA_IN_USE
*/
- public abstract void onError(CameraDevice camera, int error); // Must implement
+ public abstract void onError(@NonNull CameraDevice camera,
+ @ErrorCode int error); // Must implement
}
/**
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index d99cce7..20e1610 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -16,6 +16,9 @@
package android.hardware.camera2;
+import android.annotation.RequiresPermission;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.hardware.ICameraService;
import android.hardware.ICameraServiceListener;
@@ -86,6 +89,7 @@
*
* @return The list of currently connected camera devices.
*/
+ @NonNull
public String[] getCameraIdList() throws CameraAccessException {
synchronized (mLock) {
// ID list creation handles various known failures in device enumeration, so only
@@ -121,7 +125,8 @@
* @throws IllegalArgumentException if the handler is {@code null} but the current thread has
* no looper.
*/
- public void registerAvailabilityCallback(AvailabilityCallback callback, Handler handler) {
+ public void registerAvailabilityCallback(@NonNull AvailabilityCallback callback,
+ @Nullable Handler handler) {
if (handler == null) {
Looper looper = Looper.myLooper();
if (looper == null) {
@@ -142,7 +147,7 @@
*
* @param callback The callback to remove from the notification list
*/
- public void unregisterAvailabilityCallback(AvailabilityCallback callback) {
+ public void unregisterAvailabilityCallback(@NonNull AvailabilityCallback callback) {
CameraManagerGlobal.get().unregisterAvailabilityCallback(callback);
}
@@ -168,7 +173,7 @@
* @throws IllegalArgumentException if the handler is {@code null} but the current thread has
* no looper.
*/
- public void registerTorchCallback(TorchCallback callback, Handler handler) {
+ public void registerTorchCallback(@NonNull TorchCallback callback, @Nullable Handler handler) {
if (handler == null) {
Looper looper = Looper.myLooper();
if (looper == null) {
@@ -188,7 +193,7 @@
*
* @param callback The callback to remove from the notification list
*/
- public void unregisterTorchCallback(TorchCallback callback) {
+ public void unregisterTorchCallback(@NonNull TorchCallback callback) {
CameraManagerGlobal.get().unregisterTorchCallback(callback);
}
@@ -201,15 +206,13 @@
*
* @throws IllegalArgumentException if the cameraId does not match any
* known camera device.
- * @throws CameraAccessException if the camera is disabled by device policy, or
- * the camera device has been disconnected.
- * @throws SecurityException if the application does not have permission to
- * access the camera
+ * @throws CameraAccessException if the camera device has been disconnected.
*
* @see #getCameraIdList
* @see android.app.admin.DevicePolicyManager#setCameraDisabled
*/
- public CameraCharacteristics getCameraCharacteristics(String cameraId)
+ @NonNull
+ public CameraCharacteristics getCameraCharacteristics(@NonNull String cameraId)
throws CameraAccessException {
CameraCharacteristics characteristics = null;
@@ -431,8 +434,9 @@
* @see #getCameraIdList
* @see android.app.admin.DevicePolicyManager#setCameraDisabled
*/
- public void openCamera(String cameraId, final CameraDevice.StateCallback callback,
- Handler handler)
+ @RequiresPermission(android.Manifest.permission.CAMERA)
+ public void openCamera(@NonNull String cameraId,
+ @NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler)
throws CameraAccessException {
if (cameraId == null) {
@@ -444,7 +448,7 @@
handler = new Handler();
} else {
throw new IllegalArgumentException(
- "Looper doesn't exist in the calling thread");
+ "Handler argument is null, but no looper exists in the calling thread");
}
}
@@ -490,7 +494,8 @@
* or previously available camera device, or the camera device doesn't have a
* flash unit.
*/
- public void setTorchMode(String cameraId, boolean enabled) throws CameraAccessException {
+ public void setTorchMode(@NonNull String cameraId, boolean enabled)
+ throws CameraAccessException {
CameraManagerGlobal.get().setTorchMode(cameraId, enabled);
}
@@ -517,7 +522,7 @@
*
* @param cameraId The unique identifier of the new camera.
*/
- public void onCameraAvailable(String cameraId) {
+ public void onCameraAvailable(@NonNull String cameraId) {
// default empty implementation
}
@@ -532,7 +537,7 @@
*
* @param cameraId The unique identifier of the disconnected camera.
*/
- public void onCameraUnavailable(String cameraId) {
+ public void onCameraUnavailable(@NonNull String cameraId) {
// default empty implementation
}
}
@@ -572,7 +577,7 @@
* @param cameraId The unique identifier of the camera whose torch mode has become
* unavailable.
*/
- public void onTorchModeUnavailable(String cameraId) {
+ public void onTorchModeUnavailable(@NonNull String cameraId) {
// default empty implementation
}
@@ -589,7 +594,7 @@
* off. {@code false} when the torch mode has becomes off and available to
* be turned on.
*/
- public void onTorchModeChanged(String cameraId, boolean enabled) {
+ public void onTorchModeChanged(@NonNull String cameraId, boolean enabled) {
// default empty implementation
}
}
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 6baa660..cf091a9 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -16,6 +16,7 @@
package android.hardware.camera2;
+import android.annotation.NonNull;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.PublicKey;
import android.hardware.camera2.impl.SyntheticKey;
@@ -103,6 +104,7 @@
* @return List of the keys contained in this map.
*/
@SuppressWarnings("unchecked")
+ @NonNull
public List<TKey> getKeys() {
Class<CameraMetadata<TKey>> thisClass = (Class<CameraMetadata<TKey>>) getClass();
return Collections.unmodifiableList(
diff --git a/core/java/android/hardware/camera2/CaptureFailure.java b/core/java/android/hardware/camera2/CaptureFailure.java
index c168ff1..8bb33f1 100644
--- a/core/java/android/hardware/camera2/CaptureFailure.java
+++ b/core/java/android/hardware/camera2/CaptureFailure.java
@@ -15,6 +15,12 @@
*/
package android.hardware.camera2;
+import android.annotation.NonNull;
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* A report of failed capture for a single image capture from the image sensor.
*
@@ -43,6 +49,13 @@
*/
public static final int REASON_FLUSHED = 1;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ {REASON_ERROR,
+ REASON_FLUSHED })
+ public @interface FailureReason {};
+
private final CaptureRequest mRequest;
private final int mReason;
private final boolean mDropped;
@@ -52,8 +65,8 @@
/**
* @hide
*/
- public CaptureFailure(CaptureRequest request, int reason, boolean dropped, int sequenceId,
- long frameNumber) {
+ public CaptureFailure(CaptureRequest request, int reason,
+ boolean dropped, int sequenceId, long frameNumber) {
mRequest = request;
mReason = reason;
mDropped = dropped;
@@ -81,6 +94,7 @@
*
* @return The request associated with this failed capture. Never {@code null}.
*/
+ @NonNull
public CaptureRequest getRequest() {
return mRequest;
}
@@ -110,6 +124,7 @@
* @see #REASON_ERROR
* @see #REASON_FLUSHED
*/
+ @FailureReason
public int getReason() {
return mReason;
}
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 5fb9abc..8f7b983 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -16,6 +16,8 @@
package android.hardware.camera2;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.PublicKey;
import android.hardware.camera2.impl.SyntheticKey;
@@ -117,6 +119,7 @@
*
* @return String representation of the key name
*/
+ @NonNull
public String getName() {
return mKey.getName();
}
@@ -239,6 +242,7 @@
* @param key The result field to read.
* @return The value of that key, or {@code null} if the field is not set.
*/
+ @Nullable
public <T> T get(Key<T> key) {
return mSettings.get(key);
}
@@ -268,6 +272,7 @@
* {@inheritDoc}
*/
@Override
+ @NonNull
public List<Key<?>> getKeys() {
// Force the javadoc for this function to show up on the CaptureRequest page
return super.getKeys();
@@ -286,6 +291,7 @@
* no tag has been set.
* @see Builder#setTag
*/
+ @Nullable
public Object getTag() {
return mUserTag;
}
@@ -476,7 +482,7 @@
*
* @param outputTarget Surface to use as an output target for this request
*/
- public void addTarget(Surface outputTarget) {
+ public void addTarget(@NonNull Surface outputTarget) {
mRequest.mSurfaceSet.add(outputTarget);
}
@@ -487,7 +493,7 @@
*
* @param outputTarget Surface to use as an output target for this request
*/
- public void removeTarget(Surface outputTarget) {
+ public void removeTarget(@NonNull Surface outputTarget) {
mRequest.mSurfaceSet.remove(outputTarget);
}
@@ -499,7 +505,7 @@
* @param value The value to set the field to, which must be of a matching
* type to the key.
*/
- public <T> void set(Key<T> key, T value) {
+ public <T> void set(@NonNull Key<T> key, T value) {
mRequest.mSettings.set(key, value);
}
@@ -512,6 +518,7 @@
* @param key The metadata field to read.
* @return The value of that key, or {@code null} if the field is not set.
*/
+ @Nullable
public <T> T get(Key<T> key) {
return mRequest.mSettings.get(key);
}
@@ -527,7 +534,7 @@
* @param tag an arbitrary Object to store with this request
* @see CaptureRequest#getTag
*/
- public void setTag(Object tag) {
+ public void setTag(@Nullable Object tag) {
mRequest.mUserTag = tag;
}
@@ -543,6 +550,7 @@
* @return A new capture request instance, ready for submission to the
* camera device.
*/
+ @NonNull
public CaptureRequest build() {
return new CaptureRequest(mRequest);
}
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 0277c5b..f4017d0 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -16,6 +16,8 @@
package android.hardware.camera2;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.CaptureResultExtras;
import android.hardware.camera2.impl.PublicKey;
@@ -102,6 +104,7 @@
*
* @return String representation of the key name
*/
+ @NonNull
public String getName() {
return mKey.getName();
}
@@ -216,6 +219,7 @@
* @param key The result field to read.
* @return The value of that key, or {@code null} if the field is not set.
*/
+ @Nullable
public <T> T get(Key<T> key) {
T value = mResults.get(key);
if (VERBOSE) Log.v(TAG, "#get for Key = " + key.getName() + ", returned value = " + value);
@@ -259,6 +263,7 @@
* {@inheritDoc}
*/
@Override
+ @NonNull
public List<Key<?>> getKeys() {
// Force the javadoc for this function to show up on the CaptureResult page
return super.getKeys();
@@ -285,6 +290,7 @@
*
* @return The request associated with this result. Never {@code null}.
*/
+ @NonNull
public CaptureRequest getRequest() {
return mRequest;
}
diff --git a/core/java/android/hardware/camera2/DngCreator.java b/core/java/android/hardware/camera2/DngCreator.java
index f16d650..70afe5b 100644
--- a/core/java/android/hardware/camera2/DngCreator.java
+++ b/core/java/android/hardware/camera2/DngCreator.java
@@ -16,6 +16,9 @@
package android.hardware.camera2;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.ImageFormat;
@@ -80,7 +83,8 @@
* {@link android.hardware.camera2.CameraCharacteristics}.
* @param metadata a metadata object to generate tags from.
*/
- public DngCreator(CameraCharacteristics characteristics, CaptureResult metadata) {
+ public DngCreator(@NonNull CameraCharacteristics characteristics,
+ @NonNull CaptureResult metadata) {
if (characteristics == null || metadata == null) {
throw new IllegalArgumentException("Null argument to DngCreator constructor");
}
@@ -126,6 +130,7 @@
* </ul>
* @return this {@link #DngCreator} object.
*/
+ @NonNull
public DngCreator setOrientation(int orientation) {
if (orientation < ExifInterface.ORIENTATION_UNDEFINED ||
orientation > ExifInterface.ORIENTATION_ROTATE_270) {
@@ -150,7 +155,8 @@
* @throws java.lang.IllegalArgumentException if the given thumbnail image has a dimension
* larger than {@link #MAX_THUMBNAIL_DIMENSION}.
*/
- public DngCreator setThumbnail(Bitmap pixels) {
+ @NonNull
+ public DngCreator setThumbnail(@NonNull Bitmap pixels) {
if (pixels == null) {
throw new IllegalArgumentException("Null argument to setThumbnail");
}
@@ -185,7 +191,8 @@
* @throws java.lang.IllegalArgumentException if the given thumbnail image has a dimension
* larger than {@link #MAX_THUMBNAIL_DIMENSION}.
*/
- public DngCreator setThumbnail(Image pixels) {
+ @NonNull
+ public DngCreator setThumbnail(@NonNull Image pixels) {
if (pixels == null) {
throw new IllegalArgumentException("Null argument to setThumbnail");
}
@@ -226,7 +233,8 @@
* @throws java.lang.IllegalArgumentException if the given location object doesn't
* contain enough information to set location metadata.
*/
- public DngCreator setLocation(Location location) {
+ @NonNull
+ public DngCreator setLocation(@NonNull Location location) {
if (location == null) {
throw new IllegalArgumentException("Null location passed to setLocation");
}
@@ -258,7 +266,8 @@
* @param description the user description string.
* @return this {@link #DngCreator} object.
*/
- public DngCreator setDescription(String description) {
+ @NonNull
+ public DngCreator setDescription(@NonNull String description) {
if (description == null) {
throw new IllegalArgumentException("Null description passed to setDescription.");
}
@@ -293,8 +302,8 @@
* set to write a well-formatted DNG file.
* @throws java.lang.IllegalArgumentException if the size passed in does not match the
*/
- public void writeInputStream(OutputStream dngOutput, Size size, InputStream pixels, long offset)
- throws IOException {
+ public void writeInputStream(@NonNull OutputStream dngOutput, @NonNull Size size,
+ @NonNull InputStream pixels, @IntRange(from=0) long offset) throws IOException {
if (dngOutput == null) {
throw new IllegalArgumentException("Null dngOutput passed to writeInputStream");
} else if (size == null) {
@@ -345,7 +354,8 @@
* @throws java.lang.IllegalStateException if not enough metadata information has been
* set to write a well-formatted DNG file.
*/
- public void writeByteBuffer(OutputStream dngOutput, Size size, ByteBuffer pixels, long offset)
+ public void writeByteBuffer(@NonNull OutputStream dngOutput, @NonNull Size size,
+ @NonNull ByteBuffer pixels, @IntRange(from=0) long offset)
throws IOException {
if (dngOutput == null) {
throw new IllegalArgumentException("Null dngOutput passed to writeByteBuffer");
@@ -381,7 +391,8 @@
* @throws java.lang.IllegalStateException if not enough metadata information has been
* set to write a well-formatted DNG file.
*/
- public void writeImage(OutputStream dngOutput, Image pixels) throws IOException {
+ public void writeImage(@NonNull OutputStream dngOutput, @NonNull Image pixels)
+ throws IOException {
if (dngOutput == null) {
throw new IllegalArgumentException("Null dngOutput to writeImage");
} else if (pixels == null) {
diff --git a/core/java/android/hardware/camera2/TotalCaptureResult.java b/core/java/android/hardware/camera2/TotalCaptureResult.java
index fb3c098..657745c 100644
--- a/core/java/android/hardware/camera2/TotalCaptureResult.java
+++ b/core/java/android/hardware/camera2/TotalCaptureResult.java
@@ -16,6 +16,7 @@
package android.hardware.camera2;
+import android.annotation.NonNull;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.CaptureResultExtras;
@@ -96,6 +97,7 @@
*
* @return unmodifiable list of partial results
*/
+ @NonNull
public List<CaptureResult> getPartialResults() {
return Collections.unmodifiableList(mPartialResults);
}
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index 9046e81..7f4a76c 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -67,8 +67,6 @@
private final TaskSingleDrainer mIdleDrainer;
/** Drain state transitions from BUSY -> IDLE */
private final TaskSingleDrainer mAbortDrainer;
- /** Drain the UNCONFIGURED state transition */
- private final TaskSingleDrainer mUnconfigureDrainer;
/** This session is closed; all further calls will throw ISE */
private boolean mClosed = false;
@@ -121,8 +119,6 @@
/*name*/"idle");
mAbortDrainer = new TaskSingleDrainer(mDeviceHandler, new AbortDrainListener(),
/*name*/"abort");
- mUnconfigureDrainer = new TaskSingleDrainer(mDeviceHandler, new UnconfigureDrainListener(),
- /*name*/"unconf");
// CameraDevice should call configureOutputs and have it finish before constructing us
@@ -572,26 +568,6 @@
@Override
public void onUnconfigured(CameraDevice camera) {
if (VERBOSE) Log.v(TAG, mIdString + "onUnconfigured");
- synchronized (session) {
- // Ignore #onUnconfigured before #close is called.
- //
- // Normally, this is reached when this session is closed and no immediate other
- // activity happens for the camera, in which case the camera is configured to
- // null streams by this session and the UnconfigureDrainer task is started.
- // However, we can also end up here if
- //
- // 1) Session is closed
- // 2) New session is created before this session finishes closing, setting
- // mSkipUnconfigure and therefore this session does not configure null or
- // start the UnconfigureDrainer task.
- // 3) And then the new session fails to be created, so onUnconfigured fires
- // _anyway_.
- // In this second case, need to not finish a task that was never started, so
- // guard with mSkipUnconfigure
- if (mClosed && mConfigureSuccess && !mSkipUnconfigure) {
- mUnconfigureDrainer.taskFinished();
- }
- }
}
@Override
@@ -656,6 +632,19 @@
* then the drain immediately finishes.
*/
if (VERBOSE) Log.v(TAG, mIdString + "onSequenceDrained");
+
+
+ // Fire session close as soon as all sequences are complete.
+ // We may still need to unconfigure the device, but a new session might be created
+ // past this point, and notifications would then stop to this instance.
+ mStateCallback.onClosed(CameraCaptureSessionImpl.this);
+
+ // Fast path: A new capture session has replaced this one; don't wait for abort/idle
+ // as we won't get state updates any more anyway.
+ if (mSkipUnconfigure) {
+ return;
+ }
+
mAbortDrainer.beginDrain();
}
}
@@ -673,6 +662,12 @@
*
* If the camera is already "IDLE", then the drain immediately finishes.
*/
+
+ // Fast path: A new capture session has replaced this one; don't wait for idle
+ // as we won't get state updates any more anyway.
+ if (mSkipUnconfigure) {
+ return;
+ }
mIdleDrainer.beginDrain();
}
}
@@ -691,7 +686,7 @@
* The device is now IDLE, and has settled. It will not transition to
* ACTIVE or BUSY again by itself.
*
- * It's now safe to unconfigure the outputs and after it's done invoke #onClosed.
+ * It's now safe to unconfigure the outputs.
*
* This operation is idempotent; a session will not be closed twice.
*/
@@ -699,45 +694,31 @@
Log.v(TAG, mIdString + "Session drain complete, skip unconfigure: " +
mSkipUnconfigure);
- // Fast path: A new capture session has replaced this one; don't unconfigure.
+ // Fast path: A new capture session has replaced this one; don't wait for idle
+ // as we won't get state updates any more anyway.
if (mSkipUnconfigure) {
- mStateCallback.onClosed(CameraCaptureSessionImpl.this);
return;
}
- // Slow path: #close was called explicitly on this session; unconfigure first
- mUnconfigureDrainer.taskStarted();
-
+ // Final slow path: unconfigure the camera, no session has replaced us and
+ // everything is idle.
try {
// begin transition to unconfigured
mDeviceImpl.configureStreamsChecked(null, null);
} catch (CameraAccessException e) {
// OK: do not throw checked exceptions.
- Log.e(TAG, mIdString + "Exception while configuring outputs: ", e);
+ Log.e(TAG, mIdString + "Exception while unconfiguring outputs: ", e);
// TODO: call onError instead of onClosed if this happens
} catch (IllegalStateException e) {
- // Camera is already closed, so go straight to the close callback
+ // Camera is already closed, so nothing left to do
if (VERBOSE) Log.v(TAG, mIdString +
"Camera was already closed or busy, skipping unconfigure");
- mUnconfigureDrainer.taskFinished();
}
- mUnconfigureDrainer.beginDrain();
}
}
}
}
- private class UnconfigureDrainListener implements TaskDrainer.DrainListener {
- @Override
-
- public void onDrained() {
- if (VERBOSE) Log.v(TAG, mIdString + "onUnconfigureDrained");
- synchronized (CameraCaptureSessionImpl.this) {
- // The device has finished unconfiguring. It's now fully closed.
- mStateCallback.onClosed(CameraCaptureSessionImpl.this);
- }
- }
- }
}
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 4508dc8..c4e8b15 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -607,6 +607,8 @@
}
public void prepare(Surface surface) throws CameraAccessException {
+ if (surface == null) throw new IllegalArgumentException("Surface is null");
+
synchronized(mInterfaceLock) {
int streamId = -1;
for (int i = 0; i < mConfiguredOutputs.size(); i++) {
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 26878c0..e175e9a 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -1906,9 +1906,6 @@
*
* @return {@link ProxyInfo} for the current global HTTP proxy or {@code null}
* if no global HTTP proxy is set.
- *
- * <p>This method requires the caller to hold the permission
- * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* @hide
*/
public ProxyInfo getGlobalProxy() {
@@ -1920,6 +1917,28 @@
}
/**
+ * Retrieve the global HTTP proxy, or if no global HTTP proxy is set, a
+ * network-specific HTTP proxy. If {@code network} is null, the
+ * network-specific proxy returned is the proxy of the default active
+ * network.
+ *
+ * @return {@link ProxyInfo} for the current global HTTP proxy, or if no
+ * global HTTP proxy is set, {@code ProxyInfo} for {@code network},
+ * or when {@code network} is {@code null},
+ * the {@code ProxyInfo} for the default active network. Returns
+ * {@code null} when no proxy applies or the caller doesn't have
+ * permission to use {@code network}.
+ * @hide
+ */
+ public ProxyInfo getProxyForNetwork(Network network) {
+ try {
+ return mService.getProxyForNetwork(network);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
* Get the current default HTTP proxy settings. If a global proxy is set it will be returned,
* otherwise if this process is bound to a {@link Network} using
* {@link #bindProcessToNetwork} then that {@code Network}'s proxy is returned, otherwise
@@ -1929,19 +1948,7 @@
* HTTP proxy is active.
*/
public ProxyInfo getDefaultProxy() {
- final Network network = getBoundNetworkForProcess();
- if (network != null) {
- final ProxyInfo globalProxy = getGlobalProxy();
- if (globalProxy != null) return globalProxy;
- final LinkProperties lp = getLinkProperties(network);
- if (lp != null) return lp.getHttpProxy();
- return null;
- }
- try {
- return mService.getDefaultProxy();
- } catch (RemoteException e) {
- return null;
- }
+ return getProxyForNetwork(getBoundNetworkForProcess());
}
/**
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index c1b4a1f..89d23a2 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -104,7 +104,7 @@
void setGlobalProxy(in ProxyInfo p);
- ProxyInfo getDefaultProxy();
+ ProxyInfo getProxyForNetwork(in Network nework);
boolean prepareVpn(String oldPackage, String newPackage, int userId);
diff --git a/core/java/android/net/IpReachabilityMonitor.java b/core/java/android/net/IpReachabilityMonitor.java
index add8774..cb8c866 100644
--- a/core/java/android/net/IpReachabilityMonitor.java
+++ b/core/java/android/net/IpReachabilityMonitor.java
@@ -73,7 +73,8 @@
private final Set<InetAddress> mIpWatchList;
private int mIpWatchListVersion;
private boolean mRunning;
- final private Thread mObserverThread;
+ private final NetlinkSocketObserver mNetlinkSocketObserver;
+ private final Thread mObserverThread;
public IpReachabilityMonitor(String ifName, Callback callback) throws IllegalArgumentException {
mInterfaceName = ifName;
@@ -88,15 +89,15 @@
mIpWatchList = new HashSet<InetAddress>();
mIpWatchListVersion = 0;
mRunning = false;
- mObserverThread = new Thread(new NetlinkSocketObserver());
+ mNetlinkSocketObserver = new NetlinkSocketObserver();
+ mObserverThread = new Thread(mNetlinkSocketObserver);
mObserverThread.start();
}
public void stop() {
- synchronized (mLock) {
- mRunning = false;
- mIpWatchList.clear();
- }
+ synchronized (mLock) { mRunning = false; }
+ clearLinkProperties();
+ mNetlinkSocketObserver.clearNetlinkSocket();
}
// TODO: add a public dump() method that can be called during a bug report.
@@ -251,6 +252,7 @@
}
+ // TODO: simply the number of objects by making this extend Thread.
private final class NetlinkSocketObserver implements Runnable {
private static final String TAG = "NetlinkSocketObserver";
private NetlinkSocket mSocket;
@@ -292,7 +294,6 @@
if (mSocket != null) {
mSocket.close();
}
- mSocket = null;
}
// TODO: Refactor the main loop to recreate the socket upon recoverable errors.
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index 67ecb5d..754c6b3 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -247,12 +247,7 @@
throw new IOException("No ConnectivityManager yet constructed, please construct one");
}
// TODO: Should this be optimized to avoid fetching the global proxy for every request?
- ProxyInfo proxyInfo = cm.getGlobalProxy();
- if (proxyInfo == null) {
- // TODO: Should this be optimized to avoid fetching LinkProperties for every request?
- final LinkProperties lp = cm.getLinkProperties(this);
- if (lp != null) proxyInfo = lp.getHttpProxy();
- }
+ final ProxyInfo proxyInfo = cm.getProxyForNetwork(this);
java.net.Proxy proxy = null;
if (proxyInfo != null) {
proxy = proxyInfo.makeProxy();
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 29dd8ad..4487cab 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -194,6 +194,12 @@
public native static boolean protectFromVpn(int socketfd);
/**
+ * Determine if {@code uid} can access network designated by {@code netId}.
+ * @return {@code true} if {@code uid} can access network, {@code false} otherwise.
+ */
+ public native static boolean queryUserAccess(int uid, int netId);
+
+ /**
* Convert a IPv4 address from an integer to an InetAddress.
* @param hostAddress an int corresponding to the IPv4 address in network byte order
*/
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index f212daf..aa4b051 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -36,6 +36,7 @@
ParcelFileDescriptor getUserIcon(int userHandle);
List<UserInfo> getUsers(boolean excludeDying);
List<UserInfo> getProfiles(int userHandle, boolean enabledOnly);
+ boolean canAddMoreManagedProfiles();
UserInfo getProfileParent(int userHandle);
UserInfo getUserInfo(int userHandle);
long getUserCreationTime(int userHandle);
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index 9a0d0d0..e523285 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -136,4 +136,8 @@
public abstract void setDeviceIdleMode(boolean enabled);
public abstract void setDeviceIdleWhitelist(int[] appids);
+
+ public abstract void updateUidProcState(int uid, int procState);
+
+ public abstract void uidGone(int uid);
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 3dee68c..11043b3 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -933,6 +933,22 @@
}
/**
+ * Checks whether it's possible to add more managed profiles. Caller must hold the MANAGE_USERS
+ * permission.
+ *
+ * @return true if more managed profiles can be added, false if limit has been reached.
+ * @hide
+ */
+ public boolean canAddMoreManagedProfiles() {
+ try {
+ return mService.canAddMoreManagedProfiles();
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not check if we can add more managed profiles", re);
+ return false;
+ }
+ }
+
+ /**
* Returns list of the profiles of userHandle including
* userHandle itself.
* Note that this returns both enabled and not enabled profiles. See
diff --git a/core/java/android/os/storage/DiskInfo.java b/core/java/android/os/storage/DiskInfo.java
index 9623695..04e54aa 100644
--- a/core/java/android/os/storage/DiskInfo.java
+++ b/core/java/android/os/storage/DiskInfo.java
@@ -50,6 +50,8 @@
public final int flags;
public long size;
public String label;
+ /** Hacky; don't rely on this count */
+ public int volumeCount;
public DiskInfo(String id, int flags) {
this.id = Preconditions.checkNotNull(id);
@@ -61,6 +63,7 @@
flags = parcel.readInt();
size = parcel.readLong();
label = parcel.readString();
+ volumeCount = parcel.readInt();
}
public @NonNull String getId() {
@@ -181,5 +184,6 @@
parcel.writeInt(this.flags);
parcel.writeLong(size);
parcel.writeString(label);
+ parcel.writeInt(volumeCount);
}
}
diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java
index 92e874f..dc965ed 100644
--- a/core/java/android/util/SparseArray.java
+++ b/core/java/android/util/SparseArray.java
@@ -134,6 +134,24 @@
}
/**
+ * @hide
+ * Removes the mapping from the specified key, if there was any, returning the old value.
+ */
+ public E removeReturnOld(int key) {
+ int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+ if (i >= 0) {
+ if (mValues[i] != DELETED) {
+ final E old = (E) mValues[i];
+ mValues[i] = DELETED;
+ mGarbage = true;
+ return old;
+ }
+ }
+ return null;
+ }
+
+ /**
* Alias for {@link #delete(int)}.
*/
public void remove(int key) {
diff --git a/core/java/android/view/ActionMode.java b/core/java/android/view/ActionMode.java
index 9f202a9..9f00455 100644
--- a/core/java/android/view/ActionMode.java
+++ b/core/java/android/view/ActionMode.java
@@ -44,6 +44,12 @@
*/
public static final int TYPE_FLOATING = 1;
+ /**
+ * Default snooze time.
+ */
+ public static final int SNOOZE_TIME_DEFAULT =
+ ViewConfiguration.getDefaultActionModeSnoozeTime();
+
private Object mTag;
private boolean mTitleOptionalHint;
private int mType = TYPE_PRIMARY;
@@ -207,6 +213,19 @@
public void invalidateContentRect() {}
/**
+ * Hide the action mode view from obstructing the content below for a short period.
+ * This only makes sense for action modes that support dynamic positioning on the screen.
+ * If this method is called again before the snooze time expires, the later snooze will
+ * cancel the former and then take effect.
+ * NOTE that there is an internal limit to how long the mode can be snoozed for. It's typically
+ * about a few seconds.
+ *
+ * @param snoozeTime The number of milliseconds to snooze for.
+ * @see #SNOOZE_TIME_DEFAULT
+ */
+ public void snooze(int snoozeTime) {}
+
+ /**
* Finish and close this action mode. The action mode's {@link ActionMode.Callback} will
* have its {@link Callback#onDestroyActionMode(ActionMode)} method called.
*/
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 4e91ad4..8c6fa3f 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -213,6 +213,11 @@
private static final int OVERFLING_DISTANCE = 6;
/**
+ * Default time to snooze an action mode for.
+ */
+ private static final int ACTION_MODE_SNOOZE_TIME_DEFAULT = 2000;
+
+ /**
* Configuration values for overriding {@link #hasPermanentMenuKey()} behavior.
* These constants must match the definition in res/values/config.xml.
*/
@@ -732,6 +737,13 @@
}
/**
+ * @return the default duration in milliseconds for {@link ActionMode#snooze(int)}.
+ */
+ public static int getDefaultActionModeSnoozeTime() {
+ return ACTION_MODE_SNOOZE_TIME_DEFAULT;
+ }
+
+ /**
* Report if the device has a permanent menu key available to the user.
*
* <p>As of Android 3.0, devices may not have a permanent menu key available.
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index bd45007..f18b7ac 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -80,12 +80,18 @@
/**
* The interpolator of the underlying Animator object. By default, we don't set the interpolator
- * on the Animator and just use its default interpolator. If the interpolator is set to a
- * non-null value on this Animator, then we use the interpolator that it was set to.
+ * on the Animator and just use its default interpolator. If the interpolator is ever set on
+ * this Animator, then we use the interpolator that it was set to.
*/
private TimeInterpolator mInterpolator;
/**
+ * A flag indicating whether the interpolator has been set on this object. If not, we don't set
+ * the interpolator on the underlying Animator, but instead just use its default interpolator.
+ */
+ private boolean mInterpolatorSet = false;
+
+ /**
* Listener for the lifecycle events of the underlying ValueAnimator object.
*/
private Animator.AnimatorListener mListener = null;
@@ -332,6 +338,7 @@
* @return This object, allowing calls to methods in this class to be chained.
*/
public ViewPropertyAnimator setInterpolator(TimeInterpolator interpolator) {
+ mInterpolatorSet = true;
mInterpolator = interpolator;
return this;
}
@@ -342,7 +349,7 @@
* @return The timing interpolator for this animation.
*/
public TimeInterpolator getInterpolator() {
- if (mInterpolator != null) {
+ if (mInterpolatorSet) {
return mInterpolator;
} else {
// Just return the default from ValueAnimator, since that's what we'd get if
@@ -890,7 +897,7 @@
if (mDurationSet) {
animator.setDuration(mDuration);
}
- if (mInterpolator != null) {
+ if (mInterpolatorSet) {
animator.setInterpolator(mInterpolator);
}
animator.start();
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 23f3b62..c4f9209 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2021,8 +2021,8 @@
mLastWasImTarget = imTarget;
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null && imTarget) {
- imm.startGettingWindowFocus(mView);
- imm.onWindowFocus(mView, mView.findFocus(),
+ imm.onPreWindowFocus(mView, true /* hasWindowFocus */);
+ imm.onPostWindowFocus(mView, mView.findFocus(),
mWindowAttributes.softInputMode,
!mHasHadWindowFocus, mWindowAttributes.flags);
}
@@ -3327,11 +3327,10 @@
.mayUseInputMethod(mWindowAttributes.flags);
InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
+ imm.onPreWindowFocus(mView, hasWindowFocus);
+ }
if (mView != null) {
- if (hasWindowFocus && imm != null && mLastWasImTarget &&
- !isInLocalFocusMode()) {
- imm.startGettingWindowFocus(mView);
- }
mAttachInfo.mKeyDispatchState.reset();
mView.dispatchWindowFocusChanged(hasWindowFocus);
mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
@@ -3341,7 +3340,7 @@
// so all of the view state is set up correctly.
if (hasWindowFocus) {
if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
- imm.onWindowFocus(mView, mView.findFocus(),
+ imm.onPostWindowFocus(mView, mView.findFocus(),
mWindowAttributes.softInputMode,
!mHasHadWindowFocus, mWindowAttributes.flags);
}
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 8dc49acd..5c8b023 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -25,60 +25,215 @@
* View.onProvideStructure}.
*/
public abstract class ViewStructure {
+ /**
+ * Set the identifier for this view.
+ *
+ * @param id The view's identifier, as per {@link View#getId View.getId()}.
+ * @param packageName The package name of the view's identifier, or null if there is none.
+ * @param typeName The type name of the view's identifier, or null if there is none.
+ * @param entryName The entry name of the view's identifier, or null if there is none.
+ */
public abstract void setId(int id, String packageName, String typeName, String entryName);
+ /**
+ * Set the basic dimensions of this view.
+ *
+ * @param left The view's left position, in pixels relative to its parent's left edge.
+ * @param top The view's top position, in pixels relative to its parent's top edge.
+ * @param scrollX How much the view's x coordinate space has been scrolled, in pixels.
+ * @param scrollY How much the view's y coordinate space has been scrolled, in pixels.
+ * @param width The view's visible width, in pixels. This is the width visible on screen,
+ * not the total data width of a scrollable view.
+ * @param height The view's visible height, in pixels. This is the height visible on
+ * screen, not the total data height of a scrollable view.
+ */
public abstract void setDimens(int left, int top, int scrollX, int scrollY, int width,
int height);
+ /**
+ * Set the visibility state of this view, as per
+ * {@link View#getVisibility View.getVisibility()}.
+ */
public abstract void setVisibility(int visibility);
/** @hide */
public abstract void setAssistBlocked(boolean state);
+ /**
+ * Set the enabled state of this view, as per {@link View#isEnabled View.isEnabled()}.
+ */
public abstract void setEnabled(boolean state);
+ /**
+ * Set the clickable state of this view, as per {@link View#isClickable View.isClickable()}.
+ */
public abstract void setClickable(boolean state);
+ /**
+ * Set the long clickable state of this view, as per
+ * {@link View#isLongClickable View.isLongClickable()}.
+ */
public abstract void setLongClickable(boolean state);
+ /**
+ * Set the stylus button pressable state of this view, as per
+ * {@link View#isStylusButtonPressable View.isStylusButtonPressable()}.
+ */
public abstract void setStylusButtonPressable(boolean state);
+ /**
+ * Set the focusable state of this view, as per {@link View#isFocusable View.isFocusable()}.
+ */
public abstract void setFocusable(boolean state);
+ /**
+ * Set the focused state of this view, as per {@link View#isFocused View.isFocused()}.
+ */
public abstract void setFocused(boolean state);
+ /**
+ * Set the accessibility focused state of this view, as per
+ * {@link View#isAccessibilityFocused View.isAccessibilityFocused()}.
+ */
public abstract void setAccessibilityFocused(boolean state);
+ /**
+ * Set the checkable state of this view, such as whether it implements the
+ * {@link android.widget.Checkable} interface.
+ */
public abstract void setCheckable(boolean state);
+ /**
+ * Set the checked state of this view, such as
+ * {@link android.widget.Checkable#isChecked Checkable.isChecked()}.
+ */
public abstract void setChecked(boolean state);
+ /**
+ * Set the selected state of this view, as per {@link View#isSelected View.isSelected()}.
+ */
public abstract void setSelected(boolean state);
+ /**
+ * Set the activated state of this view, as per {@link View#isActivated View.isActivated()}.
+ */
public abstract void setActivated(boolean state);
+ /**
+ * Set the class name of the view, as per
+ * {@link View#getAccessibilityClassName View.getAccessibilityClassName()}.
+ */
public abstract void setClassName(String className);
+ /**
+ * Set the content description of the view, as per
+ * {@link View#getContentDescription View.getContentDescription()}.
+ */
public abstract void setContentDescription(CharSequence contentDescription);
+ /**
+ * Set the text that is associated with this view. There is no selection
+ * associated with the text. The text may have style spans to supply additional
+ * display and semantic information.
+ */
public abstract void setText(CharSequence text);
+
+ /**
+ * Like {@link #setText(CharSequence)} but with an active selection
+ * extending from <var>selectionStart</var> through <var>selectionEnd</var>.
+ */
public abstract void setText(CharSequence text, int selectionStart, int selectionEnd);
+
+ /**
+ * Set default global style of the text previously set with
+ * {@link #setText}, derived from the given TextPaint object. Size, foreground color,
+ * background color, and style information will be extracted from the paint.
+ */
public abstract void setTextPaint(TextPaint paint);
+
+ /**
+ * Explicitly set default global style information for text that was previously set with
+ * {@link #setText}.
+ *
+ * @param size The size, in pixels, of the text.
+ * @param fgColor The foreground color, packed as 0xAARRGGBB.
+ * @param bgColor The background color, packed as 0xAARRGGBB.
+ * @param style Style flags, as defined by {@link android.app.AssistStructure.ViewNode}.
+ */
+ public abstract void setTextStyle(int size, int fgColor, int bgColor, int style);
+
+ /**
+ * Set optional hint text associated with this view; this is for example the text that is
+ * shown by an EditText when it is empty to indicate to the user the kind of text to input.
+ */
public abstract void setHint(CharSequence hint);
+ /**
+ * Retrieve the last {@link #setText(CharSequence)}.
+ */
public abstract CharSequence getText();
+
+ /**
+ * Retrieve the last selection start set by {@link #setText(CharSequence, int, int)}.
+ */
public abstract int getTextSelectionStart();
+
+ /**
+ * Retrieve the last selection end set by {@link #setText(CharSequence, int, int)}.
+ */
public abstract int getTextSelectionEnd();
+
+ /**
+ * Retrieve the last hint set by {@link #setHint}.
+ */
public abstract CharSequence getHint();
+ /**
+ * Get extra data associated with this view structure; the returned Bundle is mutable,
+ * allowing you to view and modify its contents. Keys placed in the Bundle should use
+ * an appropriate namespace prefix (such as com.google.MY_KEY) to avoid conflicts.
+ */
public abstract Bundle getExtras();
+
+ /**
+ * Returns true if {@link #getExtras} has been used to create extra content.
+ */
public abstract boolean hasExtras();
+ /**
+ * Set the number of children of this view, which defines the range of indices you can
+ * use with {@link #newChild} and {@link #asyncNewChild}. Calling this method again
+ * resets all of the child state of the view, removing any children that had previously
+ * been added.
+ */
public abstract void setChildCount(int num);
+
+ /**
+ * Return the child count as set by {@link #setChildCount}.
+ */
public abstract int getChildCount();
+
+ /**
+ * Create a new child {@link ViewStructure} in this view, putting into the list of
+ * children at <var>index</var>.
+ * @return Returns an fresh {@link ViewStructure} ready to be filled in.
+ */
public abstract ViewAssistStructure newChild(int index);
+ /**
+ * Like {@link #newChild}, but allows the caller to asynchronously populate the returned
+ * child. It can transfer the returned {@link ViewStructure} to another thread for it
+ * to build its content (and children etc). Once done, some thread must call
+ * {@link #asyncCommit} to tell the containing {@link ViewStructure} that the async
+ * population is done.
+ * @return Returns an fresh {@link ViewStructure} ready to be filled in.
+ */
public abstract ViewAssistStructure asyncNewChild(int index);
+
+ /**
+ * Call when done populating a {@link ViewStructure} returned by
+ * {@link #asyncNewChild}.
+ */
public abstract void asyncCommit();
/** @hide */
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 568e160..824b434 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1289,14 +1289,14 @@
void focusInLocked(View view) {
if (DEBUG) Log.v(TAG, "focusIn: " + view);
-
+
if (mCurRootView != view.getRootView()) {
// This is a request from a window that isn't in the window with
// IME focus, so ignore it.
if (DEBUG) Log.v(TAG, "Not IME target window, ignoring");
return;
}
-
+
mNextServedView = view;
scheduleCheckFocusLocked(view);
}
@@ -1392,7 +1392,7 @@
* Called by ViewAncestor when its window gets input focus.
* @hide
*/
- public void onWindowFocus(View rootView, View focusedView, int softInputMode,
+ public void onPostWindowFocus(View rootView, View focusedView, int softInputMode,
boolean first, int windowFlags) {
boolean forceNewFocus = false;
synchronized (mH) {
@@ -1441,14 +1441,27 @@
}
}
}
-
+
/** @hide */
- public void startGettingWindowFocus(View rootView) {
+ public void onPreWindowFocus(View rootView, boolean hasWindowFocus) {
synchronized (mH) {
- mCurRootView = rootView;
+ if (rootView == null) {
+ mCurRootView = null;
+ } if (hasWindowFocus) {
+ mCurRootView = rootView;
+ } else if (rootView == mCurRootView) {
+ // If the mCurRootView is losing window focus, release the strong reference to it
+ // so as not to prevent it from being garbage-collected.
+ mCurRootView = null;
+ } else {
+ if (DEBUG) {
+ Log.v(TAG, "Ignoring onPreWindowFocus()."
+ + " mCurRootView=" + mCurRootView + " rootView=" + rootView);
+ }
+ }
}
}
-
+
/**
* Report the current selection range.
*
diff --git a/core/java/android/webkit/FindActionModeCallback.java b/core/java/android/webkit/FindActionModeCallback.java
index ab6a2f9..b3b95f8 100644
--- a/core/java/android/webkit/FindActionModeCallback.java
+++ b/core/java/android/webkit/FindActionModeCallback.java
@@ -154,9 +154,9 @@
}
public void showSoftInput() {
- mInput.startGettingWindowFocus(mEditText.getRootView());
- mInput.focusIn(mEditText);
- mInput.showSoftInput(mEditText, 0);
+ if (mEditText.requestFocus()) {
+ mInput.showSoftInput(mEditText, 0);
+ }
}
public void updateMatchCount(int matchIndex, int matchCount, boolean isEmptyFind) {
diff --git a/core/java/android/webkit/ViewAssistStructure.java b/core/java/android/webkit/ViewAssistStructure.java
index 6f7a645..bbaceee 100644
--- a/core/java/android/webkit/ViewAssistStructure.java
+++ b/core/java/android/webkit/ViewAssistStructure.java
@@ -137,6 +137,11 @@
}
@Override
+ public void setTextStyle(int size, int fgColor, int bgColor, int style) {
+ mV.setTextStyle(size, fgColor, bgColor, style);
+ }
+
+ @Override
public void setHint(CharSequence hint) {
mV.setHint(hint);
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index c829783..d558c7b 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -233,6 +233,24 @@
final CursorAnchorInfoNotifier mCursorAnchorInfoNotifier = new CursorAnchorInfoNotifier();
+ private final Runnable mHideFloatingToolbar = new Runnable() {
+ @Override
+ public void run() {
+ if (mSelectionActionMode != null) {
+ mSelectionActionMode.snooze(ActionMode.SNOOZE_TIME_DEFAULT);
+ }
+ }
+ };
+
+ private final Runnable mShowFloatingToolbar = new Runnable() {
+ @Override
+ public void run() {
+ if (mSelectionActionMode != null) {
+ mSelectionActionMode.snooze(0); // snooze off.
+ }
+ }
+ };
+
Editor(TextView textView) {
mTextView = textView;
// Synchronize the filter list, which places the undo input filter at the end.
@@ -358,6 +376,9 @@
mTextView.removeCallbacks(mSelectionModeWithoutSelectionRunnable);
}
+ mTextView.removeCallbacks(mHideFloatingToolbar);
+ mTextView.removeCallbacks(mShowFloatingToolbar);
+
destroyDisplayListsData();
if (mSpellChecker != null) {
@@ -1169,6 +1190,8 @@
}
void onTouchEvent(MotionEvent event) {
+ updateFloatingToolbarVisibility(event);
+
if (hasSelectionController()) {
getSelectionController().onTouchEvent(event);
}
@@ -1189,6 +1212,37 @@
}
}
+ private void updateFloatingToolbarVisibility(MotionEvent event) {
+ if (mSelectionActionMode != null) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_MOVE:
+ hideFloatingToolbar();
+ break;
+ case MotionEvent.ACTION_UP: // fall through
+ case MotionEvent.ACTION_CANCEL:
+ showFloatingToolbar();
+ }
+ }
+ }
+
+ private void hideFloatingToolbar() {
+ if (mSelectionActionMode != null) {
+ mTextView.removeCallbacks(mShowFloatingToolbar);
+ // Delay the "hide" a little bit just in case a "show" will happen almost immediately.
+ mTextView.postDelayed(mHideFloatingToolbar, 100);
+ }
+ }
+
+ private void showFloatingToolbar() {
+ if (mSelectionActionMode != null) {
+ mTextView.removeCallbacks(mHideFloatingToolbar);
+ // Delay "show" so it doesn't interfere with click confirmations
+ // or double-clicks that could "dismiss" the floating toolbar.
+ int delay = ViewConfiguration.getDoubleTapTimeout();
+ mTextView.postDelayed(mShowFloatingToolbar, delay);
+ }
+ }
+
public void beginBatchEdit() {
mInBatchEditControllers = true;
final InputMethodState ims = mInputMethodState;
@@ -3661,6 +3715,8 @@
@Override
public boolean onTouchEvent(MotionEvent ev) {
+ updateFloatingToolbarVisibility(ev);
+
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
startTouchUpFilter(getCurrentCursorOffset());
diff --git a/core/java/com/android/internal/alsa/AlsaCardsParser.java b/core/java/com/android/internal/alsa/AlsaCardsParser.java
index 5c0a888..67ee085 100644
--- a/core/java/com/android/internal/alsa/AlsaCardsParser.java
+++ b/core/java/com/android/internal/alsa/AlsaCardsParser.java
@@ -29,7 +29,7 @@
*/
public class AlsaCardsParser {
private static final String TAG = "AlsaCardsParser";
- protected static final boolean DEBUG = true;
+ protected static final boolean DEBUG = false;
private static final String kCardsFilePath = "/proc/asound/cards";
diff --git a/core/java/com/android/internal/app/ProcessMap.java b/core/java/com/android/internal/app/ProcessMap.java
index 6ff0304..23a8bd7 100644
--- a/core/java/com/android/internal/app/ProcessMap.java
+++ b/core/java/com/android/internal/app/ProcessMap.java
@@ -39,14 +39,16 @@
return value;
}
- public void remove(String name, int uid) {
+ public E remove(String name, int uid) {
SparseArray<E> uids = mMap.get(name);
if (uids != null) {
- uids.remove(uid);
+ final E old = uids.removeReturnOld(uid);
if (uids.size() == 0) {
mMap.remove(name);
}
+ return old;
}
+ return null;
}
public ArrayMap<String, SparseArray<E>> getMap() {
diff --git a/core/java/com/android/internal/view/FloatingActionMode.java b/core/java/com/android/internal/view/FloatingActionMode.java
index c3f4da7..0195208 100644
--- a/core/java/com/android/internal/view/FloatingActionMode.java
+++ b/core/java/com/android/internal/view/FloatingActionMode.java
@@ -30,6 +30,9 @@
public class FloatingActionMode extends ActionMode {
+ private static final int MAX_SNOOZE_TIME = 3000;
+ private static final int MOVING_HIDE_DELAY = 300;
+
private final Context mContext;
private final ActionMode.Callback2 mCallback;
private final MenuBuilder mMenu;
@@ -37,13 +40,29 @@
private final Rect mContentRectOnWindow;
private final Rect mPreviousContentRectOnWindow;
private final int[] mViewPosition;
+ private final Rect mViewRect;
+ private final Rect mScreenRect;
private final View mOriginatingView;
+
+ private final Runnable mMovingOff = new Runnable() {
+ public void run() {
+ mFloatingToolbarVisibilityHelper.setMoving(false);
+ }
+ };
+
+ private final Runnable mSnoozeOff = new Runnable() {
+ public void run() {
+ mFloatingToolbarVisibilityHelper.setSnoozed(false);
+ }
+ };
+
private FloatingToolbar mFloatingToolbar;
+ private FloatingToolbarVisibilityHelper mFloatingToolbarVisibilityHelper;
public FloatingActionMode(
Context context, ActionMode.Callback2 callback, View originatingView) {
- mContext = context;
- mCallback = callback;
+ mContext = Preconditions.checkNotNull(context);
+ mCallback = Preconditions.checkNotNull(callback);
mMenu = new MenuBuilder(context).setDefaultShowAsAction(
MenuItem.SHOW_AS_ACTION_IF_ROOM);
setType(ActionMode.TYPE_FLOATING);
@@ -51,7 +70,10 @@
mContentRectOnWindow = new Rect();
mPreviousContentRectOnWindow = new Rect();
mViewPosition = new int[2];
- mOriginatingView = originatingView;
+ mViewRect = new Rect();
+ mScreenRect = new Rect();
+ mOriginatingView = Preconditions.checkNotNull(originatingView);
+ mOriginatingView.getLocationInWindow(mViewPosition);
}
public void setFloatingToolbar(FloatingToolbar floatingToolbar) {
@@ -63,6 +85,7 @@
return mCallback.onActionItemClicked(FloatingActionMode.this, item);
}
});
+ mFloatingToolbarVisibilityHelper = new FloatingToolbarVisibilityHelper(mFloatingToolbar);
}
@Override
@@ -82,7 +105,7 @@
@Override
public void invalidate() {
- Preconditions.checkNotNull(mFloatingToolbar);
+ checkToolbarInitialized();
mCallback.onPrepareActionMode(this, mMenu);
mFloatingToolbar.updateLayout();
invalidateContentRect();
@@ -90,32 +113,79 @@
@Override
public void invalidateContentRect() {
- Preconditions.checkNotNull(mFloatingToolbar);
+ checkToolbarInitialized();
mCallback.onGetContentRect(this, mOriginatingView, mContentRect);
repositionToolbar();
}
public void updateViewLocationInWindow() {
- Preconditions.checkNotNull(mFloatingToolbar);
+ checkToolbarInitialized();
mOriginatingView.getLocationInWindow(mViewPosition);
+ mViewRect.set(
+ mViewPosition[0],
+ mViewPosition[1],
+ mViewPosition[0] + mOriginatingView.getWidth(),
+ mViewPosition[1] + mOriginatingView.getHeight());
repositionToolbar();
}
private void repositionToolbar() {
+ checkToolbarInitialized();
mContentRectOnWindow.set(
mContentRect.left + mViewPosition[0],
mContentRect.top + mViewPosition[1],
mContentRect.right + mViewPosition[0],
mContentRect.bottom + mViewPosition[1]);
if (!mContentRectOnWindow.equals(mPreviousContentRectOnWindow)) {
+ if (!mPreviousContentRectOnWindow.isEmpty()) {
+ notifyContentRectMoving();
+ }
mFloatingToolbar.setContentRect(mContentRectOnWindow);
mFloatingToolbar.updateLayout();
}
mPreviousContentRectOnWindow.set(mContentRectOnWindow);
+
+ if (isContentRectWithinBounds()) {
+ mFloatingToolbarVisibilityHelper.setOutOfBounds(false);
+ } else {
+ mFloatingToolbarVisibilityHelper.setOutOfBounds(true);
+ }
+ }
+
+ private boolean isContentRectWithinBounds() {
+ mScreenRect.set(
+ 0,
+ 0,
+ mContext.getResources().getDisplayMetrics().widthPixels,
+ mContext.getResources().getDisplayMetrics().heightPixels);
+
+ return Rect.intersects(mContentRectOnWindow, mScreenRect)
+ && Rect.intersects(mContentRectOnWindow, mViewRect);
+ }
+
+ private void notifyContentRectMoving() {
+ mOriginatingView.removeCallbacks(mMovingOff);
+ mFloatingToolbarVisibilityHelper.setMoving(true);
+ mOriginatingView.postDelayed(mMovingOff, MOVING_HIDE_DELAY);
+ }
+
+ @Override
+ public void snooze(int snoozeTime) {
+ checkToolbarInitialized();
+ snoozeTime = Math.min(MAX_SNOOZE_TIME, snoozeTime);
+ mOriginatingView.removeCallbacks(mSnoozeOff);
+ if (snoozeTime <= 0) {
+ mSnoozeOff.run();
+ } else {
+ mFloatingToolbarVisibilityHelper.setSnoozed(true);
+ mOriginatingView.postDelayed(mSnoozeOff, snoozeTime);
+ }
}
@Override
public void finish() {
+ checkToolbarInitialized();
+ reset();
mCallback.onDestroyActionMode(this);
}
@@ -144,4 +214,56 @@
return new MenuInflater(mContext);
}
+ /**
+ * @throws IlllegalStateException
+ */
+ private void checkToolbarInitialized() {
+ Preconditions.checkState(mFloatingToolbar != null);
+ Preconditions.checkState(mFloatingToolbarVisibilityHelper != null);
+ }
+
+ private void reset() {
+ mOriginatingView.removeCallbacks(mMovingOff);
+ mOriginatingView.removeCallbacks(mSnoozeOff);
+ }
+
+
+ /**
+ * A helper that shows/hides the floating toolbar depending on certain states.
+ */
+ private static final class FloatingToolbarVisibilityHelper {
+
+ private final FloatingToolbar mToolbar;
+
+ private boolean mSnoozed;
+ private boolean mMoving;
+ private boolean mOutOfBounds;
+
+ public FloatingToolbarVisibilityHelper(FloatingToolbar toolbar) {
+ mToolbar = Preconditions.checkNotNull(toolbar);
+ }
+
+ public void setSnoozed(boolean snoozed) {
+ mSnoozed = snoozed;
+ updateToolbarVisibility();
+ }
+
+ public void setMoving(boolean moving) {
+ mMoving = moving;
+ updateToolbarVisibility();
+ }
+
+ public void setOutOfBounds(boolean outOfBounds) {
+ mOutOfBounds = outOfBounds;
+ updateToolbarVisibility();
+ }
+
+ private void updateToolbarVisibility() {
+ if (mSnoozed || mMoving || mOutOfBounds) {
+ mToolbar.hide();
+ } else if (mToolbar.isHidden()) {
+ mToolbar.show();
+ }
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index fdc3547..b7a53b0 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -357,7 +357,7 @@
mShowAnimation = createGrowFadeInFromBottom(mContentContainer);
mDismissAnimation = createShrinkFadeOutFromBottomAnimation(
mContentContainer,
- 0,
+ 150, // startDelay
new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -367,7 +367,7 @@
});
mHideAnimation = createShrinkFadeOutFromBottomAnimation(
mContentContainer,
- 150,
+ 0, // startDelay
new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
diff --git a/core/java/com/android/internal/widget/TextViewInputDisabler.java b/core/java/com/android/internal/widget/TextViewInputDisabler.java
new file mode 100644
index 0000000..fb0b3b9
--- /dev/null
+++ b/core/java/com/android/internal/widget/TextViewInputDisabler.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.text.InputFilter;
+import android.text.Spanned;
+import android.widget.TextView;
+
+/**
+ * Helper class to disable input on a TextView. The input is disabled by swapping in an InputFilter
+ * that discards all changes. Use with care if you have customized InputFilter on the target
+ * TextView.
+ */
+public class TextViewInputDisabler {
+ private TextView mTextView;
+ private InputFilter[] mDefaultFilters;
+ private InputFilter[] mNoInputFilters = new InputFilter[] {
+ new InputFilter () {
+ @Override
+ public CharSequence filter(CharSequence source, int start, int end, Spanned dest,
+ int dstart, int dend) {
+ return "";
+ }
+ }
+ };
+
+ public TextViewInputDisabler(TextView textView) {
+ mTextView = textView;
+ mDefaultFilters = mTextView.getFilters();
+ }
+
+ public void setInputEnabled(boolean enabled) {
+ mTextView.setFilters(enabled ? mDefaultFilters : mNoInputFilters);
+ }
+}
diff --git a/core/jni/android/graphics/Region.cpp b/core/jni/android/graphics/Region.cpp
index cf02e39..3bab2df 100644
--- a/core/jni/android/graphics/Region.cpp
+++ b/core/jni/android/graphics/Region.cpp
@@ -212,10 +212,14 @@
android::Parcel* p = android::parcelForJavaObject(env, parcel);
- SkRegion* region = new SkRegion;
- size_t size = p->readInt32();
- size_t actualSize = region->readFromMemory(p->readInplace(size), size);
+ const size_t size = p->readInt32();
+ const void* regionData = p->readInplace(size);
+ if (regionData == nullptr) {
+ return 0;
+ }
+ SkRegion* region = new SkRegion;
+ size_t actualSize = region->readFromMemory(regionData, size);
if (size != actualSize) {
delete region;
return 0;
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 60e8ed0..fada7ac2 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -291,6 +291,11 @@
return (jboolean) !protectFromVpn(socket);
}
+static jboolean android_net_utils_queryUserAccess(JNIEnv *env, jobject thiz, jint uid, jint netId)
+{
+ return (jboolean) !queryUserAccess(uid, netId);
+}
+
// ----------------------------------------------------------------------------
@@ -311,6 +316,7 @@
{ "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
{ "bindSocketToNetwork", "(II)I", (void*) android_net_utils_bindSocketToNetwork },
{ "protectFromVpn", "(I)Z", (void*)android_net_utils_protectFromVpn },
+ { "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess },
{ "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDhcpFilter },
};
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 0d7d868..21c4a8d 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1035,9 +1035,6 @@
<!-- The color applied to framework switch thumbs in their normal state. -->
<attr name="colorSwitchThumbNormal" format="color" />
- <!-- @hide The background used by framework controls. -->
- <attr name="controlBackground" format="reference" />
-
<!-- The color applied to the edge effect on scrolling containers. -->
<attr name="colorEdgeEffect" format="color" />
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index c2371ee..165d1f8 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -572,16 +572,16 @@
<style name="Widget.Material.CompoundButton" parent="Widget.CompoundButton"/>
<style name="Widget.Material.CompoundButton.CheckBox" parent="Widget.CompoundButton.CheckBox">
- <item name="background">?attr/controlBackground</item>
+ <item name="background">@drawable/control_background_material</item>
</style>
<style name="Widget.Material.CompoundButton.RadioButton" parent="Widget.CompoundButton.RadioButton">
- <item name="background">?attr/controlBackground</item>
+ <item name="background">@drawable/control_background_material</item>
</style>
<style name="Widget.Material.CompoundButton.Star" parent="Widget.CompoundButton.Star">
<item name="button">@drawable/btn_star_material</item>
- <item name="background">?attr/controlBackground</item>
+ <item name="background">@drawable/control_background_material</item>
</style>
<style name="Widget.Material.CompoundButton.Switch">
@@ -590,7 +590,7 @@
<item name="switchTextAppearance">@style/TextAppearance.Material.Widget.Switch</item>
<item name="textOn">@string/capital_on</item>
<item name="textOff">@string/capital_off</item>
- <item name="background">?attr/controlBackground</item>
+ <item name="background">@drawable/control_background_material</item>
<item name="showText">false</item>
</style>
@@ -727,7 +727,7 @@
<item name="paddingStart">16dip</item>
<item name="paddingEnd">16dip</item>
<item name="mirrorForRtl">true</item>
- <item name="background">?attr/controlBackground</item>
+ <item name="background">@drawable/control_background_material</item>
</style>
<style name="Widget.Material.RatingBar" parent="Widget.RatingBar">
@@ -809,7 +809,7 @@
</style>
<style name="Widget.Material.Toolbar.Button.Navigation" parent="Widget.Material">
- <item name="background">?attr/controlBackground</item>
+ <item name="background">@drawable/control_background_material</item>
<item name="minWidth">56dp</item>
<item name="scaleType">center</item>
<item name="paddingStart">@dimen/action_bar_navigation_padding_start_material</item>
@@ -866,7 +866,7 @@
</style>
<style name="Widget.Material.ActionButton.CloseMode">
- <item name="background">?attr/controlBackground</item>
+ <item name="background">@drawable/control_background_material</item>
</style>
<style name="Widget.Material.ActionButton.Overflow">
@@ -962,7 +962,7 @@
</style>
<style name="Widget.Material.MediaRouteButton">
- <item name="background">?attr/controlBackground</item>
+ <item name="background">@drawable/control_background_material</item>
<item name="externalRouteEnabledDrawable">@drawable/ic_media_route_material</item>
<item name="minWidth">56dp</item>
<item name="minHeight">48dp</item>
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index f02fed1..3f8071f 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -390,8 +390,6 @@
<item name="colorControlHighlight">@color/ripple_material_dark</item>
<item name="colorButtonNormal">@color/btn_default_material_dark</item>
<item name="colorSwitchThumbNormal">@color/switch_thumb_material_dark</item>
-
- <item name="controlBackground">@drawable/control_background_material</item>
</style>
<!-- Material theme (light version). -->
@@ -740,8 +738,6 @@
<item name="colorControlHighlight">@color/ripple_material_light</item>
<item name="colorButtonNormal">@color/btn_default_material_light</item>
<item name="colorSwitchThumbNormal">@color/switch_thumb_material_light</item>
-
- <item name="controlBackground">@drawable/control_background_material</item>
</style>
<!-- Variant of the material (light) theme that has a solid (opaque) action bar
diff --git a/core/tests/coretests/src/android/net/netlink/NetlinkSocketTest.java b/core/tests/coretests/src/android/net/netlink/NetlinkSocketTest.java
index b32de78..c599fe3 100644
--- a/core/tests/coretests/src/android/net/netlink/NetlinkSocketTest.java
+++ b/core/tests/coretests/src/android/net/netlink/NetlinkSocketTest.java
@@ -90,4 +90,27 @@
s.close();
}
+
+ public void testRepeatedCloseCallsAreQuiet() throws Exception {
+ // Create a working NetlinkSocket.
+ NetlinkSocket s = new NetlinkSocket(OsConstants.NETLINK_ROUTE);
+ assertNotNull(s);
+ s.connectToKernel();
+ NetlinkSocketAddress localAddr = s.getLocalAddress();
+ assertNotNull(localAddr);
+ assertEquals(0, localAddr.getGroupsMask());
+ assertTrue(0 != localAddr.getPortId());
+ // Close once.
+ s.close();
+ // Test that it is closed.
+ boolean expectedErrorSeen = false;
+ try {
+ localAddr = s.getLocalAddress();
+ } catch (ErrnoException e) {
+ expectedErrorSeen = true;
+ }
+ assertTrue(expectedErrorSeen);
+ // Close once more.
+ s.close();
+ }
}
diff --git a/data/fonts/fallback_fonts.xml b/data/fonts/fallback_fonts.xml
index 0aa588b..5857de0 100644
--- a/data/fonts/fallback_fonts.xml
+++ b/data/fonts/fallback_fonts.xml
@@ -35,14 +35,14 @@
<familyset>
<family>
<fileset>
- <file variant="elegant">NotoNaskh-Regular.ttf</file>
- <file variant="elegant">NotoNaskh-Bold.ttf</file>
+ <file variant="elegant">NotoNaskhArabic-Regular.ttf</file>
+ <file variant="elegant">NotoNaskhArabic-Bold.ttf</file>
</fileset>
</family>
<family>
<fileset>
- <file variant="compact">NotoNaskhUI-Regular.ttf</file>
- <file variant="compact">NotoNaskhUI-Bold.ttf</file>
+ <file variant="compact">NotoNaskhArabicUI-Regular.ttf</file>
+ <file variant="compact">NotoNaskhArabicUI-Bold.ttf</file>
</fileset>
</family>
<family>
@@ -387,6 +387,11 @@
</family>
<family>
<fileset>
+ <file>NotoSansTibetan-Regular.ttf</file>
+ </fileset>
+ </family>
+ <family>
+ <fileset>
<file>NotoSansTifinagh-Regular.ttf</file>
</fileset>
</family>
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index f903aab..62da0ff 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -94,12 +94,12 @@
<!-- fallback fonts -->
<family variant="elegant">
- <font weight="400" style="normal">NotoNaskh-Regular.ttf</font>
- <font weight="700" style="normal">NotoNaskh-Bold.ttf</font>
+ <font weight="400" style="normal">NotoNaskhArabic-Regular.ttf</font>
+ <font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font>
</family>
<family variant="compact">
- <font weight="400" style="normal">NotoNaskhUI-Regular.ttf</font>
- <font weight="700" style="normal">NotoNaskhUI-Bold.ttf</font>
+ <font weight="400" style="normal">NotoNaskhArabicUI-Regular.ttf</font>
+ <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
</family>
<family>
<font weight="400" style="normal">NotoSansEthiopic-Regular.ttf</font>
@@ -320,6 +320,9 @@
<font weight="400" style="normal">NotoSansTaiViet-Regular.ttf</font>
</family>
<family>
+ <font weight="400" style="normal">NotoSansTibetan-Regular.ttf</font>
+ </family>
+ <family>
<font weight="400" style="normal">NotoSansTifinagh-Regular.ttf</font>
</family>
<family>
diff --git a/docs/html/preview/images/bugs.png b/docs/html/preview/images/bugs.png
deleted file mode 100644
index 46adf05..0000000
--- a/docs/html/preview/images/bugs.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/preview/images/dev-prev.png b/docs/html/preview/images/dev-prev.png
deleted file mode 100644
index eae6ede..0000000
--- a/docs/html/preview/images/dev-prev.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/preview/images/m-preview-timeline.png b/docs/html/preview/images/m-preview-timeline.png
index a065c21..bda0b46 100644
--- a/docs/html/preview/images/m-preview-timeline.png
+++ b/docs/html/preview/images/m-preview-timeline.png
Binary files differ
diff --git a/docs/html/preview/images/updates.png b/docs/html/preview/images/updates.png
deleted file mode 100644
index f165c5a..0000000
--- a/docs/html/preview/images/updates.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/training/articles/keystore.jd b/docs/html/training/articles/keystore.jd
index 4005a05..20963f5 100644
--- a/docs/html/training/articles/keystore.jd
+++ b/docs/html/training/articles/keystore.jd
@@ -5,6 +5,7 @@
<div id="qv">
<h2>In this document</h2>
<ol>
+ <li><a href="#SecurityFeatures">Security Features</a></li>
<li><a href="#WhichShouldIUse">Choosing Between a Keychain or the Android Keystore Provider</a></li>
<li><a href="#UsingAndroidKeyStore">Using Android Keystore Provider
</a></li>
@@ -31,7 +32,8 @@
keystore, they can be used for cryptographic operations with the key material
remaining non-exportable. Moreover, it offers facilities to restrict when and
how keys can be used, such as requiring user authentication for key use or
- restricting encryption keys to be used only in certain block modes.</p>
+ restricting encryption keys to be used only in certain block modes. See
+ <a href="#SecurityFeatures">Security Features</a> section for more information.</p>
<p>The Keystore system is used by the {@link
android.security.KeyChain} API as well as the Android
@@ -39,6 +41,67 @@
(API level 18). This document goes over when and how to use the
Android Keystore provider.</p>
+
+<h2 id="SecurityFeatures">Security Features</h2>
+
+Android Keystore system protects key material from unauthorized use. Firstly, Android Keystore
+mitigates unauthorized use of key material outside of the Android device by preventing extraction of
+the key material from application processes and from the Android device as a whole. Secondly,
+Android KeyStore mitigates unauthorized use of key material on the Android device by making apps
+specify authorized uses of their keys and then enforcing these restrictions.
+
+<h3 id="ExtractionPrevention">Extraction Prevention</h3>
+
+Key material of Android Keystore keys is protected from extraction using two security measures:
+<ul>
+<li>Key material never enters the application process. When an application performs cryptographic
+ operations using an Android Keystore key, behind the scenes plaintext, ciphertext, and messages to
+ be signed or verified are fed to a system process which carries out the cryptographic operations.
+ If the app's process is compromised, the attacker may be able to use the app's keys but will not
+ be able to extract their key material (for example, to be used outside of the Android device).
+ </li>
+<li>Key material may be bound to the secure hardware (e.g., Trusted Execution Environment (TEE),
+ Secure Element (SE)) of the Android device. When this feature is enabled for a key, its key
+ material is never exposed outside of secure hardware. If the Android OS is compromised or an
+ attacker can read the device's internal storage, the attacker may be able to use any app's Android
+ Keystore keys on the Android device, but not extract them from the device. This feature is enabled
+ only if the device's secure hardware supports the particular combination of key algorithm, block
+ modes, padding schemes, and digests with which the key is authorized to be used. To check whether
+ the feature is enabled for a key, obtain a {@link android.security.keystore.KeyInfo} for the key
+ and inspect the return value of
+ {@link android.security.keystore.KeyInfo#isInsideSecureHardware() KeyInfo.isInsideSecurityHardware()}.
+ </li>
+</ul>
+
+<h3 id="KeyUseAuthorizations">Key Use Authorizations</h3>
+
+To mitigate unauthorized use of keys on the Android device, Android Keystore lets apps specify
+authorized uses of their keys when generating or importing the keys. Once a key is generated or
+imported, its authorizations can not be changed. Authorizations are then enforced by the Android
+Keystore whenever the key is used.
+
+<p>Supported key use authorizations fall into the following categories:
+<ul>
+<li><em>cryptography</em>: authorized key algorithm, operations or purposes (encrypt, decrypt, sign,
+ verify), padding schemes, block modes, digests with which the key can be used</li>
+<li><em>temporal validity interval</em>: interval of time during which the key is authorized for
+ use</li>
+<li><em>user authentication</em>: the key can only be used if the user has been authenticated
+ recently enough. See <a href="#UserAuthentication">Requiring User Authentication For Key Use</a>.
+ </li>
+</ul>
+
+<p>As an additional security measure, for keys whose key material is inside secure hardware (see
+ {@link android.security.keystore.KeyInfo#isInsideSecureHardware() KeyInfo.isInsideSecurityHardware()})
+ some key use authorizations may be enforced by secure hardware, depending on the Android device.
+ Cryptographic and user authentication authorizations are likely to be enforced by secure hardware.
+ Temporal validity interval authorizations are unlikely to be enforced by the secure hardware
+ because it normally does not have an independent secure real-time clock.
+
+<p>Whether a key's user authentication authorization is enforced by the secure hardware can be
+ queried using
+ {@link android.security.keystore.KeyInfo#isUserAuthenticationRequirementEnforcedBySecureHardware() KeyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware()}.
+
<h2 id="WhichShouldIUse">Choosing Between a Keychain or the
Android Keystore Provider</h2>
@@ -129,7 +192,7 @@
for use as soon as the user unlocks the secure lock screen or confirms their secure lock screen
credentials using the {@link android.app.KeyguardManager#createConfirmDeviceCredentialIntent(CharSequence, CharSequence) KeyguardManager.createConfirmDeviceCredentialIntent}
flow. Each key specifies for how long the authorization remains valid for that key. Such keys
- can only be generated or imported if the secure lock screen is enabled (see {@link android.app.KeyguardManager#isDeviceSecure()}).
+ can only be generated or imported if the secure lock screen is enabled (see {@link android.app.KeyguardManager#isDeviceSecure() KeyguardManager.isDeviceSecure()}).
These keys become permanently invalidated once the secure lock screen is disabled or forcibly
reset (e.g. by a Device Admin).</li>
<li>User authentication is required for every use of the key. In this mode, a specific operation
diff --git a/keystore/java/android/security/keystore/AndroidKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
similarity index 96%
rename from keystore/java/android/security/keystore/AndroidKeyPairGeneratorSpi.java
rename to keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
index 2c393fd..4b45fd7 100644
--- a/keystore/java/android/security/keystore/AndroidKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -52,19 +52,19 @@
* <p>
* This class can not be directly instantiated and must instead be used via the
* {@link KeyPairGenerator#getInstance(String)
- * KeyPairGenerator.getInstance("AndroidKeyPairGenerator")} API.
+ * KeyPairGenerator.getInstance("AndroidKeyStore")} API.
*
- * {@hide}
+ * @hide
*/
-public abstract class AndroidKeyPairGeneratorSpi extends KeyPairGeneratorSpi {
+public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGeneratorSpi {
- public static class RSA extends AndroidKeyPairGeneratorSpi {
+ public static class RSA extends AndroidKeyStoreKeyPairGeneratorSpi {
public RSA() {
super(KeyProperties.KEY_ALGORITHM_RSA);
}
}
- public static class EC extends AndroidKeyPairGeneratorSpi {
+ public static class EC extends AndroidKeyStoreKeyPairGeneratorSpi {
public EC() {
super(KeyProperties.KEY_ALGORITHM_EC);
}
@@ -94,7 +94,7 @@
private int mKeyType;
private int mKeySize;
- protected AndroidKeyPairGeneratorSpi(@KeyProperties.KeyAlgorithmEnum String algorithm) {
+ protected AndroidKeyStoreKeyPairGeneratorSpi(@KeyProperties.KeyAlgorithmEnum String algorithm) {
mAlgorithm = algorithm;
}
@@ -283,7 +283,8 @@
@Override
public void initialize(int keysize, SecureRandom random) {
- throw new IllegalArgumentException("cannot specify keysize with AndroidKeyPairGenerator");
+ throw new IllegalArgumentException(
+ "cannot specify keysize with AndroidKeyStore KeyPairGenerator");
}
@Override
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
index b20a122..649a515 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
@@ -49,8 +49,8 @@
put("KeyStore.AndroidKeyStore", PACKAGE_NAME + ".AndroidKeyStoreSpi");
// java.security.KeyPairGenerator
- put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyPairGeneratorSpi$EC");
- put("KeyPairGenerator.RSA", PACKAGE_NAME + ".AndroidKeyPairGeneratorSpi$RSA");
+ put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$EC");
+ put("KeyPairGenerator.RSA", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$RSA");
// javax.crypto.KeyGenerator
put("KeyGenerator.AES", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$AES");
@@ -111,6 +111,7 @@
*
* @throws IllegalArgumentException if the provided primitive is not supported or is not backed
* by AndroidKeyStore provider.
+ * @throws IllegalStateException if the provided primitive is not initialized.
*/
public static long getKeyStoreOperationHandle(Object cryptoPrimitive) {
if (cryptoPrimitive == null) {
@@ -118,15 +119,17 @@
}
Object spi;
if (cryptoPrimitive instanceof Mac) {
- spi = ((Mac) cryptoPrimitive).getSpi();
+ spi = ((Mac) cryptoPrimitive).getCurrentSpi();
} else if (cryptoPrimitive instanceof Cipher) {
- spi = ((Cipher) cryptoPrimitive).getSpi();
+ spi = ((Cipher) cryptoPrimitive).getCurrentSpi();
} else {
throw new IllegalArgumentException("Unsupported crypto primitive: " + cryptoPrimitive);
}
- if (!(spi instanceof KeyStoreCryptoOperation)) {
+ if (spi == null) {
+ throw new IllegalStateException("Crypto primitive not initialized");
+ } else if (!(spi instanceof KeyStoreCryptoOperation)) {
throw new IllegalArgumentException(
- "Crypto primitive not backed by AndroidKeyStore: " + cryptoPrimitive
+ "Crypto primitive not backed by AndroidKeyStore provider: " + cryptoPrimitive
+ ", spi: " + spi);
}
return ((KeyStoreCryptoOperation) spi).getOperationHandle();
diff --git a/libs/hwui/utils/RingBuffer.h b/libs/hwui/utils/RingBuffer.h
index 6d0a06b..d822cb2 100644
--- a/libs/hwui/utils/RingBuffer.h
+++ b/libs/hwui/utils/RingBuffer.h
@@ -31,7 +31,7 @@
RingBuffer() {}
~RingBuffer() {}
- constexpr size_t capacity() { return SIZE; }
+ constexpr size_t capacity() const { return SIZE; }
size_t size() { return mCount; }
T& next() {
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 4526839..0f1be6b 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -17,6 +17,7 @@
package android.media;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -724,6 +725,26 @@
return USAGE_UNKNOWN;
}
}
+ /**
+ * @hide
+ * CANDIDATE FOR PUBLIC (or at least SYSTEM) API
+ * Returns the stream type matching the given attributes for volume control.
+ * Use this method to derive the stream type needed to configure the volume
+ * control slider in an {@link Activity} with {@link Activity#setVolumeControlStream(int)}.
+ * <BR>Do not use this method to set the stream type on an audio player object
+ * (e.g. {@link AudioTrack}, {@link MediaPlayer}), use <code>AudioAttributes</code> instead.
+ * @param aa non-null AudioAttributes.
+ * @return a valid stream type for <code>Activity</code> or stream volume control that matches
+ * the attributes, or {@link AudioManager#USE_DEFAULT_STREAM_TYPE} if there isn't a direct
+ * match. Note that <code>USE_DEFAULT_STREAM_TYPE</code> is not a valid value
+ * for {@link AudioManager#setStreamVolume(int, int, int)}.
+ */
+ public static int getVolumeControlStream(@NonNull AudioAttributes aa) {
+ if (aa == null) {
+ throw new IllegalArgumentException("Invalid null audio attributes");
+ }
+ return toVolumeStreamType(true /*fromGetVolumeControlStream*/, aa);
+ }
/**
* @hide
@@ -732,13 +753,19 @@
* @param aa non-null AudioAttributes.
* @return a valid stream type for volume control that matches the attributes.
*/
- public static int toLegacyStreamType(AudioAttributes aa) {
+ public static int toLegacyStreamType(@NonNull AudioAttributes aa) {
+ return toVolumeStreamType(false /*fromGetVolumeControlStream*/, aa);
+ }
+
+ private static int toVolumeStreamType(boolean fromGetVolumeControlStream, AudioAttributes aa) {
// flags to stream type mapping
if ((aa.getFlags() & FLAG_AUDIBILITY_ENFORCED) == FLAG_AUDIBILITY_ENFORCED) {
- return AudioSystem.STREAM_SYSTEM_ENFORCED;
+ return fromGetVolumeControlStream ?
+ AudioSystem.STREAM_SYSTEM : AudioSystem.STREAM_SYSTEM_ENFORCED;
}
if ((aa.getFlags() & FLAG_SCO) == FLAG_SCO) {
- return AudioSystem.STREAM_BLUETOOTH_SCO;
+ return fromGetVolumeControlStream ?
+ AudioSystem.STREAM_VOICE_CALL : AudioSystem.STREAM_BLUETOOTH_SCO;
}
// usage to stream type mapping
@@ -753,7 +780,8 @@
case USAGE_VOICE_COMMUNICATION:
return AudioSystem.STREAM_VOICE_CALL;
case USAGE_VOICE_COMMUNICATION_SIGNALLING:
- return AudioSystem.STREAM_DTMF;
+ return fromGetVolumeControlStream ?
+ AudioSystem.STREAM_VOICE_CALL : AudioSystem.STREAM_DTMF;
case USAGE_ALARM:
return AudioSystem.STREAM_ALARM;
case USAGE_NOTIFICATION_RINGTONE:
@@ -765,8 +793,15 @@
case USAGE_NOTIFICATION_EVENT:
return AudioSystem.STREAM_NOTIFICATION;
case USAGE_UNKNOWN:
+ return fromGetVolumeControlStream ?
+ AudioManager.USE_DEFAULT_STREAM_TYPE : AudioSystem.STREAM_MUSIC;
default:
- return AudioSystem.STREAM_MUSIC;
+ if (fromGetVolumeControlStream) {
+ throw new IllegalArgumentException("Unknown usage value " + aa.getUsage() +
+ " in audio attributes");
+ } else {
+ return AudioSystem.STREAM_MUSIC;
+ }
}
}
diff --git a/media/java/android/media/midi/MidiReceiver.java b/media/java/android/media/midi/MidiReceiver.java
index 85c451f..12a5f04 100644
--- a/media/java/android/media/midi/MidiReceiver.java
+++ b/media/java/android/media/midi/MidiReceiver.java
@@ -87,29 +87,38 @@
}
/**
- * Called to send MIDI data to the receiver
+ * Called to send MIDI data to the receiver without a timestamp.
+ * Data will be processed by receiver in the order sent.
* Data will get split into multiple calls to {@link #onSend} if count exceeds
- * {@link #getMaxMessageSize}.
+ * {@link #getMaxMessageSize}. Blocks until all the data is sent or an exception occurs.
+ * In the latter case, the amount of data sent prior to the exception is not provided to caller.
+ * The communication should be considered corrupt. The sender should reestablish
+ * communication, reset all controllers and send all notes off.
*
* @param msg a byte array containing the MIDI data
* @param offset the offset of the first byte of the data in the array to be sent
* @param count the number of bytes of MIDI data in the array to be sent
- * @throws IOException
+ * @throws IOException if the data could not be sent in entirety
*/
public void send(byte[] msg, int offset, int count) throws IOException {
- send(msg, offset, count, System.nanoTime());
+ // TODO add public static final TIMESTAMP_NONE = 0L
+ send(msg, offset, count, 0L);
}
/**
- * Called to send MIDI data to the receiver to be handled at a specified time in the future
+ * Called to send MIDI data to the receiver with a specified timestamp.
+ * Data will be processed by receiver in order first by timestamp, then in the order sent.
* Data will get split into multiple calls to {@link #onSend} if count exceeds
- * {@link #getMaxMessageSize}.
+ * {@link #getMaxMessageSize}. Blocks until all the data is sent or an exception occurs.
+ * In the latter case, the amount of data sent prior to the exception is not provided to caller.
+ * The communication should be considered corrupt. The sender should reestablish
+ * communication, reset all controllers and send all notes off.
*
* @param msg a byte array containing the MIDI data
* @param offset the offset of the first byte of the data in the array to be sent
* @param count the number of bytes of MIDI data in the array to be sent
- * @param timestamp the timestamp of the message (based on {@link java.lang.System#nanoTime}
- * @throws IOException
+ * @param timestamp the timestamp of the message, based on {@link java.lang.System#nanoTime}
+ * @throws IOException if the data could not be sent in entirety
*/
public void send(byte[] msg, int offset, int count, long timestamp)
throws IOException {
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 66b904b..fed0ddf 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -173,10 +173,7 @@
break;
}
case DO_TIME_SHIFT_SET_PLAYBACK_PARAMS: {
- PlaybackParams params = new PlaybackParams()
- .setSpeed((Float) msg.obj)
- .setAudioFallbackMode(msg.arg1);
- mTvInputSessionImpl.timeShiftSetPlaybackParams(params);
+ mTvInputSessionImpl.timeShiftSetPlaybackParams((PlaybackParams) msg.obj);
break;
}
case DO_TIME_SHIFT_ENABLE_POSITION_TRACKING: {
diff --git a/packages/DocumentsUI/res/layout/fragment_pick.xml b/packages/DocumentsUI/res/layout/fragment_pick.xml
index 87dc4f8..40d4eb5 100644
--- a/packages/DocumentsUI/res/layout/fragment_pick.xml
+++ b/packages/DocumentsUI/res/layout/fragment_pick.xml
@@ -19,13 +19,13 @@
android:layout_height="wrap_content"
android:orientation="horizontal"
android:baselineAligned="false"
- android:gravity="center_vertical"
+ android:gravity="end"
+ android:paddingEnd="8dp"
android:minHeight="?android:attr/listPreferredItemHeightSmall">
<Button
android:id="@android:id/button2"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:layout_weight="1"
android:text="@android:string/cancel"
android:visibility="gone"
style="?android:attr/buttonBarNegativeButtonStyle" />
@@ -33,7 +33,5 @@
android:id="@android:id/button1"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:layout_weight="1"
- android:textAllCaps="false"
style="?android:attr/buttonBarPositiveButtonStyle" />
</LinearLayout>
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index 5281087..9794273 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -44,8 +44,6 @@
<string name="menu_share">Share</string>
<!-- Menu item title that deletes the selected documents [CHAR LIMIT=24] -->
<string name="menu_delete">Delete</string>
- <!-- Menu item title that selects the current directory [CHAR LIMIT=48] -->
- <string name="menu_select">Select \"<xliff:g id="directory" example="My Directory">^1</xliff:g>\"</string>
<!-- Menu item title that selects all documents in the current directory [CHAR LIMIT=24] -->
<string name="menu_select_all">Select All</string>
<!-- Menu item title that copies the selected documents [CHAR LIMIT=24] -->
@@ -65,7 +63,9 @@
<!-- Menu item that hides the sizes of displayed files [CHAR LIMIT=24] -->
<string name="menu_file_size_hide">Hide file size</string>
- <!-- Button label that copies files to the current directory [CHAR LIMIT=48] -->
+ <!-- Button label that select the current directory [CHAR LIMIT=24] -->
+ <string name="button_select">Select</string>
+ <!-- Button label that copies files to the current directory [CHAR LIMIT=24] -->
<string name="button_copy">Copy</string>
<!-- Action mode title summarizing the number of documents selected [CHAR LIMIT=32] -->
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index cded717..8482438 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -571,9 +571,7 @@
mState.action == ACTION_OPEN_COPY_DESTINATION) {
final PickFragment pick = PickFragment.get(fm);
if (pick != null) {
- final CharSequence displayName = (mState.stack.size() <= 1) ? root.title
- : cwd.displayName;
- pick.setPickTarget(mState.action, cwd, displayName);
+ pick.setPickTarget(mState.action, cwd);
}
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
index e899379..d9b8568 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
@@ -38,15 +38,19 @@
public class PickFragment extends Fragment {
public static final String TAG = "PickFragment";
+ private int mAction;
private DocumentInfo mPickTarget;
-
private View mContainer;
private Button mPick;
private Button mCancel;
public static void show(FragmentManager fm) {
- final PickFragment fragment = new PickFragment();
+ // Fragment can be restored by FragmentManager automatically.
+ if (get(fm) != null) {
+ return;
+ }
+ final PickFragment fragment = new PickFragment();
final FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.container_save, fragment, TAG);
ft.commitAllowingStateLoss();
@@ -67,8 +71,7 @@
mCancel = (Button) mContainer.findViewById(android.R.id.button2);
mCancel.setOnClickListener(mCancelListener);
- setPickTarget(0, null, null);
-
+ updateView();
return mContainer;
}
@@ -92,32 +95,38 @@
/**
* @param action Which action defined in BaseActivity.State is the picker shown for.
*/
- public void setPickTarget(int action,
- DocumentInfo pickTarget,
- CharSequence displayName) {
- if (mContainer != null) {
- if (pickTarget != null) {
- final Locale locale = getResources().getConfiguration().locale;
- switch (action) {
- case BaseActivity.State.ACTION_OPEN_TREE:
- final String raw = getString(R.string.menu_select).toUpperCase(locale);
- mPick.setText(TextUtils.expandTemplate(raw, displayName));
- mCancel.setVisibility(View.GONE);
- break;
- case BaseActivity.State.ACTION_OPEN_COPY_DESTINATION:
- mPick.setText(getString(R.string.button_copy).toUpperCase(locale));
- mCancel.setVisibility(View.VISIBLE);
- break;
- default:
- throw new IllegalArgumentException("Illegal action for PickFragment.");
- }
- }
- if (pickTarget != null && pickTarget.isCreateSupported()) {
- mContainer.setVisibility(View.VISIBLE);
- } else {
- mContainer.setVisibility(View.GONE);
- }
- }
+ public void setPickTarget(int action, DocumentInfo pickTarget) {
+ mAction = action;
mPickTarget = pickTarget;
+ if (mContainer != null) {
+ updateView();
+ }
+ }
+
+ /**
+ * Applies the state of fragment to the view components.
+ */
+ private void updateView() {
+ switch (mAction) {
+ case BaseActivity.State.ACTION_OPEN_TREE:
+ mPick.setText(R.string.button_select);
+ mCancel.setVisibility(View.GONE);
+ break;
+ case BaseActivity.State.ACTION_OPEN_COPY_DESTINATION:
+ mPick.setText(R.string.button_copy);
+ mCancel.setVisibility(View.VISIBLE);
+ break;
+ default:
+ mContainer.setVisibility(View.GONE);
+ return;
+ }
+
+ if (mPickTarget != null && (
+ mAction == BaseActivity.State.ACTION_OPEN_TREE ||
+ mPickTarget.isCreateSupported())) {
+ mContainer.setVisibility(View.VISIBLE);
+ } else {
+ mContainer.setVisibility(View.GONE);
+ }
}
}
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java
index 3fcc3c3..f18c451 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java
@@ -35,6 +35,8 @@
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
+import com.android.internal.widget.TextViewInputDisabler;
+
import java.util.List;
/**
* Displays an alphanumeric (latin-1) key entry for the user to enter
@@ -49,6 +51,8 @@
InputMethodManager mImm;
private TextView mPasswordEntry;
+ private TextViewInputDisabler mPasswordEntryDisabler;
+
private Interpolator mLinearOutSlowInInterpolator;
private Interpolator mFastOutLinearInInterpolator;
@@ -70,7 +74,7 @@
protected void resetState() {
mSecurityMessageDisplay.setMessage(R.string.kg_password_instructions, false);
- mPasswordEntry.setEnabled(true);
+ setPasswordEntryEnabled(true);
}
@Override
@@ -123,6 +127,7 @@
Context.INPUT_METHOD_SERVICE);
mPasswordEntry = (TextView) findViewById(getPasswordTextViewId());
+ mPasswordEntryDisabler = new TextViewInputDisabler(mPasswordEntry);
mPasswordEntry.setKeyListener(TextKeyListener.getInstance());
mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT
| InputType.TYPE_TEXT_VARIATION_PASSWORD);
@@ -185,7 +190,7 @@
@Override
protected void setPasswordEntryEnabled(boolean enabled) {
- mPasswordEntry.setEnabled(enabled);
+ mPasswordEntryDisabler.setInputEnabled(enabled);
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 5d6b2f1..d3e7104 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -51,11 +51,13 @@
private final Collection<BluetoothCallback> mCallbacks =
new ArrayList<BluetoothCallback>();
+ private android.os.Handler mReceiverHandler;
+
interface Handler {
void onReceive(Context context, Intent intent, BluetoothDevice device);
}
- void addHandler(String action, Handler handler) {
+ private void addHandler(String action, Handler handler) {
mHandlerMap.put(action, handler);
mAdapterIntentFilter.addAction(action);
}
@@ -103,11 +105,18 @@
// Dock event broadcasts
addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler());
- mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter);
+ mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler);
}
void registerProfileIntentReceiver() {
- mContext.registerReceiver(mBroadcastReceiver, mProfileIntentFilter);
+ mContext.registerReceiver(mBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler);
+ }
+
+ public void setReceiverHandler(android.os.Handler handler) {
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ mReceiverHandler = handler;
+ mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler);
+ registerProfileIntentReceiver();
}
/** Register to start receiving callbacks for Bluetooth events. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java
index 111484b..a318efc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java
@@ -42,14 +42,21 @@
}
final int w = mDetail.getWidth() - x;
final int h = mDetail.getHeight() - y;
+ int innerR = 0;
+ if (x < 0 || w < 0 || y < 0 || h < 0) {
+ innerR = Math.abs(x);
+ innerR = Math.min(innerR, Math.abs(y));
+ innerR = Math.min(innerR, Math.abs(w));
+ innerR = Math.min(innerR, Math.abs(h));
+ }
int r = (int) Math.ceil(Math.sqrt(x * x + y * y));
r = (int) Math.max(r, Math.ceil(Math.sqrt(w * w + y * y)));
r = (int) Math.max(r, Math.ceil(Math.sqrt(w * w + h * h)));
r = (int) Math.max(r, Math.ceil(Math.sqrt(x * x + h * h)));
if (in) {
- mAnimator = ViewAnimationUtils.createCircularReveal(mDetail, x, y, 0, r);
+ mAnimator = ViewAnimationUtils.createCircularReveal(mDetail, x, y, innerR, r);
} else {
- mAnimator = ViewAnimationUtils.createCircularReveal(mDetail, x, y, r, 0);
+ mAnimator = ViewAnimationUtils.createCircularReveal(mDetail, x, y, r, innerR);
}
mAnimator.setDuration((long)(mAnimator.getDuration() * 1.5));
if (listener != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index b4ae20d..cd4f299 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -215,9 +215,19 @@
mFooter.refreshState();
}
- public void showDetailAdapter(boolean show, DetailAdapter adapter) {
+ public void showDetailAdapter(boolean show, DetailAdapter adapter, int[] locationInWindow) {
+ int xInWindow = locationInWindow[0];
+ int yInWindow = locationInWindow[1];
+ mDetail.getLocationInWindow(locationInWindow);
+
Record r = new Record();
r.detailAdapter = adapter;
+ r.x = xInWindow - locationInWindow[0];
+ r.y = yInWindow - locationInWindow[1];
+
+ locationInWindow[0] = xInWindow;
+ locationInWindow[1] = yInWindow;
+
showDetail(show, r);
}
@@ -337,7 +347,13 @@
if (r instanceof TileRecord) {
handleShowDetailTile((TileRecord) r, show);
} else {
- handleShowDetailImpl(r, show, getWidth() /* x */, 0/* y */);
+ int x = 0;
+ int y = 0;
+ if (r != null) {
+ x = r.x;
+ y = r.y;
+ }
+ handleShowDetailImpl(r, show, x, y);
}
}
@@ -558,6 +574,8 @@
private static class Record {
View detailView;
DetailAdapter detailAdapter;
+ int x;
+ int y;
}
protected static final class TileRecord extends Record {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DelegateViewHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DelegateViewHelper.java
index 9e2207e..2dc521e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DelegateViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DelegateViewHelper.java
@@ -105,6 +105,13 @@
return mPanelShowing;
}
+ public void abortCurrentGesture() {
+ if (mStarted) {
+ mStarted = false;
+ mBar.setInteracting(StatusBarManager.WINDOW_NAVIGATION_BAR, false);
+ }
+ }
+
public void setSourceView(View view) {
mSourceView = view;
if (mSourceView != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
index 82f5a9e..f11d83c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -42,6 +42,8 @@
private boolean mKeyguardMode;
final UserManager mUserManager;
+ private final int[] mTmpInt2 = new int[2];
+
public MultiUserSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
mUserManager = UserManager.get(getContext());
@@ -77,7 +79,15 @@
UserSwitcherController userSwitcherController =
mQsPanel.getHost().getUserSwitcherController();
if (userSwitcherController != null) {
- mQsPanel.showDetailAdapter(true, userSwitcherController.userDetailAdapter);
+ View center = getChildCount() > 0 ? getChildAt(0) : this;
+
+ center.getLocationInWindow(mTmpInt2);
+ mTmpInt2[0] += center.getWidth() / 2;
+ mTmpInt2[1] += center.getHeight() / 2;
+
+ mQsPanel.showDetailAdapter(true,
+ userSwitcherController.userDetailAdapter,
+ mTmpInt2);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 7077a17..1dec227 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -245,6 +245,11 @@
return intercept;
}
+ public void abortCurrentGesture() {
+ mDelegateHelper.abortCurrentGesture();
+ getHomeButton().abortCurrentGesture();
+ }
+
private H mHandler = new H();
public View getCurrentView() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 9ef9211..c10be7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -117,6 +117,7 @@
* intercepted yet.
*/
private boolean mIntercepting;
+ private boolean mPanelExpanded;
private boolean mQsExpanded;
private boolean mQsExpandedWhenExpandingStarted;
private boolean mQsFullyExpanded;
@@ -1496,13 +1497,22 @@
updateHeader();
updateUnlockIcon();
updateNotificationTranslucency();
- mHeadsUpManager.setIsExpanded(!isFullyCollapsed());
+ updatePanelExpanded();
mNotificationStackScroller.setShadeExpanded(!isFullyCollapsed());
if (DEBUG) {
invalidate();
}
}
+ private void updatePanelExpanded() {
+ boolean isExpanded = !isFullyCollapsed();
+ if (mPanelExpanded != isExpanded) {
+ mHeadsUpManager.setIsExpanded(isExpanded);
+ mStatusBar.setPanelExpanded(isExpanded);
+ mPanelExpanded = isExpanded;
+ }
+ }
+
/**
* @return a temporary override of {@link #mQsMaxExpansionHeight}, which is needed when
* collapsing QS / the panel when QS was scrolled
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 2a9df19..9fe591e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -1013,7 +1013,7 @@
invokeAssistGesture(true /* vibrate */);
awakenDreams();
if (mNavigationBarView != null) {
- mNavigationBarView.getHomeButton().abortCurrentGesture();
+ mNavigationBarView.abortCurrentGesture();
}
}
};
@@ -1967,6 +1967,10 @@
return !mUnlockMethodCache.isCurrentlyInsecure();
}
+ public void setPanelExpanded(boolean isExpanded) {
+ mStatusBarWindowManager.setPanelExpanded(isExpanded);
+ }
+
/**
* All changes to the status bar and notifications funnel through here and are batched.
*/
@@ -2027,7 +2031,7 @@
// Expand the window to encompass the full screen in anticipation of the drag.
// This is only possible to do atomically because the status bar is at the top of the screen!
- mStatusBarWindowManager.setStatusBarExpanded(true);
+ mStatusBarWindowManager.setPanelVisible(true);
mStatusBarView.setFocusable(false);
visibilityChanged(true);
@@ -2156,7 +2160,7 @@
visibilityChanged(false);
// Shrink the window to the size of the status bar only
- mStatusBarWindowManager.setStatusBarExpanded(false);
+ mStatusBarWindowManager.setPanelVisible(false);
mStatusBarWindowManager.setForceStatusBarVisible(false);
mStatusBarView.setFocusable(true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index 422d868..4f1c652 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -114,12 +114,12 @@
}
private void applyFocusableFlag(State state) {
+ boolean panelFocusable = state.statusBarFocusable && state.panelExpanded;
if (state.isKeyguardShowingAndNotOccluded() && state.keyguardNeedsInput
- && state.bouncerShowing
- || BaseStatusBar.ENABLE_REMOTE_INPUT && state.statusBarExpanded) {
+ && state.bouncerShowing || BaseStatusBar.ENABLE_REMOTE_INPUT && panelFocusable) {
mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
- } else if (state.isKeyguardShowingAndNotOccluded() || state.statusBarFocusable) {
+ } else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) {
mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
mLpChanged.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
} else {
@@ -130,7 +130,7 @@
private void applyHeight(State state) {
boolean expanded = !state.forceCollapsed && (state.isKeyguardShowingAndNotOccluded()
- || state.statusBarExpanded || state.keyguardFadingAway || state.bouncerShowing
+ || state.panelVisible || state.keyguardFadingAway || state.bouncerShowing
|| state.headsUpShowing);
if (expanded) {
mLpChanged.height = ViewGroup.LayoutParams.MATCH_PARENT;
@@ -213,9 +213,9 @@
apply(mCurrentState);
}
- public void setStatusBarExpanded(boolean expanded) {
- mCurrentState.statusBarExpanded = expanded;
- mCurrentState.statusBarFocusable = expanded;
+ public void setPanelVisible(boolean visible) {
+ mCurrentState.panelVisible = visible;
+ mCurrentState.statusBarFocusable = visible;
apply(mCurrentState);
}
@@ -267,11 +267,17 @@
apply(mCurrentState);
}
+ public void setPanelExpanded(boolean isExpanded) {
+ mCurrentState.panelExpanded = isExpanded;
+ apply(mCurrentState);
+ }
+
private static class State {
boolean keyguardShowing;
boolean keyguardOccluded;
boolean keyguardNeedsInput;
- boolean statusBarExpanded;
+ boolean panelVisible;
+ boolean panelExpanded;
boolean statusBarFocusable;
boolean bouncerShowing;
boolean keyguardFadingAway;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
index 5893cb2..0eb7197 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
@@ -45,7 +45,7 @@
private static final String EXTRA_START_CONNECT_SSID = "wifi_start_connect_ssid";
private static final int[] ICONS = {
- R.drawable.ic_qs_wifi_0,
+ R.drawable.ic_qs_wifi_full_0,
R.drawable.ic_qs_wifi_full_1,
R.drawable.ic_qs_wifi_full_2,
R.drawable.ic_qs_wifi_full_3,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 8d4f302..114427c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -18,7 +18,9 @@
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
+import android.os.Handler;
import android.os.Looper;
+import android.os.Message;
import android.util.Log;
import com.android.settingslib.bluetooth.BluetoothCallback;
@@ -42,9 +44,12 @@
private boolean mConnecting;
private CachedBluetoothDevice mLastDevice;
+ private final H mHandler = new H();
+
public BluetoothControllerImpl(Context context, Looper bgLooper) {
mLocalBluetoothManager = LocalBluetoothManager.getInstance(context, null);
if (mLocalBluetoothManager != null) {
+ mLocalBluetoothManager.getEventManager().setReceiverHandler(new Handler(bgLooper));
mLocalBluetoothManager.getEventManager().registerCallback(this);
onBluetoothStateChanged(
mLocalBluetoothManager.getBluetoothAdapter().getBluetoothState());
@@ -71,7 +76,7 @@
public void addStateChangedCallback(Callback cb) {
mCallbacks.add(cb);
- fireStateChange(cb);
+ mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
}
@Override
@@ -132,22 +137,6 @@
: null;
}
- private void firePairedDevicesChanged() {
- for (Callback cb : mCallbacks) {
- cb.onBluetoothDevicesChanged();
- }
- }
-
- private void fireStateChange() {
- for (Callback cb : mCallbacks) {
- fireStateChange(cb);
- }
- }
-
- private void fireStateChange(Callback cb) {
- cb.onBluetoothStateChange(mEnabled, mConnecting);
- }
-
private void updateConnected() {
if (mLastDevice != null && mLastDevice.isConnected()) {
// Our current device is still valid.
@@ -163,7 +152,7 @@
@Override
public void onBluetoothStateChanged(int bluetoothState) {
mEnabled = bluetoothState == BluetoothAdapter.STATE_ON;
- fireStateChange();
+ mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
}
@Override
@@ -175,25 +164,25 @@
public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
cachedDevice.registerCallback(this);
updateConnected();
- firePairedDevicesChanged();
+ mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
}
@Override
public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
updateConnected();
- firePairedDevicesChanged();
+ mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
}
@Override
public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
updateConnected();
- firePairedDevicesChanged();
+ mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
}
@Override
public void onDeviceAttributesChanged() {
updateConnected();
- firePairedDevicesChanged();
+ mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
}
@Override
@@ -201,6 +190,39 @@
mConnecting = state == BluetoothAdapter.STATE_CONNECTING;
mLastDevice = cachedDevice;
updateConnected();
- fireStateChange();
+ mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
+ }
+
+ private final class H extends Handler {
+ private static final int MSG_PAIRED_DEVICES_CHANGED = 1;
+ private static final int MSG_STATE_CHANGED = 2;
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_PAIRED_DEVICES_CHANGED:
+ firePairedDevicesChanged();
+ break;
+ case MSG_STATE_CHANGED:
+ fireStateChange();
+ break;
+ }
+ }
+
+ private void firePairedDevicesChanged() {
+ for (BluetoothController.Callback cb : mCallbacks) {
+ cb.onBluetoothDevicesChanged();
+ }
+ }
+
+ private void fireStateChange() {
+ for (BluetoothController.Callback cb : mCallbacks) {
+ fireStateChange(cb);
+ }
+ }
+
+ private void fireStateChange(BluetoothController.Callback cb) {
+ cb.onBluetoothStateChange(mEnabled, mConnecting);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
index 23813d1..ad21555 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
@@ -148,6 +148,11 @@
android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null);
// Kick current state into place
+ final List<DiskInfo> disks = mStorageManager.getDisks();
+ for (DiskInfo disk : disks) {
+ onDiskScannedInternal(disk, disk.volumeCount);
+ }
+
final List<VolumeInfo> vols = mStorageManager.getVolumes();
for (VolumeInfo vol : vols) {
onVolumeStateChangedInternal(vol);
@@ -194,7 +199,7 @@
}
private void onDiskScannedInternal(DiskInfo disk, int volumeCount) {
- if (volumeCount == 0) {
+ if (volumeCount == 0 && disk.size > 0) {
// No supported volumes found, give user option to format
final CharSequence title = mContext.getString(
R.string.ext_media_unmountable_notification_title, disk.getDescription());
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
index 6e0ca3c..1c6462e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
@@ -724,7 +724,7 @@
}
row.slider.setProgress(newProgress);
}
- if (mAutomute) {
+ if (mAutomute && mShowing) {
if (vlevel == 0 && !row.ss.muted && row.stream == AudioManager.STREAM_MUSIC) {
mController.setStreamMute(row.stream, true);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
index 3a8081f..c6d9e46 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
@@ -180,7 +180,7 @@
pw.print(" mEnabled: "); pw.println(mEnabled);
pw.print(" mDestroyed: "); pw.println(mDestroyed);
pw.print(" mVolumePolicy: "); pw.println(mVolumePolicy);
- pw.print(" mEnabled: "); pw.println(mEnabled);
+ pw.print(" mState: "); pw.println(mState.toString(4));
pw.print(" mShowDndTile: "); pw.println(mShowDndTile);
pw.print(" mHasVibrator: "); pw.println(mHasVibrator);
pw.print(" mRemoteStreams: "); pw.println(mMediaSessionsCallbacksW.mRemoteStreams
@@ -960,25 +960,45 @@
@Override
public String toString() {
+ return toString(0);
+ }
+
+ public String toString(int indent) {
final StringBuilder sb = new StringBuilder("{");
+ if (indent > 0) sep(sb, indent);
for (int i = 0; i < states.size(); i++) {
- if (i > 0) sb.append(',');
+ if (i > 0) {
+ sep(sb, indent);
+ }
final int stream = states.keyAt(i);
final StreamState ss = states.valueAt(i);
sb.append(AudioSystem.streamToString(stream)).append(":").append(ss.level)
- .append("[").append(ss.levelMin).append("..").append(ss.levelMax);
+ .append('[').append(ss.levelMin).append("..").append(ss.levelMax)
+ .append(']');
if (ss.muted) sb.append(" [MUTED]");
}
- sb.append(",ringerModeExternal:").append(ringerModeExternal);
- sb.append(",ringerModeInternal:").append(ringerModeInternal);
- sb.append(",zenMode:").append(zenMode);
- sb.append(",effectsSuppressor:").append(effectsSuppressor);
- sb.append(",effectsSuppressorName:").append(effectsSuppressorName);
- sb.append(",zenModeConfig:").append(zenModeConfig);
- sb.append(",activeStream:").append(activeStream);
+ sep(sb, indent); sb.append("ringerModeExternal:").append(ringerModeExternal);
+ sep(sb, indent); sb.append("ringerModeInternal:").append(ringerModeInternal);
+ sep(sb, indent); sb.append("zenMode:").append(zenMode);
+ sep(sb, indent); sb.append("effectsSuppressor:").append(effectsSuppressor);
+ sep(sb, indent); sb.append("effectsSuppressorName:").append(effectsSuppressorName);
+ sep(sb, indent); sb.append("zenModeConfig:").append(zenModeConfig);
+ sep(sb, indent); sb.append("activeStream:").append(activeStream);
+ if (indent > 0) sep(sb, indent);
return sb.append('}').toString();
}
+ private static void sep(StringBuilder sb, int indent) {
+ if (indent > 0) {
+ sb.append('\n');
+ for (int i = 0; i < indent; i++) {
+ sb.append(' ');
+ }
+ } else {
+ sb.append(',');
+ }
+ }
+
public Condition getManualExitCondition() {
return zenModeConfig != null && zenModeConfig.manualRule != null
? zenModeConfig.manualRule.condition : null;
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 6c1023c..01cc2ca 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -1725,28 +1725,68 @@
// At package-changed we only care about looking at new transport states
if (changed) {
try {
+ String[] components =
+ intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
+
if (MORE_DEBUG) {
Slog.i(TAG, "Package " + pkgName + " changed; rechecking");
+ for (int i = 0; i < components.length; i++) {
+ Slog.i(TAG, " * " + components[i]);
+ }
}
- // unbind existing possibly-stale connections to that package's transports
+
+ // In general we need to try to bind any time we see a component enable
+ // state change, because that change may have made a transport available.
+ // However, because we currently only support a single transport component
+ // per package, we can skip the bind attempt if the change (a) affects a
+ // package known to host a transport, but (b) does not affect the known
+ // transport component itself.
+ //
+ // In addition, if the change *is* to a known transport component, we need
+ // to unbind it before retrying the binding.
+ boolean tryBind = true;
synchronized (mTransports) {
TransportConnection conn = mTransportConnections.get(pkgName);
if (conn != null) {
+ // We have a bound transport in this package; do we need to rebind it?
final ServiceInfo svc = conn.mTransport;
ComponentName svcName =
new ComponentName(svc.packageName, svc.name);
- String flatName = svcName.flattenToShortString();
- Slog.i(TAG, "Unbinding " + svcName);
-
- mContext.unbindService(conn);
- mTransportConnections.remove(pkgName);
- mTransports.remove(mTransportNames.get(flatName));
- mTransportNames.remove(flatName);
+ if (svc.packageName.equals(pkgName)) {
+ final String className = svcName.getClassName();
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Checking need to rebind " + className);
+ }
+ // See whether it's the transport component within this package
+ boolean isTransport = false;
+ for (int i = 0; i < components.length; i++) {
+ if (className.equals(components[i])) {
+ // Okay, it's an existing transport component.
+ final String flatName = svcName.flattenToShortString();
+ mContext.unbindService(conn);
+ mTransportConnections.remove(pkgName);
+ mTransports.remove(mTransportNames.get(flatName));
+ mTransportNames.remove(flatName);
+ isTransport = true;
+ break;
+ }
+ }
+ if (!isTransport) {
+ // A non-transport component within a package that is hosting
+ // a bound transport
+ tryBind = false;
+ }
+ }
}
}
- // and then (re)bind as appropriate
- PackageInfo app = mPackageManager.getPackageInfo(pkgName, 0);
- checkForTransportAndBind(app);
+ // and now (re)bind as appropriate
+ if (tryBind) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Yes, need to recheck binding");
+ }
+ PackageInfo app = mPackageManager.getPackageInfo(pkgName, 0);
+ checkForTransportAndBind(app);
+ }
} catch (NameNotFoundException e) {
// Nope, can't find it - just ignore
if (MORE_DEBUG) {
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index 7d427d6..79c66b9 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -408,11 +408,19 @@
}
}
if (repCbs != null) {
- for (int i=0; i<repCbs.size(); i++) {
- try {
- repCbs.get(i).mCallback.opChanged(code, packageName);
- } catch (RemoteException e) {
+ // There are components watching for mode changes such as window manager
+ // and location manager which are in our process. The callbacks in these
+ // components may require permissions our remote caller does not have.
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ for (int i = 0; i < repCbs.size(); i++) {
+ try {
+ repCbs.get(i).mCallback.opChanged(code, packageName);
+ } catch (RemoteException e) {
+ }
}
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
}
}
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index c46fa76..66fd36f 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -205,6 +205,7 @@
// Clear registered LE apps to force shut-off
synchronized (this) {
mBleAppCount = 0;
+ mBleApps.clear();
}
if (st == BluetoothAdapter.STATE_BLE_ON) {
//if state is BLE_ON make sure you trigger disableBLE part
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 1919c37..7ac2655 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -2688,7 +2688,7 @@
actionToken);
}
- public ProxyInfo getDefaultProxy() {
+ private ProxyInfo getDefaultProxy() {
// this information is already available as a world read/writable jvm property
// so this API change wouldn't have a benifit. It also breaks the passing
// of proxy info to all the JVMs.
@@ -2700,6 +2700,22 @@
}
}
+ public ProxyInfo getProxyForNetwork(Network network) {
+ if (network == null) return getDefaultProxy();
+ final ProxyInfo globalProxy = getGlobalProxy();
+ if (globalProxy != null) return globalProxy;
+ if (!NetworkUtils.queryUserAccess(Binder.getCallingUid(), network.netId)) return null;
+ // Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which
+ // caller may not have.
+ final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+ if (nai == null) return null;
+ synchronized (nai) {
+ final ProxyInfo proxyInfo = nai.linkProperties.getHttpProxy();
+ if (proxyInfo == null) return null;
+ return new ProxyInfo(proxyInfo);
+ }
+ }
+
// Convert empty ProxyInfo's to null as null-checks are used to determine if proxies are present
// (e.g. if mGlobalProxy==null fall back to network-specific proxy, if network-specific
// proxy is null then there is no proxy in place).
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index e00cf5b..d48953d 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -973,6 +973,7 @@
}
}
+ disk.volumeCount = volumeCount;
mCallbacks.notifyDiskScanned(disk, volumeCount);
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 62f168d..d214a20 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -2355,9 +2355,9 @@
callingUid = packageUid;
}
checkReadAccountsPermission();
- UserAccounts accounts = getUserAccounts(userId);
long identityToken = clearCallingIdentity();
try {
+ UserAccounts accounts = getUserAccounts(userId);
synchronized (accounts.cacheLock) {
return getAccountsFromCacheLocked(accounts, type, callingUid, callingPackage);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
index 925fae0a..ec2bd67 100644
--- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -71,6 +71,7 @@
static final boolean DEBUG_TASKS = DEBUG_ALL || false;
static final boolean DEBUG_THUMBNAILS = DEBUG_ALL || false;
static final boolean DEBUG_TRANSITION = DEBUG_ALL || false;
+ static final boolean DEBUG_UID_OBSERVERS = DEBUG_ALL || false;
static final boolean DEBUG_URI_PERMISSION = DEBUG_ALL || false;
static final boolean DEBUG_USER_LEAVING = DEBUG_ALL || false;
static final boolean DEBUG_VISIBILITY = DEBUG_ALL || false;
@@ -104,6 +105,8 @@
static final String POSTFIX_TASKS = (APPEND_CATEGORY_NAME) ? "_Tasks" : "";
static final String POSTFIX_THUMBNAILS = (APPEND_CATEGORY_NAME) ? "_Thumbnails" : "";
static final String POSTFIX_TRANSITION = (APPEND_CATEGORY_NAME) ? "_Transition" : "";
+ static final String POSTFIX_UID_OBSERVERS = (APPEND_CATEGORY_NAME)
+ ? "_UidObservers" : "";
static final String POSTFIX_URI_PERMISSION = (APPEND_CATEGORY_NAME) ? "_UriPermission" : "";
static final String POSTFIX_USER_LEAVING = (APPEND_CATEGORY_NAME) ? "_UserLeaving" : "";
static final String POSTFIX_VISIBILITY = (APPEND_CATEGORY_NAME) ? "_Visibility" : "";
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2d74618..6842304 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -129,6 +129,7 @@
import android.app.IProcessObserver;
import android.app.IServiceConnection;
import android.app.IStopUserCallback;
+import android.app.IUidObserver;
import android.app.IUiAutomationConnection;
import android.app.IUserSwitchObserver;
import android.app.Instrumentation;
@@ -253,7 +254,6 @@
public final class ActivityManagerService extends ActivityManagerNative
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
- private static final String USER_DATA_DIR = "/data/user/";
// File that stores last updated system version and called preboot receivers
static final String CALLED_PRE_BOOTS_FILENAME = "called_pre_boots.dat";
@@ -278,6 +278,7 @@
private static final String TAG_SERVICE = TAG + POSTFIX_SERVICE;
private static final String TAG_STACK = TAG + POSTFIX_STACK;
private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
+ private static final String TAG_UID_OBSERVERS = TAG + POSTFIX_UID_OBSERVERS;
private static final String TAG_URI_PERMISSION = TAG + POSTFIX_URI_PERMISSION;
private static final String TAG_VISIBILITY = TAG + POSTFIX_VISIBILITY;
@@ -655,9 +656,14 @@
long mPreviousProcessVisibleTime;
/**
+ * Track all uids that have actively running processes.
+ */
+ final SparseArray<UidRecord> mActiveUids = new SparseArray<>();
+
+ /**
* Which uses have been started, so are allowed to run code.
*/
- final SparseArray<UserStartedState> mStartedUsers = new SparseArray<UserStartedState>();
+ final SparseArray<UserStartedState> mStartedUsers = new SparseArray<>();
/**
* LRU list of history of current users. Most recently current is at the end.
@@ -1023,6 +1029,11 @@
private IVoiceInteractionSession mRunningVoice;
/**
+ * For some direct access we need to power manager.
+ */
+ PowerManagerInternal mLocalPowerManager;
+
+ /**
* We want to hold a wake lock while running a voice interaction session, since
* this may happen with the screen off and we need to keep the CPU running to
* be able to continue to interact with the user.
@@ -1174,7 +1185,7 @@
final long[] mTmpLong = new long[1];
- static class ProcessChangeItem {
+ static final class ProcessChangeItem {
static final int CHANGE_ACTIVITIES = 1<<0;
static final int CHANGE_PROCESS_STATE = 1<<1;
int changes;
@@ -1184,14 +1195,17 @@
boolean foregroundActivities;
}
- final RemoteCallbackList<IProcessObserver> mProcessObservers
- = new RemoteCallbackList<IProcessObserver>();
+ final RemoteCallbackList<IProcessObserver> mProcessObservers = new RemoteCallbackList<>();
ProcessChangeItem[] mActiveProcessChanges = new ProcessChangeItem[5];
- final ArrayList<ProcessChangeItem> mPendingProcessChanges
- = new ArrayList<ProcessChangeItem>();
- final ArrayList<ProcessChangeItem> mAvailProcessChanges
- = new ArrayList<ProcessChangeItem>();
+ final ArrayList<ProcessChangeItem> mPendingProcessChanges = new ArrayList<>();
+ final ArrayList<ProcessChangeItem> mAvailProcessChanges = new ArrayList<>();
+
+ final RemoteCallbackList<IUidObserver> mUidObservers = new RemoteCallbackList<>();
+ UidRecord.ChangeItem[] mActiveUidChanges = new UidRecord.ChangeItem[5];
+
+ final ArrayList<UidRecord.ChangeItem> mPendingUidChanges = new ArrayList<>();
+ final ArrayList<UidRecord.ChangeItem> mAvailUidChanges = new ArrayList<>();
/**
* Runtime CPU use collection thread. This object's lock is used to
@@ -1316,6 +1330,7 @@
static final int POST_DUMP_HEAP_NOTIFICATION_MSG = 51;
static final int DELETE_DUMPHEAP_MSG = 52;
static final int FOREGROUND_PROFILE_CHANGED_MSG = 53;
+ static final int DISPATCH_UIDS_CHANGED = 54;
static final int FIRST_ACTIVITY_STACK_MSG = 100;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1538,6 +1553,19 @@
d.dismiss();
break;
}
+ case DISPATCH_PROCESSES_CHANGED: {
+ dispatchProcessesChanged();
+ break;
+ }
+ case DISPATCH_PROCESS_DIED: {
+ final int pid = msg.arg1;
+ final int uid = msg.arg2;
+ dispatchProcessDied(pid, uid);
+ break;
+ }
+ case DISPATCH_UIDS_CHANGED: {
+ dispatchUidsChanged();
+ } break;
}
}
}
@@ -1723,16 +1751,6 @@
sendMessageDelayed(nmsg, POWER_CHECK_DELAY);
}
} break;
- case DISPATCH_PROCESSES_CHANGED: {
- dispatchProcessesChanged();
- break;
- }
- case DISPATCH_PROCESS_DIED: {
- final int pid = msg.arg1;
- final int uid = msg.arg2;
- dispatchProcessDied(pid, uid);
- break;
- }
case REPORT_MEM_USAGE_MSG: {
final ArrayList<ProcessMemInfo> memInfos = (ArrayList<ProcessMemInfo>)msg.obj;
Thread thread = new Thread() {
@@ -2091,7 +2109,6 @@
app.pid = MY_PID;
app.maxAdj = ProcessList.SYSTEM_ADJ;
app.makeActive(mSystemThread.getApplicationThread(), mProcessStats);
- mProcessNames.put(app.processName, app.uid, app);
synchronized (mPidsSelfLocked) {
mPidsSelfLocked.put(app.pid, app);
}
@@ -2343,6 +2360,7 @@
public void initPowerManagement() {
mStackSupervisor.initPowerManagement();
mBatteryStatsService.initPowerManagement();
+ mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class);
PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
mVoiceWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*voice*");
mVoiceWakeLock.setReferenceCounted(false);
@@ -3073,10 +3091,6 @@
return null;
}
app.crashHandler = crashHandler;
- mProcessNames.put(processName, app.uid, app);
- if (isolated) {
- mIsolatedProcesses.put(app.uid, app);
- }
checkTime(startTime, "startProcess: done creating new process record");
} else {
// If this is a new package in the process, add the package to the list
@@ -3562,7 +3576,6 @@
mActiveProcessChanges = new ProcessChangeItem[N];
}
mPendingProcessChanges.toArray(mActiveProcessChanges);
- mAvailProcessChanges.addAll(mPendingProcessChanges);
mPendingProcessChanges.clear();
if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
"*** Delivering " + N + " process changes");
@@ -3595,6 +3608,12 @@
}
}
mProcessObservers.finishBroadcast();
+
+ synchronized (this) {
+ for (int j=0; j<N; j++) {
+ mAvailProcessChanges.add(mActiveProcessChanges[j]);
+ }
+ }
}
private void dispatchProcessDied(int pid, int uid) {
@@ -3612,6 +3631,67 @@
mProcessObservers.finishBroadcast();
}
+ private void dispatchUidsChanged() {
+ int N;
+ synchronized (this) {
+ N = mPendingUidChanges.size();
+ if (mActiveUidChanges.length < N) {
+ mActiveUidChanges = new UidRecord.ChangeItem[N];
+ }
+ for (int i=0; i<N; i++) {
+ final UidRecord.ChangeItem change = mPendingUidChanges.get(i);
+ mActiveUidChanges[i] = change;
+ change.uidRecord.pendingChange = null;
+ change.uidRecord = null;
+ }
+ mPendingUidChanges.clear();
+ if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+ "*** Delivering " + N + " uid changes");
+ }
+
+ if (mLocalPowerManager != null) {
+ for (int j=0; j<N; j++) {
+ UidRecord.ChangeItem item = mActiveUidChanges[j];
+ if (item.gone) {
+ mLocalPowerManager.uidGone(item.uid);
+ } else {
+ mLocalPowerManager.updateUidProcState(item.uid, item.processState);
+ }
+ }
+ }
+
+ int i = mUidObservers.beginBroadcast();
+ while (i > 0) {
+ i--;
+ final IUidObserver observer = mUidObservers.getBroadcastItem(i);
+ if (observer != null) {
+ try {
+ for (int j=0; j<N; j++) {
+ UidRecord.ChangeItem item = mActiveUidChanges[j];
+ if (item.gone) {
+ if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+ "UID gone uid=" + item.uid);
+ observer.onUidGone(item.uid);
+ } else {
+ if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+ "UID CHANGED uid=" + item.uid
+ + ": " + item.processState);
+ observer.onUidStateChanged(item.uid, item.processState);
+ }
+ }
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ mUidObservers.finishBroadcast();
+
+ synchronized (this) {
+ for (int j=0; j<N; j++) {
+ mAvailUidChanges.add(mActiveUidChanges[j]);
+ }
+ }
+ }
+
@Override
public final int startActivity(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
@@ -5623,6 +5703,47 @@
return didSomething;
}
+ private final ProcessRecord removeProcessNameLocked(final String name, final int uid) {
+ ProcessRecord old = mProcessNames.remove(name, uid);
+ if (old != null) {
+ old.uidRecord.numProcs--;
+ if (old.uidRecord.numProcs == 0) {
+ // No more processes using this uid, tell clients it is gone.
+ if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+ "No more processes in " + old.uidRecord);
+ enqueueUidChangeLocked(old.uidRecord, true);
+ mActiveUids.remove(uid);
+ }
+ old.uidRecord = null;
+ }
+ mIsolatedProcesses.remove(uid);
+ return old;
+ }
+
+ private final void addProcessNameLocked(ProcessRecord proc) {
+ // We shouldn't already have a process under this name, but just in case we
+ // need to clean up whatever may be there now.
+ ProcessRecord old = removeProcessNameLocked(proc.processName, proc.uid);
+ if (old != null) {
+ Slog.wtf(TAG, "Already have existing proc " + old + " when adding " + proc);
+ }
+ UidRecord uidRec = mActiveUids.get(proc.uid);
+ if (uidRec == null) {
+ uidRec = new UidRecord(proc.uid);
+ // This is the first appearance of the uid, report it now!
+ if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+ "Creating new process uid: " + uidRec);
+ mActiveUids.put(proc.uid, uidRec);
+ enqueueUidChangeLocked(uidRec, false);
+ }
+ proc.uidRecord = uidRec;
+ uidRec.numProcs++;
+ mProcessNames.put(proc.processName, proc.uid, proc);
+ if (proc.isolated) {
+ mIsolatedProcesses.put(proc.uid, proc);
+ }
+ }
+
private final boolean removeProcessLocked(ProcessRecord app,
boolean callerWillRestart, boolean allowRestart, String reason) {
final String name = app.processName;
@@ -5630,8 +5751,7 @@
if (DEBUG_PROCESSES) Slog.d(TAG_PROCESSES,
"Force removing proc " + app.toShortString() + " (" + name + "/" + uid + ")");
- mProcessNames.remove(name, uid);
- mIsolatedProcesses.remove(app.uid);
+ removeProcessNameLocked(name, uid);
if (mHeavyWeightProcess == app) {
mHandler.sendMessage(mHandler.obtainMessage(CANCEL_HEAVY_NOTIFICATION_MSG,
mHeavyWeightProcess.userId, 0));
@@ -5684,8 +5804,7 @@
Slog.w(TAG, "Process " + app + " failed to attach");
EventLog.writeEvent(EventLogTags.AM_PROCESS_START_TIMEOUT, app.userId,
pid, app.uid, app.processName);
- mProcessNames.remove(app.processName, app.uid);
- mIsolatedProcesses.remove(app.uid);
+ removeProcessNameLocked(app.processName, app.uid);
if (mHeavyWeightProcess == app) {
mHandler.sendMessage(mHandler.obtainMessage(CANCEL_HEAVY_NOTIFICATION_MSG,
mHeavyWeightProcess.userId, 0));
@@ -9874,7 +9993,6 @@
final ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess,
boolean isolated, int isolatedUid) {
String proc = customProcess != null ? customProcess : info.processName;
- BatteryStatsImpl.Uid.Proc ps = null;
BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
final int userId = UserHandle.getUserId(info.uid);
int uid = info.uid;
@@ -9909,6 +10027,7 @@
&& (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) {
r.persistent = true;
}
+ addProcessNameLocked(r);
return r;
}
@@ -9923,10 +10042,6 @@
if (app == null) {
app = newProcessRecordLocked(info, null, isolated, 0);
- mProcessNames.put(info.processName, app.uid, app);
- if (isolated) {
- mIsolatedProcesses.put(app.uid, app);
- }
updateLruProcessLocked(app, false, null);
updateOomAdjLocked();
}
@@ -10602,6 +10717,21 @@
}
}
+ public void registerUidObserver(IUidObserver observer) {
+ enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
+ "registerUidObserver()");
+ synchronized (this) {
+ mUidObservers.register(observer);
+ }
+ }
+
+ @Override
+ public void unregisterUidObserver(IUidObserver observer) {
+ synchronized (this) {
+ mUidObservers.unregister(observer);
+ }
+ }
+
@Override
public boolean convertFromTranslucent(IBinder token) {
final long origId = Binder.clearCallingIdentity();
@@ -12920,6 +13050,20 @@
}
}
+ if (mActiveUids.size() > 0) {
+ if (needSep) {
+ pw.println();
+ }
+ pw.println(" UID states:");
+ for (int i=0; i<mActiveUids.size(); i++) {
+ UidRecord uidRec = mActiveUids.valueAt(i);
+ pw.print(" UID "); UserHandle.formatUid(pw, uidRec.uid);
+ pw.print(": "); pw.println(uidRec);
+ }
+ needSep = true;
+ printedAnything = true;
+ }
+
if (mLruProcesses.size() > 0) {
if (needSep) {
pw.println();
@@ -15162,7 +15306,7 @@
mAvailProcessChanges.add(item);
}
}
- mHandler.obtainMessage(DISPATCH_PROCESS_DIED, app.pid, app.info.uid, null).sendToTarget();
+ mUiHandler.obtainMessage(DISPATCH_PROCESS_DIED, app.pid, app.info.uid, null).sendToTarget();
// If the caller is restarting this app, then leave it in its
// current lists and let the caller take care of it.
@@ -15173,8 +15317,7 @@
if (!app.persistent || app.isolated) {
if (DEBUG_PROCESSES || DEBUG_CLEANUP) Slog.v(TAG_CLEANUP,
"Removing non-persistent process during cleanup: " + app);
- mProcessNames.remove(app.processName, app.uid);
- mIsolatedProcesses.remove(app.uid);
+ removeProcessNameLocked(app.processName, app.uid);
if (mHeavyWeightProcess == app) {
mHandler.sendMessage(mHandler.obtainMessage(CANCEL_HEAVY_NOTIFICATION_MSG,
mHeavyWeightProcess.userId, 0));
@@ -15206,7 +15349,7 @@
if (index < 0) {
ProcessList.remove(app.pid);
}
- mProcessNames.put(app.processName, app.uid, app);
+ addProcessNameLocked(app);
startProcessLocked(app, "restart", app.processName);
return true;
} else if (app.pid > 0 && app.pid != MY_PID) {
@@ -18289,7 +18432,7 @@
if (NA > 0) {
item = mAvailProcessChanges.remove(NA-1);
if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
- "Retreiving available item: " + item);
+ "Retrieving available item: " + item);
} else {
item = new ProcessChangeItem();
if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
@@ -18301,7 +18444,7 @@
if (mPendingProcessChanges.size() == 0) {
if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
"*** Enqueueing dispatch processes changed!");
- mHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED).sendToTarget();
+ mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED).sendToTarget();
}
mPendingProcessChanges.add(item);
}
@@ -18320,6 +18463,31 @@
return success;
}
+ private final void enqueueUidChangeLocked(UidRecord uidRec, boolean gone) {
+ if (uidRec.pendingChange == null) {
+ if (mPendingUidChanges.size() == 0) {
+ if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+ "*** Enqueueing dispatch uid changed!");
+ mUiHandler.obtainMessage(DISPATCH_UIDS_CHANGED).sendToTarget();
+ }
+ final int NA = mAvailUidChanges.size();
+ if (NA > 0) {
+ uidRec.pendingChange = mAvailUidChanges.remove(NA-1);
+ if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+ "Retrieving available item: " + uidRec.pendingChange);
+ } else {
+ uidRec.pendingChange = new UidRecord.ChangeItem();
+ if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+ "Allocating new item: " + uidRec.pendingChange);
+ }
+ uidRec.pendingChange.uidRecord = uidRec;
+ uidRec.pendingChange.uid = uidRec.uid;
+ mPendingUidChanges.add(uidRec.pendingChange);
+ }
+ uidRec.pendingChange.gone = gone;
+ uidRec.pendingChange.processState = uidRec.setProcState;
+ }
+
private void maybeUpdateUsageStats(ProcessRecord app) {
if (DEBUG_USAGE_STATS) {
Slog.d(TAG, "Checking proc [" + Arrays.toString(app.getPackageList())
@@ -18463,6 +18631,14 @@
Slog.i(TAG, "updateOomAdj: top=" + TOP_ACT, e);
}
+ // Reset state in all uid records.
+ for (int i=mActiveUids.size()-1; i>=0; i--) {
+ final UidRecord uidRec = mActiveUids.valueAt(i);
+ if (false && DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+ "Starting update of " + uidRec);
+ uidRec.reset();
+ }
+
mAdjSeq++;
mNewNumServiceProcs = 0;
mNewNumAServiceProcs = 0;
@@ -18610,6 +18786,12 @@
// good to avoid having whatever code was running in them
// left sitting around after no longer needed.
app.kill("isolated not needed", true);
+ } else {
+ // Keeping this process, update its uid.
+ final UidRecord uidRec = app.uidRecord;
+ if (uidRec != null && uidRec.curProcState > app.curProcState) {
+ uidRec.curProcState = app.curProcState;
+ }
}
if (app.curProcState >= ActivityManager.PROCESS_STATE_HOME
@@ -18805,6 +18987,18 @@
requestPssAllProcsLocked(now, false, mProcessStats.isMemFactorLowered());
}
+ // Update from any uid changes.
+ for (int i=mActiveUids.size()-1; i>=0; i--) {
+ final UidRecord uidRec = mActiveUids.valueAt(i);
+ if (uidRec.setProcState != uidRec.curProcState) {
+ if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+ "Changes in " + uidRec + ": proc state from " + uidRec.setProcState
+ + " to " + uidRec.curProcState);
+ uidRec.setProcState = uidRec.curProcState;
+ enqueueUidChangeLocked(uidRec, false);
+ }
+ }
+
if (mProcessStats.shouldWriteNowLocked(now)) {
mHandler.post(new Runnable() {
@Override public void run() {
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 29e14f8..14759c3 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -62,8 +62,8 @@
final int userId; // user of process.
final String processName; // name of the process
// List of packages running in the process
- final ArrayMap<String, ProcessStats.ProcessStateHolder> pkgList
- = new ArrayMap<String, ProcessStats.ProcessStateHolder>();
+ final ArrayMap<String, ProcessStats.ProcessStateHolder> pkgList = new ArrayMap<>();
+ UidRecord uidRecord; // overall state of process's uid.
ArraySet<String> pkgDeps; // additional packages we have a dependency on
IApplicationThread thread; // the actual proc... may be null only if
// 'persistent' is true (in which case we
@@ -142,24 +142,20 @@
Object adjTarget; // Debugging: target component impacting oom_adj.
Runnable crashHandler; // Optional local handler to be invoked in the process crash.
- // contains HistoryRecord objects
- final ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>();
+ // all activities running in the process
+ final ArrayList<ActivityRecord> activities = new ArrayList<>();
// all ServiceRecord running in this process
- final ArraySet<ServiceRecord> services = new ArraySet<ServiceRecord>();
+ final ArraySet<ServiceRecord> services = new ArraySet<>();
// services that are currently executing code (need to remain foreground).
- final ArraySet<ServiceRecord> executingServices
- = new ArraySet<ServiceRecord>();
+ final ArraySet<ServiceRecord> executingServices = new ArraySet<>();
// All ConnectionRecord this process holds
- final ArraySet<ConnectionRecord> connections
- = new ArraySet<ConnectionRecord>();
+ final ArraySet<ConnectionRecord> connections = new ArraySet<>();
// all IIntentReceivers that are registered from this process.
- final ArraySet<ReceiverList> receivers = new ArraySet<ReceiverList>();
+ final ArraySet<ReceiverList> receivers = new ArraySet<>();
// class (String) -> ContentProviderRecord
- final ArrayMap<String, ContentProviderRecord> pubProviders
- = new ArrayMap<String, ContentProviderRecord>();
+ final ArrayMap<String, ContentProviderRecord> pubProviders = new ArrayMap<>();
// All ContentProviderRecord process is using
- final ArrayList<ContentProviderConnection> conProviders
- = new ArrayList<ContentProviderConnection>();
+ final ArrayList<ContentProviderConnection> conProviders = new ArrayList<>();
boolean execServicesFg; // do we need to be executing services in the foreground?
boolean persistent; // always keep this application running?
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
new file mode 100644
index 0000000..b4efbf0
--- /dev/null
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.app.ActivityManager;
+import android.os.UserHandle;
+
+/**
+ * Overall information about a uid that has actively running processes.
+ */
+public final class UidRecord {
+ final int uid;
+ int curProcState;
+ int setProcState = ActivityManager.PROCESS_STATE_NONEXISTENT;
+ int numProcs;
+
+ static final class ChangeItem {
+ UidRecord uidRecord;
+ int uid;
+ boolean gone;
+ int processState;
+ }
+
+ ChangeItem pendingChange;
+
+ public UidRecord(int _uid) {
+ uid = _uid;
+ reset();
+ }
+
+ public void reset() {
+ curProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("UidRecord{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(' ');
+ UserHandle.formatUid(sb, uid);
+ sb.append(' ');
+ sb.append(ProcessList.makeProcStateString(curProcState));
+ sb.append(" / ");
+ sb.append(numProcs);
+ sb.append(" procs}");
+ return sb.toString();
+ }
+}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 24ab3b8..1a79568 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -81,6 +81,7 @@
import android.app.IActivityManager;
import android.app.INotificationManager;
import android.app.IProcessObserver;
+import android.app.IUidObserver;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.usage.UsageStatsManagerInternal;
@@ -153,10 +154,8 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.AppOpsService;
import com.android.server.DeviceIdleController;
import com.android.server.LocalServices;
-import com.android.server.SystemConfig;
import com.google.android.collect.Lists;
import org.xmlpull.v1.XmlPullParser;
@@ -294,9 +293,8 @@
/** Set of currently active {@link Notification} tags. */
private final ArraySet<String> mActiveNotifs = new ArraySet<String>();
- /** Foreground at both UID and PID granularity. */
+ /** Foreground at UID granularity. */
final SparseIntArray mUidState = new SparseIntArray();
- final SparseArray<SparseIntArray> mUidPidState = new SparseArray<>();
/** The current maximum process state that we are considering to be foreground. */
private int mCurForegroundState = ActivityManager.PROCESS_STATE_TOP;
@@ -411,7 +409,7 @@
updateScreenOn();
try {
- mActivityManager.registerProcessObserver(mProcessObserver);
+ mActivityManager.registerUidObserver(mUidObserver);
mNetworkManager.registerObserver(mAlertObserver);
} catch (RemoteException e) {
// ignored; both services live in system_server
@@ -477,40 +475,16 @@
}
- private IProcessObserver mProcessObserver = new IProcessObserver.Stub() {
- @Override
- public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
- }
-
- @Override
- public void onProcessStateChanged(int pid, int uid, int procState) {
+ private IUidObserver mUidObserver = new IUidObserver.Stub() {
+ @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
synchronized (mRulesLock) {
- // because a uid can have multiple pids running inside, we need to
- // remember all pid states and summarize foreground at uid level.
-
- // record foreground for this specific pid
- SparseIntArray pidState = mUidPidState.get(uid);
- if (pidState == null) {
- pidState = new SparseIntArray(2);
- mUidPidState.put(uid, pidState);
- }
- pidState.put(pid, procState);
- computeUidStateLocked(uid);
+ updateUidStateLocked(uid, procState);
}
}
- @Override
- public void onProcessDied(int pid, int uid) {
+ @Override public void onUidGone(int uid) throws RemoteException {
synchronized (mRulesLock) {
- // clear records and recompute, when they exist
- final SparseIntArray pidState = mUidPidState.get(uid);
- if (pidState != null) {
- pidState.delete(pid);
- if (pidState.size() <= 0) {
- mUidPidState.remove(uid);
- }
- computeUidStateLocked(uid);
- }
+ removeUidStateLocked(uid);
}
}
};
@@ -1919,14 +1893,6 @@
fout.print(state);
fout.print(state <= mCurForegroundState ? " (fg)" : " (bg)");
- fout.print(" pids=");
- final int foregroundIndex = mUidPidState.indexOfKey(uid);
- if (foregroundIndex < 0) {
- fout.print("UNKNOWN");
- } else {
- dumpSparseIntArray(fout, mUidPidState.valueAt(foregroundIndex));
- }
-
fout.print(" rules=");
final int rulesIndex = mUidRules.indexOfKey(uid);
if (rulesIndex < 0) {
@@ -1957,36 +1923,38 @@
}
/**
- * Process state of PID changed; recompute state at UID level. If
- * changed, will trigger {@link #updateRulesForUidLocked(int)}.
+ * Process state of UID changed; if needed, will trigger
+ * {@link #updateRulesForUidLocked(int)}.
*/
- void computeUidStateLocked(int uid) {
- final SparseIntArray pidState = mUidPidState.get(uid);
-
- // current pid is dropping foreground; examine other pids
- int uidState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
- if (pidState != null) {
- final int size = pidState.size();
- for (int i = 0; i < size; i++) {
- final int state = pidState.valueAt(i);
- if (state < uidState) {
- uidState = state;
- }
- }
- }
-
+ void updateUidStateLocked(int uid, int uidState) {
final int oldUidState = mUidState.get(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
if (oldUidState != uidState) {
// state changed, push updated rules
mUidState.put(uid, uidState);
- final boolean oldForeground = oldUidState <= mCurForegroundState;
- final boolean newForeground = uidState <= mCurForegroundState;
- if (oldForeground != newForeground) {
- updateRulesForUidLocked(uid);
+ updateRulesForUidStateChangeLocked(uid, oldUidState, uidState);
+ }
+ }
+
+ void removeUidStateLocked(int uid) {
+ final int index = mUidState.indexOfKey(uid);
+ if (index >= 0) {
+ final int oldUidState = mUidState.valueAt(index);
+ mUidState.removeAt(index);
+ if (oldUidState != ActivityManager.PROCESS_STATE_CACHED_EMPTY) {
+ updateRulesForUidStateChangeLocked(uid, oldUidState,
+ ActivityManager.PROCESS_STATE_CACHED_EMPTY);
}
}
}
+ void updateRulesForUidStateChangeLocked(int uid, int oldUidState, int newUidState) {
+ final boolean oldForeground = oldUidState <= mCurForegroundState;
+ final boolean newForeground = newUidState <= mCurForegroundState;
+ if (oldForeground != newForeground) {
+ updateRulesForUidLocked(uid);
+ }
+ }
+
private void updateScreenOn() {
synchronized (mRulesLock) {
try {
@@ -2381,16 +2349,6 @@
}
}
- private static void dumpSparseIntArray(PrintWriter fout, SparseIntArray value) {
- fout.print("[");
- final int size = value.size();
- for (int i = 0; i < size; i++) {
- fout.print(value.keyAt(i) + "=" + value.valueAt(i));
- if (i < size - 1) fout.print(",");
- }
- fout.print("]");
- }
-
@Override
public void factoryReset(String subscriber) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 509289b..74e8e4d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -369,11 +369,14 @@
/** Permission grant: grant the permission as an install permission. */
private static final int GRANT_INSTALL = 2;
+ /** Permission grant: grant the permission as an install permission for a legacy app. */
+ private static final int GRANT_INSTALL_LEGACY = 3;
+
/** Permission grant: grant the permission as a runtime one. */
- private static final int GRANT_RUNTIME = 3;
+ private static final int GRANT_RUNTIME = 4;
/** Permission grant: grant as runtime a permission that was granted as an install time one. */
- private static final int GRANT_UPGRADE = 4;
+ private static final int GRANT_UPGRADE = 5;
final ServiceThread mHandlerThread;
@@ -3180,6 +3183,12 @@
final PermissionsState permissionsState = sb.getPermissionsState();
+ final int flags = permissionsState.getPermissionFlags(name, userId);
+ if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
+ throw new SecurityException("Cannot grant system fixed permission: "
+ + name + " for package: " + packageName);
+ }
+
final int result = permissionsState.grantRuntimePermission(bp, userId);
switch (result) {
case PermissionsState.PERMISSION_OPERATION_FAILURE: {
@@ -3237,6 +3246,12 @@
final PermissionsState permissionsState = sb.getPermissionsState();
+ final int flags = permissionsState.getPermissionFlags(name, userId);
+ if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
+ throw new SecurityException("Cannot revoke system fixed permission: "
+ + name + " for package: " + packageName);
+ }
+
if (permissionsState.revokeRuntimePermission(bp, userId) ==
PermissionsState.PERMISSION_OPERATION_FAILURE) {
return;
@@ -4133,6 +4148,7 @@
final int userId = UserHandle.getCallingUserId();
ArrayList<ResolveInfo> result = new ArrayList<ResolveInfo>();
+ ArrayList<ResolveInfo> alwaysList = new ArrayList<ResolveInfo>();
ArrayList<ResolveInfo> undefinedList = new ArrayList<ResolveInfo>();
ArrayList<ResolveInfo> neverList = new ArrayList<ResolveInfo>();
ArrayList<ResolveInfo> matchAllList = new ArrayList<ResolveInfo>();
@@ -4155,27 +4171,22 @@
// Try to get the status from User settings first
int status = getDomainVerificationStatusLPr(ps, userId);
if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
- result.add(info);
+ alwaysList.add(info);
} else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
neverList.add(info);
- } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
+ } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED ||
+ status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK) {
undefinedList.add(info);
}
}
}
- // Add all undefined Apps as we want them to appear in the Disambiguation dialog.
- result.addAll(undefinedList);
- // If there is nothing selected, add all candidates and remove the ones that the User
- // has explicitely put into the INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER state and
- // also remove Browser Apps ones.
- // If there is still none after this pass, add all Browser Apps and
- // let the User decide with the Disambiguation dialog if there are several ones.
- if (result.size() == 0) {
- result.addAll(candidates);
- }
- result.removeAll(neverList);
- result.removeAll(matchAllList);
- if (result.size() == 0) {
+ // First try to add the "always" if there is any
+ if (alwaysList.size() > 0) {
+ result.addAll(alwaysList);
+ } else {
+ // Add all undefined Apps as we want them to appear in the Disambiguation dialog.
+ result.addAll(undefinedList);
+ // Also add Browsers (all of them or only the default one)
if ((flags & MATCH_ALL) != 0) {
result.addAll(matchAllList);
} else {
@@ -4200,6 +4211,13 @@
result.addAll(matchAllList);
}
}
+
+ // If there is nothing selected, add all candidates and remove the ones that the User
+ // has explicitely put into the INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER state
+ if (result.size() == 0) {
+ result.addAll(candidates);
+ result.removeAll(neverList);
+ }
}
}
if (DEBUG_PREFERRED) {
@@ -6301,7 +6319,6 @@
}
final String path = scanFile.getPath();
- final String codePath = pkg.applicationInfo.getCodePath();
final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, pkgSetting);
if (isSystemApp(pkg) && !pkg.isUpdatedSystemApp()) {
setBundledAppAbisAndRoots(pkg, pkgSetting);
@@ -6327,127 +6344,27 @@
setNativeLibraryPaths(pkg);
} else {
- // TODO: We can probably be smarter about this stuff. For installed apps,
- // we can calculate this information at install time once and for all. For
- // system apps, we can probably assume that this information doesn't change
- // after the first boot scan. As things stand, we do lots of unnecessary work.
+ if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
+ deriveNonSystemPackageAbi(pkg, scanFile, cpuAbiOverride, true /* extract libs */);
+ } else {
+ // Verify the ABIs haven't changed since we last deduced them.
+ String oldPrimaryCpuAbi = pkg.applicationInfo.primaryCpuAbi;
+ String oldSecondaryCpuAbi = pkg.applicationInfo.secondaryCpuAbi;
- // Give ourselves some initial paths; we'll come back for another
- // pass once we've determined ABI below.
- setNativeLibraryPaths(pkg);
-
- final boolean isAsec = pkg.isForwardLocked() || isExternal(pkg);
- final String nativeLibraryRootStr = pkg.applicationInfo.nativeLibraryRootDir;
- final boolean useIsaSpecificSubdirs = pkg.applicationInfo.nativeLibraryRootRequiresIsa;
-
- NativeLibraryHelper.Handle handle = null;
- try {
- handle = NativeLibraryHelper.Handle.create(scanFile);
- // TODO(multiArch): This can be null for apps that didn't go through the
- // usual installation process. We can calculate it again, like we
- // do during install time.
- //
- // TODO(multiArch): Why do we need to rescan ASEC apps again ? It seems totally
- // unnecessary.
- final File nativeLibraryRoot = new File(nativeLibraryRootStr);
-
- // Null out the abis so that they can be recalculated.
- pkg.applicationInfo.primaryCpuAbi = null;
- pkg.applicationInfo.secondaryCpuAbi = null;
- if (isMultiArch(pkg.applicationInfo)) {
- // Warn if we've set an abiOverride for multi-lib packages..
- // By definition, we need to copy both 32 and 64 bit libraries for
- // such packages.
- if (pkg.cpuAbiOverride != null
- && !NativeLibraryHelper.CLEAR_ABI_OVERRIDE.equals(pkg.cpuAbiOverride)) {
- Slog.w(TAG, "Ignoring abiOverride for multi arch application.");
- }
-
- int abi32 = PackageManager.NO_NATIVE_LIBRARIES;
- int abi64 = PackageManager.NO_NATIVE_LIBRARIES;
- if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
- if (isAsec) {
- abi32 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_32_BIT_ABIS);
- } else {
- abi32 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
- nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS,
- useIsaSpecificSubdirs);
- }
- }
-
- maybeThrowExceptionForMultiArchCopy(
- "Error unpackaging 32 bit native libs for multiarch app.", abi32);
-
- if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
- if (isAsec) {
- abi64 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_64_BIT_ABIS);
- } else {
- abi64 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
- nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS,
- useIsaSpecificSubdirs);
- }
- }
-
- maybeThrowExceptionForMultiArchCopy(
- "Error unpackaging 64 bit native libs for multiarch app.", abi64);
-
- if (abi64 >= 0) {
- pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[abi64];
- }
-
- if (abi32 >= 0) {
- final String abi = Build.SUPPORTED_32_BIT_ABIS[abi32];
- if (abi64 >= 0) {
- pkg.applicationInfo.secondaryCpuAbi = abi;
- } else {
- pkg.applicationInfo.primaryCpuAbi = abi;
- }
- }
- } else {
- String[] abiList = (cpuAbiOverride != null) ?
- new String[] { cpuAbiOverride } : Build.SUPPORTED_ABIS;
-
- // Enable gross and lame hacks for apps that are built with old
- // SDK tools. We must scan their APKs for renderscript bitcode and
- // not launch them if it's present. Don't bother checking on devices
- // that don't have 64 bit support.
- boolean needsRenderScriptOverride = false;
- if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && cpuAbiOverride == null &&
- NativeLibraryHelper.hasRenderscriptBitcode(handle)) {
- abiList = Build.SUPPORTED_32_BIT_ABIS;
- needsRenderScriptOverride = true;
- }
-
- final int copyRet;
- if (isAsec) {
- copyRet = NativeLibraryHelper.findSupportedAbi(handle, abiList);
- } else {
- copyRet = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
- nativeLibraryRoot, abiList, useIsaSpecificSubdirs);
- }
-
- if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) {
- throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
- "Error unpackaging native libs for app, errorCode=" + copyRet);
- }
-
- if (copyRet >= 0) {
- pkg.applicationInfo.primaryCpuAbi = abiList[copyRet];
- } else if (copyRet == PackageManager.NO_NATIVE_LIBRARIES && cpuAbiOverride != null) {
- pkg.applicationInfo.primaryCpuAbi = cpuAbiOverride;
- } else if (needsRenderScriptOverride) {
- pkg.applicationInfo.primaryCpuAbi = abiList[0];
- }
+ // TODO: The only purpose of this code is to update the native library paths
+ // based on the final install location. We can simplify this and avoid having
+ // to scan the package again.
+ deriveNonSystemPackageAbi(pkg, scanFile, cpuAbiOverride, false /* extract libs */);
+ if (!TextUtils.equals(oldPrimaryCpuAbi, pkg.applicationInfo.primaryCpuAbi)) {
+ throw new IllegalStateException("unexpected abi change for " + pkg.packageName + " ("
+ + oldPrimaryCpuAbi + "-> " + pkg.applicationInfo.primaryCpuAbi);
}
- } catch (IOException ioe) {
- Slog.e(TAG, "Unable to get canonical file " + ioe.toString());
- } finally {
- IoUtils.closeQuietly(handle);
- }
- // Now that we've calculated the ABIs and determined if it's an internal app,
- // we will go ahead and populate the nativeLibraryPath.
- setNativeLibraryPaths(pkg);
+ if (!TextUtils.equals(oldSecondaryCpuAbi, pkg.applicationInfo.secondaryCpuAbi)) {
+ throw new IllegalStateException("unexpected abi change for " + pkg.packageName + " ("
+ + oldSecondaryCpuAbi + "-> " + pkg.applicationInfo.secondaryCpuAbi);
+ }
+ }
if (DEBUG_INSTALL) Slog.i(TAG, "Linking native library dir for " + path);
final int[] userIds = sUserManager.getUserIds();
@@ -6478,9 +6395,21 @@
Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0];
}
+ // If there's a mismatch between the abi-override in the package setting
+ // and the abiOverride specified for the install. Warn about this because we
+ // would've already compiled the app without taking the package setting into
+ // account.
+ if ((scanFlags & SCAN_NO_DEX) == 0 && (scanFlags & SCAN_NEW_INSTALL) != 0) {
+ if (cpuAbiOverride == null && pkgSetting.cpuAbiOverrideString != null) {
+ Slog.w(TAG, "Ignoring persisted ABI override " + cpuAbiOverride +
+ " for package: " + pkg.packageName);
+ }
+ }
+
pkgSetting.primaryCpuAbiString = pkg.applicationInfo.primaryCpuAbi;
pkgSetting.secondaryCpuAbiString = pkg.applicationInfo.secondaryCpuAbi;
pkgSetting.cpuAbiOverrideString = cpuAbiOverride;
+
// Copy the derived override back to the parsed package, so that we can
// update the package settings accordingly.
pkg.cpuAbiOverride = cpuAbiOverride;
@@ -6974,6 +6903,144 @@
}
/**
+ * Derive the ABI of a non-system package located at {@code scanFile}. This information
+ * is derived purely on the basis of the contents of {@code scanFile} and
+ * {@code cpuAbiOverride}.
+ *
+ * If {@code extractLibs} is true, native libraries are extracted from the app if required.
+ */
+ public void deriveNonSystemPackageAbi(PackageParser.Package pkg, File scanFile,
+ String cpuAbiOverride, boolean extractLibs)
+ throws PackageManagerException {
+ // TODO: We can probably be smarter about this stuff. For installed apps,
+ // we can calculate this information at install time once and for all. For
+ // system apps, we can probably assume that this information doesn't change
+ // after the first boot scan. As things stand, we do lots of unnecessary work.
+
+ // Give ourselves some initial paths; we'll come back for another
+ // pass once we've determined ABI below.
+ setNativeLibraryPaths(pkg);
+
+ // We would never need to extract libs for forward-locked and external packages,
+ // since the container service will do it for us.
+ if (pkg.isForwardLocked() || isExternal(pkg)) {
+ extractLibs = false;
+ }
+
+ final String nativeLibraryRootStr = pkg.applicationInfo.nativeLibraryRootDir;
+ final boolean useIsaSpecificSubdirs = pkg.applicationInfo.nativeLibraryRootRequiresIsa;
+
+ NativeLibraryHelper.Handle handle = null;
+ try {
+ handle = NativeLibraryHelper.Handle.create(scanFile);
+ // TODO(multiArch): This can be null for apps that didn't go through the
+ // usual installation process. We can calculate it again, like we
+ // do during install time.
+ //
+ // TODO(multiArch): Why do we need to rescan ASEC apps again ? It seems totally
+ // unnecessary.
+ final File nativeLibraryRoot = new File(nativeLibraryRootStr);
+
+ // Null out the abis so that they can be recalculated.
+ pkg.applicationInfo.primaryCpuAbi = null;
+ pkg.applicationInfo.secondaryCpuAbi = null;
+ if (isMultiArch(pkg.applicationInfo)) {
+ // Warn if we've set an abiOverride for multi-lib packages..
+ // By definition, we need to copy both 32 and 64 bit libraries for
+ // such packages.
+ if (pkg.cpuAbiOverride != null
+ && !NativeLibraryHelper.CLEAR_ABI_OVERRIDE.equals(pkg.cpuAbiOverride)) {
+ Slog.w(TAG, "Ignoring abiOverride for multi arch application.");
+ }
+
+ int abi32 = PackageManager.NO_NATIVE_LIBRARIES;
+ int abi64 = PackageManager.NO_NATIVE_LIBRARIES;
+ if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
+ if (extractLibs) {
+ abi32 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
+ nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS,
+ useIsaSpecificSubdirs);
+ } else {
+ abi32 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_32_BIT_ABIS);
+ }
+ }
+
+ maybeThrowExceptionForMultiArchCopy(
+ "Error unpackaging 32 bit native libs for multiarch app.", abi32);
+
+ if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
+ if (extractLibs) {
+ abi64 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
+ nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS,
+ useIsaSpecificSubdirs);
+ } else {
+ abi64 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_64_BIT_ABIS);
+ }
+ }
+
+ maybeThrowExceptionForMultiArchCopy(
+ "Error unpackaging 64 bit native libs for multiarch app.", abi64);
+
+ if (abi64 >= 0) {
+ pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[abi64];
+ }
+
+ if (abi32 >= 0) {
+ final String abi = Build.SUPPORTED_32_BIT_ABIS[abi32];
+ if (abi64 >= 0) {
+ pkg.applicationInfo.secondaryCpuAbi = abi;
+ } else {
+ pkg.applicationInfo.primaryCpuAbi = abi;
+ }
+ }
+ } else {
+ String[] abiList = (cpuAbiOverride != null) ?
+ new String[] { cpuAbiOverride } : Build.SUPPORTED_ABIS;
+
+ // Enable gross and lame hacks for apps that are built with old
+ // SDK tools. We must scan their APKs for renderscript bitcode and
+ // not launch them if it's present. Don't bother checking on devices
+ // that don't have 64 bit support.
+ boolean needsRenderScriptOverride = false;
+ if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && cpuAbiOverride == null &&
+ NativeLibraryHelper.hasRenderscriptBitcode(handle)) {
+ abiList = Build.SUPPORTED_32_BIT_ABIS;
+ needsRenderScriptOverride = true;
+ }
+
+ final int copyRet;
+ if (extractLibs) {
+ copyRet = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
+ nativeLibraryRoot, abiList, useIsaSpecificSubdirs);
+ } else {
+ copyRet = NativeLibraryHelper.findSupportedAbi(handle, abiList);
+ }
+
+ if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) {
+ throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Error unpackaging native libs for app, errorCode=" + copyRet);
+ }
+
+ if (copyRet >= 0) {
+ pkg.applicationInfo.primaryCpuAbi = abiList[copyRet];
+ } else if (copyRet == PackageManager.NO_NATIVE_LIBRARIES && cpuAbiOverride != null) {
+ pkg.applicationInfo.primaryCpuAbi = cpuAbiOverride;
+ } else if (needsRenderScriptOverride) {
+ pkg.applicationInfo.primaryCpuAbi = abiList[0];
+ }
+ }
+ } catch (IOException ioe) {
+ Slog.e(TAG, "Unable to get canonical file " + ioe.toString());
+ } finally {
+ IoUtils.closeQuietly(handle);
+ }
+
+ // Now that we've calculated the ABIs and determined if it's an internal app,
+ // we will go ahead and populate the nativeLibraryPath.
+ setNativeLibraryPaths(pkg);
+ }
+
+ /**
* Adjusts ABIs for a set of packages belonging to a shared user so that they all match.
* i.e, so that all packages can be run inside a single process if required.
*
@@ -7689,7 +7756,7 @@
case PermissionInfo.PROTECTION_DANGEROUS: {
if (pkg.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1) {
// For legacy apps dangerous permissions are install time ones.
- grant = GRANT_INSTALL;
+ grant = GRANT_INSTALL_LEGACY;
} else if (ps.isSystem()) {
final int[] updatedUserIds = ps.getPermissionsUpdatedForUserIds();
if (origPermissions.hasInstallPermission(bp.name)) {
@@ -7749,6 +7816,28 @@
switch (grant) {
case GRANT_INSTALL: {
+ // Revoke this as runtime permission to handle the case of
+ // a runtime permssion being downgraded to an install one.
+ for (int userId : UserManagerService.getInstance().getUserIds()) {
+ if (origPermissions.getRuntimePermissionState(
+ bp.name, userId) != null) {
+ // Revoke the runtime permission and clear the flags.
+ origPermissions.revokeRuntimePermission(bp, userId);
+ origPermissions.updatePermissionFlags(bp, userId,
+ PackageManager.MASK_PERMISSION_FLAGS, 0);
+ // If we revoked a permission permission, we have to write.
+ changedRuntimePermissionUserIds = ArrayUtils.appendInt(
+ changedRuntimePermissionUserIds, userId);
+ }
+ }
+ // Grant an install permission.
+ if (permissionsState.grantInstallPermission(bp) !=
+ PermissionsState.PERMISSION_OPERATION_FAILURE) {
+ changedInstallPermission = true;
+ }
+ } break;
+
+ case GRANT_INSTALL_LEGACY: {
// Grant an install permission.
if (permissionsState.grantInstallPermission(bp) !=
PermissionsState.PERMISSION_OPERATION_FAILURE) {
@@ -11566,6 +11655,16 @@
} else if (!forwardLocked && !pkg.applicationInfo.isExternalAsec()) {
// Enable SCAN_NO_DEX flag to skip dexopt at a later stage
scanFlags |= SCAN_NO_DEX;
+
+ try {
+ deriveNonSystemPackageAbi(pkg, new File(pkg.codePath), args.abiOverride,
+ true /* extract libs */);
+ } catch (PackageManagerException pme) {
+ Slog.e(TAG, "Error deriving application ABI", pme);
+ res.setError(INSTALL_FAILED_INTERNAL_ERROR, "Error ");
+ return;
+ }
+
// Run dexopt before old package gets removed, to minimize time when app is unavailable
int result = mPackageDexOptimizer
.performDexOpt(pkg, null /* instruction sets */, true /* forceDex */,
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 40c8ca3..15d1535a 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -580,6 +580,28 @@
* Check if we've hit the limit of how many users can be created.
*/
private boolean isUserLimitReachedLocked() {
+ return getAliveUsersExcludingGuestsCountLocked() >= UserManager.getMaxSupportedUsers();
+ }
+
+ @Override
+ public boolean canAddMoreManagedProfiles() {
+ checkManageUsersPermission("check if more managed profiles can be added.");
+ if (ActivityManager.isLowRamDeviceStatic()) {
+ return false;
+ }
+ synchronized(mPackagesLock) {
+ // Limit number of managed profiles that can be created
+ if (numberOfUsersOfTypeLocked(UserInfo.FLAG_MANAGED_PROFILE, true)
+ >= MAX_MANAGED_PROFILES) {
+ return false;
+ }
+ int usersCount = getAliveUsersExcludingGuestsCountLocked();
+ // We allow creating a managed profile in the special case where there is only one user.
+ return usersCount == 1 || usersCount < UserManager.getMaxSupportedUsers();
+ }
+ }
+
+ private int getAliveUsersExcludingGuestsCountLocked() {
int aliveUserCount = 0;
final int totalUserCount = mUsers.size();
// Skip over users being removed
@@ -590,7 +612,7 @@
aliveUserCount++;
}
}
- return aliveUserCount >= UserManager.getMaxSupportedUsers();
+ return aliveUserCount;
}
/**
@@ -1176,7 +1198,11 @@
Log.w(LOG_TAG, "Cannot add user. DISALLOW_ADD_USER is enabled.");
return null;
}
+ if (ActivityManager.isLowRamDeviceStatic()) {
+ return null;
+ }
final boolean isGuest = (flags & UserInfo.FLAG_GUEST) != 0;
+ final boolean isManagedProfile = (flags & UserInfo.FLAG_MANAGED_PROFILE) != 0;
final long ident = Binder.clearCallingIdentity();
UserInfo userInfo = null;
try {
@@ -1187,21 +1213,18 @@
parent = getUserInfoLocked(parentId);
if (parent == null) return null;
}
- // If we're not adding a guest user and the limit has been reached,
- // cannot add a user.
- if (!isGuest && isUserLimitReachedLocked()) {
+ if (isManagedProfile && !canAddMoreManagedProfiles()) {
+ return null;
+ }
+ if (!isGuest && !isManagedProfile && isUserLimitReachedLocked()) {
+ // If we're not adding a guest user or a managed profile and the limit has
+ // been reached, cannot add a user.
return null;
}
// If we're adding a guest and there already exists one, bail.
if (isGuest && findCurrentGuestUserLocked() != null) {
return null;
}
- // Limit number of managed profiles that can be created
- if ((flags & UserInfo.FLAG_MANAGED_PROFILE) != 0
- && numberOfUsersOfTypeLocked(UserInfo.FLAG_MANAGED_PROFILE, true)
- >= MAX_MANAGED_PROFILES) {
- return null;
- }
int userId = getNextAvailableIdLocked();
userInfo = new UserInfo(userId, name, null, flags);
File userPath = new File(mBaseUserPath, Integer.toString(userId));
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 74df0a0..5aea746 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -16,6 +16,8 @@
package com.android.server.power;
+import android.app.ActivityManager;
+import android.util.SparseIntArray;
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IBatteryStats;
import com.android.internal.os.BackgroundThread;
@@ -436,6 +438,8 @@
// Set of app ids that we will always respect the wake locks for.
int[] mDeviceIdleWhitelist = new int[0];
+ private final SparseIntArray mUidState = new SparseIntArray();
+
// True if theater mode is enabled
private boolean mTheaterModeEnabled;
@@ -2316,6 +2320,24 @@
}
}
+ void updateUidProcStateInternal(int uid, int procState) {
+ synchronized (mLock) {
+ mUidState.put(uid, procState);
+ if (mDeviceIdleMode) {
+ updateWakeLockDisabledStatesLocked();
+ }
+ }
+ }
+
+ void uidGoneInternal(int uid) {
+ synchronized (mLock) {
+ mUidState.delete(uid);
+ if (mDeviceIdleMode) {
+ updateWakeLockDisabledStatesLocked();
+ }
+ }
+ }
+
private void updateWakeLockDisabledStatesLocked() {
boolean changed = false;
final int numWakeLocks = mWakeLocks.size();
@@ -2349,7 +2371,10 @@
// If we are in idle mode, we will ignore all partial wake locks that are
// for application uids that are not whitelisted.
if (appid >= Process.FIRST_APPLICATION_UID &&
- Arrays.binarySearch(mDeviceIdleWhitelist, appid) < 0) {
+ Arrays.binarySearch(mDeviceIdleWhitelist, appid) < 0 &&
+ mUidState.get(wakeLock.mOwnerUid,
+ ActivityManager.PROCESS_STATE_CACHED_EMPTY)
+ > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
disabled = true;
}
}
@@ -2650,6 +2675,13 @@
pw.println("Screen dim duration: " + screenDimDuration + " ms");
pw.println();
+ pw.println("UID states:");
+ for (int i=0; i<mUidState.size(); i++) {
+ pw.print(" UID "); UserHandle.formatUid(pw, mUidState.keyAt(i));
+ pw.print(": "); pw.println(mUidState.valueAt(i));
+ }
+
+ pw.println();
pw.println("Wake Locks: size=" + mWakeLocks.size());
for (WakeLock wl : mWakeLocks) {
pw.println(" " + wl);
@@ -3451,5 +3483,15 @@
public void setDeviceIdleWhitelist(int[] appids) {
setDeviceIdleWhitelistInternal(appids);
}
+
+ @Override
+ public void updateUidProcState(int uid, int procState) {
+ updateUidProcStateInternal(uid, procState);
+ }
+
+ @Override
+ public void uidGone(int uid) {
+ uidGoneInternal(uid);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 0357de2..60bbc48 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -325,8 +325,7 @@
mListeners.add(listener);
}
- public void notifyAppTransitionFinishedLocked(AppWindowAnimator animator) {
- IBinder token = animator != null ? animator.mAppToken.token : null;
+ public void notifyAppTransitionFinishedLocked(IBinder token) {
for (int i = 0; i < mListeners.size(); i++) {
mListeners.get(i).onAppTransitionFinishedLocked(token);
}
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index 3feec82..2e89385 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -345,7 +345,7 @@
for (int i = 0; i < numAllAppWinAnimators; i++) {
mAllAppWinAnimators.get(i).finishExit();
}
- mService.mAppTransition.notifyAppTransitionFinishedLocked(this);
+ mService.mAppTransition.notifyAppTransitionFinishedLocked(mAppToken.token);
return false;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d94f5d9..d956d76 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -252,11 +252,6 @@
static final int LAYER_OFFSET_DIM = 1;
/**
- * Blur surface layer is immediately below dim layer.
- */
- static final int LAYER_OFFSET_BLUR = 2;
-
- /**
* FocusedStackFrame layer is immediately above focused window.
*/
static final int LAYER_OFFSET_FOCUSED_STACK = 1;
@@ -266,27 +261,12 @@
* the thumbnail (or in other words as far as possible above the window
* below it).
*/
- static final int LAYER_OFFSET_THUMBNAIL = WINDOW_LAYER_MULTIPLIER-1;
-
- /**
- * Layer at which to put the rotation freeze snapshot.
- */
- static final int FREEZE_LAYER = (TYPE_LAYER_MULTIPLIER * 200) + 1;
-
- /**
- * Layer at which to put the mask for emulated screen sizes.
- */
- static final int MASK_LAYER = TYPE_LAYER_MULTIPLIER * 200;
+ static final int LAYER_OFFSET_THUMBNAIL = WINDOW_LAYER_MULTIPLIER - 1;
/** The maximum length we will accept for a loaded animation duration:
* this is 10 seconds.
*/
- static final int MAX_ANIMATION_DURATION = 10*1000;
-
- /** Amount of time (in milliseconds) to animate the fade-in-out transition for
- * compatible windows.
- */
- static final int DEFAULT_FADE_IN_OUT_DURATION = 400;
+ static final int MAX_ANIMATION_DURATION = 10 * 1000;
/** Amount of time (in milliseconds) to delay before declaring a window freeze timeout. */
static final int WINDOW_FREEZE_TIMEOUT_DURATION = 2000;
@@ -381,48 +361,43 @@
/**
* All currently active sessions with clients.
*/
- final ArraySet<Session> mSessions = new ArraySet<Session>();
+ final ArraySet<Session> mSessions = new ArraySet<>();
/**
* Mapping from an IWindow IBinder to the server's Window object.
* This is also used as the lock for all of our state.
* NOTE: Never call into methods that lock ActivityManagerService while holding this object.
*/
- final HashMap<IBinder, WindowState> mWindowMap = new HashMap<IBinder, WindowState>();
+ final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();
/**
* Mapping from a token IBinder to a WindowToken object.
*/
- final HashMap<IBinder, WindowToken> mTokenMap = new HashMap<IBinder, WindowToken>();
+ final HashMap<IBinder, WindowToken> mTokenMap = new HashMap<>();
/**
* List of window tokens that have finished starting their application,
* and now need to have the policy remove their windows.
*/
- final ArrayList<AppWindowToken> mFinishedStarting = new ArrayList<AppWindowToken>();
+ final ArrayList<AppWindowToken> mFinishedStarting = new ArrayList<>();
/**
* Fake windows added to the window manager. Note: ordered from top to
* bottom, opposite of mWindows.
*/
- final ArrayList<FakeWindowImpl> mFakeWindows = new ArrayList<FakeWindowImpl>();
+ final ArrayList<FakeWindowImpl> mFakeWindows = new ArrayList<>();
/**
* Windows that are being resized. Used so we can tell the client about
* the resize after closing the transaction in which we resized the
* underlying surface.
*/
- final ArrayList<WindowState> mResizingWindows = new ArrayList<WindowState>();
+ final ArrayList<WindowState> mResizingWindows = new ArrayList<>();
/**
* Windows whose animations have ended and now must be removed.
*/
- final ArrayList<WindowState> mPendingRemove = new ArrayList<WindowState>();
-
- /**
- * Stacks whose animations have ended and whose tasks, apps, selves may now be removed.
- */
- final ArraySet<TaskStack> mPendingStacksRemove = new ArraySet<TaskStack>();
+ final ArrayList<WindowState> mPendingRemove = new ArrayList<>();
/**
* Used when processing mPendingRemove to avoid working on the original array.
@@ -432,13 +407,13 @@
/**
* Windows whose surface should be destroyed.
*/
- final ArrayList<WindowState> mDestroySurface = new ArrayList<WindowState>();
+ final ArrayList<WindowState> mDestroySurface = new ArrayList<>();
/**
* Windows that have lost input focus and are waiting for the new
* focus window to be displayed before they are told about this.
*/
- ArrayList<WindowState> mLosingFocus = new ArrayList<WindowState>();
+ ArrayList<WindowState> mLosingFocus = new ArrayList<>();
/**
* This is set when we have run out of memory, and will either be an empty
@@ -449,7 +424,7 @@
/**
* Windows that clients are waiting to have drawn.
*/
- ArrayList<WindowState> mWaitingForDrawn = new ArrayList<WindowState>();
+ ArrayList<WindowState> mWaitingForDrawn = new ArrayList<>();
/**
* And the callback to make when they've all been drawn.
*/
@@ -466,7 +441,7 @@
* This array is essentially a cache for all userId for
* {@link android.app.admin.DevicePolicyManager#getScreenCaptureDisabled}
*/
- SparseArray<Boolean> mScreenCaptureDisabled = new SparseArray<Boolean>();
+ SparseArray<Boolean> mScreenCaptureDisabled = new SparseArray<>();
IInputMethodManager mInputMethodManager;
@@ -840,8 +815,7 @@
boolean mInTouchMode;
private ViewServer mViewServer;
- private final ArrayList<WindowChangeListener> mWindowChangeListeners =
- new ArrayList<WindowChangeListener>();
+ private final ArrayList<WindowChangeListener> mWindowChangeListeners = new ArrayList<>();
private boolean mWindowsChanged = false;
public interface WindowChangeListener {
@@ -859,6 +833,10 @@
// For example, when this flag is true, there will be no wallpaper service.
final boolean mOnlyCore;
+ // List of clients without a transtiton animation that we notify once we are done transitioning
+ // since they won't be notified through the app window animator.
+ private final List<IBinder> mNoAnimationNotifyOnTransitionFinished = new ArrayList<>();
+
/** Listener to notify activity manager about app transitions. */
private final WindowManagerInternal.AppTransitionListener mActivityManagerAppTransitionNotifier
= new WindowManagerInternal.AppTransitionListener() {
@@ -3209,10 +3187,13 @@
}
}
if ((attrChanges&WindowManager.LayoutParams.FORMAT_CHANGED) != 0) {
- // To change the format, we need to re-build the surface.
- winAnimator.destroySurfaceLocked();
- toBeDisplayed = true;
- surfaceChanged = true;
+ // If the format can be changed in place yaay!
+ // If not, fall back to a surface re-build
+ if (!winAnimator.tryChangeFormatInPlaceLocked()) {
+ winAnimator.destroySurfaceLocked();
+ toBeDisplayed = true;
+ surfaceChanged = true;
+ }
}
try {
if (!win.mHasSurface) {
@@ -9225,6 +9206,7 @@
transit = AppTransition.TRANSIT_UNSET;
}
mSkipAppTransitionAnimation = false;
+ mNoAnimationNotifyOnTransitionFinished.clear();
mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
@@ -9394,7 +9376,13 @@
appAnimator.animation = null;
}
wtoken.inPendingTransaction = false;
- setTokenVisibilityLocked(wtoken, animLp, true, transit, false, voiceInteraction);
+ if (!setTokenVisibilityLocked(
+ wtoken, animLp, true, transit, false, voiceInteraction)){
+ // This token isn't going to be animating. Add it to the list of tokens to
+ // be notified of app transition complete since the notification will not be
+ // sent be the app window animator.
+ mNoAnimationNotifyOnTransitionFinished.add(wtoken.token);
+ }
wtoken.updateReportedVisibilityLocked();
wtoken.waitingToShow = false;
@@ -9561,6 +9549,12 @@
mAppTransition.setIdle();
+ for (int i = mNoAnimationNotifyOnTransitionFinished.size() - 1; i >= 0; i--) {
+ final IBinder token = mNoAnimationNotifyOnTransitionFinished.get(i);
+ mAppTransition.notifyAppTransitionFinishedLocked(token);
+ }
+ mNoAnimationNotifyOnTransitionFinished.clear();
+
if (mDeferredHideWallpaper != null) {
hideWallpapersLocked(mDeferredHideWallpaper);
mDeferredHideWallpaper = null;
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 424e2e2..e9023fd 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -146,6 +146,9 @@
boolean mKeyguardGoingAwayAnimation;
+ /** The pixel format of the underlying SurfaceControl */
+ int mSurfaceFormat;
+
/** This is set when there is no Surface */
static final int NO_SURFACE = 0;
/** This is set after the Surface has been created but before the window has been drawn. During
@@ -845,6 +848,7 @@
flags |= SurfaceControl.OPAQUE;
}
+ mSurfaceFormat = format;
if (DEBUG_SURFACE_TRACE) {
mSurfaceControl = new SurfaceTrace(
mSession.mSurfaceSession,
@@ -1610,6 +1614,28 @@
}
}
+ /**
+ * Try to change the pixel format without recreating the surface. This
+ * will be common in the case of changing from PixelFormat.OPAQUE to
+ * PixelFormat.TRANSLUCENT in the hardware-accelerated case as both
+ * requested formats resolve to the same underlying SurfaceControl format
+ * @return True if format was succesfully changed, false otherwise
+ */
+ boolean tryChangeFormatInPlaceLocked() {
+ if (mSurfaceControl == null) {
+ return false;
+ }
+ final LayoutParams attrs = mWin.getAttrs();
+ final boolean isHwAccelerated = (attrs.flags &
+ WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;
+ final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : attrs.format;
+ if (format == mSurfaceFormat) {
+ setOpaqueLocked(!PixelFormat.formatHasAlpha(attrs.format));
+ return true;
+ }
+ return false;
+ }
+
void setOpaqueLocked(boolean isOpaque) {
if (mSurfaceControl == null) {
return;
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index 27c97d0..638783d 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -50,7 +50,7 @@
*/
public final class UsbAlsaManager {
private static final String TAG = UsbAlsaManager.class.getSimpleName();
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
private static final String ALSA_DIRECTORY = "/dev/snd/";
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index f05a1ef..a25d327 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -151,6 +151,7 @@
private final CharSequence mShortDescription;
private final List<String> mSupportedUriSchemes;
private final Icon mIcon;
+ private boolean mIsEnabled;
/**
* Helper class for creating a {@link PhoneAccount}.
@@ -165,6 +166,7 @@
private CharSequence mShortDescription;
private List<String> mSupportedUriSchemes = new ArrayList<String>();
private Icon mIcon;
+ private boolean mIsEnabled = false;
/**
* Creates a builder with the specified {@link PhoneAccountHandle} and label.
@@ -190,6 +192,7 @@
mShortDescription = phoneAccount.getShortDescription();
mSupportedUriSchemes.addAll(phoneAccount.getSupportedUriSchemes());
mIcon = phoneAccount.getIcon();
+ mIsEnabled = phoneAccount.isEnabled();
}
/**
@@ -288,6 +291,18 @@
}
/**
+ * Sets the enabled state of the phone account.
+ *
+ * @param isEnabled The enabled state.
+ * @return The builder.
+ * @hide
+ */
+ public Builder setIsEnabled(boolean isEnabled) {
+ mIsEnabled = isEnabled;
+ return this;
+ }
+
+ /**
* Creates an instance of a {@link PhoneAccount} based on the current builder settings.
*
* @return The {@link PhoneAccount}.
@@ -307,7 +322,8 @@
mHighlightColor,
mLabel,
mShortDescription,
- mSupportedUriSchemes);
+ mSupportedUriSchemes,
+ mIsEnabled);
}
}
@@ -320,7 +336,8 @@
int highlightColor,
CharSequence label,
CharSequence shortDescription,
- List<String> supportedUriSchemes) {
+ List<String> supportedUriSchemes,
+ boolean isEnabled) {
mAccountHandle = account;
mAddress = address;
mSubscriptionAddress = subscriptionAddress;
@@ -330,6 +347,7 @@
mLabel = label;
mShortDescription = shortDescription;
mSupportedUriSchemes = Collections.unmodifiableList(supportedUriSchemes);
+ mIsEnabled = isEnabled;
}
public static Builder builder(
@@ -437,6 +455,15 @@
}
/**
+ * Indicates whether the user has enabled this phone account or not {@code PhoneAccounts}.
+ *
+ * @return The {@code true} if the account is enabled by the user, {@code false} otherwise.
+ */
+ public boolean isEnabled() {
+ return mIsEnabled;
+ }
+
+ /**
* Determines if the {@link PhoneAccount} supports calls to/from addresses with a specified URI
* scheme.
*
@@ -466,6 +493,14 @@
return mHighlightColor;
}
+ /**
+ * Sets the enabled state of the phone account.
+ * @hide
+ */
+ public void setIsEnabled(boolean isEnabled) {
+ mIsEnabled = isEnabled;
+ }
+
//
// Parcelable implementation
//
@@ -500,12 +535,14 @@
out.writeCharSequence(mLabel);
out.writeCharSequence(mShortDescription);
out.writeStringList(mSupportedUriSchemes);
+
if (mIcon == null) {
out.writeInt(0);
} else {
out.writeInt(1);
mIcon.writeToParcel(out, flags);
}
+ out.writeByte((byte) (mIsEnabled ? 1 : 0));
}
public static final Creator<PhoneAccount> CREATOR
@@ -547,11 +584,14 @@
} else {
mIcon = null;
}
+ mIsEnabled = in.readByte() == 1;
}
@Override
public String toString() {
- StringBuilder sb = new StringBuilder().append("[PhoneAccount: ")
+ StringBuilder sb = new StringBuilder().append("[[")
+ .append(mIsEnabled ? 'X' : ' ')
+ .append("] PhoneAccount: ")
.append(mAccountHandle)
.append(" Capabilities: ")
.append(mCapabilities)
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index c8ed2b0..308c204 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -377,15 +377,23 @@
}
/**
- * Return the {@link PhoneAccount} which is the user-chosen default for making outgoing phone
- * calls with a specified URI scheme.
+ * Return the {@link PhoneAccount} which will be used to place outgoing calls to addresses with
+ * the specified {@code uriScheme}. This {@link PhoneAccount} will always be a member of the
+ * list which is returned from invoking {@link #getCallCapablePhoneAccounts()}. The specific
+ * account returned depends on the following priorities:
+ * <ul>
+ * <li> If the user-selected default {@link PhoneAccount} supports the specified scheme, it will
+ * be returned.
+ * </li>
+ * <li> If there exists only one {@link PhoneAccount} that supports the specified scheme, it
+ * will be returned.
+ * </li>
+ * </ul>
* <p>
- * Apps must be prepared for this method to return {@code null}, indicating that there currently
- * exists no user-chosen default {@code PhoneAccount}.
- * <p>
+ * If no {@link PhoneAccount} fits the criteria above, this method will return {@code null}.
+ *
* @param uriScheme The URI scheme.
- * @return The {@link PhoneAccountHandle} corresponding to the user-chosen default for outgoing
- * phone calls for a specified URI scheme.
+ * @return The {@link PhoneAccountHandle} corresponding to the account to be used.
*/
public PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme) {
try {
@@ -403,7 +411,7 @@
* Return the {@link PhoneAccount} which is the user-chosen default for making outgoing phone
* calls. This {@code PhoneAccount} will always be a member of the list which is returned from
* calling {@link #getCallCapablePhoneAccounts()}
- *
+ * <p>
* Apps must be prepared for this method to return {@code null}, indicating that there currently
* exists no user-chosen default {@code PhoneAccount}.
*
@@ -422,7 +430,7 @@
}
/**
- * Sets the default account for making outgoing phone calls.
+ * Sets the user-chosen default for making outgoing phone calls.
* @hide
*/
public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) {
@@ -439,6 +447,7 @@
* Returns the current SIM call manager. Apps must be prepared for this method to return
* {@code null}, indicating that there currently exists no user-chosen default
* {@code PhoneAccount}.
+ *
* @return The phone account handle of the current sim call manager.
*/
public PhoneAccountHandle getSimCallManager() {
@@ -454,6 +463,7 @@
/**
* Sets the SIM call manager to the specified phone account.
+ *
* @param accountHandle The phone account handle of the account to set as the sim call manager.
* @hide
*/
@@ -469,6 +479,7 @@
/**
* Returns the list of registered SIM call managers.
+ *
* @return List of registered SIM call managers.
* @hide
*/
@@ -497,16 +508,6 @@
}
/**
- * Returns the list of registered SIM call managers.
- * @return List of registered SIM call managers.
- * @hide
- */
- @SystemApi
- public List<PhoneAccountHandle> getRegisteredConnectionManagers() {
- return getSimCallManagers();
- }
-
- /**
* Returns a list of the {@link PhoneAccountHandle}s which can be used to make and receive phone
* calls which support the specified URI scheme.
* <P>
@@ -534,20 +535,33 @@
/**
- * Return a list of {@link PhoneAccountHandle}s which can be used to make and receive phone
- * calls.
+ * Returns a list of {@link PhoneAccountHandle}s which can be used to make and receive phone
+ * calls. The returned list includes only those accounts which have been explicitly enabled
+ * by the user.
*
* @see #EXTRA_PHONE_ACCOUNT_HANDLE
* @return A list of {@code PhoneAccountHandle} objects.
- *
*/
public List<PhoneAccountHandle> getCallCapablePhoneAccounts() {
+ return getCallCapablePhoneAccounts(false);
+ }
+
+ /**
+ * Returns a list of {@link PhoneAccountHandle}s including those which have not been enabled
+ * by the user.
+ *
+ * @return A list of {@code PhoneAccountHandle} objects.
+ * @hide
+ */
+ public List<PhoneAccountHandle> getCallCapablePhoneAccounts(boolean includeDisabledAccounts) {
try {
if (isServiceConnected()) {
- return getTelecomService().getCallCapablePhoneAccounts(mContext.getOpPackageName());
+ return getTelecomService().getCallCapablePhoneAccounts(
+ includeDisabledAccounts, mContext.getOpPackageName());
}
} catch (RemoteException e) {
- Log.e(TAG, "Error calling ITelecomService#getCallCapablePhoneAccounts", e);
+ Log.e(TAG, "Error calling ITelecomService#getCallCapablePhoneAccounts(" +
+ includeDisabledAccounts + ")", e);
}
return new ArrayList<>();
}
@@ -1163,6 +1177,25 @@
}
}
+ /**
+ * Enables and disables specified phone account.
+ *
+ * @param handle Handle to the phone account.
+ * @param isEnabled Enable state of the phone account.
+ * @hide
+ */
+ @SystemApi
+ public void enablePhoneAccount(PhoneAccountHandle handle, boolean isEnabled) {
+ ITelecomService service = getTelecomService();
+ if (service != null) {
+ try {
+ service.enablePhoneAccount(handle, isEnabled);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error enablePhoneAbbount", e);
+ }
+ }
+ }
+
private ITelecomService getTelecomService() {
return ITelecomService.Stub.asInterface(ServiceManager.getService(Context.TELECOM_SERVICE));
}
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index bc76f06..aa02021 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -53,7 +53,8 @@
/**
* @see TelecomServiceImpl#getCallCapablePhoneAccounts
*/
- List<PhoneAccountHandle> getCallCapablePhoneAccounts(String callingPackage);
+ List<PhoneAccountHandle> getCallCapablePhoneAccounts(
+ boolean includeDisabledAccounts, String callingPackage);
/**
* @see TelecomManager#getPhoneAccountsSupportingScheme
@@ -226,4 +227,9 @@
* @see TelecomServiceImpl#placeCall
*/
void placeCall(in Uri handle, in Bundle extras, String callingPackage);
+
+ /**
+ * @see TelecomServiceImpl#enablePhoneAccount
+ */
+ void enablePhoneAccount(in PhoneAccountHandle accountHandle, boolean isEnabled);
}
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
index 572fdc9..771cf57 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
@@ -444,7 +444,7 @@
@Override
public int getDimensionPixelSize(int index, int defValue) {
try {
- return getDimension(index);
+ return getDimension(index, null);
} catch (RuntimeException e) {
String s = getString(index);
@@ -474,12 +474,12 @@
@Override
public int getLayoutDimension(int index, String name) {
try {
- // this will throw an exception
- return getDimension(index);
+ // this will throw an exception if not found.
+ return getDimension(index, name);
} catch (RuntimeException e) {
if (LayoutInflater_Delegate.sIsInInclude) {
- throw new RuntimeException();
+ throw new RuntimeException("Layout Dimension '" + name + "' not found.");
}
Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
@@ -494,9 +494,13 @@
return getDimensionPixelSize(index, defValue);
}
- private int getDimension(int index) {
+ /** @param name attribute name, used for error reporting. */
+ private int getDimension(int index, @Nullable String name) {
String s = getString(index);
if (s == null) {
+ if (name != null) {
+ throw new RuntimeException("Attribute '" + name + "' not found");
+ }
throw new RuntimeException();
}
// Check if the value is a magic constant that doesn't require a unit.
diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
index 87762a6..32ee9e8 100644
--- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
+++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
@@ -53,9 +53,14 @@
*/
private static final String[] sClassPrefixList = {
"android.widget.",
- "android.webkit."
+ "android.webkit.",
+ "android.app."
};
+ public static String[] getClassPrefixList() {
+ return sClassPrefixList;
+ }
+
protected BridgeInflater(LayoutInflater original, Context newContext) {
super(original, newContext);
newContext = getBaseContext(newContext);
diff --git a/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java b/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java
index 4072302..27b406a 100644
--- a/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java
@@ -181,7 +181,8 @@
// ---- END CHANGES
params = group.generateLayoutParams(attrs);
-
+ } catch (RuntimeException ignored) {
+ // Ignore, just fail over to child attrs.
} finally {
// ---- START CHANGES
sIsInInclude = false;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index eb5f597..59f07a7 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -138,8 +138,9 @@
private final Stack<BridgeXmlBlockParser> mParserStack = new Stack<BridgeXmlBlockParser>();
private SharedPreferences mSharedPreferences;
+ private ClassLoader mClassLoader;
- /**
+ /**
* @param projectKey An Object identifying the project. This is used for the cache mechanism.
* @param metrics the {@link DisplayMetrics}.
* @param renderResources the configured resources (both framework and projects) for this
@@ -462,7 +463,21 @@
@Override
public ClassLoader getClassLoader() {
- return this.getClass().getClassLoader();
+ if (mClassLoader == null) {
+ mClassLoader = new ClassLoader(getClass().getClassLoader()) {
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ for (String prefix : BridgeInflater.getClassPrefixList()) {
+ if (name.startsWith(prefix)) {
+ // These are framework classes and should not be loaded from the app.
+ throw new ClassNotFoundException(name + " not found");
+ }
+ }
+ return BridgeContext.this.mLayoutlibCallback.findClass(name);
+ }
+ };
+ }
+ return mClassLoader;
}
@Override
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
index 2b95488..f3a0d58 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
@@ -275,7 +275,6 @@
mContext.getRenderResources().setLogger(null);
}
ParserFactory.setParserFactory(null);
-
}
public static BridgeContext getCurrentContext() {
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
index aa51c46..c8b2b84 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
@@ -728,7 +728,7 @@
// Check if method needs to replaced by a call to a different method.
- if (ReplaceMethodCallsAdapter.isReplacementNeeded(owner, name, desc)) {
+ if (ReplaceMethodCallsAdapter.isReplacementNeeded(owner, name, desc, mOwnerClass)) {
mReplaceMethodCallClasses.add(mOwnerClass);
}
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
index bd6f070..3aa7cdf 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
@@ -72,6 +72,9 @@
/** FQCN Names of classes to refactor. All reference to old-FQCN will be updated to new-FQCN.
* map old-FQCN => new-FQCN */
private final HashMap<String, String> mRefactorClasses;
+ /** Methods to inject. FQCN of class in which method should be injected => runnable that does
+ * the injection. */
+ private final Map<String, ICreateInfo.InjectMethodRunnable> mInjectedMethodsMap;
/**
* Creates a new generator that can generate the output JAR with the stubbed classes.
@@ -165,6 +168,8 @@
}
returnTypes.add(binaryToInternalClassName(className));
}
+
+ mInjectedMethodsMap = createInfo.getInjectedMethodsMap();
}
/**
@@ -337,7 +342,7 @@
ClassVisitor cv = cw;
if (mReplaceMethodCallsClasses.contains(className)) {
- cv = new ReplaceMethodCallsAdapter(cv);
+ cv = new ReplaceMethodCallsAdapter(cv, className);
}
cv = new RefactorClassAdapter(cv, mRefactorClasses);
@@ -345,6 +350,10 @@
cv = new RenameClassAdapter(cv, className, newName);
}
+ String binaryNewName = newName.replace('/', '.');
+ if (mInjectedMethodsMap.keySet().contains(binaryNewName)) {
+ cv = new InjectMethodsAdapter(cv, mInjectedMethodsMap.get(binaryNewName));
+ }
cv = new TransformClassAdapter(mLog, mStubMethods, mDeleteReturns.get(className),
newName, cv, stubNativesOnly);
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 245cd61..deb94c4 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -26,7 +26,9 @@
import com.android.tools.layoutlib.java.UnsafeByteSequence;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
import java.util.Set;
/**
@@ -105,6 +107,7 @@
return JAVA_PKG_CLASSES;
}
+ @Override
public Set<String> getExcludedClasses() {
String[] refactoredClasses = getJavaPkgClasses();
int count = refactoredClasses.length / 2 + EXCLUDED_CLASSES.length;
@@ -115,6 +118,12 @@
excludedClasses.addAll(Arrays.asList(EXCLUDED_CLASSES));
return excludedClasses;
}
+
+ @Override
+ public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
+ return INJECTED_METHODS;
+ }
+
//-----
/**
@@ -286,5 +295,11 @@
private final static String[] DELETE_RETURNS =
new String[] {
null }; // separator, for next class/methods list.
+
+ private final static Map<String, InjectMethodRunnable> INJECTED_METHODS =
+ new HashMap<String, InjectMethodRunnable>(1) {{
+ put("android.content.Context",
+ InjectMethodRunnables.CONTEXT_GET_FRAMEWORK_CLASS_LOADER);
+ }};
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
index e49a668..ac10639 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
@@ -16,6 +16,9 @@
package com.android.tools.layoutlib.create;
+import org.objectweb.asm.ClassVisitor;
+
+import java.util.Map;
import java.util.Set;
/**
@@ -27,33 +30,33 @@
* Returns the list of class from layoutlib_create to inject in layoutlib.
* The list can be empty but must not be null.
*/
- public abstract Class<?>[] getInjectedClasses();
+ Class<?>[] getInjectedClasses();
/**
* Returns the list of methods to rewrite as delegates.
* The list can be empty but must not be null.
*/
- public abstract String[] getDelegateMethods();
+ String[] getDelegateMethods();
/**
* Returns the list of classes on which to delegate all native methods.
* The list can be empty but must not be null.
*/
- public abstract String[] getDelegateClassNatives();
+ String[] getDelegateClassNatives();
/**
* Returns The list of methods to stub out. Each entry must be in the form
* "package.package.OuterClass$InnerClass#MethodName".
* The list can be empty but must not be null.
*/
- public abstract String[] getOverriddenMethods();
+ String[] getOverriddenMethods();
/**
* Returns the list of classes to rename, must be an even list: the binary FQCN
* of class to replace followed by the new FQCN.
* The list can be empty but must not be null.
*/
- public abstract String[] getRenamedClasses();
+ String[] getRenamedClasses();
/**
* Returns the list of classes for which the methods returning them should be deleted.
@@ -62,7 +65,7 @@
* the methods to delete.
* The list can be empty but must not be null.
*/
- public abstract String[] getDeleteReturns();
+ String[] getDeleteReturns();
/**
* Returns the list of classes to refactor, must be an even list: the
@@ -70,7 +73,18 @@
* to the old class should be updated to the new class.
* The list can be empty but must not be null.
*/
- public abstract String[] getJavaPkgClasses();
+ String[] getJavaPkgClasses();
- public abstract Set<String> getExcludedClasses();
+ Set<String> getExcludedClasses();
+
+ /**
+ * Returns a map from binary FQCN className to {@link InjectMethodRunnable} which will be
+ * called to inject methods into a class.
+ * Can be empty but must not be null.
+ */
+ Map<String, InjectMethodRunnable> getInjectedMethodsMap();
+
+ abstract class InjectMethodRunnable {
+ public abstract void generateMethods(ClassVisitor cv);
+ }
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java
new file mode 100644
index 0000000..39d46d7
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create;
+
+import com.android.tools.layoutlib.create.ICreateInfo.InjectMethodRunnable;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+
+import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
+import static org.objectweb.asm.Opcodes.ALOAD;
+import static org.objectweb.asm.Opcodes.ARETURN;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
+
+public class InjectMethodRunnables {
+ public static final ICreateInfo.InjectMethodRunnable CONTEXT_GET_FRAMEWORK_CLASS_LOADER
+ = new InjectMethodRunnable() {
+ @Override
+ public void generateMethods(ClassVisitor cv) {
+ // generated by compiling the class:
+ // class foo { public ClassLoader getFrameworkClassLoader() { return getClass().getClassLoader(); } }
+ // and then running ASMifier on it:
+ // java -classpath asm-debug-all-5.0.2.jar:. org.objectweb.asm.util.ASMifier foo
+ MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "getFrameworkClassLoader",
+ "()Ljava/lang/ClassLoader;", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass",
+ "()Ljava/lang/Class;");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getClassLoader",
+ "()Ljava/lang/ClassLoader;");
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ // generated code ends.
+ }
+ };
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java
new file mode 100644
index 0000000..ea2b9c9
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create;
+
+import com.android.tools.layoutlib.create.ICreateInfo.InjectMethodRunnable;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Injects methods into some classes.
+ */
+public class InjectMethodsAdapter extends ClassVisitor {
+
+ private final ICreateInfo.InjectMethodRunnable mRunnable;
+
+ public InjectMethodsAdapter(ClassVisitor cv, InjectMethodRunnable runnable) {
+ super(Opcodes.ASM4, cv);
+ mRunnable = runnable;
+ }
+
+ @Override
+ public void visitEnd() {
+ mRunnable.generateMethods(this);
+ super.visitEnd();
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
index 384d8ca..4369148 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
@@ -62,14 +62,13 @@
// Case 1: java.lang.System.arraycopy()
METHOD_REPLACERS.add(new MethodReplacer() {
@Override
- public boolean isNeeded(String owner, String name, String desc) {
+ public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return JAVA_LANG_SYSTEM.equals(owner) && "arraycopy".equals(name) &&
ARRAYCOPY_DESCRIPTORS.contains(desc);
}
@Override
public void replace(MethodInformation mi) {
- assert isNeeded(mi.owner, mi.name, mi.desc);
mi.desc = "(Ljava/lang/Object;ILjava/lang/Object;II)V";
}
});
@@ -81,14 +80,13 @@
Type.getMethodDescriptor(STRING, Type.getType(Locale.class));
@Override
- public boolean isNeeded(String owner, String name, String desc) {
+ public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return JAVA_LOCALE_CLASS.equals(owner) && "()Ljava/lang/String;".equals(desc) &&
("toLanguageTag".equals(name) || "getScript".equals(name));
}
@Override
public void replace(MethodInformation mi) {
- assert isNeeded(mi.owner, mi.name, mi.desc);
mi.opcode = Opcodes.INVOKESTATIC;
mi.owner = ANDROID_LOCALE_CLASS;
mi.desc = LOCALE_TO_STRING;
@@ -103,7 +101,7 @@
Type.getType(Locale.class), STRING);
@Override
- public boolean isNeeded(String owner, String name, String desc) {
+ public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return JAVA_LOCALE_CLASS.equals(owner) &&
("adjustLanguageCode".equals(name) && desc.equals(STRING_TO_STRING) ||
"forLanguageTag".equals(name) && desc.equals(STRING_TO_LOCALE));
@@ -111,7 +109,6 @@
@Override
public void replace(MethodInformation mi) {
- assert isNeeded(mi.owner, mi.name, mi.desc);
mi.owner = ANDROID_LOCALE_CLASS;
}
});
@@ -119,14 +116,13 @@
// Case 4: java.lang.System.log?()
METHOD_REPLACERS.add(new MethodReplacer() {
@Override
- public boolean isNeeded(String owner, String name, String desc) {
+ public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return JAVA_LANG_SYSTEM.equals(owner) && name.length() == 4
&& name.startsWith("log");
}
@Override
public void replace(MethodInformation mi) {
- assert isNeeded(mi.owner, mi.name, mi.desc);
assert mi.desc.equals("(Ljava/lang/String;Ljava/lang/Throwable;)V")
|| mi.desc.equals("(Ljava/lang/String;)V");
mi.name = "log";
@@ -142,7 +138,7 @@
private final String LINKED_HASH_MAP = Type.getInternalName(LinkedHashMap.class);
@Override
- public boolean isNeeded(String owner, String name, String desc) {
+ public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return LINKED_HASH_MAP.equals(owner) &&
"eldest".equals(name) &&
VOID_TO_MAP_ENTRY.equals(desc);
@@ -150,26 +146,64 @@
@Override
public void replace(MethodInformation mi) {
- assert isNeeded(mi.owner, mi.name, mi.desc);
mi.opcode = Opcodes.INVOKESTATIC;
mi.owner = Type.getInternalName(LinkedHashMap_Delegate.class);
mi.desc = Type.getMethodDescriptor(
Type.getType(Map.Entry.class), Type.getType(LinkedHashMap.class));
}
});
+
+ // Case 6: android.content.Context.getClassLoader() in LayoutInflater
+ METHOD_REPLACERS.add(new MethodReplacer() {
+ // When LayoutInflater asks for a class loader, we must return the class loader that
+ // cannot return app's custom views/classes. This is so that in case of any failure
+ // or exception when instantiating the views, the IDE can replace it with a mock view
+ // and have proper error handling. However, if a custom view asks for the class
+ // loader, we must return a class loader that can find app's custom views as well.
+ // Thus, we rewrite the call to get class loader in LayoutInflater to
+ // getFrameworkClassLoader and inject a new method in Context. This leaves the normal
+ // method: Context.getClassLoader() free to be used by the apps.
+ private final String VOID_TO_CLASS_LOADER =
+ Type.getMethodDescriptor(Type.getType(ClassLoader.class));
+
+ @Override
+ public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
+ return owner.equals("android/content/Context") &&
+ sourceClass.equals("android/view/LayoutInflater") &&
+ name.equals("getClassLoader") &&
+ desc.equals(VOID_TO_CLASS_LOADER);
+ }
+
+ @Override
+ public void replace(MethodInformation mi) {
+ mi.name = "getFrameworkClassLoader";
+ }
+ });
}
- public static boolean isReplacementNeeded(String owner, String name, String desc) {
+ /**
+ * If a method some.package.Class.Method(args) is called from some.other.Class,
+ * @param owner some/package/Class
+ * @param name Method
+ * @param desc (args)returnType
+ * @param sourceClass some/other/Class
+ * @return if the method invocation needs to be replaced by some other class.
+ */
+ public static boolean isReplacementNeeded(String owner, String name, String desc,
+ String sourceClass) {
for (MethodReplacer replacer : METHOD_REPLACERS) {
- if (replacer.isNeeded(owner, name, desc)) {
+ if (replacer.isNeeded(owner, name, desc, sourceClass)) {
return true;
}
}
return false;
}
- public ReplaceMethodCallsAdapter(ClassVisitor cv) {
+ private final String mOriginalClassName;
+
+ public ReplaceMethodCallsAdapter(ClassVisitor cv, String originalClassName) {
super(Opcodes.ASM4, cv);
+ mOriginalClassName = originalClassName;
}
@Override
@@ -187,7 +221,7 @@
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
for (MethodReplacer replacer : METHOD_REPLACERS) {
- if (replacer.isNeeded(owner, name, desc)) {
+ if (replacer.isNeeded(owner, name, desc, mOriginalClassName)) {
MethodInformation mi = new MethodInformation(opcode, owner, name, desc);
replacer.replace(mi);
opcode = mi.opcode;
@@ -216,13 +250,12 @@
}
private interface MethodReplacer {
- public boolean isNeeded(String owner, String name, String desc);
+ boolean isNeeded(String owner, String name, String desc, String sourceClass);
/**
* Updates the MethodInformation with the new values of the method attributes -
* opcode, owner, name and desc.
- *
*/
- public void replace(MethodInformation mi);
+ void replace(MethodInformation mi);
}
}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
index cf91386..2c21470 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
@@ -19,7 +19,9 @@
import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import org.junit.After;
@@ -32,13 +34,17 @@
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@@ -130,6 +136,11 @@
// methods deleted from their return type.
return new String[0];
}
+
+ @Override
+ public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
+ return new HashMap<String, InjectMethodRunnable>(0);
+ }
};
AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
@@ -200,6 +211,11 @@
// methods deleted from their return type.
return new String[0];
}
+
+ @Override
+ public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
+ return new HashMap<String, InjectMethodRunnable>(0);
+ }
};
AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
@@ -278,6 +294,11 @@
// methods deleted from their return type.
return new String[0];
}
+
+ @Override
+ public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
+ return new HashMap<String, InjectMethodRunnable>(0);
+ }
};
AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
@@ -303,6 +324,118 @@
filesFound.keySet().toArray());
}
+ @Test
+ public void testMethodInjection() throws IOException, LogAbortException,
+ ClassNotFoundException, IllegalAccessException, InstantiationException,
+ NoSuchMethodException, InvocationTargetException {
+ ICreateInfo ci = new ICreateInfo() {
+ @Override
+ public Class<?>[] getInjectedClasses() {
+ return new Class<?>[0];
+ }
+
+ @Override
+ public String[] getDelegateMethods() {
+ return new String[0];
+ }
+
+ @Override
+ public String[] getDelegateClassNatives() {
+ return new String[0];
+ }
+
+ @Override
+ public String[] getOverriddenMethods() {
+ // methods to force override
+ return new String[0];
+ }
+
+ @Override
+ public String[] getRenamedClasses() {
+ // classes to rename (so that we can replace them)
+ return new String[0];
+ }
+
+ @Override
+ public String[] getJavaPkgClasses() {
+ // classes to refactor (so that we can replace them)
+ return new String[0];
+ }
+
+ @Override
+ public Set<String> getExcludedClasses() {
+ return new HashSet<String>(0);
+ }
+
+ @Override
+ public String[] getDeleteReturns() {
+ // methods deleted from their return type.
+ return new String[0];
+ }
+
+ @Override
+ public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
+ HashMap<String, InjectMethodRunnable> map =
+ new HashMap<String, InjectMethodRunnable>(1);
+ map.put("mock_android.util.EmptyArray",
+ InjectMethodRunnables.CONTEXT_GET_FRAMEWORK_CLASS_LOADER);
+ return map;
+ }
+ };
+
+ AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
+ AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
+ null, // derived from
+ new String[] { // include classes
+ "**"
+ },
+ ci.getExcludedClasses(),
+ new String[] { /* include files */
+ "mock_android/data/data*"
+ });
+ aa.analyze();
+ agen.generate();
+ Map<String, ClassReader> output = new TreeMap<String, ClassReader>();
+ Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+ parseZip(mOsDestJar, output, filesFound);
+ final String modifiedClass = "mock_android.util.EmptyArray";
+ final String modifiedClassPath = modifiedClass.replace('.', '/').concat(".class");
+ ZipFile zipFile = new ZipFile(mOsDestJar);
+ ZipEntry entry = zipFile.getEntry(modifiedClassPath);
+ assertNotNull(entry);
+ final byte[] bytes;
+ final InputStream inputStream = zipFile.getInputStream(entry);
+ try {
+ bytes = getByteArray(inputStream);
+ } finally {
+ inputStream.close();
+ }
+ ClassLoader classLoader = new ClassLoader(getClass().getClassLoader()) {
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ if (name.equals(modifiedClass)) {
+ return defineClass(null, bytes, 0, bytes.length);
+ }
+ throw new ClassNotFoundException(name + " not found.");
+ }
+ };
+ Class<?> emptyArrayClass = classLoader.loadClass(modifiedClass);
+ Object emptyArrayInstance = emptyArrayClass.newInstance();
+ Method method = emptyArrayClass.getMethod("getFrameworkClassLoader");
+ Object cl = method.invoke(emptyArrayInstance);
+ assertEquals(classLoader, cl);
+ }
+
+ private static byte[] getByteArray(InputStream stream) throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ int read;
+ while ((read = stream.read(buffer, 0, buffer.length)) > -1) {
+ bos.write(buffer, 0, read);
+ }
+ return bos.toByteArray();
+ }
+
private void parseZip(String jarPath,
Map<String, ClassReader> classes,
Map<String, InputStream> filesFound) throws IOException {