Merge "Relax access on deprecated fields for compatibility." 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/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/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/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/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/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/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/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/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/training/wearables/ui/exit.jd b/docs/html/training/wearables/ui/exit.jd
index 6b205a57..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/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 d5d5d51..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()) {
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/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/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/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/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/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 8cf1020..221a0a6 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -9293,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 "
@@ -12900,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) {
@@ -15660,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
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/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/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/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 2a3d7ab8..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 b065b88c..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());
+ }
+}