Merge "Link to new app notification settings from the inspector." into lmp-dev
diff --git a/api/current.txt b/api/current.txt
index d4f2cc5..a51216e3 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -479,6 +479,7 @@
field public static final int dashWidth = 16843174; // 0x10101a6
field public static final int data = 16842798; // 0x101002e
field public static final int datePickerDialogTheme = 16843951; // 0x10104af
+ field public static final int datePickerMode = 16843958; // 0x10104b6
field public static final int datePickerStyle = 16843612; // 0x101035c
field public static final int dateTextAppearance = 16843593; // 0x1010349
field public static final int dayOfWeekBackgroundColor = 16843926; // 0x1010496
@@ -1313,6 +1314,7 @@
field public static final int tileModeX = 16843897; // 0x1010479
field public static final int tileModeY = 16843898; // 0x101047a
field public static final int timePickerDialogTheme = 16843936; // 0x10104a0
+ field public static final int timePickerMode = 16843959; // 0x10104b7
field public static final int timePickerStyle = 16843935; // 0x101049f
field public static final int timeZone = 16843724; // 0x10103cc
field public static final int tint = 16843041; // 0x1010121
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 5684a7a..90b8b86 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -32,7 +32,6 @@
import android.media.session.MediaSession;
import android.net.Uri;
import android.os.BadParcelableException;
-import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -1900,8 +1899,7 @@
mPriority = PRIORITY_DEFAULT;
mPeople = new ArrayList<String>();
- mColorUtil = context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.L ?
- NotificationColorUtil.getInstance() : null;
+ mColorUtil = NotificationColorUtil.getInstance();
}
/**
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 04e6227..3087506 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -864,15 +864,6 @@
}
/**
- * Kept during L development to simplify updating unbundled apps.
- * TODO: Remove after 2014-08-04
- * @hide
- */
- public String getBadgedLabelForUser(String label, UserHandle user) {
- return (String) getBadgedLabelForUser((CharSequence) label, user);
- }
-
- /**
* If the target user is a managed profile of the calling user or the caller
* is itself a managed profile, then this returns a drawable to use as a small
* icon to include in a view to distinguish it from the original icon.
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 74c66c8..d0a2eab 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -84,6 +84,9 @@
public class DatePicker extends FrameLayout {
private static final String LOG_TAG = DatePicker.class.getSimpleName();
+ private static final int MODE_SPINNER = 1;
+ private static final int MODE_CALENDAR = 2;
+
private final DatePickerDelegate mDelegate;
/**
@@ -120,24 +123,28 @@
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DatePicker,
defStyleAttr, defStyleRes);
- final boolean legacyMode = a.getBoolean(R.styleable.DatePicker_legacyMode, true);
+ int mode = a.getInt(R.styleable.DatePicker_datePickerMode, MODE_SPINNER);
a.recycle();
- if (legacyMode) {
- mDelegate = createLegacyUIDelegate(context, attrs, defStyleAttr, defStyleRes);
- } else {
- mDelegate = createNewUIDelegate(context, attrs, defStyleAttr, defStyleRes);
+ switch (mode) {
+ case MODE_CALENDAR:
+ mDelegate = createCalendarUIDelegate(context, attrs, defStyleAttr, defStyleRes);
+ break;
+ case MODE_SPINNER:
+ default:
+ mDelegate = createSpinnerUIDelegate(context, attrs, defStyleAttr, defStyleRes);
+ break;
}
}
- private DatePickerDelegate createLegacyUIDelegate(Context context, AttributeSet attrs,
+ private DatePickerDelegate createSpinnerUIDelegate(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
- return new LegacyDatePickerDelegate(this, context, attrs, defStyleAttr, defStyleRes);
+ return new DatePickerSpinnerDelegate(this, context, attrs, defStyleAttr, defStyleRes);
}
- private DatePickerDelegate createNewUIDelegate(Context context, AttributeSet attrs,
+ private DatePickerDelegate createCalendarUIDelegate(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
- return new android.widget.DatePickerDelegate(this, context, attrs, defStyleAttr,
+ return new DatePickerCalendarDelegate(this, context, attrs, defStyleAttr,
defStyleRes);
}
@@ -454,7 +461,7 @@
/**
* A delegate implementing the basic DatePicker
*/
- private static class LegacyDatePickerDelegate extends AbstractDatePickerDelegate {
+ private static class DatePickerSpinnerDelegate extends AbstractDatePickerDelegate {
private static final String DATE_FORMAT = "MM/dd/yyyy";
@@ -500,7 +507,7 @@
private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
- LegacyDatePickerDelegate(DatePicker delegator, Context context, AttributeSet attrs,
+ DatePickerSpinnerDelegate(DatePicker delegator, Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(delegator, context);
diff --git a/core/java/android/widget/DatePickerDelegate.java b/core/java/android/widget/DatePickerCalendarDelegate.java
similarity index 99%
rename from core/java/android/widget/DatePickerDelegate.java
rename to core/java/android/widget/DatePickerCalendarDelegate.java
index ddc565d..c0c76ac 100644
--- a/core/java/android/widget/DatePickerDelegate.java
+++ b/core/java/android/widget/DatePickerCalendarDelegate.java
@@ -52,7 +52,7 @@
/**
* A delegate for picking up a date (day / month / year).
*/
-class DatePickerDelegate extends DatePicker.AbstractDatePickerDelegate implements
+class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate implements
View.OnClickListener, DatePickerController {
private static final int UNINITIALIZED = -1;
@@ -113,7 +113,7 @@
private HashSet<OnDateChangedListener> mListeners = new HashSet<OnDateChangedListener>();
- public DatePickerDelegate(DatePicker delegator, Context context, AttributeSet attrs,
+ public DatePickerCalendarDelegate(DatePicker delegator, Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(delegator, context);
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 026a8ee..c488666 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -45,6 +45,9 @@
*/
@Widget
public class TimePicker extends FrameLayout {
+ private static final int MODE_SPINNER = 1;
+ private static final int MODE_CLOCK = 2;
+
private final TimePickerDelegate mDelegate;
/**
@@ -77,15 +80,19 @@
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.TimePicker, defStyleAttr, defStyleRes);
- final boolean legacyMode = a.getBoolean(R.styleable.TimePicker_legacyMode, true);
+ int mode = a.getInt(R.styleable.TimePicker_timePickerMode, MODE_SPINNER);
a.recycle();
- if (legacyMode) {
- mDelegate = new LegacyTimePickerDelegate(
- this, context, attrs, defStyleAttr, defStyleRes);
- } else {
- mDelegate = new android.widget.TimePickerDelegate(
- this, context, attrs, defStyleAttr, defStyleRes);
+ switch (mode) {
+ case MODE_CLOCK:
+ mDelegate = new TimePickerSpinnerDelegate(
+ this, context, attrs, defStyleAttr, defStyleRes);
+ break;
+ case MODE_SPINNER:
+ default:
+ mDelegate = new TimePickerClockDelegate(
+ this, context, attrs, defStyleAttr, defStyleRes);
+ break;
}
}
diff --git a/core/java/android/widget/LegacyTimePickerDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
similarity index 98%
rename from core/java/android/widget/LegacyTimePickerDelegate.java
rename to core/java/android/widget/TimePickerClockDelegate.java
index 70db211..59ec6d3 100644
--- a/core/java/android/widget/LegacyTimePickerDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -44,7 +44,7 @@
/**
* A delegate implementing the basic TimePicker
*/
-class LegacyTimePickerDelegate extends TimePicker.AbstractTimePickerDelegate {
+class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate {
private static final boolean DEFAULT_ENABLED_STATE = true;
@@ -100,8 +100,8 @@
}
};
- public LegacyTimePickerDelegate(TimePicker delegator, Context context, AttributeSet attrs,
- int defStyleAttr, int defStyleRes) {
+ public TimePickerClockDelegate(TimePicker delegator, Context context, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
super(delegator, context);
// process style attributes
diff --git a/core/java/android/widget/TimePickerDelegate.java b/core/java/android/widget/TimePickerSpinnerDelegate.java
similarity index 99%
rename from core/java/android/widget/TimePickerDelegate.java
rename to core/java/android/widget/TimePickerSpinnerDelegate.java
index c68619c..4e9a39f 100644
--- a/core/java/android/widget/TimePickerDelegate.java
+++ b/core/java/android/widget/TimePickerSpinnerDelegate.java
@@ -51,7 +51,7 @@
/**
* A view for selecting the time of day, in either 24 hour or AM/PM mode.
*/
-class TimePickerDelegate extends TimePicker.AbstractTimePickerDelegate implements
+class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate implements
RadialTimePickerView.OnValueSelectedListener {
private static final String TAG = "TimePickerDelegate";
@@ -119,8 +119,8 @@
private Calendar mTempCalendar;
- public TimePickerDelegate(TimePicker delegator, Context context, AttributeSet attrs,
- int defStyleAttr, int defStyleRes) {
+ public TimePickerSpinnerDelegate(TimePicker delegator, Context context, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
super(delegator, context);
// process style attributes
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 2baa599..771690e 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4260,8 +4260,6 @@
<attr name="internalLayout" format="reference" />
<!-- @hide The layout of the legacy DatePicker. -->
<attr name="legacyLayout" />
- <!-- @hide Enables or disable the use of the legacy layout for the DatePicker. -->
- <attr name="legacyMode" />
<!-- The background color for the date selector 's day of week. -->
<attr name="dayOfWeekBackgroundColor" format="color" />
<!-- The text color for the date selector's day of week. -->
@@ -4288,6 +4286,15 @@
if the text color does not explicitly have a color set for the
selected state. -->
<attr name="calendarSelectedTextColor" format="color" />
+ <!-- Defines the look of the widget. Prior to the L release, the only choice was
+ spinner. As of L, with the Material theme selected, the default layout is calendar,
+ but this attribute can be used to force spinner to be used instead. -->
+ <attr name="datePickerMode">
+ <!-- Date picker with spinner controls to select the date. -->
+ <enum name="spinner" value="1" />
+ <!-- Date picker with calendar to select the date. -->
+ <enum name="calendar" value="2" />
+ </attr>
</declare-styleable>
<declare-styleable name="TwoLineListItem">
@@ -4557,8 +4564,6 @@
</declare-styleable>
<declare-styleable name="TimePicker">
- <!-- @hide Enables or disable the use of the legacy layout for the TimePicker. -->
- <attr name="legacyMode" format="boolean" />
<!-- @hide The layout of the legacy time picker. -->
<attr name="legacyLayout" format="reference" />
<!-- @hide The layout of the time picker. -->
@@ -4587,6 +4592,15 @@
<attr name="amPmSelectedBackgroundColor" format="color" />
<!-- The color for the hours/minutes selector of the TimePicker. -->
<attr name="numbersSelectorColor" format="color" />
+ <!-- Defines the look of the widget. Prior to the L release, the only choice was
+ spinner. As of L, with the Material theme selected, the default layout is clock,
+ but this attribute can be used to force spinner to be used instead. -->
+ <attr name="timePickerMode">
+ <!-- Time picker with spinner controls to select the time. -->
+ <enum name="spinner" value="1" />
+ <!-- Time picker with clock face to select the time. -->
+ <enum name="clock" value="2" />
+ </attr>
</declare-styleable>
<!-- ========================= -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 08398f0..73aaafd 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2562,4 +2562,7 @@
<public type="raw" name="nodomain"/>
<public type="attr" name="contentRatingSystemXml"/>
+
+ <public type="attr" name="datePickerMode"/>
+ <public type="attr" name="timePickerMode"/>
</resources>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 4a70952..a5cac6b 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -568,12 +568,12 @@
</style>
<style name="Widget.TimePicker">
- <item name="legacyMode">true</item>
+ <item name="timePickerMode">spinner</item>
<item name="legacyLayout">@layout/time_picker_legacy</item>
</style>
<style name="Widget.DatePicker">
- <item name="legacyMode">true</item>
+ <item name="datePickerMode">spinner</item>
<item name="legacyLayout">@layout/date_picker_legacy</item>
<item name="calendarViewShown">false</item>
</style>
diff --git a/core/res/res/values/styles_holo.xml b/core/res/res/values/styles_holo.xml
index d79e46d..2a54ccf 100644
--- a/core/res/res/values/styles_holo.xml
+++ b/core/res/res/values/styles_holo.xml
@@ -462,7 +462,7 @@
</style>
<style name="Widget.Holo.TimePicker" parent="Widget.TimePicker">
- <item name="legacyMode">true</item>
+ <item name="timePickerMode">spinner</item>
<item name="legacyLayout">@layout/time_picker_legacy_holo</item>
<!-- Attributes for new-style TimePicker. -->
<item name="internalLayout">@layout/time_picker_holo</item>
@@ -479,7 +479,7 @@
</style>
<style name="Widget.Holo.DatePicker" parent="Widget.DatePicker">
- <item name="legacyMode">true</item>
+ <item name="datePickerMode">spinner</item>
<item name="legacyLayout">@layout/date_picker_legacy_holo</item>
<item name="internalLayout">@layout/date_picker_holo</item>
<item name="calendarViewShown">true</item>
@@ -886,7 +886,7 @@
<style name="Widget.Holo.Light.NumberPicker" parent="Widget.Holo.NumberPicker" />
<style name="Widget.Holo.Light.TimePicker" parent="Widget.TimePicker">
- <item name="legacyMode">true</item>
+ <item name="timePickerMode">spinner</item>
<item name="legacyLayout">@layout/time_picker_legacy_holo</item>
<!-- Non-legacy styling -->
<item name="internalLayout">@layout/time_picker_holo</item>
@@ -903,7 +903,7 @@
</style>
<style name="Widget.Holo.Light.DatePicker" parent="Widget.DatePicker">
- <item name="legacyMode">true</item>
+ <item name="datePickerMode">spinner</item>
<item name="legacyLayout">@layout/date_picker_legacy_holo</item>
<item name="internalLayout">@layout/date_picker_holo</item>
<item name="calendarViewShown">true</item>
diff --git a/core/res/res/values/styles_leanback.xml b/core/res/res/values/styles_leanback.xml
index a899c4d..256ef00 100644
--- a/core/res/res/values/styles_leanback.xml
+++ b/core/res/res/values/styles_leanback.xml
@@ -23,12 +23,12 @@
</style>
<style name="Widget.Leanback.TimePicker" parent="Widget.Material.TimePicker">
- <item name="legacyMode">true</item>
+ <item name="timePickerMode">spinner</item>
<item name="legacyLayout">@layout/time_picker_legacy_leanback</item>
</style>
<style name="Widget.Leanback.DatePicker" parent="Widget.Material.DatePicker">
- <item name="legacyMode">true</item>
+ <item name="datePickerMode">spinner</item>
</style>
<style name="Widget.Leanback.NumberPicker" parent="Widget.Material.NumberPicker">
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 2dc0438..97d4bf6 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -587,7 +587,7 @@
</style>
<style name="Widget.Material.TimePicker" parent="Widget.TimePicker">
- <item name="legacyMode">false</item>
+ <item name="timePickerMode">clock</item>
<item name="legacyLayout">@layout/time_picker_legacy_holo</item>
<!-- Attributes for new-style TimePicker. -->
<item name="internalLayout">@layout/time_picker_holo</item>
@@ -604,7 +604,7 @@
</style>
<style name="Widget.Material.DatePicker" parent="Widget.DatePicker">
- <item name="legacyMode">false</item>
+ <item name="datePickerMode">calendar</item>
<item name="legacyLayout">@layout/date_picker_legacy_holo</item>
<!-- Attributes for new-style DatePicker. -->
<item name="internalLayout">@layout/date_picker_holo</item>
diff --git a/docs/html/training/articles/security-gms-provider.jd b/docs/html/training/articles/security-gms-provider.jd
new file mode 100644
index 0000000..0d3cf1e
--- /dev/null
+++ b/docs/html/training/articles/security-gms-provider.jd
@@ -0,0 +1,298 @@
+page.title=Updating Your Security Provider to Protect Against SSL Exploits
+page.tags="network","certificates"
+
+page.article=true
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+<h2>In this document</h2>
+<ol class="nolist">
+ <li><a href="#patching">Patching the Security Provider with
+ ProviderInstaller</a></li>
+ <li><a href="#example_sync">Patching Synchronously</a></li>
+ <li><a href="#example_async">Patching Asynchronously</a></li>
+
+</ol>
+
+
+<h2>See also</h2>
+<ul>
+ <li><a href="{@docRoot}google/play-services/">Google Play Services</a></li>
+ <li><a href="https://www.openssl.org/news/secadv_20140605.txt">OpenSSL
+ Security Advisory [05 Jun 2014]: SSL/TLS MITM vulnerability
+ (CVE-2014-0224)</a></li>
+ <li><a href="http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-0224">
+ Vulnerability Summary for CVE-2014-0224</a></li>
+</ul>
+</div>
+</div>
+
+
+<p> Android relies on a security {@link java.security.Provider Provider} to
+provide secure network communications. However, from time to time,
+vulnerabilities are found in the default security provider. To protect against
+these vulnerabilities, <a href="{@docRoot}google/play-services/">Google Play
+services</a> provides a way to automatically update a device's security provider
+to protect against known exploits. By calling Google Play services methods, your
+app can ensure that it's running on a device that has the latest updates to
+protect against known exploits.</p>
+
+<p>For example, a vulnerability was discovered in OpenSSL
+(<a href="http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-0224">CVE-2014-0224</a>)
+that can leave apps open to a "man-in-the-middle" attack that decrypts
+secure traffic without either side knowing. With Google Play services version
+5.0, a fix is available, but apps must ensure that this fix is installed. By
+using the Google Play services methods, your app can ensure that it's running
+on a device that's secured against that attack.</p>
+
+<p class="caution"><strong>Caution: </strong>Updating a device's security {@link
+java.security.Provider Provider} does <em>not</em> update {@link
+android.net.SSLCertificateSocketFactory
+android.net.SSLCertificateSocketFactory}. Rather than using this class, we
+encourage app developers to use high-level methods for interacting with
+cryptography. Most apps can use APIs like {@link
+javax.net.ssl.HttpsURLConnection}, {@link org.apache.http.client.HttpClient},
+and {@link android.net.http.AndroidHttpClient} without needing to set a custom
+{@link javax.net.ssl.TrustManager} or create an {@link
+android.net.SSLCertificateSocketFactory}.</p>
+
+<h2 id="patching">Patching the Security Provider with ProviderInstaller</h2>
+
+<p>To update a device's security provider, use the
+<a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html">{@code ProviderInstaller}</a>
+class. You can verify that the security provider is up-to-date (and update it,
+if necessary) by calling
+that class's <a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html##installIfNeeded(android.content.Context)">{@code installIfNeeded()}</a>
+(or <a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html#installIfNeededAsync(android.content.Context, com.google.android.gms.security.ProviderInstaller.ProviderInstallListener)">{@code installIfNeededAsync()}</a>)
+method.</p>
+
+<p>When you call <a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html##installIfNeeded(android.content.Context)">{@code installIfNeeded()}</a>, the
+<a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html">{@code ProviderInstaller}</a>
+does the following:</p>
+
+<ul>
+ <li>If the device's {@link java.security.Provider Provider} is successfully
+ updated (or is already up-to-date), the method returns normally.</li>
+ <li>If the device's Google Play services library is out of date, the method
+ throws
+ <a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesRepairableException.html">{@code GooglePlayServicesRepairableException}</a>.
+ The app can then catch this exception and show
+ the user an appropriate dialog box to update Google Play services.</li>
+ <li>If a non-recoverable error occurs, the method throws
+ <a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesNotAvailableException.html">{@code GooglePlayServicesNotAvailableException}</a>
+ to indicate that it is unable to update the {@link java.security.Provider
+ Provider}. The app can then catch the exception and choose an appropriate
+ course of action, such as displaying the standard
+ <a href="{@docRoot}reference/com/google/android/gms/common/SupportErrorDialogFragment.html">fix-it flow diagram</a>.</li>
+</ul>
+
+<p>The
+<a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html#installIfNeededAsync(android.content.Context, com.google.android.gms.security.ProviderInstaller.ProviderInstallListener)">{@code installIfNeededAsync()}</a>
+method behaves similarly, except that instead of
+throwing exceptions, it calls the appropriate callback method to indicate
+success or failure.</p>
+
+<p>If <a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html##installIfNeeded(android.content.Context)">{@code installIfNeeded()}</a>
+needs to install a new {@link java.security.Provider Provider}, this can take
+anywhere from 30-50 milliseconds (on more recent devices) to 350 ms (on older
+devices). If the security provider is already up-to-date, the method takes a
+negligible amount of time. To avoid affecting user experience:</p>
+
+<ul>
+ <li>Call
+ <a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html##installIfNeeded(android.content.Context)">{@code installIfNeeded()}</a>
+ from background networking threads immediately when the threads are loaded,
+ instead of waiting for the thread to try to use the network. (There's no harm
+ in calling the method multiple times, since it returns immediately if the
+ security provider doesn't need updating.)</li>
+
+ <li>If user experience will be affected by the thread blocking--for example,
+ if the call is from an activity in the UI thread--call the asynchronous
+ version of the method,
+ <a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html#installIfNeededAsync(android.content.Context, com.google.android.gms.security.ProviderInstaller.ProviderInstallListener)">{@code installIfNeededAsync()}</a>.
+ (Of course, if you do this, you need to wait for the operation to finish
+ before you attempt any secure communications. The
+ <a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html">{@code ProviderInstaller}</a>
+ calls your listener's <a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.ProviderInstallListener.html#onProviderInstalled()">{@code onProviderInstalled()}</a>
+ method to signal success.)</li>
+</ul>
+
+<p class="warning"><strong>Warning:</strong> If the
+<a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html">{@code ProviderInstaller}</a>
+is unable to install an updated {@link java.security.Provider Provider},
+your device's security provider might be vulnerable to known exploits. Your app
+should behave as if all HTTP communication is unencrypted.</p>
+
+<p>Once the {@link java.security.Provider Provider} is updated, all calls to
+security APIs (including SSL APIs) are routed through it.
+(However, this does not apply to {@link android.net.SSLCertificateSocketFactory
+android.net.SSLCertificateSocketFactory}, which remains vulnerable to such
+exploits as
+<a href="http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-0224">CVE-2014-0224</a>.)</p>
+
+<h2 id="example_sync">Patching Synchronously</h2>
+
+<p>The simplest way to patch the security provider is to call the synchronous
+method <a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html##installIfNeeded(android.content.Context)">{@code installIfNeeded()}</a>.
+This is appropriate if user experience won't be affected by the thread blocking
+while it waits for the operation to finish.</p>
+
+<p>For example, here's an implementation of a <a href="{@docRoot}training/sync-adapters">sync adapter</a> that updates the security provider. Since a sync
+adapter runs in the background, it's okay if the thread blocks while waiting
+for the security provider to be updated. The sync adapter calls
+<a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html##installIfNeeded(android.content.Context)">{@code installIfNeeded()}</a> to
+update the security provider. If the method returns normally, the sync adapter
+knows the security provider is up-to-date. If the method throws an exception,
+the sync adapter can take appropriate action (such as prompting the user to
+update Google Play services).</p>
+
+<pre>/**
+ * Sample sync adapter using {@link ProviderInstaller}.
+ */
+public class SyncAdapter extends AbstractThreadedSyncAdapter {
+
+ ...
+
+ // This is called each time a sync is attempted; this is okay, since the
+ // overhead is negligible if the security provider is up-to-date.
+ @Override
+ public void onPerformSync(Account account, Bundle extras, String authority,
+ ContentProviderClient provider, SyncResult syncResult) {
+ try {
+ ProviderInstaller.installIfNeeded(getContext());
+ } catch (GooglePlayServicesRepairableException e) {
+
+ // Indicates that Google Play services is out of date, disabled, etc.
+
+ // Prompt the user to install/update/enable Google Play services.
+ GooglePlayServicesUtil.showErrorNotification(
+ e.getConnectionStatusCode(), getContext());
+
+ // Notify the SyncManager that a soft error occurred.
+ syncResult.stats.numIOExceptions++;
+ return;
+
+ } catch (GooglePlayServicesNotAvailableException e) {
+ // Indicates a non-recoverable error; the ProviderInstaller is not able
+ // to install an up-to-date Provider.
+
+ // Notify the SyncManager that a hard error occurred.
+ syncResult.stats.numAuthExceptions++;
+ return;
+ }
+
+ // If this is reached, you know that the provider was already up-to-date,
+ // or was successfully updated.
+ }
+}</pre>
+
+<h2 id="example_async">Patching Asynchronously</h2>
+
+<p>Updating the security provider can take as much as 350 milliseconds (on
+older devices). If you're doing the update on a thread that directly affects
+user experience, such as the UI thread, you don't want to make a synchronous
+call to update the provider, since that can result in the app or device
+freezing until the operation finishes. Instead, you should use the asynchronous
+method
+<a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html#installIfNeededAsync(android.content.Context, com.google.android.gms.security.ProviderInstaller.ProviderInstallListener)">{@code installIfNeededAsync()}</a>.
+That method indicates its success or failure by calling callbacks.</p>
+
+<p>For example, here's some code that updates the security provider in an
+activity in the UI thread. The activity calls <a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.html#installIfNeededAsync(android.content.Context, com.google.android.gms.security.ProviderInstaller.ProviderInstallListener)">{@code installIfNeededAsync()}</a>
+to update the provider, and designates itself as the listener to receive success
+or failure notifications. If the security provider is up-to-date or is
+successfully updated, the activity's
+<a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.ProviderInstallListener.html#onProviderInstalled()">{@code onProviderInstalled()}</a>
+method is called, and the activity knows communication is secure. If the
+provider cannot be updated, the activity's
+<a href="{@docRoot}reference/com/google/android/gms/security/ProviderInstaller.ProviderInstallListener.html#onProviderInstallFailed(int, android.content.Intent)">{@code onProviderInstallFailed()}</a>
+method is called, and the activity can take appropriate action (such as
+prompting the user to update Google Play services).</p>
+
+<pre>/**
+ * Sample activity using {@link ProviderInstaller}.
+ */
+public class MainActivity extends Activity
+ implements ProviderInstaller.ProviderInstallListener {
+
+ private static final int ERROR_DIALOG_REQUEST_CODE = 1;
+
+ private boolean mRetryProviderInstall;
+
+ //Update the security provider when the activity is created.
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ProviderInstaller.installIfNeededAsync(this, this);
+ }
+
+ /**
+ * This method is only called if the provider is successfully updated
+ * (or is already up-to-date).
+ */
+ @Override
+ protected void onProviderInstalled() {
+ // Provider is up-to-date, app can make secure network calls.
+ }
+
+ /**
+ * This method is called if updating fails; the error code indicates
+ * whether the error is recoverable.
+ */
+ @Override
+ protected void onProviderInstallFailed(int errorCode, Intent recoveryIntent) {
+ if (GooglePlayServicesUtil.isUserRecoverableError(errorCode)) {
+ // Recoverable error. Show a dialog prompting the user to
+ // install/update/enable Google Play services.
+ GooglePlayServicesUtil.showErrorDialogFragment(
+ errorCode,
+ this,
+ ERROR_DIALOG_REQUEST_CODE,
+ new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ // The user chose not to take the recovery action
+ onProviderInstallerNotAvailable();
+ }
+ });
+ } else {
+ // Google Play services is not available.
+ onProviderInstallerNotAvailable();
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode,
+ Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == ERROR_DIALOG_REQUEST_CODE) {
+ // Adding a fragment via GooglePlayServicesUtil.showErrorDialogFragment
+ // before the instance state is restored throws an error. So instead,
+ // set a flag here, which will cause the fragment to delay until
+ // onPostResume.
+ mRetryProviderInstall = true;
+ }
+ }
+
+ /**
+ * On resume, check to see if we flagged that we need to reinstall the
+ * provider.
+ */
+ @Override
+ protected void onPostResume() {
+ super.onPostResult();
+ if (mRetryProviderInstall) {
+ // We can now safely retry installation.
+ ProviderInstall.installIfNeededAsync(this, this);
+ }
+ mRetryProviderInstall = false;
+ }
+
+ private void onProviderInstallerNotAvailable() {
+ // This is reached if the provider cannot be updated for some reason.
+ // App should consider all HTTP communication to be vulnerable, and take
+ // appropriate action.
+ }
+}
+</pre>
diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs
index 4407d30..4dd57ab 100644
--- a/docs/html/training/training_toc.cs
+++ b/docs/html/training/training_toc.cs
@@ -1487,6 +1487,14 @@
>Security with HTTPS and SSL</a>
</li>
+ <li>
+ <a href="<?cs var:toroot ?>training/articles/security-gms-provider.html"
+ description=
+ "How to use and update Google Play services security provider, to
+ protect against SSL exploits."
+ >Updating Your Security Provider to Protect Against SSL Exploits</a>
+ </li>
+
<li class="nav-section">
<div class="nav-section-header">
<a href="<?cs var:toroot ?>training/enterprise/index.html"
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index 6e1b80a..b786f94 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -77,9 +77,9 @@
* data.</p>
*
* @param width
- * The width in pixels of the Images that this reader will produce.
+ * The default width in pixels of the Images that this reader will produce.
* @param height
- * The height in pixels of the Images that this reader will produce.
+ * The default height in pixels of the Images that this reader will produce.
* @param format
* The format of the Image that this reader will produce. This
* must be one of the {@link android.graphics.ImageFormat} or
@@ -130,39 +130,43 @@
}
/**
- * The width of each {@link Image}, in pixels.
+ * The default width of {@link Image Images}, in pixels.
*
- * <p>ImageReader guarantees that all Images acquired from ImageReader (for example, with
- * {@link #acquireNextImage}) will have the same dimensions as specified in
- * {@link #newInstance}.</p>
+ * <p>The width may be overridden by the producer sending buffers to this
+ * ImageReader's Surface. If so, the actual width of the images can be
+ * found using {@link Image#getWidth}.</p>
*
- * @return the width of an Image
+ * @return the expected width of an Image
*/
public int getWidth() {
return mWidth;
}
/**
- * The height of each {@link Image}, in pixels.
+ * The default height of {@link Image Images}, in pixels.
*
- * <p>ImageReader guarantees that all Images acquired from ImageReader (for example, with
- * {@link #acquireNextImage}) will have the same dimensions as specified in
- * {@link #newInstance}.</p>
+ * <p>The height may be overridden by the producer sending buffers to this
+ * ImageReader's Surface. If so, the actual height of the images can be
+ * found using {@link Image#getHeight}.</p>
*
- * @return the height of an Image
+ * @return the expected height of an Image
*/
public int getHeight() {
return mHeight;
}
/**
- * The {@link ImageFormat image format} of each Image.
+ * The default {@link ImageFormat image format} of {@link Image Images}.
*
- * <p>ImageReader guarantees that all {@link Image Images} acquired from ImageReader
- * (for example, with {@link #acquireNextImage}) will have the same format as specified in
- * {@link #newInstance}.</p>
+ * <p>Some color formats may be overridden by the producer sending buffers to
+ * this ImageReader's Surface if the default color format allows. ImageReader
+ * guarantees that all {@link Image Images} acquired from ImageReader
+ * (for example, with {@link #acquireNextImage}) will have a "compatible"
+ * format to what was specified in {@link #newInstance}.
+ * As of now, each format is only compatible to itself.
+ * The actual format of the images can be found using {@link Image#getFormat}.</p>
*
- * @return the format of an Image
+ * @return the expected format of an Image
*
* @see ImageFormat
*/
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index fc2dc02..4eab9c7 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -50,12 +50,8 @@
android:visibility="gone"
/>
- <include
- layout="@layout/keyguard_status_bar"
- android:visibility="invisible" />
-
<com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer
- android:layout_width="@dimen/notification_panel_width"
+ android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:id="@+id/notification_container_parent"
@@ -64,8 +60,9 @@
<com.android.systemui.statusbar.phone.ObservableScrollView
android:id="@+id/scroll_view"
- android:layout_width="match_parent"
+ android:layout_width="@dimen/notification_panel_width"
android:layout_height="match_parent"
+ android:layout_gravity="@integer/notification_panel_layout_gravity"
android:scrollbars="none"
android:overScrollMode="never"
android:fillViewport="true">
@@ -97,8 +94,9 @@
<com.android.systemui.statusbar.stack.NotificationStackScrollLayout
android:id="@+id/notification_stack_scroller"
- android:layout_width="match_parent"
+ android:layout_width="@dimen/notification_panel_width"
android:layout_height="match_parent"
+ android:layout_gravity="@integer/notification_panel_layout_gravity"
android:layout_marginBottom="@dimen/close_handle_underlap"/>
<ViewStub
@@ -109,6 +107,10 @@
android:layout_gravity="end"
android:layout="@layout/keyguard_user_switcher" />
+ <include
+ layout="@layout/keyguard_status_bar"
+ android:visibility="invisible" />
+
</com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer>
<include layout="@layout/status_bar_expanded_header" />
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 2d82d6e..7cdc078 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -72,9 +72,6 @@
<!-- Margin on the left side of the carrier text on Keyguard -->
<dimen name="keyguard_carrier_text_margin">24dp</dimen>
- <!-- end margin for system icons if multi user switch is hidden -->
- <dimen name="system_icons_switcher_hidden_expanded_margin">20dp</dimen>
-
<!-- The width/height of the phone/camera/unlock icon on keyguard. -->
<dimen name="keyguard_affordance_height">80dp</dimen>
<dimen name="keyguard_affordance_width">120dp</dimen>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 1cdcc2b..c3ea8f8 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -103,4 +103,6 @@
<color name="notification_guts_btn_color">#FFFFFFFF</color>
<color name="search_panel_card_color">#ffffff</color>
+
+ <color name="keyguard_user_switcher_background_gradient_color">#77000000</color>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 35b58d7..e70422b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -44,6 +44,7 @@
import com.android.systemui.statusbar.KeyguardAffordanceView;
import com.android.systemui.statusbar.MirrorView;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.StackStateAnimator;
@@ -62,6 +63,7 @@
private KeyguardAffordanceHelper mAfforanceHelper;
private StatusBarHeaderView mHeader;
+ private KeyguardUserSwitcher mKeyguardUserSwitcher;
private KeyguardStatusBarView mKeyguardStatusBar;
private View mQsContainer;
private QSPanel mQsPanel;
@@ -225,11 +227,18 @@
mHeader.post(mUpdateHeader);
}
- lp = (FrameLayout.LayoutParams) mNotificationContainerParent.getLayoutParams();
+ lp = (FrameLayout.LayoutParams) mNotificationStackScroller.getLayoutParams();
if (lp.width != panelWidth) {
lp.width = panelWidth;
lp.gravity = panelGravity;
- mNotificationContainerParent.setLayoutParams(lp);
+ mNotificationStackScroller.setLayoutParams(lp);
+ }
+
+ lp = (FrameLayout.LayoutParams) mScrollView.getLayoutParams();
+ if (lp.width != panelWidth) {
+ lp.width = panelWidth;
+ lp.gravity = panelGravity;
+ mScrollView.setLayoutParams(lp);
}
}
@@ -940,6 +949,9 @@
&& !mStackScrollerOverscrolling && mQsScrimEnabled
? View.VISIBLE
: View.INVISIBLE);
+ if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) {
+ mKeyguardUserSwitcher.hide();
+ }
}
private void setQsExpansion(float height) {
@@ -1705,6 +1717,10 @@
}
}
+ public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
+ mKeyguardUserSwitcher = keyguardUserSwitcher;
+ }
+
private final Runnable mUpdateHeader = new Runnable() {
@Override
public void run() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
index 7c6e47c..57b7401 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -35,6 +35,7 @@
private View mScrollView;
private View mUserSwitcher;
private View mStackScroller;
+ private View mKeyguardStatusBar;
private boolean mInflated;
public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) {
@@ -46,6 +47,7 @@
super.onFinishInflate();
mScrollView = findViewById(R.id.scroll_view);
mStackScroller = findViewById(R.id.notification_stack_scroller);
+ mKeyguardStatusBar = findViewById(R.id.keyguard_header);
ViewStub userSwitcher = (ViewStub) findViewById(R.id.keyguard_user_switcher);
userSwitcher.setOnInflateListener(this);
mUserSwitcher = userSwitcher;
@@ -61,18 +63,30 @@
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
boolean userSwitcherVisible = mInflated && mUserSwitcher.getVisibility() == View.VISIBLE;
+ boolean statusBarVisible = mKeyguardStatusBar.getVisibility() == View.VISIBLE;
// Invert the order of the scroll view and user switcher such that the notifications receive
// touches first but the panel gets drawn above.
if (child == mScrollView) {
return super.drawChild(canvas, mStackScroller, drawingTime);
} else if (child == mStackScroller) {
- return super.drawChild(canvas, userSwitcherVisible ? mUserSwitcher : mScrollView,
+ return super.drawChild(canvas,
+ userSwitcherVisible && statusBarVisible ? mUserSwitcher
+ : statusBarVisible ? mKeyguardStatusBar
+ : userSwitcherVisible ? mUserSwitcher
+ : mScrollView,
drawingTime);
} else if (child == mUserSwitcher) {
- return super.drawChild(canvas, userSwitcherVisible ? mScrollView : mUserSwitcher,
+ return super.drawChild(canvas,
+ userSwitcherVisible && statusBarVisible ? mKeyguardStatusBar
+ : mScrollView,
drawingTime);
- } else {
+ } else if (child == mKeyguardStatusBar) {
+ return super.drawChild(canvas,
+ userSwitcherVisible && statusBarVisible ? mScrollView
+ : mScrollView,
+ drawingTime);
+ }else {
return super.drawChild(canvas, child, drawingTime);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index e8b167c..1d678af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -805,7 +805,7 @@
mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext,
(ViewStub) mStatusBarWindow.findViewById(R.id.keyguard_user_switcher),
- mKeyguardStatusBar, mUserSwitcherController);
+ mKeyguardStatusBar, mNotificationPanel, mUserSwitcherController);
// Set up the quick settings tile panel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
index ffc6898..203196e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
@@ -18,7 +18,6 @@
import android.content.Context;
import android.database.DataSetObserver;
-import android.provider.Settings;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -28,6 +27,8 @@
import com.android.systemui.R;
import com.android.systemui.qs.tiles.UserDetailItemView;
import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
+import com.android.systemui.statusbar.phone.StatusBarHeaderView;
import com.android.systemui.statusbar.phone.UserAvatarView;
/**
@@ -44,11 +45,14 @@
private final boolean mSimpleUserSwitcher;
public KeyguardUserSwitcher(Context context, ViewStub userSwitcher,
- KeyguardStatusBarView statusBarView, UserSwitcherController userSwitcherController) {
+ KeyguardStatusBarView statusBarView, NotificationPanelView panelView,
+ UserSwitcherController userSwitcherController) {
if (context.getResources().getBoolean(R.bool.config_keyguardUserSwitcher) || ALWAYS_ON) {
mUserSwitcher = (ViewGroup) userSwitcher.inflate();
+ mUserSwitcher.setBackground(new KeyguardUserSwitcherScrim(mUserSwitcher));
mStatusBarView = statusBarView;
mStatusBarView.setKeyguardUserSwitcher(this);
+ panelView.setKeyguardUserSwitcher(this);
mAdapter = new Adapter(context, userSwitcherController);
mAdapter.registerDataSetObserver(mDataSetObserver);
mSimpleUserSwitcher = userSwitcherController.isSimpleUserSwitcher();
@@ -75,7 +79,7 @@
* @see android.os.UserManager#isUserSwitcherEnabled()
*/
private boolean shouldExpandByDefault() {
- return mSimpleUserSwitcher || mAdapter.getSwitchableUsers() > 1;
+ return mSimpleUserSwitcher;
}
public void show() {
@@ -87,7 +91,7 @@
}
public void hide() {
- if (mUserSwitcher != null) {
+ if (mUserSwitcher != null && mUserSwitcher.getVisibility() == View.VISIBLE) {
// TODO: animate
mUserSwitcher.setVisibility(View.GONE);
mStatusBarView.setKeyguardUserSwitcherShowing(false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java
new file mode 100644
index 0000000..3356afd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java
@@ -0,0 +1,92 @@
+/*
+ * 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.android.systemui.statusbar.policy;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.RadialGradient;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.graphics.drawable.Drawable;
+import android.util.LayoutDirection;
+import android.view.View;
+
+import com.android.systemui.R;
+
+/**
+ * Gradient background for the user switcher on Keyguard.
+ */
+public class KeyguardUserSwitcherScrim extends Drawable
+ implements View.OnLayoutChangeListener {
+
+ private static final float OUTER_EXTENT = 2.5f;
+ private static final float INNER_EXTENT = 0.75f;
+
+ private int mDarkColor;
+ private int mTop;
+ private Paint mRadialGradientPaint = new Paint();
+
+ public KeyguardUserSwitcherScrim(View host) {
+ host.addOnLayoutChangeListener(this);
+ mDarkColor = host.getResources().getColor(
+ R.color.keyguard_user_switcher_background_gradient_color);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ boolean isLtr = getLayoutDirection() == LayoutDirection.LTR;
+ Rect bounds = getBounds();
+ float width = bounds.width() * OUTER_EXTENT;
+ float height = (mTop + bounds.height()) * OUTER_EXTENT;
+ canvas.translate(0, -mTop);
+ canvas.scale(1, height/width);
+ canvas.drawRect(isLtr ? bounds.right - width : 0, 0,
+ isLtr ? bounds.right : bounds.left + width, width, mRadialGradientPaint);
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
+ int oldTop, int oldRight, int oldBottom) {
+ if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) {
+ int width = right - left;
+ float radius = width * OUTER_EXTENT;
+ boolean isLtr = getLayoutDirection() == LayoutDirection.LTR;
+ mRadialGradientPaint.setShader(
+ new RadialGradient(isLtr ? width : 0, 0, radius,
+ new int[] { mDarkColor, Color.TRANSPARENT},
+ new float[] { Math.max(0f, width * INNER_EXTENT / radius), 1f},
+ Shader.TileMode.CLAMP));
+ mTop = top;
+ }
+ }
+}