Merge "Avoid race condition in heads up snooze code path." into lmp-mr1-dev
diff --git a/Android.mk b/Android.mk
index ba45e6f..1416472 100644
--- a/Android.mk
+++ b/Android.mk
@@ -100,6 +100,7 @@
core/java/android/bluetooth/IBluetoothA2dpSink.aidl \
core/java/android/bluetooth/IBluetoothAvrcpController.aidl \
core/java/android/bluetooth/IBluetoothCallback.aidl \
+ core/java/android/bluetooth/IBluetoothProfileServiceConnection.aidl \
core/java/android/bluetooth/IBluetoothHeadset.aidl \
core/java/android/bluetooth/IBluetoothHeadsetPhone.aidl \
core/java/android/bluetooth/IBluetoothHealth.aidl \
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index e18aa5c..5a44a74 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -114,6 +114,15 @@
private boolean mPlayingBackwards = false;
/**
+ * Flag to indicate whether this animator is playing in reverse mode, specifically
+ * by being started or interrupted by a call to reverse(). This flag is different than
+ * mPlayingBackwards, which indicates merely whether the current iteration of the
+ * animator is playing in reverse. It is used in corner cases to determine proper end
+ * behavior.
+ */
+ private boolean mReversing;
+
+ /**
* This variable tracks the current iteration that is playing. When mCurrentIteration exceeds the
* repeatCount (if repeatCount!=INFINITE), the animation ends
*/
@@ -545,21 +554,51 @@
* Sets the position of the animation to the specified fraction. This fraction should
* be between 0 and the total fraction of the animation, including any repetition. That is,
* a fraction of 0 will position the animation at the beginning, a value of 1 at the end,
- * and a value of 2 at the beginning of a reversing animator that repeats once. If
+ * and a value of 2 at the end of a reversing animator that repeats once. If
* the animation has not yet been started, then it will not advance forward after it is
* set to this fraction; it will simply set the fraction to this value and perform any
* appropriate actions based on that fraction. If the animation is already running, then
* setCurrentFraction() will set the current fraction to this value and continue
- * playing from that point.
+ * playing from that point. {@link AnimatorListener} events are not called
+ * due to changing the fraction; those events are only processed while the animation
+ * is running.
*
- * @param fraction The fraction to which the animation is advanced or rewound.
+ * @param fraction The fraction to which the animation is advanced or rewound. Values
+ * outside the range of 0 to the maximum fraction for the animator will be clamped to
+ * the correct range.
*/
public void setCurrentFraction(float fraction) {
initAnimation();
+ if (fraction < 0) {
+ fraction = 0;
+ }
+ int iteration = (int) fraction;
+ if (fraction == 1) {
+ iteration -= 1;
+ } else if (fraction > 1) {
+ if (iteration < (mRepeatCount + 1) || mRepeatCount == INFINITE) {
+ if (mRepeatMode == REVERSE) {
+ mPlayingBackwards = (iteration % 2) != 0;
+ }
+ fraction = fraction % 1f;
+ } else {
+ fraction = 1;
+ iteration -= 1;
+ }
+ } else {
+ mPlayingBackwards = mReversing;
+ }
+ mCurrentIteration = iteration;
+ long seekTime = (long) (mDuration * fraction);
+ long currentTime = AnimationUtils.currentAnimationTimeMillis();
+ mStartTime = currentTime - seekTime;
if (mPlayingState != RUNNING) {
mSeekFraction = fraction;
mPlayingState = SEEKED;
}
+ if (mPlayingBackwards) {
+ fraction = 1f - fraction;
+ }
animateValue(fraction);
}
@@ -962,8 +1001,30 @@
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
+ mReversing = playBackwards;
mPlayingBackwards = playBackwards;
- mCurrentIteration = 0;
+ if (playBackwards && mSeekFraction != -1) {
+ if (mSeekFraction == 0 && mCurrentIteration == 0) {
+ // special case: reversing from seek-to-0 should act as if not seeked at all
+ mSeekFraction = 0;
+ } else if (mRepeatCount == INFINITE) {
+ mSeekFraction = 1 - (mSeekFraction % 1);
+ } else {
+ mSeekFraction = 1 + mRepeatCount - (mCurrentIteration + mSeekFraction);
+ }
+ mCurrentIteration = (int) mSeekFraction;
+ mSeekFraction = mSeekFraction % 1;
+ }
+ if (mCurrentIteration > 0 && mRepeatMode == REVERSE &&
+ (mCurrentIteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) {
+ // if we were seeked to some other iteration in a reversing animator,
+ // figure out the correct direction to start playing based on the iteration
+ if (playBackwards) {
+ mPlayingBackwards = (mCurrentIteration % 2) == 0;
+ } else {
+ mPlayingBackwards = (mCurrentIteration % 2) != 0;
+ }
+ }
int prevPlayingState = mPlayingState;
mPlayingState = STOPPED;
mStarted = true;
@@ -1071,6 +1132,7 @@
long currentPlayTime = currentTime - mStartTime;
long timeLeft = mDuration - currentPlayTime;
mStartTime = currentTime - timeLeft;
+ mReversing = !mReversing;
} else if (mStarted) {
end();
} else {
@@ -1113,6 +1175,8 @@
mStarted = false;
mStartListenersCalled = false;
mPlayingBackwards = false;
+ mReversing = false;
+ mCurrentIteration = 0;
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, getNameForTrace(),
System.identityHashCode(this));
@@ -1201,8 +1265,16 @@
case RUNNING:
case SEEKED:
float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;
+ if (mDuration == 0 && mRepeatCount != INFINITE) {
+ // Skip to the end
+ mCurrentIteration = mRepeatCount;
+ if (!mReversing) {
+ mPlayingBackwards = false;
+ }
+ }
if (fraction >= 1f) {
- if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) {
+ if (mCurrentIteration < mRepeatCount ||
+ (mRepeatCount == INFINITE && mDuration != 0)) {
// Time to repeat
if (mListeners != null) {
int numListeners = mListeners.size();
@@ -1213,7 +1285,7 @@
if (mRepeatMode == REVERSE) {
mPlayingBackwards = !mPlayingBackwards;
}
- mCurrentIteration += (int)fraction;
+ mCurrentIteration += (int) fraction;
fraction = fraction % 1f;
mStartTime += mDuration;
} else {
@@ -1313,6 +1385,7 @@
}
anim.mSeekFraction = -1;
anim.mPlayingBackwards = false;
+ anim.mReversing = false;
anim.mCurrentIteration = 0;
anim.mInitialized = false;
anim.mPlayingState = STOPPED;
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 06a26ec..e8d08b8 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2363,6 +2363,13 @@
reply.writeNoException();
return true;
}
+
+ case SYSTEM_BACKUP_RESTORED: {
+ data.enforceInterface(IActivityManager.descriptor);
+ systemBackupRestored();
+ reply.writeNoException();
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -5458,5 +5465,16 @@
reply.recycle();
}
+ @Override
+ public void systemBackupRestored() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(SYSTEM_BACKUP_RESTORED, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java
index 49ab7c1..6c2511e 100644
--- a/core/java/android/app/ApplicationErrorReport.java
+++ b/core/java/android/app/ApplicationErrorReport.java
@@ -168,10 +168,20 @@
PackageManager pm = context.getPackageManager();
// look for receiver in the installer package
- String candidate = pm.getInstallerPackageName(packageName);
- ComponentName result = getErrorReportReceiver(pm, packageName, candidate);
- if (result != null) {
- return result;
+ String candidate = null;
+ ComponentName result = null;
+
+ try {
+ candidate = pm.getInstallerPackageName(packageName);
+ } catch (IllegalArgumentException e) {
+ // the package could already removed
+ }
+
+ if (candidate != null) {
+ result = getErrorReportReceiver(pm, packageName, candidate);
+ if (result != null) {
+ return result;
+ }
}
// if the error app is on the system image, look for system apps
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 1ccbd27..e505d69 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -469,6 +469,8 @@
public void notifyLaunchTaskBehindComplete(IBinder token) throws RemoteException;
public void notifyEnterAnimationComplete(IBinder token) throws RemoteException;
+ public void systemBackupRestored() throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -790,4 +792,5 @@
int START_IN_PLACE_ANIMATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+240;
int CHECK_PERMISSION_WITH_TOKEN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+241;
int REGISTER_TASK_STACK_LISTENER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+242;
+ int SYSTEM_BACKUP_RESTORED = IBinder.FIRST_CALL_TRANSACTION+243;
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 4753099..ead89b3 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -504,6 +504,22 @@
}
return false;
}
+ /**
+ * Return true if the given administrator component is currently being removed
+ * for the user.
+ * @hide
+ */
+ public boolean isRemovingAdmin(ComponentName who, int userId) {
+ if (mService != null) {
+ try {
+ return mService.isRemovingAdmin(who, userId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return false;
+ }
+
/**
* Return a list of all currently active device administrator's component
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index d144ae8..0ca60c0 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -196,4 +196,6 @@
void setAutoTimeRequired(in ComponentName who, int userHandle, boolean required);
boolean getAutoTimeRequired();
+
+ boolean isRemovingAdmin(in ComponentName adminReceiver, int userHandle);
}
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index c203a8e..ea2dca0 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -928,7 +928,7 @@
BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == 0) return false;
if (VDBG) Log.d(TAG, "writeCharacteristic() - uuid: " + characteristic.getUuid());
- if (mService == null || mClientIf == 0) return false;
+ if (mService == null || mClientIf == 0 || characteristic.getValue() == null) return false;
BluetoothGattService service = characteristic.getService();
if (service == null) return false;
@@ -1015,7 +1015,7 @@
*/
public boolean writeDescriptor(BluetoothGattDescriptor descriptor) {
if (VDBG) Log.d(TAG, "writeDescriptor() - uuid: " + descriptor.getUuid());
- if (mService == null || mClientIf == 0) return false;
+ if (mService == null || mClientIf == 0 || descriptor.getValue() == null) return false;
BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
if (characteristic == null) return false;
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 353f0fb..546a50e 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -20,9 +20,10 @@
import android.annotation.SdkConstant.SdkConstantType;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
@@ -221,11 +222,14 @@
*/
public static final int STATE_AUDIO_CONNECTED = 12;
+ private static final int MESSAGE_HEADSET_SERVICE_CONNECTED = 100;
+ private static final int MESSAGE_HEADSET_SERVICE_DISCONNECTED = 101;
private Context mContext;
private ServiceListener mServiceListener;
private IBluetoothHeadset mService;
private BluetoothAdapter mAdapter;
+ private boolean mIsClosed;
final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
new IBluetoothStateChangeCallback.Stub() {
@@ -233,14 +237,7 @@
if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
if (!up) {
if (VDBG) Log.d(TAG,"Unbinding service...");
- synchronized (mConnection) {
- try {
- mService = null;
- mContext.unbindService(mConnection);
- } catch (Exception re) {
- Log.e(TAG,"",re);
- }
- }
+ doUnbind();
} else {
synchronized (mConnection) {
try {
@@ -263,6 +260,7 @@
mContext = context;
mServiceListener = l;
mAdapter = BluetoothAdapter.getDefaultAdapter();
+ mIsClosed = false;
IBluetoothManager mgr = mAdapter.getBluetoothManager();
if (mgr != null) {
@@ -277,15 +275,26 @@
}
boolean doBind() {
- Intent intent = new Intent(IBluetoothHeadset.class.getName());
- ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
- intent.setComponent(comp);
- if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
- android.os.Process.myUserHandle())) {
- Log.e(TAG, "Could not bind to Bluetooth Headset Service with " + intent);
- return false;
+ try {
+ return mAdapter.getBluetoothManager().bindBluetoothProfileService(
+ BluetoothProfile.HEADSET, mConnection);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to bind HeadsetService", e);
}
- return true;
+ return false;
+ }
+
+ void doUnbind() {
+ synchronized (mConnection) {
+ if (mService != null) {
+ try {
+ mAdapter.getBluetoothManager().unbindBluetoothProfileService(
+ BluetoothProfile.HEADSET, mConnection);
+ } catch (RemoteException e) {
+ Log.e(TAG,"Unable to unbind HeadsetService", e);
+ }
+ }
+ }
}
/**
@@ -305,18 +314,8 @@
Log.e(TAG,"",e);
}
}
-
- synchronized (mConnection) {
- if (mService != null) {
- try {
- mService = null;
- mContext.unbindService(mConnection);
- } catch (Exception re) {
- Log.e(TAG,"",re);
- }
- }
- }
- mServiceListener = null;
+ mIsClosed = true;
+ doUnbind();
}
/**
@@ -930,21 +929,21 @@
return false;
}
- private final ServiceConnection mConnection = new ServiceConnection() {
+ private final IBluetoothProfileServiceConnection mConnection
+ = new IBluetoothProfileServiceConnection.Stub() {
+ @Override
public void onServiceConnected(ComponentName className, IBinder service) {
if (DBG) Log.d(TAG, "Proxy object connected");
mService = IBluetoothHeadset.Stub.asInterface(service);
-
- if (mServiceListener != null) {
- mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, BluetoothHeadset.this);
- }
+ mHandler.sendMessage(mHandler.obtainMessage(
+ MESSAGE_HEADSET_SERVICE_CONNECTED));
}
+ @Override
public void onServiceDisconnected(ComponentName className) {
if (DBG) Log.d(TAG, "Proxy object disconnected");
mService = null;
- if (mServiceListener != null) {
- mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
- }
+ mHandler.sendMessage(mHandler.obtainMessage(
+ MESSAGE_HEADSET_SERVICE_DISCONNECTED));
}
};
@@ -968,4 +967,28 @@
private static void log(String msg) {
Log.d(TAG, msg);
}
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_HEADSET_SERVICE_CONNECTED: {
+ if (mServiceListener != null) {
+ mServiceListener.onServiceConnected(BluetoothProfile.HEADSET,
+ BluetoothHeadset.this);
+ }
+ break;
+ }
+ case MESSAGE_HEADSET_SERVICE_DISCONNECTED: {
+ if (mServiceListener != null) {
+ mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
+ }
+ if (mIsClosed){
+ mServiceListener = null;
+ }
+ break;
+ }
+ }
+ }
+ };
}
diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java
index 1e22eb3..194a53e 100644
--- a/core/java/android/bluetooth/BluetoothUuid.java
+++ b/core/java/android/bluetooth/BluetoothUuid.java
@@ -66,6 +66,8 @@
ParcelUuid.fromString("00001116-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid BNEP =
ParcelUuid.fromString("0000000f-0000-1000-8000-00805F9B34FB");
+ public static final ParcelUuid PBAP_PCE =
+ ParcelUuid.fromString("0000112e-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid PBAP_PSE =
ParcelUuid.fromString("0000112f-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid MAP =
diff --git a/core/java/android/bluetooth/IBluetoothManager.aidl b/core/java/android/bluetooth/IBluetoothManager.aidl
index 493d2f8..7411d3f 100644
--- a/core/java/android/bluetooth/IBluetoothManager.aidl
+++ b/core/java/android/bluetooth/IBluetoothManager.aidl
@@ -19,6 +19,7 @@
import android.bluetooth.IBluetooth;
import android.bluetooth.IBluetoothGatt;
import android.bluetooth.IBluetoothManagerCallback;
+import android.bluetooth.IBluetoothProfileServiceConnection;
import android.bluetooth.IBluetoothStateChangeCallback;
/**
@@ -38,6 +39,9 @@
boolean disable(boolean persist);
IBluetoothGatt getBluetoothGatt();
+ boolean bindBluetoothProfileService(int profile, IBluetoothProfileServiceConnection proxy);
+ void unbindBluetoothProfileService(int profile, IBluetoothProfileServiceConnection proxy);
+
String getAddress();
String getName();
}
diff --git a/core/java/android/bluetooth/IBluetoothProfileServiceConnection.aidl b/core/java/android/bluetooth/IBluetoothProfileServiceConnection.aidl
new file mode 100755
index 0000000..96c59e2
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothProfileServiceConnection.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2014 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.bluetooth;
+
+import android.content.ComponentName;
+import android.os.IBinder;
+
+/**
+ * Callback for bluetooth profile connections.
+ *
+ * {@hide}
+ */
+interface IBluetoothProfileServiceConnection {
+ void onServiceConnected(in ComponentName comp, in IBinder service);
+ void onServiceDisconnected(in ComponentName comp);
+}
diff --git a/core/java/android/hardware/location/GeofenceHardwareImpl.java b/core/java/android/hardware/location/GeofenceHardwareImpl.java
index 5c7a8da..6e5d064 100644
--- a/core/java/android/hardware/location/GeofenceHardwareImpl.java
+++ b/core/java/android/hardware/location/GeofenceHardwareImpl.java
@@ -23,6 +23,7 @@
import android.location.Location;
import android.os.Handler;
import android.os.IBinder;
+import android.os.IInterface;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
@@ -30,6 +31,7 @@
import android.util.SparseArray;
import java.util.ArrayList;
+import java.util.Iterator;
/**
* This class manages the geofences which are handled by hardware.
@@ -558,8 +560,34 @@
try {
callback.onGeofenceRemove(geofenceId, msg.arg2);
} catch (RemoteException e) {}
+ IBinder callbackBinder = callback.asBinder();
+ boolean callbackInUse = false;
synchronized (mGeofences) {
mGeofences.remove(geofenceId);
+ // Check if the underlying binder is still useful for other geofences,
+ // if no, unlink the DeathRecipient to avoid memory leak.
+ for (int i = 0; i < mGeofences.size(); i++) {
+ if (mGeofences.valueAt(i).asBinder() == callbackBinder) {
+ callbackInUse = true;
+ break;
+ }
+ }
+ }
+
+ // Remove the reaper associated with this binder.
+ if (!callbackInUse) {
+ for (Iterator<Reaper> iterator = mReapers.iterator();
+ iterator.hasNext();) {
+ Reaper reaper = iterator.next();
+ if (reaper.mCallback != null &&
+ reaper.mCallback.asBinder() == callbackBinder) {
+ iterator.remove();
+ reaper.unlinkToDeath();
+ if (DEBUG) Log.d(TAG, String.format("Removed reaper %s " +
+ "because binder %s is no longer needed.",
+ reaper, callbackBinder));
+ }
+ }
}
}
releaseWakeLock();
@@ -803,8 +831,9 @@
@Override
public int hashCode() {
int result = 17;
- result = 31 * result + (mCallback != null ? mCallback.hashCode() : 0);
- result = 31 * result + (mMonitorCallback != null ? mMonitorCallback.hashCode() : 0);
+ result = 31 * result + (mCallback != null ? mCallback.asBinder().hashCode() : 0);
+ result = 31 * result + (mMonitorCallback != null
+ ? mMonitorCallback.asBinder().hashCode() : 0);
result = 31 * result + mMonitoringType;
return result;
}
@@ -815,9 +844,38 @@
if (obj == this) return true;
Reaper rhs = (Reaper) obj;
- return rhs.mCallback == mCallback && rhs.mMonitorCallback == mMonitorCallback &&
+ return binderEquals(rhs.mCallback, mCallback) &&
+ binderEquals(rhs.mMonitorCallback, mMonitorCallback) &&
rhs.mMonitoringType == mMonitoringType;
}
+
+ /**
+ * Compares the underlying Binder of the given two IInterface objects and returns true if
+ * they equals. null values are accepted.
+ */
+ private boolean binderEquals(IInterface left, IInterface right) {
+ if (left == null) {
+ return right == null;
+ } else {
+ return right == null ? false : left.asBinder() == right.asBinder();
+ }
+ }
+
+ /**
+ * Unlinks this DeathRecipient.
+ */
+ private boolean unlinkToDeath() {
+ if (mMonitorCallback != null) {
+ return mMonitorCallback.asBinder().unlinkToDeath(this, 0);
+ } else if (mCallback != null) {
+ return mCallback.asBinder().unlinkToDeath(this, 0);
+ }
+ return true;
+ }
+
+ private boolean callbackEquals(IGeofenceHardwareCallback cb) {
+ return mCallback != null && mCallback.asBinder() == cb.asBinder();
+ }
}
int getAllowedResolutionLevel(int pid, int uid) {
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 4fe418a..4215f20 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -100,7 +100,7 @@
/**
* Identical to {@link #CONNECTIVITY_ACTION} broadcast, but sent without any
- * historic {@link Settings.Global#CONNECTIVITY_CHANGE_DELAY}.
+ * applicable {@link Settings.Global#CONNECTIVITY_CHANGE_DELAY}.
*
* @hide
*/
@@ -428,6 +428,18 @@
public static final int DEFAULT_NETWORK_PREFERENCE = TYPE_WIFI;
/**
+ * Default value for {@link Settings.Global#CONNECTIVITY_CHANGE_DELAY} in
+ * milliseconds. This was introduced because IPv6 routes seem to take a
+ * moment to settle - trying network activity before the routes are adjusted
+ * can lead to packets using the wrong interface or having the wrong IP address.
+ * This delay is a bit crude, but in the future hopefully we will have kernel
+ * notifications letting us know when it's safe to use the new network.
+ *
+ * @hide
+ */
+ public static final int CONNECTIVITY_CHANGE_DELAY_DEFAULT = 3000;
+
+ /**
* @hide
*/
public final static int REQUEST_ID_UNSET = 0;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3bc74ae..0c036d51 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6118,7 +6118,7 @@
/**
* The number of milliseconds to delay before sending out
- * {@link ConnectivityManager#CONNECTIVITY_ACTION} broadcasts. Ignored.
+ * {@link ConnectivityManager#CONNECTIVITY_ACTION} broadcasts.
*
* @hide
*/
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 02297e3..74b7b69 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -416,7 +416,11 @@
currentTextWidth = widths[here - paraStart];
}
- v = out(source, here, endPos,
+ int ellipseEnd = endPos;
+ if (mMaximumVisibleLineCount == 1 && ellipsize == TextUtils.TruncateAt.MIDDLE) {
+ ellipseEnd = paraEnd;
+ }
+ v = out(source, here, ellipseEnd,
above, below, top, bottom,
v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, hasTabOrEmoji,
needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
@@ -704,7 +708,7 @@
int left = 0, right = len;
float ravail = (avail - ellipsisWidth) / 2;
- for (right = len; right >= 0; right--) {
+ for (right = len; right > 0; right--) {
float w = widths[right - 1 + lineStart - widthStart];
if (w + rsum > ravail) {
diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java
index c1341e1..14be269 100644
--- a/core/java/android/text/util/Linkify.java
+++ b/core/java/android/text/util/Linkify.java
@@ -402,7 +402,7 @@
}
boolean hasPrefix = false;
-
+
for (int i = 0; i < prefixes.length; i++) {
if (url.regionMatches(true, 0, prefixes[i], 0,
prefixes[i].length())) {
@@ -450,7 +450,7 @@
private static final void gatherTelLinks(ArrayList<LinkSpec> links, Spannable s) {
PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
Iterable<PhoneNumberMatch> matches = phoneUtil.findNumbers(s.toString(),
- Locale.getDefault().getCountry(), Leniency.POSSIBLE, Long.MAX_VALUE);
+ Locale.getDefault().getCountry(), Leniency.VALID, Long.MAX_VALUE);
for (PhoneNumberMatch match : matches) {
LinkSpec spec = new LinkSpec();
spec.url = "tel:" + PhoneNumberUtils.normalizeNumber(match.rawString());
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 2e5c1e0..6944c53 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -364,7 +364,7 @@
/**
* This hook is called whenever the window focus changes. See
* {@link View#onWindowFocusChanged(boolean)
- * View.onWindowFocusChanged(boolean)} for more information.
+ * View.onWindowFocusChangedNotLocked(boolean)} for more information.
*
* @param hasFocus Whether the window now has focus.
*/
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index f4f047e..094a8a1 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1102,6 +1102,13 @@
public static final int PRIVATE_FLAG_KEYGUARD = 0x00000400;
/**
+ * Flag that prevents the wallpaper behind the current window from receiving touch events.
+ *
+ * {@hide}
+ */
+ public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 0x00000800;
+
+ /**
* Control flags that are private to the platform.
* @hide
*/
diff --git a/core/java/com/android/server/backup/SystemBackupAgent.java b/core/java/com/android/server/backup/SystemBackupAgent.java
index ed7ce63..35a1a5a 100644
--- a/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -17,6 +17,7 @@
package com.android.server.backup;
+import android.app.ActivityManagerNative;
import android.app.IWallpaperManager;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
@@ -186,4 +187,13 @@
}
}
}
+
+ @Override
+ public void onRestoreFinished() {
+ try {
+ ActivityManagerNative.getDefault().systemBackupRestored();
+ } catch (RemoteException e) {
+ // Not possible since this code is running in the system process.
+ }
+ }
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3dd9770..3fdaaa6 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2914,7 +2914,8 @@
Any service that filters for this intent must be a carrier privileged app. -->
<permission android:name="android.permission.BIND_CARRIER_MESSAGING_SERVICE"
android:label="@string/permlab_bindCarrierMessagingService"
- android:description="@string/permdesc_bindCarrierMessagingService" />
+ android:description="@string/permdesc_bindCarrierMessagingService"
+ android:protectionLevel="signature|system" />
<!-- The system process is explicitly the only one allowed to launch the
confirmation UI for full backup/restore -->
diff --git a/core/res/res/drawable/btn_default_mtrl_shape.xml b/core/res/res/drawable/btn_default_mtrl_shape.xml
index 6d0f7f8..8a31d5e 100644
--- a/core/res/res/drawable/btn_default_mtrl_shape.xml
+++ b/core/res/res/drawable/btn_default_mtrl_shape.xml
@@ -21,9 +21,10 @@
android:insetTop="@dimen/button_inset_vertical_material"
android:insetRight="@dimen/button_inset_horizontal_material"
android:insetBottom="@dimen/button_inset_vertical_material">
- <shape android:shape="rectangle">
+ <shape android:shape="rectangle"
+ android:tint="?attr/colorButtonNormal">
<corners android:radius="@dimen/control_corner_material" />
- <solid android:color="?attr/colorButtonNormal" />
+ <solid android:color="@color/white" />
<padding android:left="@dimen/button_padding_horizontal_material"
android:top="@dimen/button_padding_vertical_material"
android:right="@dimen/button_padding_horizontal_material"
diff --git a/core/res/res/values/dimens_material.xml b/core/res/res/values/dimens_material.xml
index dd1f815..8cc2a8a 100644
--- a/core/res/res/values/dimens_material.xml
+++ b/core/res/res/values/dimens_material.xml
@@ -89,7 +89,7 @@
<!-- Elevation when button is pressed -->
<dimen name="button_elevation_material">4dp</dimen>
<!-- Z translation to apply when button is pressed -->
- <dimen name="button_pressed_z_material">6dp</dimen>
+ <dimen name="button_pressed_z_material">2dp</dimen>
<!-- Default insets (outer padding) around buttons -->
<dimen name="button_inset_vertical_material">6dp</dimen>
<dimen name="button_inset_horizontal_material">@dimen/control_inset_material</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e861ab2..90b8383 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2139,6 +2139,7 @@
<java-symbol type="string" name="system_error_manufacturer" />
<java-symbol type="dimen" name="fast_scroller_minimum_touch_target" />
<java-symbol type="array" name="config_cdma_international_roaming_indicators" />
+ <java-symbol type="string" name="kg_text_message_separator" />
<java-symbol type="bool" name="config_use_sim_language_file" />
</resources>
diff --git a/data/fonts/fallback_fonts.xml b/data/fonts/fallback_fonts.xml
index 0244891..f94fe66 100644
--- a/data/fonts/fallback_fonts.xml
+++ b/data/fonts/fallback_fonts.xml
@@ -229,6 +229,12 @@
</family>
<family>
<fileset>
+ <file>NotoSansCham-Regular.ttf</file>
+ <file>NotoSansCham-Bold.ttf</file>
+ </fileset>
+ </family>
+ <family>
+ <fileset>
<file>NotoSansBalinese-Regular.ttf</file>
</fileset>
</family>
@@ -259,6 +265,16 @@
</family>
<family>
<fileset>
+ <file>NotoSansCoptic-Regular.ttf</file>
+ </fileset>
+ </family>
+ <family>
+ <fileset>
+ <file>NotoSansGlagolitic-Regular.ttf</file>
+ </fileset>
+ </family>
+ <family>
+ <fileset>
<file>NotoSansHanunoo-Regular.ttf</file>
</fileset>
</family>
@@ -269,6 +285,11 @@
</family>
<family>
<fileset>
+ <file>NotoSansKayahLi-Regular.ttf</file>
+ </fileset>
+ </family>
+ <family>
+ <fileset>
<file>NotoSansLepcha-Regular.ttf</file>
</fileset>
</family>
@@ -314,6 +335,26 @@
</family>
<family>
<fileset>
+ <file>NotoSansTaiLe-Regular.ttf</file>
+ </fileset>
+ </family>
+ <family>
+ <fileset>
+ <file>NotoSansTaiTham-Regular.ttf</file>
+ </fileset>
+ </family>
+ <family>
+ <fileset>
+ <file>NotoSansTaiViet-Regular.ttf</file>
+ </fileset>
+ </family>
+ <family>
+ <fileset>
+ <file>NotoSansTifinagh-Regular.ttf</file>
+ </fileset>
+ </family>
+ <family>
+ <fileset>
<file>NotoSansYi-Regular.ttf</file>
</fileset>
</family>
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index a62e972..02bf877 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -224,6 +224,10 @@
<font weight="700" style="normal">NotoSansThaana-Bold.ttf</font>
</family>
<family>
+ <font weight="400" style="normal">NotoSansCham-Regular.ttf</font>
+ <font weight="700" style="normal">NotoSansCham-Bold.ttf</font>
+ </family>
+ <family>
<font weight="400" style="normal">NotoSansBalinese-Regular.ttf</font>
</family>
<family>
@@ -242,12 +246,21 @@
<font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font>
</family>
<family>
+ <font weight="400" style="normal">NotoSansCoptic-Regular.ttf</font>
+ </family>
+ <family>
+ <font weight="400" style="normal">NotoSansGlagolitic-Regular.ttf</font>
+ </family>
+ <family>
<font weight="400" style="normal">NotoSansHanunoo-Regular.ttf</font>
</family>
<family>
<font weight="400" style="normal">NotoSansJavanese-Regular.ttf</font>
</family>
<family>
+ <font weight="400" style="normal">NotoSansKayahLi-Regular.ttf</font>
+ </family>
+ <family>
<font weight="400" style="normal">NotoSansLepcha-Regular.ttf</font>
</family>
<family>
@@ -275,6 +288,18 @@
<font weight="400" style="normal">NotoSansTagbanwa-Regular.ttf</font>
</family>
<family>
+ <font weight="400" style="normal">NotoSansTaiLe-Regular.ttf</font>
+ </family>
+ <family>
+ <font weight="400" style="normal">NotoSansTaiTham-Regular.ttf</font>
+ </family>
+ <family>
+ <font weight="400" style="normal">NotoSansTaiViet-Regular.ttf</font>
+ </family>
+ <family>
+ <font weight="400" style="normal">NotoSansTifinagh-Regular.ttf</font>
+ </family>
+ <family>
<font weight="400" style="normal">NotoSansYi-Regular.ttf</font>
</family>
<family>
diff --git a/docs/html/about/versions/kitkat.jd b/docs/html/about/versions/kitkat.jd
index 4237c98..dff3508 100644
--- a/docs/html/about/versions/kitkat.jd
+++ b/docs/html/about/versions/kitkat.jd
@@ -148,7 +148,7 @@
</p>
<p>
- New tools give also give you powerful insight into your app's memory use. The
+ New tools also give you powerful insight into your app's memory use. The
<strong>procstats tool</strong> details memory use over time, with run times
and memory footprint for foreground apps and background services. An
on-device view is also available as a new developer option. The
diff --git a/docs/html/guide/topics/ui/settings.jd b/docs/html/guide/topics/ui/settings.jd
index f454c4e..02f1255 100644
--- a/docs/html/guide/topics/ui/settings.jd
+++ b/docs/html/guide/topics/ui/settings.jd
@@ -801,7 +801,7 @@
<h3 id="Listening">Listening for preference changes</h3>
-<p>There are several reasons you might want to be notified as soon as the use changes one of the
+<p>There are several reasons you might want to be notified as soon as the user changes one of the
preferences. In order to receive a callback when a change happens to any one of the preferences,
implement the {@link android.content.SharedPreferences.OnSharedPreferenceChangeListener
SharedPreference.OnSharedPreferenceChangeListener} interface and register the listener for the
diff --git a/docs/html/preview/index.html b/docs/html/preview/index.html
index 4f7722c..7cd029f 100644
--- a/docs/html/preview/index.html
+++ b/docs/html/preview/index.html
@@ -272,13 +272,11 @@
style="position:absolute;z-index:100;right:215px;top:375px">Android 5.0 API Overview</a>
<div style="width:440px">
-<p>Android 5.0 (Lollipop) is almost here and users will begin receiving
-device updates in November. To help you prepare, the Android 5.0 SDK is now available
-with final APIs.</p>
+<p>Android 5.0 (Lollipop) is now out of preview and available to users.</p>
-<p>Since the L Developer Preview began, various APIs and behaviors have changed,
-so if you've been using the Preview SDK
-you should update now to test your apps and take advantage of new features.</p>
+<p>If you previously developed apps with the L Developer Preview, be aware that various APIs and
+behaviors have changed, so you should update your SDK now to test your apps and take advantage of
+new features in Android 5.0.</p>
<p>To get the latest Android 5.0 SDK:</p>
diff --git a/docs/html/robots.txt b/docs/html/robots.txt
index f522220..ab379bb 100644
--- a/docs/html/robots.txt
+++ b/docs/html/robots.txt
@@ -15,4 +15,5 @@
Disallow: /guide/tutorials/
Disallow: /guide/samples/
Disallow: /community/
+Disallow: /preview/
Sitemap: http://developer.android.com/sitemap.txt
diff --git a/docs/html/samples/images/ActivitySceneTransitionBasic.png b/docs/html/samples/images/ActivitySceneTransitionBasic.png
index ea58641..a126cf8 100644
--- a/docs/html/samples/images/ActivitySceneTransitionBasic.png
+++ b/docs/html/samples/images/ActivitySceneTransitionBasic.png
Binary files differ
diff --git a/docs/html/samples/images/ActivitySceneTransitionBasic@2x.png b/docs/html/samples/images/ActivitySceneTransitionBasic@2x.png
index cd28ade..44378bb 100644
--- a/docs/html/samples/images/ActivitySceneTransitionBasic@2x.png
+++ b/docs/html/samples/images/ActivitySceneTransitionBasic@2x.png
Binary files differ
diff --git a/docs/html/samples/images/BasicManagedProfile.png b/docs/html/samples/images/BasicManagedProfile.png
index 7354842..5749db3 100644
--- a/docs/html/samples/images/BasicManagedProfile.png
+++ b/docs/html/samples/images/BasicManagedProfile.png
Binary files differ
diff --git a/docs/html/samples/images/BasicManagedProfile@2x.png b/docs/html/samples/images/BasicManagedProfile@2x.png
index c232809..860c954 100644
--- a/docs/html/samples/images/BasicManagedProfile@2x.png
+++ b/docs/html/samples/images/BasicManagedProfile@2x.png
Binary files differ
diff --git a/docs/html/samples/images/JobSchedulerSample.png b/docs/html/samples/images/JobSchedulerSample.png
index ee57bdb..a043f3e 100644
--- a/docs/html/samples/images/JobSchedulerSample.png
+++ b/docs/html/samples/images/JobSchedulerSample.png
Binary files differ
diff --git a/docs/html/samples/images/JobSchedulerSample@2x.png b/docs/html/samples/images/JobSchedulerSample@2x.png
index 3d543db..1ed2cf9 100644
--- a/docs/html/samples/images/JobSchedulerSample@2x.png
+++ b/docs/html/samples/images/JobSchedulerSample@2x.png
Binary files differ
diff --git a/docs/html/tools/sdk/ndk/index.jd b/docs/html/tools/sdk/ndk/index.jd
index 06474dc..48dceb6 100644
--- a/docs/html/tools/sdk/ndk/index.jd
+++ b/docs/html/tools/sdk/ndk/index.jd
@@ -3,28 +3,28 @@
ndk.mac64_download=android-ndk-r10c-darwin-x86_64.bin
-ndk.mac64_bytes=436952863
-ndk.mac64_checksum=bc04ef44b920cf6cd2157b6f2c3531d6
+ndk.mac64_bytes=442691567
+ndk.mac64_checksum=cb101e1e62d56ea75b215f6bc6c27fae
ndk.mac32_download=android-ndk-r10c-darwin-x86.bin
-ndk.mac32_bytes=435858709
-ndk.mac32_checksum=6b3e143f7e64d5cd337b727513e27913
+ndk.mac32_bytes=441545213
+ndk.mac32_checksum=0aeb3dc062dc457a4cd01e72eadb2379
ndk.linux64_download=android-ndk-r10c-linux-x86_64.bin
-ndk.linux64_bytes=449013322
-ndk.linux64_checksum=792c61706cd9ec6713fa1b69b2f42996
+ndk.linux64_bytes=459151600
+ndk.linux64_checksum=263b83071e6bca15f67898548d8d236e
ndk.linux32_download=android-ndk-r10c-linux-x86.bin
-ndk.linux32_bytes=438555265
-ndk.linux32_checksum=d1595d9ca5e15484e047f1ac326c4ceb
+ndk.linux32_bytes=449997190
+ndk.linux32_checksum=70ed6d8c34e7e620c145b791e8eeef89
ndk.win64_download=android-ndk-r10c-windows-x86_64.exe
-ndk.win64_bytes=458925419
-ndk.win64_checksum=af8edf5d316e1bf1a5a72e04a9faec41
+ndk.win64_bytes=472613732
+ndk.win64_checksum=9a33f96da58a7e0b70e47d27b4a880b4
ndk.win32_download=android-ndk-r10c-windows-x86.exe
-ndk.win32_bytes=433102815
-ndk.win32_checksum=805a04810719886674d3c7bff5eca53f
+ndk.win32_bytes=455427281
+ndk.win32_checksum=c0930abfae0c990c4d191cc4ebd46b68
@@ -388,6 +388,133 @@
<p>
<a href="#" onclick="return toggleContent(this)"> <img
src="/assets/images/triangle-opened.png" class="toggle-content-img" alt=""
+ >Android NDK, Revision 10d</a> <em>(December 2014)</em>
+ </p>
+ <div class="toggle-content-toggleme">
+ <dl>
+ <dt>Important changes:</dt>
+ <dd>
+ <ul>
+ <li>Made GCC 4.8 the default for all 32-bit ABIs. Deprecated GCC 4.6, and
+ will remove it next release. To restore previous behavior, either add
+ <code>NDK_TOOLCHAIN_VERSION=4.6</code> to ndk-build, or
+ add <code>--toolchain=arm-linux-androideabi-4.6</code> when executing
+ <code>make-standalone-toolchain.sh</code> on the command line. GCC 4.9 remains the
+ default for 64-bit ABIs.</li>
+
+ <li>Stopped all x86[_64] toolchains from adding <code>-mstackrealign</code> by default. The
+ NDK toolchain assumes a 16-byte stack alignment. The tools and options used by default
+ enforce this rule. A user writing assembly code must make sure to preserve stack
+ alignment, and ensure that other compilers also comply with this rule.
+ (GCC bug <a href="https://gcc.gnu.org/bugzilla/show_bug.cgi?id=38496">38496</a>)</li>
+
+ <li>Added Address Sanitizer functionality to Clang 3.5 support to the ARM and x86 ABIs.
+ For more information on this change, see the
+ <a href="https://code.google.com/p/address-sanitizer/wiki/Android">Address
+ Sanitizer</a> project.</li>
+
+ <li>Introduced the requirement, starting from API level 21, to use <code>-fPIE -pie
+ </code> when building. In API levels 16 and higher, ndk-build uses <code>PIE</code>
+ when building. This change has a number of implications, which are discussed in
+ <a href="https://code.google.com/p/android-developer-preview/issues/detail?id=888">
+ Developer Preview Issue 888</a>.
+ These implications do not apply to shared libraries.</li>
+ </ul>
+ </dd>
+ <dl>
+
+
+ <dt>Important bug fixes:</dt>
+ <dd>
+ <ul>
+ <li>Made more fixes related to
+ <a href="https://gcc.gnu.org/ml/gcc-patches/2014-10/msg00906.html">
+ A53 Errata #835769</a> in the aarch64-linux-android-4.9 linker. As part of this, GCC
+ passes a new option, <code>--fix-cortex-a53-835769</code>, when
+ <code>-mfix-cortex-a53-835769</code> (enabled by default) is specified.
+ For more information, see this
+ <a href="https://sourceware.org/ml/binutils/2014-10/msg00198.html">binutils message</a>
+ and this
+ <a href="https://sourceware.org/ml/binutils/2014-11/msg00287.html">binutils message</a>.
+ </li>
+
+ <li>Documented a fix to a libc++ <code>sscanf/vsscanf</code> hang that occurred in API level
+ 21. The fix itself had been implemented in r10c.
+ (Issue <a href="http://b.android.com/77988">77988</a>)</li>
+
+ <li>Fixed an AutoFDO (<code>-fauto-profile</code>) crash that occurred with GCC 4.9 when
+ <code>-Os</code> was specified. (Issue <a href="http://b.android.com/77571">77571</a>)</li>
+ </ul>
+ </dd>
+
+
+ <dt>Other bug fixes:</dt>
+ <dd>
+ <ul>
+ <li>Made the following header and library fixes:</li>
+ <ul>
+ <li>Added <code>posix_memalign</code> to API level 16. Also, added a prototype in
+ <code>stdlib.h</code> to API levels 16 to 19.
+ (Issue <a href="http://b.android.com/77861">77861</a>)</li>
+ <li>Fixed <code>stdatomic.h</code> so that it includes <code><atomic></code> only for
+ C++11.</li>
+ <li>Modified the following headers for standalone use: <code>sys/user.h</code>, and
+ <code>gl2ext.h</code>, <code>dlext.h</code>, <code>fts.h</code>, <code>sgidefs.h</code>
+ for API level 21.</li>
+ <li>Modified <code>sys/user.h</code> to rename <code>mxcsr_mask</code> as <code>mxcr_mask</code>,
+ and to change the data type for <code>u_ar0</code></li> from <code>unsigned long</code>
+ to </code>struct user_regs_struct*</code>.
+ <li>Changed <code>sysconf()</code> return value type from <code>int</code> to
+ <code>long</code>.</li>
+ </ul>
+
+ <li>Fixed ndk-build's handling of <code>thumb</code> for <code>LOCAL_ARM_MODE</code>: In
+ r10d, ndk-build adds <code>LOCAL_LDFLAGS+=-mthumb</code> by default, unless one of the
+ following conditions applies:</li>
+ <ul>
+ <li>You have set <code>LOCAL_ARM_MODE</code> equal to <code>arm</code>.</li>
+ <li>You are doing a debug build (with settings such as <code>APP_OPTIM=debug</code> and
+ <code>AndroidManifest.xml</code> containing <code>android:debuggable="true"</code>),
+ where ARM mode is the default in order to retain compatibility with earlier toolchains.
+ (Issue <a href="http://b.android.com/74040">74040</a>)</li>
+ </ul>
+
+ <li>Fixed <code>LOCAL_SRC_FILES</code> in ndk-build to use Windows absolute paths.
+ (Issue <a href="http://b.android.com/74333">74333</a>)</li>
+
+ <li>Removed bash-specific code from ndk-gdb. (Issue <a href="http://b.android.com/73338">73338</a>)</li>
+
+ <li>Removed bash-specific code from <code>make-standalone-toolchain.sh</code>.
+ (Issue <a href="http://b.android.com/74145">74145)</a></li>
+
+ <li>Revised documentation concerning a fix for <code>System.loadLibrary()</code> transitive
+ dependencies. (Issue <a href="http://b.android.com/41790">41790</a>)</li>
+
+ <li>Fixed a problem that was preventing 64-bit packages from extracting on Ubuntu 14.04 and
+ OS X 10.10 (Yosemite). (Issue <a href="http://b.android.com/78148">78148</a>)</li>
+
+ <li>Fixed an issue with <code>LOCAL_PCH</code> to improve Clang support. (Issue
+ <a href="http://b.android.com/77575">77575</a>)</li>
+
+ <li>Clarified "requires executable stack" warning from ld.gold. (Issue
+ <a href="http://b.android.com/79115">79115</a>)</li>
+ </ul>
+ </dd>
+
+ </dl>
+ </div>
+</div>
+
+
+
+
+
+
+
+<div class="toggle-content closed">
+ <p>
+ <a href="#" onclick="return toggleContent(this)"> <img
+ src="/assets/images/triangle-closed.png" class="toggle-content-img" alt=""
>Android NDK, Revision 10c</a> <em>(October 2014)</em>
</p>
<div class="toggle-content-toggleme">
@@ -570,10 +697,6 @@
</div>
</div>
-
-
-
-
<div class="toggle-content closed">
<p>
<a href="#" onclick="return toggleContent(this)"> <img
diff --git a/docs/html/training/app-indexing/deep-linking.jd b/docs/html/training/app-indexing/deep-linking.jd
index a52ae95..2679937 100644
--- a/docs/html/training/app-indexing/deep-linking.jd
+++ b/docs/html/training/app-indexing/deep-linking.jd
@@ -61,13 +61,15 @@
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
- <!-- Accepts URIs that begin with "example://gizmos” -->
- <data android:scheme="example"
- android:host="gizmos" />
<!-- Accepts URIs that begin with "http://www.example.com/gizmos” -->
<data android:scheme="http"
android:host="www.example.com"
- android:pathPrefix="gizmos" />
+ android:pathPrefix="/gizmos" />
+ <!-- note that the leading "/" is required for pathPrefix-->
+ <!-- Accepts URIs that begin with "example://gizmos”
+ <data android:scheme="example"
+ android:host="gizmos" />
+ -->
</intent-filter>
</activity>
</pre>
@@ -76,6 +78,11 @@
manifest, Android is able to route any {@link android.content.Intent}
that has matching URIs to your app at runtime.</p>
+<p class="note">
+ <strong>Note:</strong> Intent filters may only contain a single {@code data} element
+ for a URI pattern. Create separate intent filters to capture additional URI patterns.
+</p>
+
<p>To learn more about defining intent filters, see <a href="{@docRoot}training/basics/intents/filters.html">Allow Other Apps to Start Your Activity</a>.</p>
<h2 id="handling-intents">Read Data from Incoming Intents</h2>
diff --git a/docs/html/training/app-indexing/index.jd b/docs/html/training/app-indexing/index.jd
index 7e7241b..45afea8 100644
--- a/docs/html/training/app-indexing/index.jd
+++ b/docs/html/training/app-indexing/index.jd
@@ -24,7 +24,7 @@
</ul>
</div>
</div>
-<a class="notice-developers-video wide" href="http://www.youtube.com/watch?v=Xh_W82JgOms">
+<a class="notice-developers-video wide" href="http://www.youtube.com/watch?v=aISUYHTkTOU">
<div>
<h3>Video</h3>
<p>DevBytes: App Indexing</p>
diff --git a/docs/html/training/wearables/data-layer/index.jd b/docs/html/training/wearables/data-layer/index.jd
index 8d42ae3..85b2c33 100644
--- a/docs/html/training/wearables/data-layer/index.jd
+++ b/docs/html/training/wearables/data-layer/index.jd
@@ -30,9 +30,12 @@
<dd>The <a href="{@docRoot}reference/com/google/android/gms/wearable/MessageApi.html"><code>MessageApi</code></a> class
can send messages and is good for remote procedure calls (RPC), such as controlling a handheld's
media player from the wearable or starting an intent on the wearable from the handheld.
- The system always delivers the message when the handheld and wearable are connected and delivers
- an error when the devices are disconnected. Messages are great for one-way requests or for a
- request/response communication model.</dd>
+ Messages are also great for one-way requests or for a request/response communication model.
+ If the handheld and wearable are connected, the system queues the message for delivery and
+ returns a successful result code. If the devices are not connected, an error is returned. A
+ successful result code does not indicate that the message was delivered successfully as the
+ devices may disconnect after receiving the result code.
+</p></dd>
<dt><b>Asset</b></dt>
<dd><a href="{@docRoot}reference/com/google/android/gms/wearable/Asset.html"><code>Asset</code></a> objects are for
diff --git a/docs/html/training/wearables/ui/exit.jd b/docs/html/training/wearables/ui/exit.jd
index 6b205a5..84e1e45 100644
--- a/docs/html/training/wearables/ui/exit.jd
+++ b/docs/html/training/wearables/ui/exit.jd
@@ -67,7 +67,7 @@
android:id="@+id/dismiss_overlay"
android:layout_height="match_parent"
android:layout_width="match_parent"/>
-</FrameLayout>
+<FrameLayout>
</pre>
<p>In your activity, obtain the <code>DismissOverlayView</code> element and set some introductory
@@ -100,8 +100,8 @@
// Capture long presses
@Override
- public boolean dispatchTouchEvent(MotionEvent e) {
- return mDetector.onTouchEvent(e) || super.dispatchTouchEvent(e);
+ public boolean onTouchEvent(MotionEvent ev) {
+ return mDetector.onTouchEvent(ev) || super.onTouchEvent(ev);
}
}
</pre>
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index e9c8c2a..2a17a60 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -146,7 +146,8 @@
@Override
public Drawable mutate() {
if (!mMutated && super.mutate() == this) {
- mAnimatedVectorState.mVectorDrawable.mutate();
+ mAnimatedVectorState = new AnimatedVectorDrawableState(
+ mAnimatedVectorState, mCallback, null);
mMutated = true;
}
return this;
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index 68fd296..2748030 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -54,19 +54,20 @@
private DrawableContainerState mDrawableContainerState;
private Rect mHotspotBounds;
private Drawable mCurrDrawable;
+ private Drawable mLastDrawable;
private int mAlpha = 0xFF;
/** Whether setAlpha() has been called at least once. */
private boolean mHasAlpha;
private int mCurIndex = -1;
+ private int mLastIndex = -1;
private boolean mMutated;
// Animations.
private Runnable mAnimationRunnable;
private long mEnterAnimationEnd;
private long mExitAnimationEnd;
- private Drawable mLastDrawable;
// overrides from Drawable
@@ -255,6 +256,7 @@
if (mLastDrawable != null) {
mLastDrawable.jumpToCurrentState();
mLastDrawable = null;
+ mLastIndex = -1;
changed = true;
}
if (mCurrDrawable != null) {
@@ -426,9 +428,11 @@
}
if (mCurrDrawable != null) {
mLastDrawable = mCurrDrawable;
+ mLastIndex = mCurIndex;
mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration;
} else {
mLastDrawable = null;
+ mLastIndex = -1;
mExitAnimationEnd = 0;
}
} else if (mCurrDrawable != null) {
@@ -522,6 +526,7 @@
if (mExitAnimationEnd <= now) {
mLastDrawable.setVisible(false, false);
mLastDrawable = null;
+ mLastIndex = -1;
mExitAnimationEnd = 0;
} else {
int animAlpha = (int)((mExitAnimationEnd-now)*255)
@@ -1103,5 +1108,13 @@
protected void setConstantState(DrawableContainerState state) {
mDrawableContainerState = state;
+
+ // The locally cached drawables may have changed.
+ if (mCurIndex >= 0) {
+ mCurrDrawable = state.getChild(mCurIndex);
+ }
+ if (mLastIndex >= 0) {
+ mLastDrawable = state.getChild(mLastIndex);
+ }
}
}
diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java
index b88d9e6..acfd427 100644
--- a/graphics/java/android/graphics/drawable/InsetDrawable.java
+++ b/graphics/java/android/graphics/drawable/InsetDrawable.java
@@ -30,6 +30,7 @@
import android.graphics.ColorFilter;
import android.graphics.Insets;
import android.graphics.Outline;
+import android.graphics.PixelFormat;
import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
import android.util.AttributeSet;
@@ -317,7 +318,13 @@
@Override
public int getOpacity() {
- return mState.mDrawable.getOpacity();
+ final InsetState state = mState;
+ final int opacity = state.mDrawable.getOpacity();
+ if (opacity == PixelFormat.OPAQUE && (state.mInsetLeft > 0 || state.mInsetTop > 0
+ || state.mInsetRight > 0 || state.mInsetBottom > 0)) {
+ return PixelFormat.TRANSLUCENT;
+ }
+ return opacity;
}
@Override
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java
index 6731366..a3a220c 100644
--- a/graphics/java/android/graphics/drawable/Ripple.java
+++ b/graphics/java/android/graphics/drawable/Ripple.java
@@ -22,11 +22,8 @@
import android.animation.TimeInterpolator;
import android.graphics.Canvas;
import android.graphics.CanvasProperty;
-import android.graphics.Color;
import android.graphics.Paint;
-import android.graphics.Paint.Style;
import android.graphics.Rect;
-import android.graphics.Xfermode;
import android.util.MathUtils;
import android.view.HardwareCanvas;
import android.view.RenderNodeAnimator;
@@ -51,19 +48,12 @@
// Hardware animators.
private final ArrayList<RenderNodeAnimator> mRunningAnimations =
new ArrayList<RenderNodeAnimator>();
- private final ArrayList<RenderNodeAnimator> mPendingAnimations =
- new ArrayList<RenderNodeAnimator>();
private final RippleDrawable mOwner;
/** Bounds used for computing max radius. */
private final Rect mBounds;
- /** ARGB color for drawing this ripple. */
- private int mColor;
-
- private Xfermode mXfermode;
-
/** Maximum ripple radius. */
private float mOuterRadius;
@@ -112,6 +102,10 @@
/** Whether we were canceled externally and should avoid self-removal. */
private boolean mCanceled;
+ private boolean mHasPendingHardwareExit;
+ private int mPendingRadiusDuration;
+ private int mPendingOpacityDuration;
+
/**
* Creates a new ripple.
*/
@@ -217,10 +211,6 @@
* Draws the ripple centered at (0,0) using the specified paint.
*/
public boolean draw(Canvas c, Paint p) {
- // Store the color and xfermode, we might need them later.
- mColor = p.getColor();
- mXfermode = p.getXfermode();
-
final boolean canUseHardware = c.isHardwareAccelerated();
if (mCanUseHardware != canUseHardware && mCanUseHardware) {
// We've switched from hardware to non-hardware mode. Panic.
@@ -229,8 +219,8 @@
mCanUseHardware = canUseHardware;
final boolean hasContent;
- if (canUseHardware && mHardwareAnimating) {
- hasContent = drawHardware((HardwareCanvas) c);
+ if (canUseHardware && (mHardwareAnimating || mHasPendingHardwareExit)) {
+ hasContent = drawHardware((HardwareCanvas) c, p);
} else {
hasContent = drawSoftware(c, p);
}
@@ -238,24 +228,10 @@
return hasContent;
}
- private boolean drawHardware(HardwareCanvas c) {
- // If we have any pending hardware animations, cancel any running
- // animations and start those now.
- final ArrayList<RenderNodeAnimator> pendingAnimations = mPendingAnimations;
- final int N = pendingAnimations.size();
- if (N > 0) {
+ private boolean drawHardware(HardwareCanvas c, Paint p) {
+ if (mHasPendingHardwareExit) {
cancelHardwareAnimations(false);
-
- // We canceled old animations, but we're about to run new ones.
- mHardwareAnimating = true;
-
- for (int i = 0; i < N; i++) {
- pendingAnimations.get(i).setTarget(c);
- pendingAnimations.get(i).start();
- }
-
- mRunningAnimations.addAll(pendingAnimations);
- pendingAnimations.clear();
+ startPendingHardwareExit(c, p);
}
c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
@@ -347,8 +323,6 @@
* Starts the exit animation.
*/
public void exit() {
- cancel();
-
final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
final float remaining;
if (mAnimRadius != null && mAnimRadius.isRunning()) {
@@ -357,19 +331,33 @@
remaining = mOuterRadius;
}
+ cancel();
+
final int radiusDuration = (int) (1000 * Math.sqrt(remaining / (WAVE_TOUCH_UP_ACCELERATION
+ WAVE_TOUCH_DOWN_ACCELERATION) * mDensity) + 0.5);
final int opacityDuration = (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
if (mCanUseHardware) {
- exitHardware(radiusDuration, opacityDuration);
+ createPendingHardwareExit(radiusDuration, opacityDuration);
} else {
exitSoftware(radiusDuration, opacityDuration);
}
}
- private void exitHardware(int radiusDuration, int opacityDuration) {
- mPendingAnimations.clear();
+ private void createPendingHardwareExit(int radiusDuration, int opacityDuration) {
+ mHasPendingHardwareExit = true;
+ mPendingRadiusDuration = radiusDuration;
+ mPendingOpacityDuration = opacityDuration;
+
+ // The animation will start on the next draw().
+ invalidateSelf();
+ }
+
+ private void startPendingHardwareExit(HardwareCanvas c, Paint p) {
+ mHasPendingHardwareExit = false;
+
+ final int radiusDuration = mPendingRadiusDuration;
+ final int opacityDuration = mPendingOpacityDuration;
final float startX = MathUtils.lerp(
mClampedStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
@@ -377,12 +365,8 @@
mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
final float startRadius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
- final Paint paint = getTempPaint();
- paint.setAntiAlias(true);
- paint.setColor(mColor);
- paint.setXfermode(mXfermode);
- paint.setAlpha((int) (Color.alpha(mColor) * mOpacity + 0.5f));
- paint.setStyle(Style.FILL);
+ final Paint paint = getTempPaint(p);
+ paint.setAlpha((int) (paint.getAlpha() * mOpacity + 0.5f));
mPropPaint = CanvasProperty.createPaint(paint);
mPropRadius = CanvasProperty.createFloat(startRadius);
mPropX = CanvasProperty.createFloat(startX);
@@ -391,25 +375,33 @@
final RenderNodeAnimator radiusAnim = new RenderNodeAnimator(mPropRadius, mOuterRadius);
radiusAnim.setDuration(radiusDuration);
radiusAnim.setInterpolator(DECEL_INTERPOLATOR);
+ radiusAnim.setTarget(c);
+ radiusAnim.start();
final RenderNodeAnimator xAnim = new RenderNodeAnimator(mPropX, mOuterX);
xAnim.setDuration(radiusDuration);
xAnim.setInterpolator(DECEL_INTERPOLATOR);
+ xAnim.setTarget(c);
+ xAnim.start();
final RenderNodeAnimator yAnim = new RenderNodeAnimator(mPropY, mOuterY);
yAnim.setDuration(radiusDuration);
yAnim.setInterpolator(DECEL_INTERPOLATOR);
+ yAnim.setTarget(c);
+ yAnim.start();
final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPropPaint,
RenderNodeAnimator.PAINT_ALPHA, 0);
opacityAnim.setDuration(opacityDuration);
opacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
opacityAnim.addListener(mAnimationListener);
+ opacityAnim.setTarget(c);
+ opacityAnim.start();
- mPendingAnimations.add(radiusAnim);
- mPendingAnimations.add(opacityAnim);
- mPendingAnimations.add(xAnim);
- mPendingAnimations.add(yAnim);
+ mRunningAnimations.add(radiusAnim);
+ mRunningAnimations.add(opacityAnim);
+ mRunningAnimations.add(xAnim);
+ mRunningAnimations.add(yAnim);
mHardwareAnimating = true;
@@ -418,8 +410,6 @@
mTweenX = 1;
mTweenY = 1;
mTweenRadius = 1;
-
- invalidateSelf();
}
/**
@@ -455,10 +445,11 @@
}
}
- private Paint getTempPaint() {
+ private Paint getTempPaint(Paint original) {
if (mTempPaint == null) {
mTempPaint = new Paint();
}
+ mTempPaint.set(original);
return mTempPaint;
}
@@ -539,10 +530,7 @@
}
runningAnimations.clear();
- if (cancelPending && !mPendingAnimations.isEmpty()) {
- mPendingAnimations.clear();
- }
-
+ mHasPendingHardwareExit = false;
mHardwareAnimating = false;
}
diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java
index 69847b5..665d736 100644
--- a/graphics/java/android/graphics/drawable/RippleBackground.java
+++ b/graphics/java/android/graphics/drawable/RippleBackground.java
@@ -24,9 +24,7 @@
import android.graphics.CanvasProperty;
import android.graphics.Color;
import android.graphics.Paint;
-import android.graphics.Paint.Style;
import android.graphics.Rect;
-import android.graphics.Xfermode;
import android.util.MathUtils;
import android.view.HardwareCanvas;
import android.view.RenderNodeAnimator;
@@ -53,8 +51,6 @@
// Hardware animators.
private final ArrayList<RenderNodeAnimator> mRunningAnimations =
new ArrayList<RenderNodeAnimator>();
- private final ArrayList<RenderNodeAnimator> mPendingAnimations =
- new ArrayList<RenderNodeAnimator>();
private final RippleDrawable mOwner;
@@ -64,8 +60,6 @@
/** ARGB color for drawing this ripple. */
private int mColor;
- private Xfermode mXfermode;
-
/** Maximum ripple radius. */
private float mOuterRadius;
@@ -98,6 +92,11 @@
/** Whether we have an explicit maximum radius. */
private boolean mHasMaxRadius;
+ private boolean mHasPendingHardwareExit;
+ private int mPendingOpacityDuration;
+ private int mPendingInflectionDuration;
+ private int mPendingInflectionOpacity;
+
/**
* Creates a new ripple.
*/
@@ -144,9 +143,7 @@
* Draws the ripple centered at (0,0) using the specified paint.
*/
public boolean draw(Canvas c, Paint p) {
- // Store the color and xfermode, we might need them later.
mColor = p.getColor();
- mXfermode = p.getXfermode();
final boolean canUseHardware = c.isHardwareAccelerated();
if (mCanUseHardware != canUseHardware && mCanUseHardware) {
@@ -156,8 +153,8 @@
mCanUseHardware = canUseHardware;
final boolean hasContent;
- if (canUseHardware && mHardwareAnimating) {
- hasContent = drawHardware((HardwareCanvas) c);
+ if (canUseHardware && (mHardwareAnimating || mHasPendingHardwareExit)) {
+ hasContent = drawHardware((HardwareCanvas) c, p);
} else {
hasContent = drawSoftware(c, p);
}
@@ -169,24 +166,10 @@
return (mCanUseHardware && mHardwareAnimating) || (mOuterOpacity > 0 && mOuterRadius > 0);
}
- private boolean drawHardware(HardwareCanvas c) {
- // If we have any pending hardware animations, cancel any running
- // animations and start those now.
- final ArrayList<RenderNodeAnimator> pendingAnimations = mPendingAnimations;
- final int N = pendingAnimations.size();
- if (N > 0) {
+ private boolean drawHardware(HardwareCanvas c, Paint p) {
+ if (mHasPendingHardwareExit) {
cancelHardwareAnimations(false);
-
- // We canceled old animations, but we're about to run new ones.
- mHardwareAnimating = true;
-
- for (int i = 0; i < N; i++) {
- pendingAnimations.get(i).setTarget(c);
- pendingAnimations.get(i).start();
- }
-
- mRunningAnimations.addAll(pendingAnimations);
- pendingAnimations.clear();
+ startPendingHardwareExit(c, p);
}
c.drawCircle(mPropOuterX, mPropOuterY, mPropOuterRadius, mPropOuterPaint);
@@ -263,21 +246,32 @@
+ inflectionDuration * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f);
if (mCanUseHardware) {
- exitHardware(opacityDuration, inflectionDuration, inflectionOpacity);
+ createPendingHardwareExit(opacityDuration, inflectionDuration, inflectionOpacity);
} else {
exitSoftware(opacityDuration, inflectionDuration, inflectionOpacity);
}
}
- private void exitHardware(int opacityDuration, int inflectionDuration, int inflectionOpacity) {
- mPendingAnimations.clear();
+ private void createPendingHardwareExit(
+ int opacityDuration, int inflectionDuration, int inflectionOpacity) {
+ mHasPendingHardwareExit = true;
+ mPendingOpacityDuration = opacityDuration;
+ mPendingInflectionDuration = inflectionDuration;
+ mPendingInflectionOpacity = inflectionOpacity;
- final Paint outerPaint = getTempPaint();
- outerPaint.setAntiAlias(true);
- outerPaint.setXfermode(mXfermode);
- outerPaint.setColor(mColor);
- outerPaint.setAlpha((int) (Color.alpha(mColor) * mOuterOpacity + 0.5f));
- outerPaint.setStyle(Style.FILL);
+ // The animation will start on the next draw().
+ invalidateSelf();
+ }
+
+ private void startPendingHardwareExit(HardwareCanvas c, Paint p) {
+ mHasPendingHardwareExit = false;
+
+ final int opacityDuration = mPendingOpacityDuration;
+ final int inflectionDuration = mPendingInflectionDuration;
+ final int inflectionOpacity = mPendingInflectionOpacity;
+
+ final Paint outerPaint = getTempPaint(p);
+ outerPaint.setAlpha((int) (outerPaint.getAlpha() * mOuterOpacity + 0.5f));
mPropOuterPaint = CanvasProperty.createPaint(outerPaint);
mPropOuterRadius = CanvasProperty.createFloat(mOuterRadius);
mPropOuterX = CanvasProperty.createFloat(mOuterX);
@@ -301,8 +295,10 @@
outerFadeOutAnim.setStartDelay(inflectionDuration);
outerFadeOutAnim.setStartValue(inflectionOpacity);
outerFadeOutAnim.addListener(mAnimationListener);
+ outerFadeOutAnim.setTarget(c);
+ outerFadeOutAnim.start();
- mPendingAnimations.add(outerFadeOutAnim);
+ mRunningAnimations.add(outerFadeOutAnim);
} else {
outerOpacityAnim.addListener(mAnimationListener);
}
@@ -314,14 +310,15 @@
outerOpacityAnim.addListener(mAnimationListener);
}
- mPendingAnimations.add(outerOpacityAnim);
+ outerOpacityAnim.setTarget(c);
+ outerOpacityAnim.start();
+
+ mRunningAnimations.add(outerOpacityAnim);
mHardwareAnimating = true;
// Set up the software values to match the hardware end values.
mOuterOpacity = 0;
-
- invalidateSelf();
}
/**
@@ -340,10 +337,11 @@
}
}
- private Paint getTempPaint() {
+ private Paint getTempPaint(Paint original) {
if (mTempPaint == null) {
mTempPaint = new Paint();
}
+ mTempPaint.set(original);
return mTempPaint;
}
@@ -422,10 +420,7 @@
}
runningAnimations.clear();
- if (cancelPending && !mPendingAnimations.isEmpty()) {
- mPendingAnimations.clear();
- }
-
+ mHasPendingHardwareExit = false;
mHardwareAnimating = false;
}
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 8cbc239..13e3d54 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -27,15 +27,19 @@
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
+import android.graphics.Matrix;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PixelFormat;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.PorterDuffXfermode;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
+import android.graphics.Shader;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
@@ -56,7 +60,7 @@
* <ripple android:color="#ffff0000">
* <item android:id="@android:id/mask"
* android:drawable="@android:color/white" />
- * <ripple /></code>
+ * </ripple></code>
* </pre>
* <p>
* If a mask layer is set, the ripple effect will be masked against that layer
@@ -65,15 +69,15 @@
* If no mask layer is set, the ripple effect is masked against the composite
* of the child layers.
* <pre>
- * <code><!-- A blue ripple drawn atop a black rectangle. --/>
+ * <code><!-- A green ripple drawn atop a black rectangle. --/>
* <ripple android:color="#ff00ff00">
* <item android:drawable="@android:color/black" />
- * <ripple />
+ * </ripple>
*
- * <!-- A red ripple drawn atop a drawable resource. --/>
- * <ripple android:color="#ff00ff00">
+ * <!-- A blue ripple drawn atop a drawable resource. --/>
+ * <ripple android:color="#ff0000ff">
* <item android:drawable="@drawable/my_drawable" />
- * <ripple /></code>
+ * </ripple></code>
* </pre>
* <p>
* If no child layers or mask is specified and the ripple is set as a View
@@ -81,16 +85,17 @@
* background within the View's hierarchy. In this case, the drawing region
* may extend outside of the Drawable bounds.
* <pre>
- * <code><!-- An unbounded green ripple. --/>
- * <ripple android:color="#ff0000ff" /></code>
+ * <code><!-- An unbounded red ripple. --/>
+ * <ripple android:color="#ffff0000" /></code>
* </pre>
*
* @attr ref android.R.styleable#RippleDrawable_color
*/
public class RippleDrawable extends LayerDrawable {
- private static final PorterDuffXfermode DST_IN = new PorterDuffXfermode(Mode.DST_IN);
- private static final PorterDuffXfermode SRC_ATOP = new PorterDuffXfermode(Mode.SRC_ATOP);
- private static final PorterDuffXfermode SRC_OVER = new PorterDuffXfermode(Mode.SRC_OVER);
+ private static final int MASK_UNKNOWN = -1;
+ private static final int MASK_NONE = 0;
+ private static final int MASK_CONTENT = 1;
+ private static final int MASK_EXPLICIT = 2;
/**
* Constant for automatically determining the maximum ripple radius.
@@ -123,6 +128,13 @@
/** The current background. May be actively animating or pending entry. */
private RippleBackground mBackground;
+ private Bitmap mMaskBuffer;
+ private BitmapShader mMaskShader;
+ private Canvas mMaskCanvas;
+ private Matrix mMaskMatrix;
+ private PorterDuffColorFilter mMaskColorFilter;
+ private boolean mHasValidMask;
+
/** Whether we expect to draw a background when visible. */
private boolean mBackgroundActive;
@@ -147,9 +159,6 @@
/** Paint used to control appearance of ripples. */
private Paint mRipplePaint;
- /** Paint used to control reveal layer masking. */
- private Paint mMaskingPaint;
-
/** Target density of the display into which ripples are drawn. */
private float mDensity = 1.0f;
@@ -615,37 +624,116 @@
*/
@Override
public void draw(@NonNull Canvas canvas) {
- final boolean hasMask = mMask != null;
- final boolean hasRipples = mRipple != null || mExitingRipplesCount > 0
- || (mBackground != null && mBackground.shouldDraw());
-
// Clip to the dirty bounds, which will be the drawable bounds if we
// have a mask or content and the ripple bounds if we're projecting.
final Rect bounds = getDirtyBounds();
final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
canvas.clipRect(bounds);
- // If we have content, draw it first. If we have ripples and no mask,
- // we'll draw it into a SRC_OVER layer so that we can mask ripples
- // against it using SRC_IN.
- final boolean hasContentLayer = drawContent(canvas, bounds, hasRipples, hasMask);
+ drawContent(canvas);
+ drawBackgroundAndRipples(canvas);
- // Next, try to draw the ripples. If we have a non-opaque mask, we'll
- // draw the ripples into a SRC_OVER layer, draw the mask into a DST_IN
- // layer, and blend.
- if (hasRipples) {
- final boolean hasNonOpaqueMask = hasMask && mMask.getOpacity() != PixelFormat.OPAQUE;
- final boolean hasRippleLayer = drawBackgroundAndRipples(canvas, bounds,
- hasNonOpaqueMask, hasContentLayer);
+ canvas.restoreToCount(saveCount);
+ }
- // If drawing ripples created a layer, we have a non-opaque mask
- // that needs to be blended on top of the ripples with DST_IN.
- if (hasRippleLayer) {
- drawMaskingLayer(canvas, bounds, DST_IN);
+ @Override
+ public void invalidateSelf() {
+ super.invalidateSelf();
+
+ // Force the mask to update on the next draw().
+ mHasValidMask = false;
+ }
+
+ /**
+ * @return whether we need to use a mask
+ */
+ private void updateMaskShaderIfNeeded() {
+ if (mHasValidMask) {
+ return;
+ }
+
+ final int maskType = getMaskType();
+ if (maskType == MASK_UNKNOWN) {
+ return;
+ }
+
+ mHasValidMask = true;
+
+ if (maskType == MASK_NONE) {
+ if (mMaskBuffer != null) {
+ mMaskBuffer.recycle();
+ mMaskBuffer = null;
+ mMaskShader = null;
+ mMaskCanvas = null;
+ }
+ mMaskMatrix = null;
+ mMaskColorFilter = null;
+ return;
+ }
+
+ // Ensure we have a correctly-sized buffer.
+ final Rect bounds = getBounds();
+ if (mMaskBuffer == null
+ || mMaskBuffer.getWidth() != bounds.width()
+ || mMaskBuffer.getHeight() != bounds.height()) {
+ if (mMaskBuffer != null) {
+ mMaskBuffer.recycle();
+ }
+
+ mMaskBuffer = Bitmap.createBitmap(
+ bounds.width(), bounds.height(), Bitmap.Config.ALPHA_8);
+ mMaskShader = new BitmapShader(mMaskBuffer,
+ Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+ mMaskCanvas = new Canvas(mMaskBuffer);
+ } else {
+ mMaskBuffer.eraseColor(Color.TRANSPARENT);
+ }
+
+ if (mMaskMatrix == null) {
+ mMaskMatrix = new Matrix();
+ } else {
+ mMaskMatrix.reset();
+ }
+
+ if (mMaskColorFilter == null) {
+ mMaskColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_IN);
+ }
+
+ // Draw the appropriate mask.
+ if (maskType == MASK_EXPLICIT) {
+ drawMask(mMaskCanvas);
+ } else if (maskType == MASK_CONTENT) {
+ drawContent(mMaskCanvas);
+ }
+ }
+
+ private int getMaskType() {
+ if (mRipple == null && mExitingRipplesCount <= 0
+ && (mBackground == null || !mBackground.shouldDraw())) {
+ // We might need a mask later.
+ return MASK_UNKNOWN;
+ }
+
+ if (mMask != null) {
+ if (mMask.getOpacity() == PixelFormat.OPAQUE) {
+ // Clipping handles opaque explicit masks.
+ return MASK_NONE;
+ } else {
+ return MASK_EXPLICIT;
}
}
- canvas.restoreToCount(saveCount);
+ // Check for non-opaque, non-mask content.
+ final ChildDrawable[] array = mLayerState.mChildren;
+ final int count = mLayerState.mNum;
+ for (int i = 0; i < count; i++) {
+ if (array[i].mDrawable.getOpacity() != PixelFormat.OPAQUE) {
+ return MASK_CONTENT;
+ }
+ }
+
+ // Clipping handles opaque content.
+ return MASK_NONE;
}
/**
@@ -678,65 +766,65 @@
return -1;
}
- private boolean drawContent(Canvas canvas, Rect bounds, boolean hasRipples, boolean hasMask) {
+ private void drawContent(Canvas canvas) {
+ // Draw everything except the mask.
final ChildDrawable[] array = mLayerState.mChildren;
final int count = mLayerState.mNum;
-
- boolean needsLayer = false;
-
- if (hasRipples && !hasMask) {
- // If we only have opaque content, we don't really need a layer
- // because the ripples will be clipped to the drawable bounds.
- for (int i = 0; i < count; i++) {
- if (array[i].mDrawable.getOpacity() != PixelFormat.OPAQUE) {
- needsLayer = true;
- break;
- }
- }
- }
-
- if (needsLayer) {
- canvas.saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom,
- getMaskingPaint(SRC_OVER));
- }
-
- // Draw everything except the mask.
for (int i = 0; i < count; i++) {
if (array[i].mId != R.id.mask) {
array[i].mDrawable.draw(canvas);
}
}
-
- return needsLayer;
}
- private boolean drawBackgroundAndRipples(
- Canvas canvas, Rect bounds, boolean hasNonOpaqueMask, boolean hasContentLayer) {
- if (hasNonOpaqueMask) {
- final Paint p = getMaskingPaint(SRC_OVER);
- canvas.saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, p);
+ private void drawBackgroundAndRipples(Canvas canvas) {
+ final Ripple active = mRipple;
+ final RippleBackground background = mBackground;
+ final int count = mExitingRipplesCount;
+ if (active == null && count <= 0 && (background == null || !background.shouldDraw())) {
+ // Move along, nothing to draw here.
+ return;
}
- final PorterDuffXfermode mode = hasContentLayer ? SRC_ATOP : SRC_OVER;
final float x = mHotspotBounds.exactCenterX();
final float y = mHotspotBounds.exactCenterY();
canvas.translate(x, y);
- final Paint p = getRipplePaint();
- p.setXfermode(mode);
+ updateMaskShaderIfNeeded();
+
+ // Position the shader to account for canvas translation.
+ if (mMaskShader != null) {
+ mMaskMatrix.setTranslate(-x, -y);
+ mMaskShader.setLocalMatrix(mMaskMatrix);
+ }
// Grab the color for the current state and cut the alpha channel in
// half so that the ripple and background together yield full alpha.
final int color = mState.mColor.getColorForState(getState(), Color.BLACK);
- final int alpha = (Color.alpha(color) / 2) << 24;
- p.setColor(color & 0xFFFFFF | alpha);
+ final int halfAlpha = (Color.alpha(color) / 2) << 24;
+ final Paint p = getRipplePaint();
- final RippleBackground background = mBackground;
+ if (mMaskColorFilter != null) {
+ // The ripple timing depends on the paint's alpha value, so we need
+ // to push just the alpha channel into the paint and let the filter
+ // handle the full-alpha color.
+ final int fullAlphaColor = color | (0xFF << 24);
+ mMaskColorFilter.setColor(fullAlphaColor);
+
+ p.setColor(halfAlpha);
+ p.setColorFilter(mMaskColorFilter);
+ p.setShader(mMaskShader);
+ } else {
+ final int halfAlphaColor = (color & 0xFFFFFF) | halfAlpha;
+ p.setColor(halfAlphaColor);
+ p.setColorFilter(null);
+ p.setShader(null);
+ }
+
if (background != null && background.shouldDraw()) {
background.draw(canvas, p);
}
- final int count = mExitingRipplesCount;
if (count > 0) {
final Ripple[] ripples = mExitingRipples;
for (int i = 0; i < count; i++) {
@@ -744,27 +832,15 @@
}
}
- final Ripple active = mRipple;
if (active != null) {
active.draw(canvas, p);
}
canvas.translate(-x, -y);
-
- // Returns true if a layer was created.
- return hasNonOpaqueMask;
}
- private int drawMaskingLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
- final int restoreToCount = canvas.saveLayer(bounds.left, bounds.top,
- bounds.right, bounds.bottom, getMaskingPaint(mode));
-
- // Ensure that DST_IN blends using the entire layer.
- canvas.drawColor(Color.TRANSPARENT);
-
+ private void drawMask(Canvas canvas) {
mMask.draw(canvas);
-
- return restoreToCount;
}
private Paint getRipplePaint() {
@@ -776,15 +852,6 @@
return mRipplePaint;
}
- private Paint getMaskingPaint(PorterDuffXfermode xfermode) {
- if (mMaskingPaint == null) {
- mMaskingPaint = new Paint();
- }
- mMaskingPaint.setXfermode(xfermode);
- mMaskingPaint.setAlpha(0xFF);
- return mMaskingPaint;
- }
-
@Override
public Rect getDirtyBounds() {
if (isProjected()) {
@@ -832,6 +899,10 @@
// LayerDrawable creates a new state using createConstantState, so
// this should always be a safe cast.
mState = (RippleState) mLayerState;
+
+ // The locally cached drawable may have changed.
+ mMask = findDrawableByLayerId(R.id.mask);
+
return this;
}
diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h
index 6b84494..5a28be5 100644
--- a/include/androidfw/ResourceTypes.h
+++ b/include/androidfw/ResourceTypes.h
@@ -1828,6 +1828,9 @@
const ResTable_config* config,
Entry* outEntry) const;
+ uint32_t findEntry(const PackageGroup* group, ssize_t typeIndex, const char16_t* name,
+ size_t nameLen, uint32_t* outTypeSpecFlags) const;
+
status_t parsePackage(
const ResTable_package* const pkg, const Header* const header);
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 6dfb4dc..d7b9765 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -4173,6 +4173,9 @@
String8(name, nameLen).string(),
String8(package, packageLen).string()));
+ const String16 attr("attr");
+ const String16 attrPrivate("^attr-private");
+
const size_t NG = mPackageGroups.size();
for (size_t ig=0; ig<NG; ig++) {
const PackageGroup* group = mPackageGroups[ig];
@@ -4185,64 +4188,72 @@
const size_t packageCount = group->packages.size();
for (size_t pi = 0; pi < packageCount; pi++) {
- ssize_t ti = group->packages[pi]->typeStrings.indexOfString(type, typeLen);
- if (ti < 0) {
- continue;
- }
+ const char16_t* targetType = type;
+ size_t targetTypeLen = typeLen;
- ti += group->packages[pi]->typeIdOffset;
-
- const TypeList& typeList = group->types[ti];
- if (typeList.isEmpty()) {
- TABLE_NOISY(printf("Expected type structure not found in package %s for index %d\n",
- String8(group->name).string(), ti));
- continue;
- }
-
- const size_t typeCount = typeList.size();
- for (size_t i = 0; i < typeCount; i++) {
- const Type* t = typeList[i];
- const ssize_t ei = t->package->keyStrings.indexOfString(name, nameLen);
- if (ei < 0) {
+ do {
+ ssize_t ti = group->packages[pi]->typeStrings.indexOfString(
+ targetType, targetTypeLen);
+ if (ti < 0) {
continue;
}
- const size_t configCount = t->configs.size();
- for (size_t j = 0; j < configCount; j++) {
- const TypeVariant tv(t->configs[j]);
- for (TypeVariant::iterator iter = tv.beginEntries();
- iter != tv.endEntries();
- iter++) {
- const ResTable_entry* entry = *iter;
- if (entry == NULL) {
- continue;
- }
+ ti += group->packages[pi]->typeIdOffset;
- if (dtohl(entry->key.index) == (size_t) ei) {
- uint32_t resId = Res_MAKEID(group->id - 1, ti, iter.index());
- if (outTypeSpecFlags) {
- Entry result;
- if (getEntry(group, ti, iter.index(), NULL, &result) != NO_ERROR) {
- ALOGW("Failed to find spec flags for %s:%s/%s (0x%08x)",
- String8(group->name).string(),
- String8(String16(type, typeLen)).string(),
- String8(String16(name, nameLen)).string(),
- resId);
- return 0;
- }
- *outTypeSpecFlags = result.specFlags;
-
- if (fakePublic) {
- *outTypeSpecFlags |= ResTable_typeSpec::SPEC_PUBLIC;
- }
- }
- return resId;
- }
+ const uint32_t identifier = findEntry(group, ti, name, nameLen,
+ outTypeSpecFlags);
+ if (identifier != 0) {
+ if (fakePublic && outTypeSpecFlags) {
+ *outTypeSpecFlags |= ResTable_typeSpec::SPEC_PUBLIC;
}
+ return identifier;
+ }
+ } while (strzcmp16(attr.string(), attr.size(), targetType, targetTypeLen) == 0
+ && (targetType = attrPrivate.string())
+ && (targetTypeLen = attrPrivate.size())
+ );
+ }
+ break;
+ }
+ return 0;
+}
+
+uint32_t ResTable::findEntry(const PackageGroup* group, ssize_t typeIndex, const char16_t* name,
+ size_t nameLen, uint32_t* outTypeSpecFlags) const {
+ const TypeList& typeList = group->types[typeIndex];
+ const size_t typeCount = typeList.size();
+ for (size_t i = 0; i < typeCount; i++) {
+ const Type* t = typeList[i];
+ const ssize_t ei = t->package->keyStrings.indexOfString(name, nameLen);
+ if (ei < 0) {
+ continue;
+ }
+
+ const size_t configCount = t->configs.size();
+ for (size_t j = 0; j < configCount; j++) {
+ const TypeVariant tv(t->configs[j]);
+ for (TypeVariant::iterator iter = tv.beginEntries();
+ iter != tv.endEntries();
+ iter++) {
+ const ResTable_entry* entry = *iter;
+ if (entry == NULL) {
+ continue;
+ }
+
+ if (dtohl(entry->key.index) == (size_t) ei) {
+ uint32_t resId = Res_MAKEID(group->id - 1, typeIndex, iter.index());
+ if (outTypeSpecFlags) {
+ Entry result;
+ if (getEntry(group, typeIndex, iter.index(), NULL, &result) != NO_ERROR) {
+ ALOGW("Failed to find spec flags for 0x%08x", resId);
+ return 0;
+ }
+ *outTypeSpecFlags = result.specFlags;
+ }
+ return resId;
}
}
}
- break;
}
return 0;
}
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 1029fcc..4513643 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -648,7 +648,7 @@
maxChannels = 48;
} else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_VORBIS)) {
bitRates = Range.create(32000, 500000);
- sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000, 192000 };
+ sampleRateRange = Range.create(8000, 192000);
maxChannels = 255;
} else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_OPUS)) {
bitRates = Range.create(6000, 510000);
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index da6f3fc..94c9690 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -42,7 +42,7 @@
private static final String TAG = "TvInputSessionWrapper";
private static final int MESSAGE_HANDLING_DURATION_THRESHOLD_MILLIS = 50;
- private static final int MESSAGE_TUNE_DURATION_THRESHOLD_MILLIS = 1000;
+ private static final int MESSAGE_TUNE_DURATION_THRESHOLD_MILLIS = 2000;
private static final int DO_RELEASE = 1;
private static final int DO_SET_MAIN = 2;
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index 779d486..26aedbd 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -323,24 +323,22 @@
* <p>
* This should be called as soon as possible during the service's startup.
* It may only be called once.
- *
- * @return The media session token, must not be null.
*/
public void setSessionToken(final MediaSession.Token token) {
if (token == null) {
throw new IllegalArgumentException("Session token may not be null.");
}
+ if (mSession != null) {
+ throw new IllegalStateException("The session token has already been set.");
+ }
+ mSession = token;
mHandler.post(new Runnable() {
@Override
public void run() {
- if (mSession != null) {
- throw new IllegalStateException("The session token has already been set.");
- }
- mSession = token;
for (IBinder key : mConnections.keySet()) {
ConnectionRecord connection = mConnections.get(key);
try {
- connection.callbacks.onConnect(connection.root.getRootId(), mSession,
+ connection.callbacks.onConnect(connection.root.getRootId(), token,
connection.root.getExtras());
} catch (RemoteException e) {
Log.w(TAG, "Connection for " + connection.pkg + " is no longer valid.");
diff --git a/packages/Keyguard/res/values/strings.xml b/packages/Keyguard/res/values/strings.xml
index b97b5a3..368d789 100644
--- a/packages/Keyguard/res/values/strings.xml
+++ b/packages/Keyguard/res/values/strings.xml
@@ -380,9 +380,6 @@
you will be asked to unlock your phone using an email account.\n\n
Try again in <xliff:g id="number">%d</xliff:g> seconds.
</string>
- <!-- Sequence of characters used to separate message strings in keyguard. Typically just em-dash
- with spaces on either side. [CHAR LIMIT=3] -->
- <string name="kg_text_message_separator" product="default">" \u2014 "</string>
<!-- The delete-widget drop target button text -->
<string name="kg_reordering_delete_drop_target_text">Remove</string>
diff --git a/packages/Keyguard/src/com/android/keyguard/CarrierText.java b/packages/Keyguard/src/com/android/keyguard/CarrierText.java
index 55bfe49..7f4ce59 100644
--- a/packages/Keyguard/src/com/android/keyguard/CarrierText.java
+++ b/packages/Keyguard/src/com/android/keyguard/CarrierText.java
@@ -16,11 +16,17 @@
package com.android.keyguard;
+import java.util.List;
+import java.util.Locale;
+
import android.content.Context;
import android.content.res.TypedArray;
-import android.text.method.SingleLineTransformationMethod;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
import android.text.TextUtils;
+import android.text.method.SingleLineTransformationMethod;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.View;
import android.widget.TextView;
@@ -28,29 +34,19 @@
import com.android.internal.telephony.IccCardConstants.State;
import com.android.internal.widget.LockPatternUtils;
-import java.util.Locale;
-
public class CarrierText extends TextView {
+ private static final boolean DEBUG = KeyguardConstants.DEBUG;
+ private static final String TAG = "CarrierText";
+
private static CharSequence mSeparator;
private LockPatternUtils mLockPatternUtils;
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
- private CharSequence mPlmn;
- private CharSequence mSpn;
- private State mSimState;
-
@Override
- public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) {
- mPlmn = plmn;
- mSpn = spn;
- updateCarrierText(mSimState, mPlmn, mSpn);
- }
-
- @Override
- public void onSimStateChanged(int subId, int slotId, State simState) {
- mSimState = simState;
- updateCarrierText(mSimState, mPlmn, mSpn);
+ public void onRefreshCarrierInfo() {
+ updateCarrierText();
}
public void onScreenTurnedOff(int why) {
@@ -93,14 +89,50 @@
setTransformationMethod(new CarrierTextTransformationMethod(mContext, useAllCaps));
}
- protected void updateCarrierText(State simState, CharSequence plmn, CharSequence spn) {
- setText(getCarrierTextForSimState(simState, plmn, spn));
+ protected void updateCarrierText() {
+ boolean allSimsMissing = true;
+ CharSequence displayText = null;
+
+ List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
+ final int N = subs.size();
+ if (DEBUG) Log.d(TAG, "updateCarrierText(): " + N);
+ for (int i = 0; i < N; i++) {
+ State simState = mKeyguardUpdateMonitor.getSimState(subs.get(i).getSubscriptionId());
+ CharSequence carrierName = subs.get(i).getCarrierName();
+ CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
+ if (DEBUG) Log.d(TAG, "Handling " + simState + " " + carrierName);
+ if (carrierTextForSimState != null) {
+ allSimsMissing = false;
+ displayText = concatenate(displayText, carrierTextForSimState);
+ }
+ }
+ if (allSimsMissing) {
+ if (N != 0) {
+ // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
+ // This depends on mPlmn containing the text "Emergency calls only" when the radio
+ // has some connectivity. Otherwise, it should be null or empty and just show
+ // "No SIM card"
+ // Grab the first subscripton, because they all should contain the emergency text,
+ // described above.
+ displayText = makeCarrierStringOnEmergencyCapable(
+ getContext().getText(R.string.keyguard_missing_sim_message_short),
+ subs.get(0).getCarrierName());
+ } else {
+ // We don't have a SubscriptionInfo to get the emergency calls only from.
+ // Lets just make it ourselves.
+ displayText = makeCarrierStringOnEmergencyCapable(
+ getContext().getText(R.string.keyguard_missing_sim_message_short),
+ getContext().getText(com.android.internal.R.string.emergency_calls_only));
+ }
+ }
+ setText(displayText);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mSeparator = getResources().getString(R.string.kg_text_message_separator);
+ mSeparator = getResources().getString(
+ com.android.internal.R.string.kg_text_message_separator);
final boolean screenOn = KeyguardUpdateMonitor.getInstance(mContext).isScreenOn();
setSelected(screenOn); // Allow marquee to work.
}
@@ -108,13 +140,14 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mCallback);
+ mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+ mKeyguardUpdateMonitor.registerCallback(mCallback);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mCallback);
+ mKeyguardUpdateMonitor.removeCallback(mCallback);
}
/**
@@ -122,36 +155,31 @@
* and SPN as well as device capabilities, such as being emergency call capable.
*
* @param simState
- * @param plmn
+ * @param text
* @param spn
- * @return
+ * @return Carrier text if not in missing state, null otherwise.
*/
private CharSequence getCarrierTextForSimState(IccCardConstants.State simState,
- CharSequence plmn, CharSequence spn) {
+ CharSequence text) {
CharSequence carrierText = null;
StatusMode status = getStatusForIccState(simState);
switch (status) {
case Normal:
- carrierText = concatenate(plmn, spn);
+ carrierText = text;
break;
case SimNotReady:
- carrierText = null; // nothing to display yet.
+ // Null is reserved for denoting missing, in this case we have nothing to display.
+ carrierText = ""; // nothing to display yet.
break;
case NetworkLocked:
carrierText = makeCarrierStringOnEmergencyCapable(
- mContext.getText(R.string.keyguard_network_locked_message), plmn);
+ mContext.getText(R.string.keyguard_network_locked_message), text);
break;
case SimMissing:
- // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
- // This depends on mPlmn containing the text "Emergency calls only" when the radio
- // has some connectivity. Otherwise, it should be null or empty and just show
- // "No SIM card"
- carrierText = makeCarrierStringOnEmergencyCapable(
- getContext().getText(R.string.keyguard_missing_sim_message_short),
- plmn);
+ carrierText = null;
break;
case SimPermDisabled:
@@ -160,21 +188,19 @@
break;
case SimMissingLocked:
- carrierText = makeCarrierStringOnEmergencyCapable(
- getContext().getText(R.string.keyguard_missing_sim_message_short),
- plmn);
+ carrierText = null;
break;
case SimLocked:
carrierText = makeCarrierStringOnEmergencyCapable(
getContext().getText(R.string.keyguard_sim_locked_message),
- plmn);
+ text);
break;
case SimPukLocked:
carrierText = makeCarrierStringOnEmergencyCapable(
getContext().getText(R.string.keyguard_sim_puk_locked_message),
- plmn);
+ text);
break;
}
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java b/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java
index 9bc2a4d..236cbf6 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java
@@ -153,7 +153,8 @@
mUpdateMonitor.registerCallback(mInfoCallback);
mHandler = new Handler(Looper.myLooper());
- mSeparator = getResources().getString(R.string.kg_text_message_separator);
+ mSeparator = getResources().getString(
+ com.android.internal.R.string.kg_text_message_separator);
update();
}
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index ff07dd7..8458ae0 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -97,7 +97,6 @@
// Callback messages
private static final int MSG_TIME_UPDATE = 301;
private static final int MSG_BATTERY_UPDATE = 302;
- private static final int MSG_CARRIER_INFO_UPDATE = 303;
private static final int MSG_SIM_STATE_CHANGE = 304;
private static final int MSG_RINGER_MODE_CHANGED = 305;
private static final int MSG_PHONE_STATE_CHANGED = 306;
@@ -126,8 +125,6 @@
private final Context mContext;
HashMap<Integer, SimData> mSimDatas = new HashMap<Integer, SimData>();
- private CharSequence mTelephonyPlmn;
- private CharSequence mTelephonySpn;
private int mRingMode;
private int mPhoneState;
private boolean mKeyguardIsVisible;
@@ -168,9 +165,6 @@
case MSG_BATTERY_UPDATE:
handleBatteryUpdate((BatteryStatus) msg.obj);
break;
- case MSG_CARRIER_INFO_UPDATE:
- handleCarrierInfoUpdate();
- break;
case MSG_SIM_STATE_CHANGE:
handleSimStateChange(msg.arg1, msg.arg2, (State) msg.obj);
break;
@@ -290,6 +284,7 @@
KeyguardUpdateMonitorCallback cb = mCallbacks.get(j).get();
if (cb != null) {
cb.onSimStateChanged(data.subId, data.slotId, data.simState);
+ cb.onRefreshCarrierInfo();
}
}
}
@@ -435,10 +430,6 @@
|| Intent.ACTION_TIME_CHANGED.equals(action)
|| Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
mHandler.sendEmptyMessage(MSG_TIME_UPDATE);
- } else if (TelephonyIntents.SPN_STRINGS_UPDATED_ACTION.equals(action)) {
- mTelephonyPlmn = getTelephonyPlmnFrom(intent);
- mTelephonySpn = getTelephonySpnFrom(intent);
- mHandler.sendEmptyMessage(MSG_CARRIER_INFO_UPDATE);
} else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
final int status = intent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN);
final int plugged = intent.getIntExtra(EXTRA_PLUGGED, 0);
@@ -682,7 +673,6 @@
// Take a guess at initial SIM state, battery status and PLMN until we get an update
mBatteryStatus = new BatteryStatus(BATTERY_STATUS_UNKNOWN, 100, 0, 0);
- mTelephonyPlmn = getDefaultPlmn();
// Watch for interesting updates
final IntentFilter filter = new IntentFilter();
@@ -692,7 +682,6 @@
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
- filter.addAction(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION);
filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
filter.addAction(Intent.ACTION_USER_REMOVED);
context.registerReceiver(mBroadcastReceiver, filter);
@@ -940,21 +929,6 @@
}
/**
- * Handle {@link #MSG_CARRIER_INFO_UPDATE}
- */
- private void handleCarrierInfoUpdate() {
- if (DEBUG) Log.d(TAG, "handleCarrierInfoUpdate: plmn = " + mTelephonyPlmn
- + ", spn = " + mTelephonySpn);
-
- for (int i = 0; i < mCallbacks.size(); i++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
- if (cb != null) {
- cb.onRefreshCarrierInfo(mTelephonyPlmn, mTelephonySpn);
- }
- }
- }
-
- /**
* Handle {@link #MSG_SIM_STATE_CHANGE}
*/
private void handleSimStateChange(int subId, int slotId, State state) {
@@ -1087,18 +1061,6 @@
}
/**
- * @param intent The intent with action {@link TelephonyIntents#SPN_STRINGS_UPDATED_ACTION}
- * @return The string to use for the plmn, or null if it should not be shown.
- */
- private CharSequence getTelephonyPlmnFrom(Intent intent) {
- if (intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) {
- final String plmn = intent.getStringExtra(TelephonyIntents.EXTRA_PLMN);
- return (plmn != null) ? plmn : getDefaultPlmn();
- }
- return null;
- }
-
- /**
* @return The default plmn (no service)
*/
private CharSequence getDefaultPlmn() {
@@ -1106,20 +1068,6 @@
}
/**
- * @param intent The intent with action {@link Telephony.Intents#SPN_STRINGS_UPDATED_ACTION}
- * @return The string to use for the plmn, or null if it should not be shown.
- */
- private CharSequence getTelephonySpnFrom(Intent intent) {
- if (intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) {
- final String spn = intent.getStringExtra(TelephonyIntents.EXTRA_SPN);
- if (spn != null) {
- return spn;
- }
- }
- return null;
- }
-
- /**
* Remove the given observer's callback.
*
* @param callback The callback to remove
@@ -1159,7 +1107,7 @@
callback.onTimeChanged();
callback.onRingerModeChanged(mRingMode);
callback.onPhoneStateChanged(mPhoneState);
- callback.onRefreshCarrierInfo(mTelephonyPlmn, mTelephonySpn);
+ callback.onRefreshCarrierInfo();
callback.onClockVisibilityChanged();
for (Entry<Integer, SimData> data : mSimDatas.entrySet()) {
final SimData state = data.getValue();
@@ -1219,14 +1167,6 @@
}
}
- public CharSequence getTelephonyPlmn() {
- return mTelephonyPlmn;
- }
-
- public CharSequence getTelephonySpn() {
- return mTelephonySpn;
- }
-
/**
* @return Whether the device is provisioned (whether they have gone through
* the setup wizard)
@@ -1289,7 +1229,7 @@
return false;
}
- private State getSimState(int subId) {
+ public State getSimState(int subId) {
if (mSimDatas.containsKey(subId)) {
return mSimDatas.get(subId).simState;
} else {
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index de72ddd..c2f355a 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -49,12 +49,8 @@
/**
* Called when the carrier PLMN or SPN changes.
- *
- * @param plmn The operator name of the registered network. May be null if it shouldn't
- * be displayed.
- * @param spn The service provider name. May be null if it shouldn't be displayed.
*/
- public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) { }
+ public void onRefreshCarrierInfo() { }
/**
* Called when the ringer mode changes.
diff --git a/packages/SystemUI/res/layout/lland.xml b/packages/SystemUI/res/layout/lland.xml
index 053225d..71a16c9 100644
--- a/packages/SystemUI/res/layout/lland.xml
+++ b/packages/SystemUI/res/layout/lland.xml
@@ -31,10 +31,10 @@
android:textSize="32sp"
android:textColor="#FFAAAAAA"
android:layout_marginTop="32dp"
- android:layout_marginLeft="16dp"
- android:layout_gravity="top|left"
- android:paddingLeft="16dp"
- android:paddingRight="16dp"
+ android:layout_marginStart="16dp"
+ android:layout_gravity="top|start"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:background="@drawable/scorecard"
diff --git a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
index 6337956..191cba5 100644
--- a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
+++ b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
@@ -9,6 +9,7 @@
36001 sysui_heads_up_status (key|3),(visible|1)
36002 sysui_fullscreen_notification (key|3)
36003 sysui_heads_up_escalation (key|3)
+36004 sysui_status_bar_state (state|1)
# ---------------------------
# PhoneStatusBarView.java
diff --git a/packages/SystemUI/src/com/android/systemui/egg/LLand.java b/packages/SystemUI/src/com/android/systemui/egg/LLand.java
index 5de09a3..fa257b1 100644
--- a/packages/SystemUI/src/com/android/systemui/egg/LLand.java
+++ b/packages/SystemUI/src/com/android/systemui/egg/LLand.java
@@ -178,6 +178,9 @@
setFocusable(true);
PARAMS = new Params(getResources());
mTimeOfDay = irand(0, SKIES.length);
+
+ // we assume everything will be laid out left|top
+ setLayoutDirection(LAYOUT_DIRECTION_LTR);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 206fc43..7ac0daf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -72,6 +72,7 @@
import com.android.systemui.statusbar.phone.StatusBarWindowManager;
import java.util.ArrayList;
+import java.util.List;
import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
@@ -1217,7 +1218,12 @@
synchronized (this) {
if (mBootCompleted) {
final UserHandle currentUser = new UserHandle(mLockPatternUtils.getCurrentUser());
- mContext.sendBroadcastAsUser(USER_PRESENT_INTENT, currentUser);
+ final UserManager um = (UserManager) mContext.getSystemService(
+ Context.USER_SERVICE);
+ List <UserInfo> userHandles = um.getProfiles(currentUser.getIdentifier());
+ for (UserInfo ui : userHandles) {
+ mContext.sendBroadcastAsUser(USER_PRESENT_INTENT, ui.getUserHandle());
+ }
} else {
mBootSendUserPresent = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 57ac4b0..2b6ac26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -683,15 +683,12 @@
Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d",
n, thisUserId, notificationUserId));
}
- synchronized (mCurrentProfiles) {
- return notificationUserId == UserHandle.USER_ALL
- || mCurrentProfiles.get(notificationUserId) != null;
- }
+ return isCurrentProfile(notificationUserId);
}
protected boolean isCurrentProfile(int userId) {
synchronized (mCurrentProfiles) {
- return mCurrentProfiles.get(userId) != null;
+ return userId == UserHandle.USER_ALL || mCurrentProfiles.get(userId) != null;
}
}
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 7a3b5e4..ec2d30c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -3795,14 +3795,19 @@
* @param state The {@link StatusBarState} to set.
*/
public void setBarState(int state) {
- // If we're visible and switched to SHADE_LOCKED (the user dragged down
- // on the lockscreen), clear notification LED, vibration, ringing.
- // Other transitions are covered in handleVisibleToUserChanged().
- if (mVisible && mState != state && state == StatusBarState.SHADE_LOCKED) {
- try {
- mBarService.clearNotificationEffects();
- } catch (RemoteException e) {
- // Ignore.
+ if (state != mState) {
+ EventLogTags.writeSysuiStatusBarState(state);
+
+ // If we're visible and switched to SHADE_LOCKED (the user dragged
+ // down on the lockscreen), clear notification LED, vibration,
+ // ringing.
+ // Other transitions are covered in handleVisibleToUserChanged().
+ if (mVisible && state == StatusBarState.SHADE_LOCKED) {
+ try {
+ mBarService.clearNotificationEffects();
+ } catch (RemoteException e) {
+ // Ignore.
+ }
}
}
mState = state;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 2781890..89aebe8 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3314,8 +3314,6 @@
public int mAccessibilityFocusedWindowId = INVALID_WINDOW_ID;
public long mAccessibilityFocusNodeId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
- public AccessibilityEvent mShowingFocusedWindowEvent;
-
private boolean mTouchInteractionInProgress;
private boolean canDispatchAccessibilityEventLocked(AccessibilityEvent event) {
@@ -3324,19 +3322,6 @@
// All events that are for changes in a global window
// state should *always* be dispatched.
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
- if (mWindowsForAccessibilityCallback != null) {
- // OK, this is fun. Sometimes the focused window is notified
- // it has focus before being shown. Historically this event
- // means that the window is focused and can be introspected.
- // But we still have not gotten the window state from the
- // window manager, so delay the notification until then.
- AccessibilityWindowInfo window = findWindowById(event.getWindowId());
- if (window == null) {
- mShowingFocusedWindowEvent = AccessibilityEvent.obtain(event);
- return false;
- }
- }
- // $fall-through$
case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:
case AccessibilityEvent.TYPE_ANNOUNCEMENT:
// All events generated by the user touching the
@@ -3428,18 +3413,6 @@
}
notifyWindowsChanged();
-
- // If we are delaying a window state change event as the window
- // source was showing when it was fired, now is the time to send.
- if (mShowingFocusedWindowEvent != null) {
- final int windowId = mShowingFocusedWindowEvent.getWindowId();
- AccessibilityWindowInfo window = findWindowById(windowId);
- if (window != null) {
- // Sending does the recycle.
- sendAccessibilityEvent(mShowingFocusedWindowEvent, mCurrentUserId);
- }
- mShowingFocusedWindowEvent = null;
- }
}
public boolean computePartialInteractiveRegionForWindowLocked(int windowId,
diff --git a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
index fcca788..4f7e57f 100644
--- a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
+++ b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
@@ -220,7 +220,7 @@
// OR 3. it looks like we use the same home app + version as before, but
// the signatures don't match so we treat them as different apps.
final boolean needHomeBackup = (homeVersion != mStoredHomeVersion)
- || Objects.equals(home, mStoredHomeComponent)
+ || !Objects.equals(home, mStoredHomeComponent)
|| (home != null
&& !BackupManagerService.signaturesMatch(mStoredHomeSigHashes, homeInfo));
if (needHomeBackup) {
@@ -534,6 +534,7 @@
+ ", redoing from start");
return;
}
+ pkg = in.readUTF();
} else {
// This is an older version of the state file in which the lead element
// is not a STATE_FILE_VERSION string. If that's the case, we want to
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 7b3e137..47bf188 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -61,6 +61,7 @@
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Locale;
+import java.util.Random;
import java.util.TimeZone;
import static android.app.AlarmManager.RTC_WAKEUP;
@@ -114,8 +115,12 @@
final Object mLock = new Object();
long mNativeData;
- private long mNextWakeup;
+
+ private final Random mFuzzer = new Random();
+ private long mNextWakeupBatchStart; // nominal start of next wakeup's delivery window
+ private long mNextWakeup; // actual scheduled next wakeup within that window
private long mNextNonWakeup;
+
int mBroadcastRefCount = 0;
PowerManager.WakeLock mWakeLock;
boolean mLastWakeLockUnimportantForLogging;
@@ -367,14 +372,27 @@
static class BatchTimeOrder implements Comparator<Batch> {
public int compare(Batch b1, Batch b2) {
- long when1 = b1.start;
- long when2 = b2.start;
- if (when1 - when2 > 0) {
+ final long start1 = b1.start;
+ final long start2 = b2.start;
+ if (start1 > start2) {
return 1;
}
- if (when1 - when2 < 0) {
+ if (start1 < start2) {
return -1;
}
+
+ // Identical trigger times. As a secondary ordering, require that
+ // the batch with the shorter allowable delivery window sorts first.
+ final long interval1 = b1.end - b1.start;
+ final long interval2 = b2.end - b2.start;
+ if (interval1 > interval2) {
+ return 1;
+ }
+ if (interval2 < interval1) {
+ return -1;
+ }
+
+ // equal start + delivery window => they're identical
return 0;
}
}
@@ -597,7 +615,7 @@
@Override
public void onStart() {
mNativeData = init();
- mNextWakeup = mNextNonWakeup = 0;
+ mNextWakeup = mNextWakeupBatchStart = mNextNonWakeup = 0;
// We have to set current TimeZone info to kernel
// because kernel doesn't keep this after reboot
@@ -805,8 +823,9 @@
"AlarmManager.set");
}
+ // Exact alarms are standalone; inexact get batched together
setImpl(type, triggerAtTime, windowLength, interval, operation,
- false, workSource, alarmClock);
+ windowLength == AlarmManager.WINDOW_EXACT, workSource, alarmClock);
}
@Override
@@ -877,7 +896,7 @@
pw.print("nowRTC="); pw.print(nowRTC);
pw.print("="); pw.print(sdf.format(new Date(nowRTC)));
- pw.print(" nowELAPSED="); TimeUtils.formatDuration(nowELAPSED, pw);
+ pw.print(" nowELAPSED="); pw.print(nowELAPSED);
pw.println();
if (!mInteractive) {
pw.print("Time since non-interactive: ");
@@ -1083,17 +1102,6 @@
return true;
}
- private Batch findFirstWakeupBatchLocked() {
- final int N = mAlarmBatches.size();
- for (int i = 0; i < N; i++) {
- Batch b = mAlarmBatches.get(i);
- if (b.hasWakeups()) {
- return b;
- }
- }
- return null;
- }
-
private AlarmManager.AlarmClockInfo getNextAlarmClockImpl(int userId) {
synchronized (mLock) {
return mNextAlarmClockForUser.get(userId);
@@ -1228,16 +1236,48 @@
// prior to that which contains no wakeups, we schedule that as well.
long nextNonWakeup = 0;
if (mAlarmBatches.size() > 0) {
- final Batch firstWakeup = findFirstWakeupBatchLocked();
+ // Find the first wakeup alarm and note the following batch as well. We'll be
+ // choosing a fuzzed delivery time within the first's allowable interval but
+ // ensuring that it does not encroach on the second's start time, to minimize
+ // alarm reordering.
+ Batch firstWakeup = null, nextAfterWakeup = null;
+ final int N = mAlarmBatches.size();
+ for (int i = 0; i < N; i++) {
+ Batch b = mAlarmBatches.get(i);
+ if (b.hasWakeups()) {
+ firstWakeup = b;
+ if (i < N-1) {
+ nextAfterWakeup = mAlarmBatches.get(i+1);
+ }
+ break;
+ }
+ }
+
+ // There's a subtlety here: we depend on the invariant that if two batches
+ // exist with the same start time, the one with the shorter delivery window
+ // is sorted before the other. This guarantees us that we need only look
+ // at the first [relevant] batch in the queue in order to schedule an alarm
+ // appropriately.
final Batch firstBatch = mAlarmBatches.get(0);
- if (firstWakeup != null && mNextWakeup != firstWakeup.start) {
- mNextWakeup = firstWakeup.start;
- setLocked(ELAPSED_REALTIME_WAKEUP, firstWakeup.start);
+ if (firstWakeup != null && mNextWakeupBatchStart != firstWakeup.start) {
+ mNextWakeupBatchStart = mNextWakeup = firstWakeup.start;
+ final long windowEnd = (nextAfterWakeup == null)
+ ? firstWakeup.end
+ : Math.min(firstWakeup.end, nextAfterWakeup.start);
+ final long interval = windowEnd - firstWakeup.start;
+ // if the interval is over maxint we're into crazy land anyway, but
+ // just in case we check and don't fuzz if the conversion to int for
+ // random-number purposes would blow up
+ if (interval > 0 && interval < Integer.MAX_VALUE) {
+ mNextWakeup += mFuzzer.nextInt((int) interval);
+ }
+ setLocked(ELAPSED_REALTIME_WAKEUP, mNextWakeup);
}
if (firstBatch != firstWakeup) {
nextNonWakeup = firstBatch.start;
}
}
+
if (mPendingNonWakeupAlarms.size() > 0) {
if (nextNonWakeup == 0 || mNextNonWakeupDeliveryTime < nextNonWakeup) {
nextNonWakeup = mNextNonWakeupDeliveryTime;
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index ebdd386..3117a17 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -18,11 +18,14 @@
import android.app.ActivityManager;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothProfile;
import android.bluetooth.IBluetooth;
import android.bluetooth.IBluetoothGatt;
import android.bluetooth.IBluetoothCallback;
+import android.bluetooth.IBluetoothHeadset;
import android.bluetooth.IBluetoothManager;
import android.bluetooth.IBluetoothManagerCallback;
+import android.bluetooth.IBluetoothProfileServiceConnection;
import android.bluetooth.IBluetoothStateChangeCallback;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -32,6 +35,7 @@
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -42,12 +46,18 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.util.Log;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.List;
+import java.util.Vector;
+
class BluetoothManagerService extends IBluetoothManager.Stub {
private static final String TAG = "BluetoothManagerService";
private static final boolean DBG = true;
@@ -67,6 +77,8 @@
private static final int ERROR_RESTART_TIME_MS = 3000;
//Maximum msec to delay MESSAGE_USER_SWITCHED
private static final int USER_SWITCHED_TIME_MS = 200;
+ // Delay for the addProxy function in msec
+ private static final int ADD_PROXY_DELAY_MS = 100;
private static final int MESSAGE_ENABLE = 1;
private static final int MESSAGE_DISABLE = 2;
@@ -83,6 +95,8 @@
private static final int MESSAGE_GET_NAME_AND_ADDRESS=200;
private static final int MESSAGE_SAVE_NAME_AND_ADDRESS=201;
private static final int MESSAGE_USER_SWITCHED = 300;
+ private static final int MESSAGE_ADD_PROXY_DELAYED = 400;
+ private static final int MESSAGE_BIND_PROFILE_SERVICE = 401;
private static final int MAX_SAVE_RETRIES=3;
private static final int MAX_ERROR_RESTART_RETRIES=6;
@@ -127,6 +141,11 @@
private int mErrorRecoveryRetryCounter;
private final int mSystemUiUid;
+ // Save a ProfileServiceConnections object for each of the bound
+ // bluetooth profile services
+ private final Map <Integer, ProfileServiceConnections> mProfileServices =
+ new HashMap <Integer, ProfileServiceConnections>();
+
private void registerForAirplaneMode(IntentFilter filter) {
final ContentResolver resolver = mContext.getContentResolver();
final String airplaneModeRadios = Settings.Global.getString(resolver,
@@ -499,6 +518,187 @@
return mBluetoothGatt;
}
+ @Override
+ public boolean bindBluetoothProfileService(int bluetoothProfile,
+ IBluetoothProfileServiceConnection proxy) {
+ if (!mEnable) {
+ if (DBG) {
+ Log.d(TAG, "Trying to bind to profile: " + bluetoothProfile +
+ ", while Bluetooth was disabled");
+ }
+ return false;
+ }
+ synchronized (mProfileServices) {
+ ProfileServiceConnections psc = mProfileServices.get(new Integer(bluetoothProfile));
+ if (psc == null) {
+ if (DBG) {
+ Log.d(TAG, "Creating new ProfileServiceConnections object for"
+ + " profile: " + bluetoothProfile);
+ }
+ Intent intent = null;
+ if (bluetoothProfile == BluetoothProfile.HEADSET) {
+ intent = new Intent(IBluetoothHeadset.class.getName());
+ } else {
+ return false;
+ }
+ psc = new ProfileServiceConnections(intent);
+ mProfileServices.put(new Integer(bluetoothProfile), psc);
+ psc.bindService();
+ }
+ }
+
+ // Introducing a delay to give the client app time to prepare
+ Message addProxyMsg = mHandler.obtainMessage(MESSAGE_ADD_PROXY_DELAYED);
+ addProxyMsg.arg1 = bluetoothProfile;
+ addProxyMsg.obj = proxy;
+ mHandler.sendMessageDelayed(addProxyMsg, ADD_PROXY_DELAY_MS);
+ return true;
+ }
+
+ @Override
+ public void unbindBluetoothProfileService(int bluetoothProfile,
+ IBluetoothProfileServiceConnection proxy) {
+ synchronized (mProfileServices) {
+ ProfileServiceConnections psc = mProfileServices.get(new Integer(bluetoothProfile));
+ if (psc == null) {
+ return;
+ }
+ psc.removeProxy(proxy);
+ }
+ }
+
+ private void unbindAllBluetoothProfileServices() {
+ synchronized (mProfileServices) {
+ for (Integer i : mProfileServices.keySet()) {
+ ProfileServiceConnections psc = mProfileServices.get(i);
+ mContext.unbindService(psc);
+ psc.removeAllProxies();
+ }
+ mProfileServices.clear();
+ }
+ }
+
+ /**
+ * This class manages the clients connected to a given ProfileService
+ * and maintains the connection with that service.
+ */
+ final private class ProfileServiceConnections implements ServiceConnection,
+ IBinder.DeathRecipient {
+ final RemoteCallbackList<IBluetoothProfileServiceConnection> mProxies =
+ new RemoteCallbackList <IBluetoothProfileServiceConnection>();
+ IBinder mService;
+ ComponentName mClassName;
+ Intent mIntent;
+
+ ProfileServiceConnections(Intent intent) {
+ mService = null;
+ mClassName = null;
+ mIntent = intent;
+ }
+
+ private void bindService() {
+ if (mIntent != null && mService == null) {
+ if (!doBind(mIntent, this, 0, UserHandle.CURRENT_OR_SELF)) {
+ Log.w(TAG, "Unable to bind with intent: " + mIntent
+ + ". Triggering retry.");
+ }
+ Message msg = mHandler.obtainMessage(MESSAGE_BIND_PROFILE_SERVICE);
+ msg.obj = this;
+ mHandler.sendMessageDelayed(msg, TIMEOUT_BIND_MS);
+ }
+ }
+
+ private void addProxy(IBluetoothProfileServiceConnection proxy) {
+ mProxies.register(proxy);
+ if (mService != null) {
+ try{
+ proxy.onServiceConnected(mClassName, mService);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to connect to proxy", e);
+ }
+ } else {
+ if (!mHandler.hasMessages(MESSAGE_BIND_PROFILE_SERVICE, this)) {
+ Message msg = mHandler.obtainMessage(MESSAGE_BIND_PROFILE_SERVICE);
+ msg.obj = this;
+ mHandler.sendMessage(msg);
+ }
+ }
+ }
+
+ private void removeProxy(IBluetoothProfileServiceConnection proxy) {
+ if (proxy != null) {
+ if (mProxies.unregister(proxy)) {
+ try {
+ proxy.onServiceDisconnected(mClassName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to disconnect proxy", e);
+ }
+ }
+ } else {
+ Log.w(TAG, "Trying to remove a null proxy");
+ }
+ }
+
+ private void removeAllProxies() {
+ onServiceDisconnected(mClassName);
+ mProxies.kill();
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ // remove timeout message
+ mHandler.removeMessages(MESSAGE_BIND_PROFILE_SERVICE, this);
+ mService = service;
+ mClassName = className;
+ try {
+ mService.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to linkToDeath", e);
+ }
+ int n = mProxies.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mProxies.getBroadcastItem(i).onServiceConnected(className, service);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to connect to proxy", e);
+ }
+ }
+ mProxies.finishBroadcast();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
+ if (mService == null) {
+ return;
+ }
+ mService.unlinkToDeath(this, 0);
+ mService = null;
+ mClassName = null;
+ int n = mProxies.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mProxies.getBroadcastItem(i).onServiceDisconnected(className);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to disconnect from proxy", e);
+ }
+ }
+ mProxies.finishBroadcast();
+ }
+
+ @Override
+ public void binderDied() {
+ if (DBG) {
+ Log.w(TAG, "Profile service for profile: " + mClassName
+ + " died.");
+ }
+ onServiceDisconnected(mClassName);
+ // Trigger rebind
+ Message msg = mHandler.obtainMessage(MESSAGE_BIND_PROFILE_SERVICE);
+ msg.obj = this;
+ mHandler.sendMessageDelayed(msg, TIMEOUT_BIND_MS);
+ }
+ }
+
private void sendBluetoothStateCallback(boolean isUp) {
int n = mStateChangeCallbacks.beginBroadcast();
if (DBG) Log.d(TAG,"Broadcasting onBluetoothStateChange("+isUp+") to " + n + " receivers.");
@@ -803,6 +1003,28 @@
}
break;
}
+ case MESSAGE_ADD_PROXY_DELAYED:
+ {
+ ProfileServiceConnections psc = mProfileServices.get(
+ new Integer(msg.arg1));
+ if (psc == null) {
+ break;
+ }
+ IBluetoothProfileServiceConnection proxy =
+ (IBluetoothProfileServiceConnection) msg.obj;
+ psc.addProxy(proxy);
+ break;
+ }
+ case MESSAGE_BIND_PROFILE_SERVICE:
+ {
+ ProfileServiceConnections psc = (ProfileServiceConnections) msg.obj;
+ removeMessages(MESSAGE_BIND_PROFILE_SERVICE, msg.obj);
+ if (psc == null) {
+ break;
+ }
+ psc.bindService();
+ break;
+ }
case MESSAGE_BLUETOOTH_SERVICE_CONNECTED:
{
if (DBG) Log.d(TAG,"MESSAGE_BLUETOOTH_SERVICE_CONNECTED: " + msg.arg1);
@@ -1005,6 +1227,7 @@
bluetoothStateChangeHandler(mState, BluetoothAdapter.STATE_ON);
}
+ unbindAllBluetoothProfileServices();
// disable
handleDisable();
// Pbap service need receive STATE_TURNING_OFF intent to close
@@ -1129,16 +1352,21 @@
int callingUser = UserHandle.getCallingUserId();
int callingUid = Binder.getCallingUid();
long callingIdentity = Binder.clearCallingIdentity();
+ UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ UserInfo ui = um.getProfileParent(callingUser);
+ int parentUser = (ui != null) ? ui.id : UserHandle.USER_NULL;
int callingAppId = UserHandle.getAppId(callingUid);
boolean valid = false;
try {
foregroundUser = ActivityManager.getCurrentUser();
valid = (callingUser == foregroundUser) ||
+ parentUser == foregroundUser ||
callingAppId == Process.NFC_UID ||
callingAppId == mSystemUiUid;
if (DBG) {
Log.d(TAG, "checkIfCallerIsForegroundUser: valid=" + valid
+ " callingUser=" + callingUser
+ + " parentUser=" + parentUser
+ " foregroundUser=" + foregroundUser);
}
} finally {
@@ -1165,6 +1393,7 @@
} else {
//If Bluetooth is off, send service down event to proxy objects, and unbind
if (!isUp && canUnbindBluetoothService()) {
+ unbindAllBluetoothProfileServices();
sendBluetoothServiceDownCallback();
unbindAndFinish();
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 84fddd7..348aa1b 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -797,6 +797,17 @@
throw new IllegalStateException("No free netIds");
}
+ private int getConnectivityChangeDelay() {
+ final ContentResolver cr = mContext.getContentResolver();
+
+ /** Check system properties for the default value then use secure settings value, if any. */
+ int defaultDelay = SystemProperties.getInt(
+ "conn." + Settings.Global.CONNECTIVITY_CHANGE_DELAY,
+ ConnectivityManager.CONNECTIVITY_CHANGE_DELAY_DEFAULT);
+ return Settings.Global.getInt(cr, Settings.Global.CONNECTIVITY_CHANGE_DELAY,
+ defaultDelay);
+ }
+
private boolean teardown(NetworkStateTracker netTracker) {
if (netTracker.teardown()) {
netTracker.setTeardownRequested(true);
@@ -1468,6 +1479,11 @@
sendGeneralBroadcast(info, CONNECTIVITY_ACTION);
}
+ private void sendConnectedBroadcastDelayed(NetworkInfo info, int delayMs) {
+ sendGeneralBroadcast(info, CONNECTIVITY_ACTION_IMMEDIATE);
+ sendGeneralBroadcastDelayed(info, CONNECTIVITY_ACTION, delayMs);
+ }
+
private void sendInetConditionBroadcast(NetworkInfo info) {
sendGeneralBroadcast(info, ConnectivityManager.INET_CONDITION_ACTION);
}
@@ -1499,6 +1515,10 @@
sendStickyBroadcast(makeGeneralIntent(info, bcastType));
}
+ private void sendGeneralBroadcastDelayed(NetworkInfo info, String bcastType, int delayMs) {
+ sendStickyBroadcastDelayed(makeGeneralIntent(info, bcastType), delayMs);
+ }
+
private void sendDataActivityBroadcast(int deviceType, boolean active, long tsNanos) {
Intent intent = new Intent(ConnectivityManager.ACTION_DATA_ACTIVITY_CHANGE);
intent.putExtra(ConnectivityManager.EXTRA_DEVICE_TYPE, deviceType);
@@ -1532,6 +1552,19 @@
}
}
+ private void sendStickyBroadcastDelayed(Intent intent, int delayMs) {
+ if (delayMs <= 0) {
+ sendStickyBroadcast(intent);
+ } else {
+ if (VDBG) {
+ log("sendStickyBroadcastDelayed: delayMs=" + delayMs + ", action="
+ + intent.getAction());
+ }
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ EVENT_SEND_STICKY_BROADCAST_INTENT, intent), delayMs);
+ }
+ }
+
void systemReady() {
// start network sampling ..
Intent intent = new Intent(ACTION_PKT_CNT_SAMPLE_INTERVAL_ELAPSED);
@@ -2076,6 +2109,17 @@
}
}
+ // Cancel any lingering so the linger timeout doesn't teardown a network.
+ // This should be called when a network begins satisfying a NetworkRequest.
+ // Note: depending on what state the NetworkMonitor is in (e.g.,
+ // if it's awaiting captive portal login, or if validation failed), this
+ // may trigger a re-evaluation of the network.
+ private void unlinger(NetworkAgentInfo nai) {
+ if (VDBG) log("Canceling linger of " + nai.name());
+ nai.networkLingered.clear();
+ nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
+ }
+
private void handleAsyncChannelHalfConnect(Message msg) {
AsyncChannel ac = (AsyncChannel) msg.obj;
if (mNetworkFactoryInfos.containsKey(msg.replyTo)) {
@@ -2111,6 +2155,7 @@
}
}
}
+
private void handleAsyncChannelDisconnected(Message msg) {
NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
if (nai != null) {
@@ -2160,11 +2205,8 @@
mNetworkForRequestId.remove(request.requestId);
sendUpdatedScoreToFactories(request, 0);
NetworkAgentInfo alternative = null;
- for (Map.Entry entry : mNetworkAgentInfos.entrySet()) {
- NetworkAgentInfo existing = (NetworkAgentInfo)entry.getValue();
- if (existing.networkInfo.isConnected() &&
- request.networkCapabilities.satisfiedByNetworkCapabilities(
- existing.networkCapabilities) &&
+ for (NetworkAgentInfo existing : mNetworkAgentInfos.values()) {
+ if (existing.satisfies(request) &&
(alternative == null ||
alternative.getCurrentScore() < existing.getCurrentScore())) {
alternative = existing;
@@ -2184,8 +2226,7 @@
requestNetworkTransitionWakelock(nai.name());
}
for (NetworkAgentInfo networkToActivate : toActivate) {
- networkToActivate.networkLingered.clear();
- networkToActivate.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
+ unlinger(networkToActivate);
rematchNetworkAndRequests(networkToActivate, false);
}
}
@@ -2220,44 +2261,35 @@
private void handleRegisterNetworkRequest(Message msg) {
final NetworkRequestInfo nri = (NetworkRequestInfo) (msg.obj);
- final NetworkCapabilities newCap = nri.request.networkCapabilities;
- int score = 0;
mNetworkRequests.put(nri.request, nri);
+ // TODO: This logic may be better replaced with a call to rematchNetworkAndRequests
+
// Check for the best currently alive network that satisfies this request
NetworkAgentInfo bestNetwork = null;
for (NetworkAgentInfo network : mNetworkAgentInfos.values()) {
if (DBG) log("handleRegisterNetworkRequest checking " + network.name());
- if (newCap.satisfiedByNetworkCapabilities(network.networkCapabilities)) {
+ if (network.satisfies(nri.request)) {
if (DBG) log("apparently satisfied. currentScore=" + network.getCurrentScore());
- if ((bestNetwork == null) ||
+ if (!nri.isRequest) {
+ // Not setting bestNetwork here as a listening NetworkRequest may be
+ // satisfied by multiple Networks. Instead the request is added to
+ // each satisfying Network and notified about each.
+ network.addRequest(nri.request);
+ notifyNetworkCallback(network, nri);
+ } else if (bestNetwork == null ||
bestNetwork.getCurrentScore() < network.getCurrentScore()) {
- if (!nri.isRequest) {
- // Not setting bestNetwork here as a listening NetworkRequest may be
- // satisfied by multiple Networks. Instead the request is added to
- // each satisfying Network and notified about each.
- network.addRequest(nri.request);
- notifyNetworkCallback(network, nri);
- } else {
- bestNetwork = network;
- }
+ bestNetwork = network;
}
}
}
if (bestNetwork != null) {
if (DBG) log("using " + bestNetwork.name());
- if (bestNetwork.networkInfo.isConnected()) {
- // Cancel any lingering so the linger timeout doesn't teardown this network
- // even though we have a request for it.
- bestNetwork.networkLingered.clear();
- bestNetwork.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
- }
- // TODO: This logic may be better replaced with a call to rematchNetworkAndRequests
+ unlinger(bestNetwork);
bestNetwork.addRequest(nri.request);
mNetworkForRequestId.put(nri.request.requestId, bestNetwork);
notifyNetworkCallback(bestNetwork, nri);
- score = bestNetwork.getCurrentScore();
if (nri.request.legacyType != TYPE_NONE) {
mLegacyTypeTracker.add(nri.request.legacyType, bestNetwork);
}
@@ -2265,6 +2297,7 @@
if (nri.isRequest) {
if (DBG) log("sending new NetworkRequest to factories");
+ final int score = bestNetwork == null ? 0 : bestNetwork.getCurrentScore();
for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score,
0, nri.request);
@@ -3966,8 +3999,7 @@
// check if it satisfies the NetworkCapabilities
if (VDBG) log(" checking if request is satisfied: " + nri.request);
- if (nri.request.networkCapabilities.satisfiedByNetworkCapabilities(
- newNetwork.networkCapabilities)) {
+ if (newNetwork.satisfies(nri.request)) {
if (!nri.isRequest) {
// This is not a request, it's a callback listener.
// Add it to newNetwork regardless of score.
@@ -4047,12 +4079,7 @@
nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_LINGER);
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING);
} else {
- // not going to linger, so kill the list of linger networks.. only
- // notify them of linger if it happens as the result of gaining another,
- // but if they transition and old network stays up, don't tell them of linger
- // or very delayed loss
- nai.networkLingered.clear();
- if (VDBG) log("Lingered for " + nai.name() + " cleared");
+ unlinger(nai);
}
}
if (keep) {
@@ -4274,7 +4301,7 @@
info.setType(type);
if (connected) {
info.setDetailedState(DetailedState.CONNECTED, null, info.getExtraInfo());
- sendConnectedBroadcast(info);
+ sendConnectedBroadcastDelayed(info, getConnectivityChangeDelay());
} else {
info.setDetailedState(DetailedState.DISCONNECTED, null, info.getExtraInfo());
Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
@@ -4305,9 +4332,10 @@
final Intent immediateIntent = new Intent(intent);
immediateIntent.setAction(CONNECTIVITY_ACTION_IMMEDIATE);
sendStickyBroadcast(immediateIntent);
- sendStickyBroadcast(intent);
+ sendStickyBroadcastDelayed(intent, getConnectivityChangeDelay());
if (newDefaultAgent != null) {
- sendConnectedBroadcast(newDefaultAgent.networkInfo);
+ sendConnectedBroadcastDelayed(newDefaultAgent.networkInfo,
+ getConnectivityChangeDelay());
}
}
}
diff --git a/services/core/java/com/android/server/MountServiceIdler.java b/services/core/java/com/android/server/MountServiceIdler.java
index bc851a3..e0b2307 100644
--- a/services/core/java/com/android/server/MountServiceIdler.java
+++ b/services/core/java/com/android/server/MountServiceIdler.java
@@ -18,12 +18,14 @@
import java.util.Calendar;
+import android.app.ActivityManagerNative;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
+import android.os.RemoteException;
import android.util.Slog;
public class MountServiceIdler extends JobService {
@@ -53,6 +55,13 @@
@Override
public boolean onStartJob(JobParameters params) {
+ // First have the activity manager do its idle maintenance. (Yes this job
+ // is really more than just mount, some day it should be renamed to be system
+ // idleer).
+ try {
+ ActivityManagerNative.getDefault().performIdleMaintenance();
+ } catch (RemoteException e) {
+ }
// The mount service will run an fstrim operation asynchronously
// on a designated separate thread, so we provide it with a callback
// that lets us cleanly end our idle timeslice. It's safe to call
@@ -98,7 +107,7 @@
private static Calendar tomorrowMidnight() {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
- calendar.set(Calendar.HOUR_OF_DAY, 0);
+ calendar.set(Calendar.HOUR_OF_DAY, 3);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6823119..221a0a6 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -30,6 +30,7 @@
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
+import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
import android.Manifest;
import android.app.AppOpsManager;
@@ -1669,7 +1670,9 @@
break;
}
case REQUEST_ALL_PSS_MSG: {
- requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
+ synchronized (ActivityManagerService.this) {
+ requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
+ }
break;
}
case START_PROFILES_MSG: {
@@ -2808,12 +2811,12 @@
if (app == null) {
checkTime(startTime, "startProcess: creating new process record");
app = newProcessRecordLocked(info, processName, isolated, isolatedUid);
- app.crashHandler = crashHandler;
if (app == null) {
Slog.w(TAG, "Failed making new process record for "
+ processName + "/" + info.uid + " isolated=" + isolated);
return null;
}
+ app.crashHandler = crashHandler;
mProcessNames.put(processName, app.uid, app);
if (isolated) {
mIsolatedProcesses.put(app.uid, app);
@@ -3905,7 +3908,7 @@
+ endIndex + " " + cur);
if (cur == top) {
// Verify start of the chain.
- if (cur.mNextAffiliate != null || cur.mNextAffiliateTaskId != -1) {
+ if (cur.mNextAffiliate != null || cur.mNextAffiliateTaskId != INVALID_TASK_ID) {
Slog.wtf(TAG, "Bad chain @" + endIndex
+ ": first task has next affiliate: " + prev);
sane = false;
@@ -3924,7 +3927,7 @@
break;
}
}
- if (cur.mPrevAffiliateTaskId == -1) {
+ if (cur.mPrevAffiliateTaskId == INVALID_TASK_ID) {
// Chain ends here.
if (cur.mPrevAffiliate != null) {
Slog.wtf(TAG, "Bad chain @" + endIndex
@@ -3989,7 +3992,8 @@
final void addRecentTaskLocked(TaskRecord task) {
final boolean isAffiliated = task.mAffiliatedTaskId != task.taskId
- || task.mNextAffiliateTaskId != -1 || task.mPrevAffiliateTaskId != -1;
+ || task.mNextAffiliateTaskId != INVALID_TASK_ID
+ || task.mPrevAffiliateTaskId != INVALID_TASK_ID;
int N = mRecentTasks.size();
// Quick case: check if the top-most recent task is the same.
@@ -5878,6 +5882,7 @@
app.hasShownUi = false;
app.debugging = false;
app.cached = false;
+ app.killedByAm = false;
mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
@@ -6219,6 +6224,17 @@
}
}
+ @Override
+ public void systemBackupRestored() {
+ synchronized (this) {
+ if (mSystemReady) {
+ mTaskPersister.restoreTasksFromOtherDeviceLocked();
+ } else {
+ Slog.w(TAG, "System backup restored before system is ready");
+ }
+ }
+ }
+
final void ensureBootCompleted() {
boolean booting;
boolean enableScreen;
@@ -8033,7 +8049,7 @@
// Compose the recent task info
ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
- rti.id = tr.getTopActivity() == null ? -1 : tr.taskId;
+ rti.id = tr.getTopActivity() == null ? INVALID_TASK_ID : tr.taskId;
rti.persistentId = tr.taskId;
rti.baseIntent = new Intent(tr.getBaseIntent());
rti.origActivity = tr.origActivity;
@@ -8256,7 +8272,7 @@
if (trimIdx >= 0) {
// If this would have caused a trim, then we'll abort because that
// means it would be added at the end of the list but then just removed.
- return -1;
+ return INVALID_TASK_ID;
}
final int N = mRecentTasks.size();
@@ -9277,9 +9293,9 @@
"Attempt to launch content provider before system ready");
}
- // Make sure that the user who owns this provider is started. If not,
+ // Make sure that the user who owns this provider is running. If not,
// we don't want to allow it to run.
- if (mStartedUsers.get(userId) == null) {
+ if (!isUserRunningLocked(userId, false)) {
Slog.w(TAG, "Unable to launch app "
+ cpi.applicationInfo.packageName + "/"
+ cpi.applicationInfo.uid + " for provider "
@@ -11143,6 +11159,7 @@
if (mRecentTasks == null) {
mRecentTasks = mTaskPersister.restoreTasksLocked();
+ mTaskPersister.restoreTasksFromOtherDeviceLocked();
if (!mRecentTasks.isEmpty()) {
mStackSupervisor.createStackForRestoredTaskHistory(mRecentTasks);
}
@@ -12883,7 +12900,7 @@
+ PowerManagerInternal.wakefulnessToString(mWakefulness));
pw.println(" mSleeping=" + mSleeping + " mLockScreenShown="
+ lockScreenShownToString());
- pw.print(" mShuttingDown=" + mShuttingDown + " mRunningVoice=" + mRunningVoice);
+ pw.println(" mShuttingDown=" + mShuttingDown + " mRunningVoice=" + mRunningVoice);
}
if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient
|| mOrigWaitForDebugger) {
@@ -15643,10 +15660,10 @@
userId = handleIncomingUser(callingPid, callingUid, userId,
true, ALLOW_NON_FULL, "broadcast", callerPackage);
- // Make sure that the user who is receiving this broadcast is started.
+ // Make sure that the user who is receiving this broadcast is running.
// If not, we will just skip it.
- if (userId != UserHandle.USER_ALL && mStartedUsers.get(userId) == null) {
+ if (userId != UserHandle.USER_ALL && !isUserRunningLocked(userId, false)) {
if (callingUid != Process.SYSTEM_UID || (intent.getFlags()
& Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0) {
Slog.w(TAG, "Skipping broadcast of " + intent
@@ -15789,6 +15806,9 @@
}
} else {
removeTasksByRemovedPackageComponentsLocked(ssp, userId);
+ if (userId == UserHandle.USER_OWNER) {
+ mTaskPersister.addOtherDeviceTasksToRecentsLocked(ssp);
+ }
}
}
break;
@@ -15806,6 +15826,9 @@
if (replacing) {
removeTasksByRemovedPackageComponentsLocked(ssp, userId);
}
+ if (userId == UserHandle.USER_OWNER) {
+ mTaskPersister.addOtherDeviceTasksToRecentsLocked(ssp);
+ }
}
break;
case Intent.ACTION_TIMEZONE_CHANGED:
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index e37d5f3..912ca62 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -16,6 +16,10 @@
package com.android.server.am;
+import static com.android.server.am.TaskPersister.DEBUG_PERSISTER;
+import static com.android.server.am.TaskPersister.DEBUG_RESTORER;
+import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
+
import android.app.ActivityManager.TaskDescription;
import android.os.PersistableBundle;
import android.os.Trace;
@@ -1052,12 +1056,12 @@
static int getTaskForActivityLocked(IBinder token, boolean onlyRoot) {
final ActivityRecord r = ActivityRecord.forToken(token);
if (r == null) {
- return -1;
+ return INVALID_TASK_ID;
}
final TaskRecord task = r.task;
final int activityNdx = task.mActivities.indexOf(r);
if (activityNdx < 0 || (onlyRoot && activityNdx > task.findEffectiveRootIndex())) {
- return -1;
+ return INVALID_TASK_ID;
}
return task.taskId;
}
@@ -1139,7 +1143,7 @@
}
}
- static ActivityRecord restoreFromXml(XmlPullParser in, int taskId,
+ static ActivityRecord restoreFromXml(XmlPullParser in,
ActivityStackSupervisor stackSupervisor) throws IOException, XmlPullParserException {
Intent intent = null;
PersistableBundle persistentState = null;
@@ -1155,8 +1159,8 @@
for (int attrNdx = in.getAttributeCount() - 1; attrNdx >= 0; --attrNdx) {
final String attrName = in.getAttributeName(attrNdx);
final String attrValue = in.getAttributeValue(attrNdx);
- if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "ActivityRecord: attribute name=" +
- attrName + " value=" + attrValue);
+ if (DEBUG_PERSISTER || DEBUG_RESTORER) Slog.d(TaskPersister.TAG,
+ "ActivityRecord: attribute name=" + attrName + " value=" + attrValue);
if (ATTR_ID.equals(attrName)) {
createTime = Long.valueOf(attrValue);
} else if (ATTR_LAUNCHEDFROMUID.equals(attrName)) {
@@ -1181,15 +1185,15 @@
(event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
if (event == XmlPullParser.START_TAG) {
final String name = in.getName();
- if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG,
- "ActivityRecord: START_TAG name=" + name);
+ if (DEBUG_PERSISTER || DEBUG_RESTORER)
+ Slog.d(TaskPersister.TAG, "ActivityRecord: START_TAG name=" + name);
if (TAG_INTENT.equals(name)) {
intent = Intent.restoreFromXml(in);
- if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG,
- "ActivityRecord: intent=" + intent);
+ if (DEBUG_PERSISTER || DEBUG_RESTORER)
+ Slog.d(TaskPersister.TAG, "ActivityRecord: intent=" + intent);
} else if (TAG_PERSISTABLEBUNDLE.equals(name)) {
persistentState = PersistableBundle.restoreFromXml(in);
- if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG,
+ if (DEBUG_PERSISTER || DEBUG_RESTORER) Slog.d(TaskPersister.TAG,
"ActivityRecord: persistentState=" + persistentState);
} else {
Slog.w(TAG, "restoreActivity: unexpected name=" + name);
@@ -1232,7 +1236,7 @@
@Override
public String toString() {
if (stringName != null) {
- return stringName + " t" + (task == null ? -1 : task.taskId) +
+ return stringName + " t" + (task == null ? INVALID_TASK_ID : task.taskId) +
(finishing ? " f}" : "}");
}
StringBuilder sb = new StringBuilder(128);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index ad86aea..d98f03c 100755
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1824,6 +1824,9 @@
// Do over!
mStackSupervisor.scheduleResumeTopActivities();
}
+ if (next == mLastScreenshotActivity) {
+ invalidateLastScreenshot();
+ }
if (mStackSupervisor.reportResumedActivityLocked(next)) {
mNoAnimActivities.clear();
if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index d9396d8..262b4f1 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1314,7 +1314,6 @@
}
}
}
- ActivityStack resultStack = resultRecord == null ? null : resultRecord.task.stack;
final int launchFlags = intent.getFlags();
@@ -1391,6 +1390,8 @@
}
}
+ final ActivityStack resultStack = resultRecord == null ? null : resultRecord.task.stack;
+
if (err != ActivityManager.START_SUCCESS) {
if (resultRecord != null) {
resultStack.sendActivityResultLocked(-1,
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index e01b983..9b7d0b2 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -296,7 +296,7 @@
public void skipCurrentReceiverLocked(ProcessRecord app) {
boolean reschedule = false;
BroadcastRecord r = app.curReceiver;
- if (r != null) {
+ if (r != null && r.queue == this) {
// The current broadcast is waiting for this app's receiver
// to be finished. Looks like that's not going to happen, so
// let the broadcast continue.
@@ -352,7 +352,7 @@
}
r.receiver = null;
r.intent.setComponent(null);
- if (r.curApp != null) {
+ if (r.curApp != null && r.curApp.curReceiver == r) {
r.curApp.curReceiver = null;
}
if (r.curFilter != null) {
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
index 9311f25..629a05d 100644
--- a/services/core/java/com/android/server/am/TaskPersister.java
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -16,16 +16,27 @@
package com.android.server.am;
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.pm.IPackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Debug;
+import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.UserHandle;
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.Xml;
+
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -38,11 +49,18 @@
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Comparator;
+import java.util.List;
+
+import libcore.io.IoUtils;
+
+import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
public class TaskPersister {
static final String TAG = "TaskPersister";
- static final boolean DEBUG = false;
+ static final boolean DEBUG_PERSISTER = false;
+ static final boolean DEBUG_RESTORER = false;
/** When not flushing don't write out files faster than this */
private static final long INTER_WRITE_DELAY_MS = 500;
@@ -67,12 +85,17 @@
// contains subdirs named after TASKS_DIRNAME and IMAGES_DIRNAME mirroring the
// ancestral device's dataset. This needs to match the RECENTS_TASK_RESTORE_DIR
// value in RecentsBackupHelper.
- private static final String RESTORED_TASKS = "restored_" + TASKS_DIRNAME;
+ private static final String RESTORED_TASKS_DIRNAME = "restored_" + TASKS_DIRNAME;
+
+ // Max time to wait for the application/package of a restored task to be installed
+ // before giving up.
+ private static final long MAX_INSTALL_WAIT_TIME = DateUtils.DAY_IN_MILLIS;
private static final String TAG_TASK = "task";
static File sImagesDir;
static File sTasksDir;
+ static File sRestoredTasksDir;
private final ActivityManagerService mService;
private final ActivityStackSupervisor mStackSupervisor;
@@ -105,10 +128,20 @@
ArrayList<WriteQueueItem> mWriteQueue = new ArrayList<WriteQueueItem>();
+ // Map of tasks that were backed-up on a different device that can be restored on this device.
+ // Data organization: <packageNameOfAffiliateTask, listOfAffiliatedTasksChains>
+ private ArrayMap<String, List<List<OtherDeviceTask>>> mOtherDeviceTasksMap =
+ new ArrayMap<>(10);
+
+ // The next time in milliseconds we will remove expired task from
+ // {@link #mOtherDeviceTasksMap} and disk. Set to {@link Long.MAX_VALUE} to never clean-up
+ // tasks.
+ private long mExpiredTasksCleanupTime = Long.MAX_VALUE;
+
TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor) {
sTasksDir = new File(systemDir, TASKS_DIRNAME);
if (!sTasksDir.exists()) {
- if (DEBUG) Slog.d(TAG, "Creating tasks directory " + sTasksDir);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "Creating tasks directory " + sTasksDir);
if (!sTasksDir.mkdir()) {
Slog.e(TAG, "Failure creating tasks directory " + sTasksDir);
}
@@ -116,12 +149,14 @@
sImagesDir = new File(systemDir, IMAGES_DIRNAME);
if (!sImagesDir.exists()) {
- if (DEBUG) Slog.d(TAG, "Creating images directory " + sTasksDir);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "Creating images directory " + sTasksDir);
if (!sImagesDir.mkdir()) {
Slog.e(TAG, "Failure creating images directory " + sImagesDir);
}
}
+ sRestoredTasksDir = new File(systemDir, RESTORED_TASKS_DIRNAME);
+
mStackSupervisor = stackSupervisor;
mService = stackSupervisor.mService;
@@ -138,8 +173,8 @@
final WriteQueueItem item = mWriteQueue.get(queueNdx);
if (item instanceof ImageWriteQueueItem &&
((ImageWriteQueueItem) item).mFilename.startsWith(taskString)) {
- if (DEBUG) Slog.d(TAG, "Removing " + ((ImageWriteQueueItem) item).mFilename +
- " from write queue");
+ if (DEBUG_PERSISTER) Slog.d(TAG, "Removing "
+ + ((ImageWriteQueueItem) item).mFilename + " from write queue");
mWriteQueue.remove(queueNdx);
}
}
@@ -184,9 +219,9 @@
} else if (mNextWriteTime == 0) {
mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
}
- if (DEBUG) Slog.d(TAG, "wakeup: task=" + task + " flush=" + flush + " mNextWriteTime="
- + mNextWriteTime + " mWriteQueue.size=" + mWriteQueue.size()
- + " Callers=" + Debug.getCallers(4));
+ if (DEBUG_PERSISTER) Slog.d(TAG, "wakeup: task=" + task + " flush=" + flush
+ + " mNextWriteTime=" + mNextWriteTime + " mWriteQueue.size="
+ + mWriteQueue.size() + " Callers=" + Debug.getCallers(4));
notifyAll();
}
@@ -228,7 +263,7 @@
} else if (mNextWriteTime == 0) {
mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
}
- if (DEBUG) Slog.d(TAG, "saveImage: filename=" + filename + " now=" +
+ if (DEBUG_PERSISTER) Slog.d(TAG, "saveImage: filename=" + filename + " now=" +
SystemClock.uptimeMillis() + " mNextWriteTime=" +
mNextWriteTime + " Callers=" + Debug.getCallers(4));
notifyAll();
@@ -262,12 +297,12 @@
}
private StringWriter saveToXml(TaskRecord task) throws IOException, XmlPullParserException {
- if (DEBUG) Slog.d(TAG, "saveToXml: task=" + task);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "saveToXml: task=" + task);
final XmlSerializer xmlSerializer = new FastXmlSerializer();
StringWriter stringWriter = new StringWriter();
xmlSerializer.setOutput(stringWriter);
- if (DEBUG) xmlSerializer.setFeature(
+ if (DEBUG_PERSISTER) xmlSerializer.setFeature(
"http://xmlpull.org/v1/doc/features.html#indent-output", true);
// save task
@@ -326,7 +361,7 @@
for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) {
File taskFile = recentFiles[taskNdx];
- if (DEBUG) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName());
+ if (DEBUG_PERSISTER) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName());
BufferedReader reader = null;
boolean deleteFile = false;
try {
@@ -339,11 +374,12 @@
event != XmlPullParser.END_TAG) {
final String name = in.getName();
if (event == XmlPullParser.START_TAG) {
- if (DEBUG) Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name);
+ if (DEBUG_PERSISTER)
+ Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name);
if (TAG_TASK.equals(name)) {
final TaskRecord task =
TaskRecord.restoreFromXml(in, mStackSupervisor);
- if (DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task=" +
+ if (DEBUG_PERSISTER) Slog.d(TAG, "restoreTasksLocked: restored task=" +
task);
if (task != null) {
task.isPersistable = true;
@@ -371,20 +407,16 @@
Slog.e(TAG, "Failing file: " + fileToString(taskFile));
deleteFile = true;
} finally {
- if (reader != null) {
- try {
- reader.close();
- } catch (IOException e) {
- }
- }
- if (!DEBUG && deleteFile) {
- if (true || DEBUG) Slog.d(TAG, "Deleting file=" + taskFile.getName());
+ IoUtils.closeQuietly(reader);
+ if (!DEBUG_PERSISTER && deleteFile) {
+ if (true || DEBUG_PERSISTER)
+ Slog.d(TAG, "Deleting file=" + taskFile.getName());
taskFile.delete();
}
}
}
- if (!DEBUG) {
+ if (!DEBUG_PERSISTER) {
removeObsoleteFiles(recoveredTaskIds);
}
@@ -415,8 +447,8 @@
}
private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) {
- if (DEBUG) Slog.d(TAG, "removeObsoleteFile: persistentTaskIds=" + persistentTaskIds +
- " files=" + files);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: persistentTaskIds="
+ + persistentTaskIds + " files=" + files);
if (files == null) {
Slog.e(TAG, "File error accessing recents directory (too many files open?).");
return;
@@ -429,14 +461,14 @@
final int taskId;
try {
taskId = Integer.valueOf(filename.substring(0, taskIdEnd));
- if (DEBUG) Slog.d(TAG, "removeObsoleteFile: Found taskId=" + taskId);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: Found taskId=" + taskId);
} catch (Exception e) {
Slog.wtf(TAG, "removeObsoleteFile: Can't parse file=" + file.getName());
file.delete();
continue;
}
if (!persistentTaskIds.contains(taskId)) {
- if (true || DEBUG) Slog.d(TAG, "removeObsoleteFile: deleting file=" +
+ if (true || DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: deleting file=" +
file.getName());
file.delete();
}
@@ -450,10 +482,363 @@
}
static Bitmap restoreImage(String filename) {
- if (DEBUG) Slog.d(TAG, "restoreImage: restoring " + filename);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "restoreImage: restoring " + filename);
return BitmapFactory.decodeFile(sImagesDir + File.separator + filename);
}
+ /**
+ * Tries to restore task that were backed-up on a different device onto this device.
+ */
+ void restoreTasksFromOtherDeviceLocked() {
+ readOtherDeviceTasksFromDisk();
+ addOtherDeviceTasksToRecentsLocked();
+ }
+
+ /**
+ * Read the tasks that were backed-up on a different device and can be restored to this device
+ * from disk and populated {@link #mOtherDeviceTasksMap} with the information. Also sets up
+ * time to clear out other device tasks that have not been restored on this device
+ * within the allotted time.
+ */
+ private void readOtherDeviceTasksFromDisk() {
+ synchronized (mOtherDeviceTasksMap) {
+ // Clear out current map and expiration time.
+ mOtherDeviceTasksMap.clear();
+ mExpiredTasksCleanupTime = Long.MAX_VALUE;
+
+ final File[] taskFiles;
+ if (!sRestoredTasksDir.exists()
+ || (taskFiles = sRestoredTasksDir.listFiles()) == null) {
+ // Nothing to do if there are no tasks to restore.
+ return;
+ }
+
+ long earliestMtime = System.currentTimeMillis();
+ SparseArray<List<OtherDeviceTask>> tasksByAffiliateIds =
+ new SparseArray<>(taskFiles.length);
+
+ // Read new tasks from disk
+ for (int i = 0; i < taskFiles.length; ++i) {
+ final File taskFile = taskFiles[i];
+ if (DEBUG_RESTORER) Slog.d(TAG, "readOtherDeviceTasksFromDisk: taskFile="
+ + taskFile.getName());
+
+ final OtherDeviceTask task = OtherDeviceTask.createFromFile(taskFile);
+
+ if (task == null) {
+ // Go ahead and remove the file on disk if we are unable to create a task from
+ // it.
+ if (DEBUG_RESTORER) Slog.e(TAG, "Unable to create task for file="
+ + taskFile.getName() + "...deleting file.");
+ taskFile.delete();
+ continue;
+ }
+
+ List<OtherDeviceTask> tasks = tasksByAffiliateIds.get(task.mAffiliatedTaskId);
+ if (tasks == null) {
+ tasks = new ArrayList<>();
+ tasksByAffiliateIds.put(task.mAffiliatedTaskId, tasks);
+ }
+ tasks.add(task);
+ final long taskMtime = taskFile.lastModified();
+ if (earliestMtime > taskMtime) {
+ earliestMtime = taskMtime;
+ }
+ }
+
+ if (tasksByAffiliateIds.size() > 0) {
+ // Sort each affiliated tasks chain by taskId which is the order they were created
+ // that should always be correct...Then add to task map.
+ for (int i = 0; i < tasksByAffiliateIds.size(); i++) {
+ List<OtherDeviceTask> chain = tasksByAffiliateIds.valueAt(i);
+ Collections.sort(chain);
+ // Package name of the root task in the affiliate chain.
+ final String packageName =
+ chain.get(chain.size()-1).mComponentName.getPackageName();
+ List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.get(packageName);
+ if (chains == null) {
+ chains = new ArrayList<>();
+ mOtherDeviceTasksMap.put(packageName, chains);
+ }
+ chains.add(chain);
+ }
+
+ // Set expiration time.
+ mExpiredTasksCleanupTime = earliestMtime + MAX_INSTALL_WAIT_TIME;
+ if (DEBUG_RESTORER) Slog.d(TAG, "Set Expiration time to "
+ + DateUtils.formatDateTime(mService.mContext, mExpiredTasksCleanupTime,
+ DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME));
+ }
+ }
+ }
+
+ /**
+ * Removed any expired tasks from {@link #mOtherDeviceTasksMap} and disk if their expiration
+ * time is less than or equal to {@link #mExpiredTasksCleanupTime}.
+ */
+ private void removeExpiredTasksIfNeeded() {
+ synchronized (mOtherDeviceTasksMap) {
+ final long now = System.currentTimeMillis();
+ if (mOtherDeviceTasksMap.isEmpty() || now < mExpiredTasksCleanupTime) {
+ return;
+ }
+
+ long earliestNonExpiredMtime = now;
+ mExpiredTasksCleanupTime = Long.MAX_VALUE;
+
+ // Remove expired backed-up tasks that have not been restored. We only want to
+ // remove task if it is safe to remove all tasks in the affiliation chain.
+ for (int i = mOtherDeviceTasksMap.size() - 1; i >= 0 ; i--) {
+
+ List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.valueAt(i);
+ for (int j = chains.size() - 1; j >= 0 ; j--) {
+
+ List<OtherDeviceTask> chain = chains.get(j);
+ boolean removeChain = true;
+ for (int k = chain.size() - 1; k >= 0 ; k--) {
+ OtherDeviceTask task = chain.get(k);
+ final long taskLastModified = task.mFile.lastModified();
+ if ((taskLastModified + MAX_INSTALL_WAIT_TIME) > now) {
+ // File has not expired yet...but we keep looping to get the earliest
+ // mtime.
+ if (earliestNonExpiredMtime > taskLastModified) {
+ earliestNonExpiredMtime = taskLastModified;
+ }
+ removeChain = false;
+ }
+ }
+ if (removeChain) {
+ for (int k = chain.size() - 1; k >= 0; k--) {
+ final File file = chain.get(k).mFile;
+ if (DEBUG_RESTORER) Slog.d(TAG, "Deleting expired file="
+ + file.getName() + " mapped to not installed component="
+ + chain.get(k).mComponentName);
+ file.delete();
+ }
+ chains.remove(j);
+ }
+ }
+ if (chains.isEmpty()) {
+ final String packageName = mOtherDeviceTasksMap.keyAt(i);
+ mOtherDeviceTasksMap.removeAt(i);
+ if (DEBUG_RESTORER) Slog.d(TAG, "Removed package=" + packageName
+ + " from task map");
+ }
+ }
+
+ // Reset expiration time if there is any task remaining.
+ if (!mOtherDeviceTasksMap.isEmpty()) {
+ mExpiredTasksCleanupTime = earliestNonExpiredMtime + MAX_INSTALL_WAIT_TIME;
+ if (DEBUG_RESTORER) Slog.d(TAG, "Reset expiration time to "
+ + DateUtils.formatDateTime(mService.mContext, mExpiredTasksCleanupTime,
+ DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME));
+ }
+ }
+ }
+
+ /**
+ * Tries to add all backed-up tasks from another device to this device recent's list.
+ */
+ private void addOtherDeviceTasksToRecentsLocked() {
+ synchronized (mOtherDeviceTasksMap) {
+ for (int i = mOtherDeviceTasksMap.size() - 1; i >= 0; i--) {
+ addOtherDeviceTasksToRecentsLocked(mOtherDeviceTasksMap.keyAt(i));
+ }
+ }
+ }
+
+ /**
+ * Tries to add backed-up tasks that are associated with the input package from
+ * another device to this device recent's list.
+ */
+ void addOtherDeviceTasksToRecentsLocked(String packageName) {
+ synchronized (mOtherDeviceTasksMap) {
+ List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.get(packageName);
+ if (chains == null) {
+ return;
+ }
+
+ for (int i = chains.size() - 1; i >= 0; i--) {
+ List<OtherDeviceTask> chain = chains.get(i);
+ if (!canAddOtherDeviceTaskChain(chain)) {
+ if (DEBUG_RESTORER) Slog.d(TAG, "Can't add task chain at index=" + i
+ + " for package=" + packageName);
+ continue;
+ }
+
+ // Generate task records for this chain.
+ List<TaskRecord> tasks = new ArrayList<>();
+ TaskRecord prev = null;
+ for (int j = chain.size() - 1; j >= 0; j--) {
+ TaskRecord task = createTaskRecordLocked(chain.get(j));
+ if (task == null) {
+ // There was a problem in creating one of this task records in this chain.
+ // There is no way we can continue...
+ if (DEBUG_RESTORER) Slog.d(TAG, "Can't create task record for file="
+ + chain.get(j).mFile + " for package=" + packageName);
+ break;
+ }
+
+ // Wire-up affiliation chain.
+ if (prev == null) {
+ task.mPrevAffiliate = null;
+ task.mPrevAffiliateTaskId = INVALID_TASK_ID;
+ task.mAffiliatedTaskId = task.taskId;
+ } else {
+ prev.mNextAffiliate = task;
+ prev.mNextAffiliateTaskId = task.taskId;
+ task.mAffiliatedTaskId = prev.mAffiliatedTaskId;
+ task.mPrevAffiliate = prev;
+ task.mPrevAffiliateTaskId = prev.taskId;
+ }
+ prev = task;
+ tasks.add(0, task);
+ }
+
+ // Add tasks to recent's if we were able to create task records for all the tasks
+ // in the chain.
+ if (tasks.size() == chain.size()) {
+ // Make sure there is space in recent's to add the new task. If there is space
+ // to the to the back.
+ // TODO: Would be more fancy to interleave the new tasks into recent's based on
+ // {@link TaskRecord.mLastTimeMoved} and drop the oldest recent's vs. just
+ // adding to the back of the list.
+ int spaceLeft =
+ ActivityManager.getMaxRecentTasksStatic()
+ - mService.mRecentTasks.size();
+ if (spaceLeft >= tasks.size()) {
+ mService.mRecentTasks.addAll(mService.mRecentTasks.size(), tasks);
+ for (int k = tasks.size() - 1; k >= 0; k--) {
+ // Persist new tasks.
+ wakeup(tasks.get(k), false);
+ }
+
+ if (DEBUG_RESTORER) Slog.d(TAG, "Added " + tasks.size()
+ + " tasks to recent's for" + " package=" + packageName);
+ } else {
+ if (DEBUG_RESTORER) Slog.d(TAG, "Didn't add to recents. tasks.size("
+ + tasks.size() + ") != chain.size(" + chain.size()
+ + ") for package=" + packageName);
+ }
+ } else {
+ if (DEBUG_RESTORER) Slog.v(TAG, "Unable to add restored tasks to recents "
+ + tasks.size() + " tasks for package=" + packageName);
+ }
+
+ // Clean-up structures
+ for (int j = chain.size() - 1; j >= 0; j--) {
+ chain.get(j).mFile.delete();
+ }
+ chains.remove(i);
+ if (chains.isEmpty()) {
+ // The fate of all backed-up tasks associated with this package has been
+ // determine. Go ahead and remove it from the to-process list.
+ mOtherDeviceTasksMap.remove(packageName);
+ if (DEBUG_RESTORER)
+ Slog.d(TAG, "Removed package=" + packageName + " from restore map");
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates and returns {@link TaskRecord} for the task from another device that can be used on
+ * this device. Returns null if the operation failed.
+ */
+ private TaskRecord createTaskRecordLocked(OtherDeviceTask other) {
+ File file = other.mFile;
+ BufferedReader reader = null;
+ TaskRecord task = null;
+ if (DEBUG_RESTORER) Slog.d(TAG, "createTaskRecordLocked: file=" + file.getName());
+
+ try {
+ reader = new BufferedReader(new FileReader(file));
+ final XmlPullParser in = Xml.newPullParser();
+ in.setInput(reader);
+
+ int event;
+ while (((event = in.next()) != XmlPullParser.END_DOCUMENT)
+ && event != XmlPullParser.END_TAG) {
+ final String name = in.getName();
+ if (event == XmlPullParser.START_TAG) {
+
+ if (TAG_TASK.equals(name)) {
+ // Create a task record using a task id that is valid for this device.
+ task = TaskRecord.restoreFromXml(
+ in, mStackSupervisor, mStackSupervisor.getNextTaskId());
+ if (DEBUG_RESTORER)
+ Slog.d(TAG, "createTaskRecordLocked: restored task=" + task);
+
+ if (task != null) {
+ task.isPersistable = true;
+ task.inRecents = true;
+ // Task can/should only be backed-up/restored for device owner.
+ task.userId = UserHandle.USER_OWNER;
+ // Clear out affiliated ids that are no longer valid on this device.
+ task.mAffiliatedTaskId = INVALID_TASK_ID;
+ task.mPrevAffiliateTaskId = INVALID_TASK_ID;
+ task.mNextAffiliateTaskId = INVALID_TASK_ID;
+ } else {
+ Slog.e(TAG, "Unable to create task for backed-up file=" + file + ": "
+ + fileToString(file));
+ }
+ } else {
+ Slog.wtf(TAG, "createTaskRecordLocked Unknown xml event=" + event
+ + " name=" + name);
+ }
+ }
+ XmlUtils.skipCurrentTag(in);
+ }
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Unable to parse " + file + ". Error ", e);
+ Slog.e(TAG, "Failing file: " + fileToString(file));
+ } finally {
+ IoUtils.closeQuietly(reader);
+ }
+
+ return task;
+ }
+
+ /**
+ * Returns true if the input task chain backed-up from another device can be restored on this
+ * device.
+ */
+ private boolean canAddOtherDeviceTaskChain(List<OtherDeviceTask> chain) {
+
+ // Get component names of all the tasks in the chain.
+ // Mainly doing this to reduce checking for a component twice if two or more
+ // affiliations belong to the same component which is highly likely.
+ ArraySet<ComponentName> componentsToCheck = new ArraySet<>();
+ for (int i = 0; i < chain.size(); i++) {
+
+ OtherDeviceTask task = chain.get(i);
+ // Quick check, we can't add the task chain if any of its task files don't exist.
+ if (!task.mFile.exists()) {
+ if (DEBUG_RESTORER)
+ Slog.d(TAG, "Can't add chain due to missing file=" + task.mFile);
+ return false;
+ }
+ componentsToCheck.add(task.mComponentName);
+ }
+
+ boolean canAdd = true;
+ try {
+ // Check to see if all the components for this task chain are installed.
+ final IPackageManager pm = AppGlobals.getPackageManager();
+ for (int i = 0; canAdd && i < componentsToCheck.size(); i++) {
+ ComponentName cn = componentsToCheck.valueAt(i);
+ canAdd &= pm.getActivityInfo(cn, 0, UserHandle.USER_OWNER) != null;
+ if (DEBUG_RESTORER) Slog.d(TAG, "ComponentName=" + cn + " installed=" + canAdd);
+ }
+ } catch (RemoteException e) {
+ // Should not happen???
+ canAdd = false;
+ }
+
+ if (DEBUG_RESTORER) Slog.d(TAG, "canAdd=" + canAdd);
+ return canAdd;
+ }
+
private class LazyTaskWriterThread extends Thread {
LazyTaskWriterThread(String name) {
@@ -472,21 +857,22 @@
probablyDone = mWriteQueue.isEmpty();
}
if (probablyDone) {
- if (DEBUG) Slog.d(TAG, "Looking for obsolete files.");
+ if (DEBUG_PERSISTER) Slog.d(TAG, "Looking for obsolete files.");
persistentTaskIds.clear();
synchronized (mService) {
final ArrayList<TaskRecord> tasks = mService.mRecentTasks;
- if (DEBUG) Slog.d(TAG, "mRecents=" + tasks);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "mRecents=" + tasks);
for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = tasks.get(taskNdx);
- if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task +
+ if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: task=" + task +
" persistable=" + task.isPersistable);
if ((task.isPersistable || task.inRecents)
- && !task.stack.isHomeStack()) {
- if (DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task);
+ && (task.stack == null || !task.stack.isHomeStack())) {
+ if (DEBUG_PERSISTER)
+ Slog.d(TAG, "adding to persistentTaskIds task=" + task);
persistentTaskIds.add(task.taskId);
} else {
- if (DEBUG) Slog.d(TAG,
+ if (DEBUG_PERSISTER) Slog.d(TAG,
"omitting from persistentTaskIds task=" + task);
}
}
@@ -500,7 +886,7 @@
if (mNextWriteTime != FLUSH_QUEUE) {
// The next write we don't have to wait so long.
mNextWriteTime = SystemClock.uptimeMillis() + INTER_WRITE_DELAY_MS;
- if (DEBUG) Slog.d(TAG, "Next write time may be in " +
+ if (DEBUG_PERSISTER) Slog.d(TAG, "Next write time may be in " +
INTER_WRITE_DELAY_MS + " msec. (" + mNextWriteTime + ")");
}
@@ -510,8 +896,13 @@
mNextWriteTime = 0; // idle.
TaskPersister.this.notifyAll(); // wake up flush() if needed.
}
+
+ // See if we need to remove any expired back-up tasks before waiting.
+ removeExpiredTasksIfNeeded();
+
try {
- if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting indefinitely.");
+ if (DEBUG_PERSISTER)
+ Slog.d(TAG, "LazyTaskWriter: waiting indefinitely.");
TaskPersister.this.wait();
} catch (InterruptedException e) {
}
@@ -521,11 +912,12 @@
item = mWriteQueue.remove(0);
long now = SystemClock.uptimeMillis();
- if (DEBUG) Slog.d(TAG, "LazyTaskWriter: now=" + now + " mNextWriteTime=" +
- mNextWriteTime + " mWriteQueue.size=" + mWriteQueue.size());
+ if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: now=" + now
+ + " mNextWriteTime=" + mNextWriteTime + " mWriteQueue.size="
+ + mWriteQueue.size());
while (now < mNextWriteTime) {
try {
- if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting " +
+ if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: waiting " +
(mNextWriteTime - now));
TaskPersister.this.wait(mNextWriteTime - now);
} catch (InterruptedException e) {
@@ -540,7 +932,7 @@
ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
final String filename = imageWriteQueueItem.mFilename;
final Bitmap bitmap = imageWriteQueueItem.mImage;
- if (DEBUG) Slog.d(TAG, "writing bitmap: filename=" + filename);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "writing bitmap: filename=" + filename);
FileOutputStream imageFile = null;
try {
imageFile = new FileOutputStream(new File(sImagesDir, filename));
@@ -548,23 +940,18 @@
} catch (Exception e) {
Slog.e(TAG, "saveImage: unable to save " + filename, e);
} finally {
- if (imageFile != null) {
- try {
- imageFile.close();
- } catch (IOException e) {
- }
- }
+ IoUtils.closeQuietly(imageFile);
}
} else if (item instanceof TaskWriteQueueItem) {
// Write out one task.
StringWriter stringWriter = null;
TaskRecord task = ((TaskWriteQueueItem) item).mTask;
- if (DEBUG) Slog.d(TAG, "Writing task=" + task);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "Writing task=" + task);
synchronized (mService) {
if (task.inRecents) {
// Still there.
try {
- if (DEBUG) Slog.d(TAG, "Saving task=" + task);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "Saving task=" + task);
stringWriter = saveToXml(task);
} catch (IOException e) {
} catch (XmlPullParserException e) {
@@ -594,4 +981,100 @@
}
}
}
+
+ /**
+ * Helper class for holding essential information about task that were backed-up on a different
+ * device that can be restored on this device.
+ */
+ private static class OtherDeviceTask implements Comparable<OtherDeviceTask> {
+ final File mFile;
+ // See {@link TaskRecord} for information on the fields below.
+ final ComponentName mComponentName;
+ final int mTaskId;
+ final int mAffiliatedTaskId;
+
+ private OtherDeviceTask(
+ File file, ComponentName componentName, int taskId, int affiliatedTaskId) {
+ mFile = file;
+ mComponentName = componentName;
+ mTaskId = taskId;
+ mAffiliatedTaskId = (affiliatedTaskId == INVALID_TASK_ID) ? taskId: affiliatedTaskId;
+ }
+
+ @Override
+ public int compareTo(OtherDeviceTask another) {
+ return mTaskId - another.mTaskId;
+ }
+
+ /**
+ * Creates a new {@link OtherDeviceTask} object based on the contents of the input file.
+ *
+ * @param file input file that contains the complete task information.
+ * @return new {@link OtherDeviceTask} object or null if we failed to create the object.
+ */
+ static OtherDeviceTask createFromFile(File file) {
+ if (file == null || !file.exists()) {
+ if (DEBUG_RESTORER)
+ Slog.d(TAG, "createFromFile: file=" + file + " doesn't exist.");
+ return null;
+ }
+
+ BufferedReader reader = null;
+
+ try {
+ reader = new BufferedReader(new FileReader(file));
+ final XmlPullParser in = Xml.newPullParser();
+ in.setInput(reader);
+
+ int event;
+ while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
+ event != XmlPullParser.START_TAG) {
+ // Skip to the start tag or end of document
+ }
+
+ if (event == XmlPullParser.START_TAG) {
+ final String name = in.getName();
+
+ if (TAG_TASK.equals(name)) {
+ ComponentName componentName = null;
+ int taskId = INVALID_TASK_ID;
+ int taskAffiliation = INVALID_TASK_ID;
+ for (int j = in.getAttributeCount() - 1; j >= 0; --j) {
+ final String attrName = in.getAttributeName(j);
+ final String attrValue = in.getAttributeValue(j);
+ if (TaskRecord.ATTR_REALACTIVITY.equals(attrName)) {
+ componentName = ComponentName.unflattenFromString(attrValue);
+ } else if (TaskRecord.ATTR_TASKID.equals(attrName)) {
+ taskId = Integer.valueOf(attrValue);
+ } else if (TaskRecord.ATTR_TASK_AFFILIATION.equals(attrName)) {
+ taskAffiliation = Integer.valueOf(attrValue);
+ }
+ }
+ if (componentName == null || taskId == INVALID_TASK_ID) {
+ if (DEBUG_RESTORER) Slog.e(TAG,
+ "createFromFile: FAILED componentName=" + componentName
+ + " taskId=" + taskId + " file=" + file);
+ return null;
+ }
+ if (DEBUG_RESTORER) Slog.d(TAG, "creating OtherDeviceTask from file="
+ + file.getName() + " componentName=" + componentName
+ + " taskId=" + taskId);
+ return new OtherDeviceTask(file, componentName, taskId, taskAffiliation);
+ } else {
+ Slog.wtf(TAG,
+ "createFromFile: Unknown xml event=" + event + " name=" + name);
+ }
+ } else {
+ Slog.wtf(TAG, "createFromFile: Unable to find start tag in file=" + file);
+ }
+ } catch (IOException | XmlPullParserException e) {
+ Slog.wtf(TAG, "Unable to parse " + file + ". Error ", e);
+ } finally {
+ IoUtils.closeQuietly(reader);
+ }
+
+ // Something went wrong...
+ return null;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index d726685..c3eda71 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -21,6 +21,8 @@
import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
import static com.android.server.am.ActivityStackSupervisor.DEBUG_ADD_REMOVE;
+import static com.android.server.am.TaskPersister.DEBUG_PERSISTER;
+import static com.android.server.am.TaskPersister.DEBUG_RESTORER;
import android.app.Activity;
import android.app.ActivityManager;
@@ -52,10 +54,10 @@
import java.util.ArrayList;
final class TaskRecord {
- private static final String ATTR_TASKID = "task_id";
+ static final String ATTR_TASKID = "task_id";
private static final String TAG_INTENT = "intent";
private static final String TAG_AFFINITYINTENT = "affinity_intent";
- private static final String ATTR_REALACTIVITY = "real_activity";
+ static final String ATTR_REALACTIVITY = "real_activity";
private static final String ATTR_ORIGACTIVITY = "orig_activity";
private static final String TAG_ACTIVITY = "activity";
private static final String ATTR_AFFINITY = "affinity";
@@ -71,7 +73,7 @@
private static final String ATTR_LASTDESCRIPTION = "last_description";
private static final String ATTR_LASTTIMEMOVED = "last_time_moved";
private static final String ATTR_NEVERRELINQUISH = "never_relinquish_identity";
- private static final String ATTR_TASK_AFFILIATION = "task_affiliation";
+ static final String ATTR_TASK_AFFILIATION = "task_affiliation";
private static final String ATTR_PREV_AFFILIATION = "prev_affiliation";
private static final String ATTR_NEXT_AFFILIATION = "next_affiliation";
private static final String ATTR_TASK_AFFILIATION_COLOR = "task_affiliation_color";
@@ -82,6 +84,8 @@
static final boolean IGNORE_RETURN_TO_RECENTS = true;
+ static final int INVALID_TASK_ID = -1;
+
final int taskId; // Unique identifier for this task.
String affinity; // The affinity name for this task, or null; may change identity.
String rootAffinity; // Initial base affinity, or null; does not change from initial root.
@@ -151,9 +155,9 @@
int mAffiliatedTaskId; // taskId of parent affiliation or self if no parent.
int mAffiliatedTaskColor; // color of the parent task affiliation.
TaskRecord mPrevAffiliate; // previous task in affiliated chain.
- int mPrevAffiliateTaskId = -1; // previous id for persistence.
+ int mPrevAffiliateTaskId = INVALID_TASK_ID; // previous id for persistence.
TaskRecord mNextAffiliate; // next task in affiliated chain.
- int mNextAffiliateTaskId = -1; // next id for persistence.
+ int mNextAffiliateTaskId = INVALID_TASK_ID; // next id for persistence.
// For relaunching the task from recents as though it was launched by the original launcher.
int mCallingUid;
@@ -363,12 +367,12 @@
void setPrevAffiliate(TaskRecord prevAffiliate) {
mPrevAffiliate = prevAffiliate;
- mPrevAffiliateTaskId = prevAffiliate == null ? -1 : prevAffiliate.taskId;
+ mPrevAffiliateTaskId = prevAffiliate == null ? INVALID_TASK_ID : prevAffiliate.taskId;
}
void setNextAffiliate(TaskRecord nextAffiliate) {
mNextAffiliate = nextAffiliate;
- mNextAffiliateTaskId = nextAffiliate == null ? -1 : nextAffiliate.taskId;
+ mNextAffiliateTaskId = nextAffiliate == null ? INVALID_TASK_ID : nextAffiliate.taskId;
}
// Close up recents linked list.
@@ -875,6 +879,10 @@
static TaskRecord restoreFromXml(XmlPullParser in, ActivityStackSupervisor stackSupervisor)
throws IOException, XmlPullParserException {
+ return restoreFromXml(in, stackSupervisor, INVALID_TASK_ID);
+ }
+ static TaskRecord restoreFromXml(XmlPullParser in, ActivityStackSupervisor stackSupervisor,
+ int inTaskId) throws IOException, XmlPullParserException {
Intent intent = null;
Intent affinityIntent = null;
ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>();
@@ -894,23 +902,23 @@
long lastActiveTime = -1;
long lastTimeOnTop = 0;
boolean neverRelinquishIdentity = true;
- int taskId = -1;
+ int taskId = inTaskId;
final int outerDepth = in.getDepth();
TaskDescription taskDescription = new TaskDescription();
- int taskAffiliation = -1;
+ int taskAffiliation = INVALID_TASK_ID;
int taskAffiliationColor = 0;
- int prevTaskId = -1;
- int nextTaskId = -1;
+ int prevTaskId = INVALID_TASK_ID;
+ int nextTaskId = INVALID_TASK_ID;
int callingUid = -1;
String callingPackage = "";
for (int attrNdx = in.getAttributeCount() - 1; attrNdx >= 0; --attrNdx) {
final String attrName = in.getAttributeName(attrNdx);
final String attrValue = in.getAttributeValue(attrNdx);
- if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: attribute name=" +
- attrName + " value=" + attrValue);
+ if (DEBUG_PERSISTER || DEBUG_RESTORER) Slog.d(TaskPersister.TAG,
+ "TaskRecord: attribute name=" + attrName + " value=" + attrValue);
if (ATTR_TASKID.equals(attrName)) {
- taskId = Integer.valueOf(attrValue);
+ if (taskId == INVALID_TASK_ID) taskId = Integer.valueOf(attrValue);
} else if (ATTR_REALACTIVITY.equals(attrName)) {
realActivity = ComponentName.unflattenFromString(attrValue);
} else if (ATTR_ORIGACTIVITY.equals(attrName)) {
@@ -966,17 +974,16 @@
(event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
if (event == XmlPullParser.START_TAG) {
final String name = in.getName();
- if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: START_TAG name=" +
- name);
+ if (DEBUG_PERSISTER || DEBUG_RESTORER)
+ Slog.d(TaskPersister.TAG, "TaskRecord: START_TAG name=" + name);
if (TAG_AFFINITYINTENT.equals(name)) {
affinityIntent = Intent.restoreFromXml(in);
} else if (TAG_INTENT.equals(name)) {
intent = Intent.restoreFromXml(in);
} else if (TAG_ACTIVITY.equals(name)) {
- ActivityRecord activity =
- ActivityRecord.restoreFromXml(in, taskId, stackSupervisor);
- if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: activity=" +
- activity);
+ ActivityRecord activity = ActivityRecord.restoreFromXml(in, stackSupervisor);
+ if (DEBUG_PERSISTER || DEBUG_RESTORER)
+ Slog.d(TaskPersister.TAG, "TaskRecord: activity=" + activity);
if (activity != null) {
activities.add(activity);
}
@@ -1082,8 +1089,9 @@
pw.print(" mNeverRelinquishIdentity="); pw.print(mNeverRelinquishIdentity);
pw.print(" mReuseTask="); pw.println(mReuseTask);
}
- if (mAffiliatedTaskId != taskId || mPrevAffiliateTaskId != -1 || mPrevAffiliate != null
- || mNextAffiliateTaskId != -1 || mNextAffiliate != null) {
+ if (mAffiliatedTaskId != taskId || mPrevAffiliateTaskId != INVALID_TASK_ID
+ || mPrevAffiliate != null || mNextAffiliateTaskId != INVALID_TASK_ID
+ || mNextAffiliate != null) {
pw.print(prefix); pw.print("affiliation="); pw.print(mAffiliatedTaskId);
pw.print(" prevAffiliation="); pw.print(mPrevAffiliateTaskId);
pw.print(" (");
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 4af920a..6feeb7c 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -96,6 +96,12 @@
networkRequests.put(networkRequest.requestId, networkRequest);
}
+ // Does this network satisfy request?
+ public boolean satisfies(NetworkRequest request) {
+ return created &&
+ request.networkCapabilities.satisfiedByNetworkCapabilities(networkCapabilities);
+ }
+
public boolean isVPN() {
return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
}
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
index da404c4..97a6e85 100644
--- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -38,6 +38,7 @@
* <li>Gather "OSD (display) name" of all acknowledge devices
* <li>Gather "Vendor id" of all acknowledge devices
* </ol>
+ * We attempt to get OSD name/vendor ID up to 5 times in case the communication fails.
*/
final class DeviceDiscoveryAction extends HdmiCecFeatureAction {
private static final String TAG = "DeviceDiscoveryAction";
@@ -87,6 +88,7 @@
private final ArrayList<DeviceInfo> mDevices = new ArrayList<>();
private final DeviceDiscoveryCallback mCallback;
private int mProcessedDeviceCount = 0;
+ private int mTimeoutRetry = 0;
/**
* Constructor.
@@ -309,6 +311,7 @@
private void increaseProcessedDeviceCount() {
mProcessedDeviceCount++;
+ mTimeoutRetry = 0;
}
private void removeDevice(int index) {
@@ -353,19 +356,23 @@
return;
}
} else {
- int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress;
- switch (mState) {
- case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
- queryPhysicalAddress(address);
- return;
- case STATE_WAITING_FOR_OSD_NAME:
- queryOsdName(address);
- return;
- case STATE_WAITING_FOR_VENDOR_ID:
- queryVendorId(address);
- default:
- return;
- }
+ sendQueryCommand();
+ }
+ }
+
+ private void sendQueryCommand() {
+ int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress;
+ switch (mState) {
+ case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
+ queryPhysicalAddress(address);
+ return;
+ case STATE_WAITING_FOR_OSD_NAME:
+ queryOsdName(address);
+ return;
+ case STATE_WAITING_FOR_VENDOR_ID:
+ queryVendorId(address);
+ default:
+ return;
}
}
@@ -375,6 +382,11 @@
return;
}
+ if (++mTimeoutRetry < HdmiConfig.TIMEOUT_RETRY) {
+ sendQueryCommand();
+ return;
+ }
+ mTimeoutRetry = 0;
Slog.v(TAG, "Timeout[State=" + mState + ", Processed=" + mProcessedDeviceCount);
removeDevice(mProcessedDeviceCount);
checkAndProceedStage();
diff --git a/services/core/java/com/android/server/hdmi/HdmiConfig.java b/services/core/java/com/android/server/hdmi/HdmiConfig.java
index 046f393..a787c12 100644
--- a/services/core/java/com/android/server/hdmi/HdmiConfig.java
+++ b/services/core/java/com/android/server/hdmi/HdmiConfig.java
@@ -39,6 +39,10 @@
// Number of retries for polling each device in address allocation mechanism.
static final int ADDRESS_ALLOCATION_RETRY = 3;
+ // Number of retries for sendCommand in actions related to new device discovery.
+ // Number 5 comes from 10 seconds for Chromecast preparation time.
+ static final int TIMEOUT_RETRY = 5;
+
// CEC spec said that it should try retransmission at least once.
// The actual number of send request for a single command will be at most
// RETRANSMISSION_COUNT + 1. Note that it affects only to normal commands
diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
index 64f0703..3d64cc5 100644
--- a/services/core/java/com/android/server/hdmi/NewDeviceAction.java
+++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
@@ -51,6 +51,7 @@
private int mVendorId;
private String mDisplayName;
+ private int mTimeoutRetry;
/**
* Constructor.
@@ -71,15 +72,22 @@
@Override
public boolean start() {
+ requestOsdName(true);
+ return true;
+ }
+
+ private void requestOsdName(boolean firstTry) {
+ if (firstTry) {
+ mTimeoutRetry = 0;
+ }
mState = STATE_WAITING_FOR_SET_OSD_NAME;
if (mayProcessCommandIfCached(mDeviceLogicalAddress, Constants.MESSAGE_SET_OSD_NAME)) {
- return true;
+ return;
}
sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(getSourceAddress(),
mDeviceLogicalAddress));
addTimer(mState, HdmiConfig.TIMEOUT_MS);
- return true;
}
@Override
@@ -103,12 +111,12 @@
} catch (UnsupportedEncodingException e) {
Slog.e(TAG, "Failed to get OSD name: " + e.getMessage());
}
- requestVendorId();
+ requestVendorId(true);
return true;
} else if (opcode == Constants.MESSAGE_FEATURE_ABORT) {
int requestOpcode = params[0] & 0xFF;
if (requestOpcode == Constants.MESSAGE_GIVE_OSD_NAME) {
- requestVendorId();
+ requestVendorId(true);
return true;
}
}
@@ -138,7 +146,10 @@
return false;
}
- private void requestVendorId() {
+ private void requestVendorId(boolean firstTry) {
+ if (firstTry) {
+ mTimeoutRetry = 0;
+ }
// At first, transit to waiting status for <Device Vendor Id>.
mState = STATE_WAITING_FOR_DEVICE_VENDOR_ID;
// If the message is already in cache, process it.
@@ -176,9 +187,17 @@
return;
}
if (state == STATE_WAITING_FOR_SET_OSD_NAME) {
+ if (++mTimeoutRetry < HdmiConfig.TIMEOUT_RETRY) {
+ requestOsdName(false);
+ return;
+ }
// Osd name request timed out. Try vendor id
- requestVendorId();
+ requestVendorId(true);
} else if (state == STATE_WAITING_FOR_DEVICE_VENDOR_ID) {
+ if (++mTimeoutRetry < HdmiConfig.TIMEOUT_RETRY) {
+ requestVendorId(false);
+ return;
+ }
// vendor id timed out. Go ahead creating the device info what we've got so far.
addDeviceInfo();
finish();
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 053c988..f11a3f9 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -443,7 +443,7 @@
synchronized (mLock) {
List<MediaSessionRecord> records = mPriorityStack.getActiveSessions(userId);
int size = records.size();
- if (size > 0) {
+ if (size > 0 && records.get(0).isPlaybackActive(false)) {
rememberMediaButtonReceiverLocked(records.get(0));
}
ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
@@ -702,8 +702,12 @@
try {
synchronized (mLock) {
+ // If we don't have a media button receiver to fall back on
+ // include non-playing sessions for dispatching
+ boolean useNotPlayingSessions = mUserRecords.get(
+ ActivityManager.getCurrentUser()).mLastMediaButtonReceiver == null;
MediaSessionRecord session = mPriorityStack
- .getDefaultMediaButtonSession(mCurrentUserId);
+ .getDefaultMediaButtonSession(mCurrentUserId, useNotPlayingSessions);
if (isVoiceKey(keyEvent.getKeyCode())) {
handleVoiceKeyEventLocked(keyEvent, needWakeLock, session);
} else {
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index c48a075..611718e 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -51,6 +51,9 @@
private MediaSessionRecord mGlobalPrioritySession;
+ // The last record that either entered one of the playing states or was
+ // added.
+ private MediaSessionRecord mLastInterestingRecord;
private MediaSessionRecord mCachedButtonReceiver;
private MediaSessionRecord mCachedDefault;
private MediaSessionRecord mCachedVolumeDefault;
@@ -65,6 +68,7 @@
public void addSession(MediaSessionRecord record) {
mSessions.add(record);
clearCache();
+ mLastInterestingRecord = record;
}
/**
@@ -93,6 +97,9 @@
mSessions.remove(record);
mSessions.add(0, record);
clearCache();
+ // This becomes the last interesting record since it entered a
+ // playing state
+ mLastInterestingRecord = record;
return true;
} else if (!MediaSession.isActiveState(newState)) {
// Just clear the volume cache when a state goes inactive
@@ -168,9 +175,11 @@
* Get the highest priority session that can handle media buttons.
*
* @param userId The user to check.
+ * @param includeNotPlaying Return a non-playing session if nothing else is
+ * available
* @return The default media button session or null.
*/
- public MediaSessionRecord getDefaultMediaButtonSession(int userId) {
+ public MediaSessionRecord getDefaultMediaButtonSession(int userId, boolean includeNotPlaying) {
if (mGlobalPrioritySession != null && mGlobalPrioritySession.isActive()) {
return mGlobalPrioritySession;
}
@@ -180,7 +189,25 @@
ArrayList<MediaSessionRecord> records = getPriorityListLocked(true,
MediaSession.FLAG_HANDLES_MEDIA_BUTTONS, userId);
if (records.size() > 0) {
- mCachedButtonReceiver = records.get(0);
+ MediaSessionRecord record = records.get(0);
+ if (record.isPlaybackActive(false)) {
+ // Since we're going to send a button event to this record make
+ // it the last interesting one.
+ mLastInterestingRecord = record;
+ mCachedButtonReceiver = record;
+ } else if (mLastInterestingRecord != null) {
+ if (records.contains(mLastInterestingRecord)) {
+ mCachedButtonReceiver = mLastInterestingRecord;
+ } else {
+ // That record is no longer used. Clear its reference.
+ mLastInterestingRecord = null;
+ }
+ }
+ if (includeNotPlaying && mCachedButtonReceiver == null) {
+ // If we really want a record and we didn't find one yet use the
+ // highest priority session even if it's not playing.
+ mCachedButtonReceiver = record;
+ }
}
return mCachedButtonReceiver;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 323b34b..aec20bc 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -201,8 +201,8 @@
private boolean mDisableNotificationEffects;
private int mCallState;
- NotificationRecord mSoundNotification;
- NotificationRecord mVibrateNotification;
+ private String mSoundNotificationKey;
+ private String mVibrateNotificationKey;
private final ArraySet<ManagedServiceInfo> mListenersDisablingEffects = new ArraySet<>();
private ComponentName mEffectsSuppressor;
@@ -222,8 +222,8 @@
final ArrayList<ToastRecord> mToastQueue = new ArrayList<ToastRecord>();
final ArrayMap<String, NotificationRecord> mSummaryByGroupKey = new ArrayMap<>();
- ArrayList<String> mLights = new ArrayList<String>();
- NotificationRecord mLedNotification;
+ // The last key in this list owns the hardware.
+ ArrayList<String> mLights = new ArrayList<>();
private AppOpsManager mAppOps;
@@ -595,7 +595,7 @@
if (DBG) Slog.d(TAG, "clearEffects");
// sound
- mSoundNotification = null;
+ mSoundNotificationKey = null;
long identity = Binder.clearCallingIdentity();
try {
@@ -609,7 +609,7 @@
}
// vibrate
- mVibrateNotification = null;
+ mVibrateNotificationKey = null;
identity = Binder.clearCallingIdentity();
try {
mVibrator.cancel();
@@ -619,7 +619,6 @@
// light
mLights.clear();
- mLedNotification = null;
updateLightsLocked();
}
}
@@ -1624,14 +1623,19 @@
if (N > 0) {
pw.println(" Lights List:");
for (int i=0; i<N; i++) {
- pw.println(" " + mLights.get(i));
+ if (i == N - 1) {
+ pw.print(" > ");
+ } else {
+ pw.print(" ");
+ }
+ pw.println(mLights.get(i));
}
pw.println(" ");
}
pw.println(" mUseAttentionLight=" + mUseAttentionLight);
pw.println(" mNotificationPulseEnabled=" + mNotificationPulseEnabled);
- pw.println(" mSoundNotification=" + mSoundNotification);
- pw.println(" mVibrateNotification=" + mVibrateNotification);
+ pw.println(" mSoundNotificationKey=" + mSoundNotificationKey);
+ pw.println(" mVibrateNotificationKey=" + mVibrateNotificationKey);
pw.println(" mDisableNotificationEffects=" + mDisableNotificationEffects);
pw.println(" mCallState=" + callStateToString(mCallState));
pw.println(" mSystemReady=" + mSystemReady);
@@ -2058,7 +2062,7 @@
boolean looping =
(notification.flags & Notification.FLAG_INSISTENT) != 0;
AudioAttributes audioAttributes = audioAttributesForNotification(notification);
- mSoundNotification = record;
+ mSoundNotificationKey = record.getKey();
// do not play notifications if stream volume is 0 (typically because
// ringer mode is silent) or if there is a user of exclusive audio focus
if ((mAudioManager.getStreamVolume(
@@ -2101,7 +2105,7 @@
if ((useDefaultVibrate || convertSoundToVibration || hasCustomVibrate)
&& !(mAudioManager.getRingerModeInternal()
== AudioManager.RINGER_MODE_SILENT)) {
- mVibrateNotification = record;
+ mVibrateNotificationKey = record.getKey();
if (useDefaultVibrate || convertSoundToVibration) {
// Escalate privileges so we can use the vibrator even if the
@@ -2132,9 +2136,6 @@
// light
// release the light
boolean wasShowLights = mLights.remove(record.getKey());
- if (mLedNotification != null && record.getKey().equals(mLedNotification.getKey())) {
- mLedNotification = null;
- }
if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && aboveThreshold) {
mLights.add(record.getKey());
updateLightsLocked();
@@ -2458,9 +2459,11 @@
mListeners.notifyRemovedLocked(r.sbn);
}
+ final String canceledKey = r.getKey();
+
// sound
- if (mSoundNotification == r) {
- mSoundNotification = null;
+ if (canceledKey.equals(mSoundNotificationKey)) {
+ mSoundNotificationKey = null;
final long identity = Binder.clearCallingIdentity();
try {
final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
@@ -2474,8 +2477,8 @@
}
// vibrate
- if (mVibrateNotification == r) {
- mVibrateNotification = null;
+ if (canceledKey.equals(mVibrateNotificationKey)) {
+ mVibrateNotificationKey = null;
long identity = Binder.clearCallingIdentity();
try {
mVibrator.cancel();
@@ -2486,10 +2489,7 @@
}
// light
- mLights.remove(r.getKey());
- if (mLedNotification == r) {
- mLedNotification = null;
- }
+ mLights.remove(canceledKey);
// Record usage stats
switch (reason) {
@@ -2521,7 +2521,7 @@
// Save it for users of getHistoricalNotifications()
mArchive.record(r.sbn);
- EventLogTags.writeNotificationCanceled(r.getKey(), reason);
+ EventLogTags.writeNotificationCanceled(canceledKey, reason);
}
/**
@@ -2727,20 +2727,22 @@
void updateLightsLocked()
{
// handle notification lights
- if (mLedNotification == null) {
- // get next notification, if any
- int n = mLights.size();
- if (n > 0) {
- mLedNotification = mNotificationsByKey.get(mLights.get(n-1));
+ NotificationRecord ledNotification = null;
+ while (ledNotification == null && !mLights.isEmpty()) {
+ final String owner = mLights.get(mLights.size() - 1);
+ ledNotification = mNotificationsByKey.get(owner);
+ if (ledNotification == null) {
+ Slog.wtfStack(TAG, "LED Notification does not exist: " + owner);
+ mLights.remove(owner);
}
}
// Don't flash while we are in a call or screen is on
- if (mLedNotification == null || mInCall || mScreenOn) {
+ if (ledNotification == null || mInCall || mScreenOn) {
mNotificationLight.turnOff();
mStatusBar.notificationLightOff();
} else {
- final Notification ledno = mLedNotification.sbn.getNotification();
+ final Notification ledno = ledNotification.sbn.getNotification();
int ledARGB = ledno.ledARGB;
int ledOnMS = ledno.ledOnMS;
int ledOffMS = ledno.ledOffMS;
@@ -2956,6 +2958,7 @@
protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
if (mListenersDisablingEffects.remove(removed)) {
updateListenerHintsLocked();
+ updateEffectsSuppressorLocked();
}
mLightTrimListeners.remove(removed);
updateNotificationGroupsDesiredLocked();
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 5ab3fa1..5375bfc 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -461,7 +461,7 @@
UserState userState = getUserStateLocked(userId);
SessionState sessionState = userState.sessionStateMap.get(sessionToken);
if (sessionState == null) {
- throw new IllegalArgumentException("Session state not found for token " + sessionToken);
+ throw new SessionNotFoundException("Session state not found for token " + sessionToken);
}
// Only the application that requested this session or the system can access it.
if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.callingUid) {
@@ -589,18 +589,22 @@
}
private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
- SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
- if (sessionState.session != null) {
- UserState userState = getUserStateLocked(userId);
- if (sessionToken == userState.mainSessionToken) {
- setMainLocked(sessionToken, false, callingUid, userId);
- }
- try {
+ SessionState sessionState = null;
+ try {
+ sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
+ if (sessionState.session != null) {
+ UserState userState = getUserStateLocked(userId);
+ if (sessionToken == userState.mainSessionToken) {
+ setMainLocked(sessionToken, false, callingUid, userId);
+ }
sessionState.session.release();
- } catch (RemoteException e) {
- Slog.e(TAG, "session process has already died", e);
}
- sessionState.session = null;
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slog.e(TAG, "error in releaseSession", e);
+ } finally {
+ if (sessionState != null) {
+ sessionState.session = null;
+ }
}
removeSessionStateLocked(sessionToken, userId);
}
@@ -648,19 +652,19 @@
}
private void setMainLocked(IBinder sessionToken, boolean isMain, int callingUid, int userId) {
- SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
- if (sessionState.hardwareSessionToken != null) {
- sessionState = getSessionStateLocked(sessionState.hardwareSessionToken,
- Process.SYSTEM_UID, userId);
- }
- ServiceState serviceState = getServiceStateLocked(sessionState.info.getComponent(), userId);
- if (!serviceState.isHardware) {
- return;
- }
- ITvInputSession session = getSessionLocked(sessionState);
try {
+ SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
+ if (sessionState.hardwareSessionToken != null) {
+ sessionState = getSessionStateLocked(sessionState.hardwareSessionToken,
+ Process.SYSTEM_UID, userId);
+ }
+ ServiceState serviceState = getServiceStateLocked(sessionState.info.getComponent(), userId);
+ if (!serviceState.isHardware) {
+ return;
+ }
+ ITvInputSession session = getSessionLocked(sessionState);
session.setMain(isMain);
- } catch (RemoteException e) {
+ } catch (RemoteException | SessionNotFoundException e) {
Slog.e(TAG, "error in setMain", e);
}
}
@@ -1085,7 +1089,7 @@
getSessionLocked(sessionState.hardwareSessionToken,
Process.SYSTEM_UID, resolvedUserId).setSurface(surface);
}
- } catch (RemoteException e) {
+ } catch (RemoteException | SessionNotFoundException e) {
Slog.e(TAG, "error in setSurface", e);
}
}
@@ -1116,7 +1120,7 @@
getSessionLocked(sessionState.hardwareSessionToken, Process.SYSTEM_UID,
resolvedUserId).dispatchSurfaceChanged(format, width, height);
}
- } catch (RemoteException e) {
+ } catch (RemoteException | SessionNotFoundException e) {
Slog.e(TAG, "error in dispatchSurfaceChanged", e);
}
}
@@ -1146,7 +1150,7 @@
Process.SYSTEM_UID, resolvedUserId).setVolume((volume > 0.0f)
? REMOTE_VOLUME_ON : REMOTE_VOLUME_OFF);
}
- } catch (RemoteException e) {
+ } catch (RemoteException | SessionNotFoundException e) {
Slog.e(TAG, "error in setVolume", e);
}
}
@@ -1183,7 +1187,7 @@
args.arg5 = sessionToken;
mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_START, args)
.sendToTarget();
- } catch (RemoteException e) {
+ } catch (RemoteException | SessionNotFoundException e) {
Slog.e(TAG, "error in tune", e);
return;
}
@@ -1205,7 +1209,7 @@
try {
getSessionLocked(sessionToken, callingUid, resolvedUserId)
.requestUnblockContent(unblockedRating);
- } catch (RemoteException e) {
+ } catch (RemoteException | SessionNotFoundException e) {
Slog.e(TAG, "error in requestUnblockContent", e);
}
}
@@ -1225,7 +1229,7 @@
try {
getSessionLocked(sessionToken, callingUid, resolvedUserId)
.setCaptionEnabled(enabled);
- } catch (RemoteException e) {
+ } catch (RemoteException | SessionNotFoundException e) {
Slog.e(TAG, "error in setCaptionEnabled", e);
}
}
@@ -1245,7 +1249,7 @@
try {
getSessionLocked(sessionToken, callingUid, resolvedUserId).selectTrack(
type, trackId);
- } catch (RemoteException e) {
+ } catch (RemoteException | SessionNotFoundException e) {
Slog.e(TAG, "error in selectTrack", e);
}
}
@@ -1266,7 +1270,7 @@
try {
getSessionLocked(sessionToken, callingUid, resolvedUserId)
.appPrivateCommand(command, data);
- } catch (RemoteException e) {
+ } catch (RemoteException | SessionNotFoundException e) {
Slog.e(TAG, "error in appPrivateCommand", e);
}
}
@@ -1287,7 +1291,7 @@
try {
getSessionLocked(sessionToken, callingUid, resolvedUserId)
.createOverlayView(windowToken, frame);
- } catch (RemoteException e) {
+ } catch (RemoteException | SessionNotFoundException e) {
Slog.e(TAG, "error in createOverlayView", e);
}
}
@@ -1307,7 +1311,7 @@
try {
getSessionLocked(sessionToken, callingUid, resolvedUserId)
.relayoutOverlayView(frame);
- } catch (RemoteException e) {
+ } catch (RemoteException | SessionNotFoundException e) {
Slog.e(TAG, "error in relayoutOverlayView", e);
}
}
@@ -1327,7 +1331,7 @@
try {
getSessionLocked(sessionToken, callingUid, resolvedUserId)
.removeOverlayView();
- } catch (RemoteException e) {
+ } catch (RemoteException | SessionNotFoundException e) {
Slog.e(TAG, "error in removeOverlayView", e);
}
}
@@ -2340,4 +2344,13 @@
}
}
}
+
+ private static class SessionNotFoundException extends IllegalArgumentException {
+ public SessionNotFoundException() {
+ }
+
+ public SessionNotFoundException(String name) {
+ super(name);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 0cbf03a..08754f9 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -159,11 +159,15 @@
}
}
- public void onWindowFocusChangedLocked() {
+ public void onWindowFocusChangedNotLocked() {
// Not relevant for the display magnifier.
- if (mWindowsForAccessibilityObserver != null) {
- mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
+ WindowsForAccessibilityObserver observer = null;
+ synchronized (mWindowManagerService) {
+ observer = mWindowsForAccessibilityObserver;
+ }
+ if (observer != null) {
+ observer.performComputeChangedWindowsNotLocked();
}
}
@@ -937,14 +941,13 @@
computeChangedWindows();
}
+ public void performComputeChangedWindowsNotLocked() {
+ mHandler.removeMessages(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS);
+ computeChangedWindows();
+ }
+
public void scheduleComputeChangedWindowsLocked() {
- // If focus changed, compute changed windows immediately as the focused window
- // is used by the accessibility manager service to determine the active window.
- if (mWindowManagerService.mCurrentFocus != null
- && mWindowManagerService.mCurrentFocus != mWindowManagerService.mLastFocus) {
- mHandler.removeMessages(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS);
- computeChangedWindows();
- } else if (!mHandler.hasMessages(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS)) {
+ if (!mHandler.hasMessages(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS)) {
mHandler.sendEmptyMessageDelayed(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS,
mRecurringAccessibilityEventsIntervalMillis);
}
@@ -955,6 +958,9 @@
Slog.i(LOG_TAG, "computeChangedWindows()");
}
+ boolean windowsChanged = false;
+ List<WindowInfo> windows = new ArrayList<WindowInfo>();
+
synchronized (mWindowManagerService.mWindowMap) {
// Do not send the windows if there is no current focus as
// the window manager is still looking for where to put it.
@@ -975,8 +981,6 @@
SparseArray<WindowState> visibleWindows = mTempWindowStates;
populateVisibleWindowsOnScreenLocked(visibleWindows);
- List<WindowInfo> windows = new ArrayList<WindowInfo>();
-
Set<IBinder> addedWindows = mTempBinderSet;
addedWindows.clear();
@@ -1074,7 +1078,6 @@
addedWindows.clear();
// We computed the windows and if they changed notify the client.
- boolean windowsChanged = false;
if (mOldWindows.size() != windows.size()) {
// Different size means something changed.
windowsChanged = true;
@@ -1096,22 +1099,24 @@
}
if (windowsChanged) {
- if (DEBUG) {
- Log.i(LOG_TAG, "Windows changed:" + windows);
- }
- // Remember the old windows to detect changes.
cacheWindows(windows);
- // Announce the change.
- mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED,
- windows).sendToTarget();
- } else {
- if (DEBUG) {
- Log.i(LOG_TAG, "No windows changed.");
- }
- // Recycle the nodes as we do not need them.
- clearAndRecycleWindows(windows);
}
}
+
+ // Now we do not hold the lock, so send the windows over.
+ if (windowsChanged) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Windows changed:" + windows);
+ }
+ mCallback.onWindowsForAccessibilityChanged(windows);
+ } else {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "No windows changed.");
+ }
+ }
+
+ // Recycle the windows as we do not need them.
+ clearAndRecycleWindows(windows);
}
private void computeWindowBoundsInScreen(WindowState windowState, Rect outBounds) {
@@ -1217,7 +1222,7 @@
return false;
}
- private void clearAndRecycleWindows(List<WindowInfo> windows) {
+ private static void clearAndRecycleWindows(List<WindowInfo> windows) {
final int windowCount = windows.size();
for (int i = windowCount - 1; i >= 0; i--) {
windows.remove(i).recycle();
@@ -1254,7 +1259,6 @@
private class MyHandler extends Handler {
public static final int MESSAGE_COMPUTE_CHANGED_WINDOWS = 1;
- public static final int MESSAGE_NOTIFY_WINDOWS_CHANGED = 2;
public MyHandler(Looper looper) {
super(looper, null, false);
@@ -1267,12 +1271,6 @@
case MESSAGE_COMPUTE_CHANGED_WINDOWS: {
computeChangedWindows();
} break;
-
- case MESSAGE_NOTIFY_WINDOWS_CHANGED: {
- List<WindowInfo> windows = (List<WindowInfo>) message.obj;
- mCallback.onWindowsForAccessibilityChanged(windows);
- clearAndRecycleWindows(windows);
- } break;
}
}
}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 0327cb3..27ac32a 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -242,6 +242,7 @@
final WindowStateAnimator universeBackground = mService.mAnimator.mUniverseBackground;
final int aboveUniverseLayer = mService.mAnimator.mAboveUniverseLayer;
boolean addedUniverse = false;
+ boolean disableWallpaperTouchEvents = false;
// If there's a drag in flight, provide a pseudowindow to catch drag input
final boolean inDrag = (mService.mDragState != null);
@@ -282,8 +283,14 @@
final boolean hasFocus = (child == mInputFocus);
final boolean isVisible = child.isVisibleLw();
+ if ((privateFlags
+ & WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS)
+ != 0) {
+ disableWallpaperTouchEvents = true;
+ }
final boolean hasWallpaper = (child == mService.mWallpaperTarget)
- && (privateFlags & WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD) == 0;
+ && (privateFlags & WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD) == 0
+ && !disableWallpaperTouchEvents;
final boolean onDefaultDisplay = (child.getDisplayId() == Display.DEFAULT_DISPLAY);
// If there's a drag in progress and 'child' is a potential drop target,
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b7857e1..2750941 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7610,7 +7610,15 @@
WindowState lastFocus;
WindowState newFocus;
+ AccessibilityController accessibilityController = null;
+
synchronized(mWindowMap) {
+ // TODO(multidisplay): Accessibility supported only of default desiplay.
+ if (mAccessibilityController != null && getDefaultDisplayContentLocked()
+ .getDisplayId() == Display.DEFAULT_DISPLAY) {
+ accessibilityController = mAccessibilityController;
+ }
+
lastFocus = mLastFocus;
newFocus = mCurrentFocus;
if (lastFocus == newFocus) {
@@ -7628,6 +7636,12 @@
}
}
+ // First notify the accessibility manager for the change so it has
+ // the windows before the newly focused one starts firing eventgs.
+ if (accessibilityController != null) {
+ accessibilityController.onWindowFocusChangedNotLocked();
+ }
+
//System.out.println("Changing focus from " + lastFocus
// + " to " + newFocus);
if (newFocus != null) {
@@ -10402,12 +10416,6 @@
mCurrentFocus = newFocus;
mLosingFocus.remove(newFocus);
- // TODO(multidisplay): Accessibilty supported only of default desiplay.
- if (mAccessibilityController != null
- && displayContent.getDisplayId() == Display.DEFAULT_DISPLAY) {
- mAccessibilityController.onWindowFocusChangedLocked();
- }
-
int focusChanged = mPolicy.focusChangedLw(oldFocus, newFocus);
if (imWindowChanged && oldFocus != mInputMethodWindow) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 87d420f..c2d8004 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -455,6 +455,14 @@
}
}
+ if (!isWindowAnimating()) {
+ //TODO (multidisplay): Accessibility is supported only for the default display.
+ if (mService.mAccessibilityController != null
+ && mWin.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ mService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
+ }
+ }
+
if (!mWin.mExiting) {
return;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2201d2b..72a3337 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -120,7 +120,6 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
-import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
@@ -265,6 +264,8 @@
= new HashMap<ComponentName, ActiveAdmin>();
final ArrayList<ActiveAdmin> mAdminList
= new ArrayList<ActiveAdmin>();
+ final ArrayList<ComponentName> mRemovingAdmins
+ = new ArrayList<ComponentName>();
// This is the list of component allowed to start lock task mode.
final List<String> mLockTaskPackages = new ArrayList<String>();
@@ -303,8 +304,6 @@
if (Intent.ACTION_USER_REMOVED.equals(action)) {
removeUserData(userHandle);
} else if (Intent.ACTION_USER_STARTED.equals(action)
- || Intent.ACTION_PACKAGE_CHANGED.equals(action)
- || Intent.ACTION_PACKAGE_REMOVED.equals(action)
|| Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
if (Intent.ACTION_USER_STARTED.equals(action)) {
@@ -313,8 +312,14 @@
mUserData.remove(userHandle);
}
}
-
- handlePackagesChanged(userHandle);
+ handlePackagesChanged(null /* check all admins */, userHandle);
+ } else if (Intent.ACTION_PACKAGE_CHANGED.equals(action)
+ || (Intent.ACTION_PACKAGE_ADDED.equals(action)
+ && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false))) {
+ handlePackagesChanged(intent.getData().getSchemeSpecificPart(), userHandle);
+ } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)
+ && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ handlePackagesChanged(intent.getData().getSchemeSpecificPart(), userHandle);
}
}
};
@@ -899,7 +904,7 @@
}
}
- private void handlePackagesChanged(int userHandle) {
+ private void handlePackagesChanged(String packageName, int userHandle) {
boolean removed = false;
if (DBG) Slog.d(LOG_TAG, "Handling package changes for user " + userHandle);
DevicePolicyData policy = getUserData(userHandle);
@@ -908,11 +913,17 @@
for (int i = policy.mAdminList.size() - 1; i >= 0; i--) {
ActiveAdmin aa = policy.mAdminList.get(i);
try {
- if (pm.getPackageInfo(aa.info.getPackageName(), 0, userHandle) == null
- || pm.getReceiverInfo(aa.info.getComponent(), 0, userHandle) == null) {
- removed = true;
- policy.mAdminList.remove(i);
- policy.mAdminMap.remove(aa.info.getComponent());
+ // If we're checking all packages or if the specific one we're checking matches,
+ // then check if the package and receiver still exist.
+ final String adminPackage = aa.info.getPackageName();
+ if (packageName == null || packageName.equals(adminPackage)) {
+ if (pm.getPackageInfo(adminPackage, 0, userHandle) == null
+ || pm.getReceiverInfo(aa.info.getComponent(), 0, userHandle)
+ == null) {
+ removed = true;
+ policy.mAdminList.remove(i);
+ policy.mAdminMap.remove(aa.info.getComponent());
+ }
}
} catch (RemoteException re) {
// Shouldn't happen
@@ -1202,6 +1213,9 @@
void removeActiveAdminLocked(final ComponentName adminReceiver, int userHandle) {
final ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver, userHandle);
if (admin != null) {
+ synchronized (this) {
+ getUserData(userHandle).mRemovingAdmins.add(adminReceiver);
+ }
sendAdminCommandLocked(admin,
DeviceAdminReceiver.ACTION_DEVICE_ADMIN_DISABLED,
new BroadcastReceiver() {
@@ -1221,9 +1235,10 @@
}
saveSettingsLocked(userHandle);
updateMaximumTimeToLockLocked(policy);
+ policy.mRemovingAdmins.remove(adminReceiver);
}
}
- });
+ });
}
}
@@ -1788,6 +1803,18 @@
}
}
+ @Override
+ public boolean isRemovingAdmin(ComponentName adminReceiver, int userHandle) {
+ if (!mHasFeature) {
+ return false;
+ }
+ enforceCrossUserPermission(userHandle);
+ synchronized (this) {
+ DevicePolicyData policyData = getUserData(userHandle);
+ return policyData.mRemovingAdmins.contains(adminReceiver);
+ }
+ }
+
public boolean hasGrantedPolicy(ComponentName adminReceiver, int policyId, int userHandle) {
if (!mHasFeature) {
return false;
@@ -4091,6 +4118,10 @@
ap.dump(" ", pw);
}
}
+ if (!policy.mRemovingAdmins.isEmpty()) {
+ p.println(" Removing Device Admins (User " + policy.mUserHandle + "): "
+ + policy.mRemovingAdmins);
+ }
pw.println(" ");
pw.print(" mPasswordOwner="); pw.println(policy.mPasswordOwner);
diff --git a/telecomm/java/android/telecom/AudioState.java b/telecomm/java/android/telecom/AudioState.java
index 3271ebf..9c03319 100644
--- a/telecomm/java/android/telecom/AudioState.java
+++ b/telecomm/java/android/telecom/AudioState.java
@@ -54,14 +54,14 @@
public static final int ROUTE_ALL = ROUTE_EARPIECE | ROUTE_BLUETOOTH | ROUTE_WIRED_HEADSET |
ROUTE_SPEAKER;
- /** @hide */
- @Deprecated public final boolean isMuted;
+ /** Note: Deprecated, please do not use if possible. */
+ @SystemApi public final boolean isMuted;
- /** @hide */
- @Deprecated public final int route;
+ /** Note: Deprecated, please do not use if possible. */
+ @SystemApi public final int route;
- /** @hide */
- @Deprecated public final int supportedRouteMask;
+ /** Note: Deprecated, please do not use if possible. */
+ @SystemApi public final int supportedRouteMask;
public AudioState(boolean muted, int route, int supportedRouteMask) {
this.isMuted = muted;
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 2a3d7ab..6621726 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -732,6 +732,26 @@
}
/**
+ * Return whether a given phone account has a voicemail number configured.
+ *
+ * @param accountHandle The handle for the account to check for a voicemail number.
+ * @return {@code true} If the given phone account has a voicemail number.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean hasVoiceMailNumber(PhoneAccountHandle accountHandle) {
+ try {
+ if (isServiceConnected()) {
+ return getTelecomService().hasVoiceMailNumber(accountHandle);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException calling isInCall().", e);
+ }
+ return false;
+ }
+
+ /**
* Returns whether there is an ongoing phone call (can be in dialing, ringing, active or holding
* states).
* <p>
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index cbd9d69..f8d7539 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -121,6 +121,11 @@
boolean isVoiceMailNumber(in PhoneAccountHandle accountHandle, String number);
/**
+ * @see TelecomServiceImpl#hasVoiceMailNumber
+ */
+ boolean hasVoiceMailNumber(in PhoneAccountHandle accountHandle);
+
+ /**
* @see TelecomServiceImpl#getDefaultPhoneApp
*/
ComponentName getDefaultPhoneApp();
diff --git a/tests/ActivityTests/AndroidManifest.xml b/tests/ActivityTests/AndroidManifest.xml
index 513f622..33d40ad 100644
--- a/tests/ActivityTests/AndroidManifest.xml
+++ b/tests/ActivityTests/AndroidManifest.xml
@@ -57,6 +57,8 @@
</service>
<receiver android:name="UserTarget">
</receiver>
+ <service android:name="IsolatedService" android:isolatedProcess="true">
+ </service>
<receiver android:name="StartEmpty" android:exported="true">
<intent-filter>
<action android:name="com.example.START_EMPTY" />
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
index b065b88..4281c68 100644
--- a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
+++ b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
@@ -62,6 +62,8 @@
ArrayList<ServiceConnection> mConnections = new ArrayList<ServiceConnection>();
+ ServiceConnection mIsolatedConnection;
+
static final int MSG_SPAM = 1;
final Handler mHandler = new Handler() {
@@ -207,6 +209,34 @@
return true;
}
});
+ menu.add("Rebind Isolated!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override public boolean onMenuItemClick(MenuItem item) {
+ Intent intent = new Intent(ActivityTestMain.this, IsolatedService.class);
+ ServiceConnection conn = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ Log.i(TAG, "Isolated service connected " + name + " " + service);
+ }
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.i(TAG, "Isolated service disconnected " + name);
+ }
+ };
+ if (mIsolatedConnection != null) {
+ Log.i(TAG, "Unbinding existing service: " + mIsolatedConnection);
+ unbindService(mIsolatedConnection);
+ mIsolatedConnection = null;
+ }
+ Log.i(TAG, "Binding new service: " + conn);
+ if (bindService(intent, conn, Context.BIND_AUTO_CREATE)) {
+ mIsolatedConnection = conn;
+ } else {
+ Toast.makeText(ActivityTestMain.this, "Failed to bind",
+ Toast.LENGTH_LONG).show();
+ }
+ return true;
+ }
+ });
menu.add("Send!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override public boolean onMenuItemClick(MenuItem item) {
Intent intent = new Intent(ActivityTestMain.this, SingleUserReceiver.class);
@@ -417,6 +447,10 @@
unbindService(conn);
}
mConnections.clear();
+ if (mIsolatedConnection != null) {
+ unbindService(mIsolatedConnection);
+ mIsolatedConnection = null;
+ }
}
@Override
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/IsolatedService.java b/tests/ActivityTests/src/com/google/android/test/activity/IsolatedService.java
new file mode 100644
index 0000000..f3bf42e
--- /dev/null
+++ b/tests/ActivityTests/src/com/google/android/test/activity/IsolatedService.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.test.activity;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.util.Log;
+
+public class IsolatedService extends Service {
+ Binder mBinder = new Binder();
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Log.i("IsolatedService", "Service created in pid " + android.os.Process.myPid());
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ Log.i("IsolatedService", "Service destroyed in pid " + android.os.Process.myPid());
+ }
+}
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index bdc6586..6d03311 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -12,6 +12,7 @@
#include "ResourceIdCache.h"
#include "SdkConstants.h"
+#include <algorithm>
#include <androidfw/ResourceTypes.h>
#include <utils/ByteOrder.h>
#include <utils/TypeHelpers.h>
@@ -19,6 +20,8 @@
#define NOISY(x) //x
+static const char* kAttrPrivateType = "^attr-private";
+
status_t compileXmlFile(const Bundle* bundle,
const sp<AaptAssets>& assets,
const String16& resourceName,
@@ -2131,8 +2134,16 @@
if (p == NULL) return 0;
sp<Type> t = p->getTypes().valueFor(type);
if (t == NULL) return 0;
- sp<ConfigList> c = t->getConfigs().valueFor(name);
- if (c == NULL) return 0;
+ sp<ConfigList> c = t->getConfigs().valueFor(name);
+ if (c == NULL) {
+ if (type != String16("attr")) {
+ return 0;
+ }
+ t = p->getTypes().valueFor(String16(kAttrPrivateType));
+ if (t == NULL) return 0;
+ c = t->getConfigs().valueFor(name);
+ if (c == NULL) return 0;
+ }
int32_t ei = c->getEntryIndex();
if (ei < 0) return 0;
@@ -2266,7 +2277,15 @@
sp<Type> t = p->getTypes().valueFor(type);
if (t == NULL) return 0;
sp<ConfigList> c = t->getConfigs().valueFor(name);
- if (c == NULL) return 0;
+ if (c == NULL) {
+ if (type != String16("attr")) {
+ return 0;
+ }
+ t = p->getTypes().valueFor(String16(kAttrPrivateType));
+ if (t == NULL) return 0;
+ c = t->getConfigs().valueFor(name);
+ if (c == NULL) return 0;
+ }
int32_t ei = c->getEntryIndex();
if (ei < 0) return 0;
return getResId(p, t, ei);
@@ -2470,6 +2489,10 @@
continue;
}
+ if (mPackageType == System) {
+ p->movePrivateAttrs();
+ }
+
// This has no sense for packages being built as AppFeature (aka with a non-zero offset).
status_t err = p->applyPublicTypeOrder();
if (err != NO_ERROR && firstError == NO_ERROR) {
@@ -2540,15 +2563,20 @@
}
}
+
// Assign resource IDs to keys in bags...
for (size_t ti = 0; ti < typeCount; ti++) {
sp<Type> t = p->getOrderedTypes().itemAt(ti);
if (t == NULL) {
continue;
}
+
const size_t N = t->getOrderedConfigs().size();
for (size_t ci=0; ci<N; ci++) {
sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
+ if (c == NULL) {
+ continue;
+ }
//printf("Ordered config #%d: %p\n", ci, c.get());
const size_t N = c->getEntries().size();
for (size_t ei=0; ei<N; ei++) {
@@ -2586,9 +2614,15 @@
if (t == NULL) {
continue;
}
+
const size_t N = t->getOrderedConfigs().size();
- sp<AaptSymbols> typeSymbols =
- outSymbols->addNestedSymbol(String8(t->getName()), t->getPos());
+ sp<AaptSymbols> typeSymbols;
+ if (t->getName() == String16(kAttrPrivateType)) {
+ typeSymbols = outSymbols->addNestedSymbol(String8("attr"), t->getPos());
+ } else {
+ typeSymbols = outSymbols->addNestedSymbol(String8(t->getName()), t->getPos());
+ }
+
if (typeSymbols == NULL) {
return UNKNOWN_ERROR;
}
@@ -2954,6 +2988,10 @@
for (size_t ei=0; ei<N; ei++) {
sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei);
+ if (cl == NULL) {
+ continue;
+ }
+
if (cl->getPublic()) {
typeSpecFlags[ei] |= htodl(ResTable_typeSpec::SPEC_PUBLIC);
}
@@ -2984,12 +3022,13 @@
// We need to write one type chunk for each configuration for
// which we have entries in this type.
- const size_t NC = t->getUniqueConfigs().size();
+ const SortedVector<ConfigDescription> uniqueConfigs(t->getUniqueConfigs());
+ const size_t NC = uniqueConfigs.size();
const size_t typeSize = sizeof(ResTable_type) + sizeof(uint32_t)*N;
for (size_t ci=0; ci<NC; ci++) {
- ConfigDescription config = t->getUniqueConfigs().itemAt(ci);
+ const ConfigDescription& config = uniqueConfigs[ci];
NOISY(printf("Writing config %d config: imsi:%d/%d lang:%c%c cnt:%c%c "
"orien:%d ui:%d touch:%d density:%d key:%d inp:%d nav:%d sz:%dx%d "
@@ -3061,7 +3100,10 @@
// Build the entries inside of this type.
for (size_t ei=0; ei<N; ei++) {
sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei);
- sp<Entry> e = cl->getEntries().valueFor(config);
+ sp<Entry> e = NULL;
+ if (cl != NULL) {
+ e = cl->getEntries().valueFor(config);
+ }
// Set the offset for this entry in its type.
uint32_t* index = (uint32_t*)
@@ -3096,9 +3138,11 @@
for (size_t i = 0; i < N; ++i) {
if (!validResources[i]) {
sp<ConfigList> c = t->getOrderedConfigs().itemAt(i);
- fprintf(stderr, "%s: no entries written for %s/%s (0x%08x)\n", log_prefix,
- String8(typeName).string(), String8(c->getName()).string(),
- Res_MAKEID(p->getAssignedId() - 1, ti, i));
+ if (c != NULL) {
+ fprintf(stderr, "%s: no entries written for %s/%s (0x%08x)\n", log_prefix,
+ String8(typeName).string(), String8(c->getName()).string(),
+ Res_MAKEID(p->getAssignedId() - 1, ti, i));
+ }
missing_entry = true;
}
}
@@ -3807,11 +3851,45 @@
*/
}
- mUniqueConfigs.add(cdesc);
-
return e;
}
+sp<ResourceTable::ConfigList> ResourceTable::Type::removeEntry(const String16& entry) {
+ ssize_t idx = mConfigs.indexOfKey(entry);
+ if (idx < 0) {
+ return NULL;
+ }
+
+ sp<ConfigList> removed = mConfigs.valueAt(idx);
+ mConfigs.removeItemsAt(idx);
+
+ Vector<sp<ConfigList> >::iterator iter = std::find(
+ mOrderedConfigs.begin(), mOrderedConfigs.end(), removed);
+ if (iter != mOrderedConfigs.end()) {
+ mOrderedConfigs.erase(iter);
+ }
+
+ mPublic.removeItem(entry);
+ return removed;
+}
+
+SortedVector<ConfigDescription> ResourceTable::Type::getUniqueConfigs() const {
+ SortedVector<ConfigDescription> unique;
+ const size_t entryCount = mOrderedConfigs.size();
+ for (size_t i = 0; i < entryCount; i++) {
+ if (mOrderedConfigs[i] == NULL) {
+ continue;
+ }
+ const DefaultKeyedVector<ConfigDescription, sp<Entry> >& configs =
+ mOrderedConfigs[i]->getEntries();
+ const size_t configCount = configs.size();
+ for (size_t j = 0; j < configCount; j++) {
+ unique.add(configs.keyAt(j));
+ }
+ }
+ return unique;
+}
+
status_t ResourceTable::Type::applyPublicEntryOrder()
{
size_t N = mOrderedConfigs.size();
@@ -3838,11 +3916,10 @@
//printf("#%d: \"%s\"\n", i, String8(e->getName()).string());
if (e->getName() == name) {
if (idx >= (int32_t)mOrderedConfigs.size()) {
- p.sourcePos.error("Public entry identifier 0x%x entry index "
- "is larger than available symbols (index %d, total symbols %d).\n",
- p.ident, idx, mOrderedConfigs.size());
- hasError = true;
- } else if (mOrderedConfigs.itemAt(idx) == NULL) {
+ mOrderedConfigs.resize(idx + 1);
+ }
+
+ if (mOrderedConfigs.itemAt(idx) == NULL) {
e->setPublic(true);
e->setPublicSourcePos(p.sourcePos);
mOrderedConfigs.replaceAt(e, idx);
@@ -4018,6 +4095,61 @@
return NO_ERROR;
}
+void ResourceTable::Package::movePrivateAttrs() {
+ sp<Type> attr = mTypes.valueFor(String16("attr"));
+ if (attr == NULL) {
+ // Nothing to do.
+ return;
+ }
+
+ Vector<sp<ConfigList> > privateAttrs;
+
+ bool hasPublic = false;
+ const Vector<sp<ConfigList> >& configs = attr->getOrderedConfigs();
+ const size_t configCount = configs.size();
+ for (size_t i = 0; i < configCount; i++) {
+ if (configs[i] == NULL) {
+ continue;
+ }
+
+ if (attr->isPublic(configs[i]->getName())) {
+ hasPublic = true;
+ } else {
+ privateAttrs.add(configs[i]);
+ }
+ }
+
+ // Only if we have public attributes do we create a separate type for
+ // private attributes.
+ if (!hasPublic) {
+ return;
+ }
+
+ // Create a new type for private attributes.
+ sp<Type> privateAttrType = getType(String16(kAttrPrivateType), SourcePos());
+
+ const size_t privateAttrCount = privateAttrs.size();
+ for (size_t i = 0; i < privateAttrCount; i++) {
+ const sp<ConfigList>& cl = privateAttrs[i];
+
+ // Remove the private attributes from their current type.
+ attr->removeEntry(cl->getName());
+
+ // Add it to the new type.
+ const DefaultKeyedVector<ConfigDescription, sp<Entry> >& entries = cl->getEntries();
+ const size_t entryCount = entries.size();
+ for (size_t j = 0; j < entryCount; j++) {
+ const sp<Entry>& oldEntry = entries[j];
+ sp<Entry> entry = privateAttrType->getEntry(
+ cl->getName(), oldEntry->getPos(), &entries.keyAt(j));
+ *entry = *oldEntry;
+ }
+
+ // Move the symbols to the new type.
+
+ }
+}
+
sp<ResourceTable::Package> ResourceTable::getPackage(const String16& package)
{
if (package != mAssetsPackage) {
diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h
index db392c8..81590bc 100644
--- a/tools/aapt/ResourceTable.h
+++ b/tools/aapt/ResourceTable.h
@@ -470,6 +470,14 @@
bool overlay = false,
bool autoAddOverlay = false);
+ bool isPublic(const String16& entry) const {
+ return mPublic.indexOfKey(entry) >= 0;
+ }
+
+ sp<ConfigList> removeEntry(const String16& entry);
+
+ SortedVector<ConfigDescription> getUniqueConfigs() const;
+
const SourcePos& getFirstPublicSourcePos() const { return *mFirstPublicSourcePos; }
int32_t getPublicIndex() const { return mPublicIndex; }
@@ -479,19 +487,16 @@
status_t applyPublicEntryOrder();
- const SortedVector<ConfigDescription>& getUniqueConfigs() const { return mUniqueConfigs; }
-
const DefaultKeyedVector<String16, sp<ConfigList> >& getConfigs() const { return mConfigs; }
const Vector<sp<ConfigList> >& getOrderedConfigs() const { return mOrderedConfigs; }
-
const SortedVector<String16>& getCanAddEntries() const { return mCanAddEntries; }
const SourcePos& getPos() const { return mPos; }
+
private:
String16 mName;
SourcePos* mFirstPublicSourcePos;
DefaultKeyedVector<String16, Public> mPublic;
- SortedVector<ConfigDescription> mUniqueConfigs;
DefaultKeyedVector<String16, sp<ConfigList> > mConfigs;
Vector<sp<ConfigList> > mOrderedConfigs;
SortedVector<String16> mCanAddEntries;
@@ -527,6 +532,8 @@
const DefaultKeyedVector<String16, sp<Type> >& getTypes() const { return mTypes; }
const Vector<sp<Type> >& getOrderedTypes() const { return mOrderedTypes; }
+ void movePrivateAttrs();
+
private:
status_t setStrings(const sp<AaptFile>& data,
ResStringPool* strings,