Merge "Update FontFamily_Delegate following Change Ia23ee6a7"
diff --git a/api/current.txt b/api/current.txt
index ee4cada..33eb074 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -608,6 +608,7 @@
field public static final int fontFamily = 16843692; // 0x10103ac
field public static final int fontFeatureSettings = 16843959; // 0x10104b7
field public static final int fontProviderAuthority = 16844114; // 0x1010552
+ field public static final int fontProviderCerts = 16844128; // 0x1010560
field public static final int fontProviderPackage = 16844122; // 0x101055a
field public static final int fontProviderQuery = 16844115; // 0x1010553
field public static final int fontStyle = 16844095; // 0x101053f
@@ -695,6 +696,8 @@
field public static final int hyphenationFrequency = 16843998; // 0x10104de
field public static final int icon = 16842754; // 0x1010002
field public static final int iconPreview = 16843337; // 0x1010249
+ field public static final int iconTint = 16844129; // 0x1010561
+ field public static final int iconTintMode = 16844130; // 0x1010562
field public static final int iconifiedByDefault = 16843514; // 0x10102fa
field public static final int id = 16842960; // 0x10100d0
field public static final int ignoreGravity = 16843263; // 0x10101ff
@@ -5506,6 +5509,7 @@
method public void enableLights(boolean);
method public void enableVibration(boolean);
method public android.media.AudioAttributes getAudioAttributes();
+ method public java.lang.String getDescription();
method public java.lang.String getGroup();
method public java.lang.String getId();
method public int getImportance();
@@ -5515,6 +5519,7 @@
method public android.net.Uri getSound();
method public long[] getVibrationPattern();
method public void setBypassDnd(boolean);
+ method public void setDescription(java.lang.String);
method public void setGroup(java.lang.String);
method public void setImportance(int);
method public void setLightColor(int);
@@ -13766,6 +13771,26 @@
field public static final android.graphics.Typeface SERIF;
}
+ public static final class Typeface.Builder {
+ ctor public Typeface.Builder();
+ method public android.graphics.Typeface build();
+ method public static android.graphics.Typeface.Builder obtain();
+ method public void recycle();
+ method public void reset();
+ method public android.graphics.Typeface.Builder setFontVariationSettings(java.lang.String);
+ method public android.graphics.Typeface.Builder setFontVariationSettings(android.text.FontConfig.Axis[]);
+ method public android.graphics.Typeface.Builder setItalic(int);
+ method public android.graphics.Typeface.Builder setSourceFromAsset(android.content.res.AssetManager, java.lang.String);
+ method public android.graphics.Typeface.Builder setSourceFromFile(java.io.File);
+ method public android.graphics.Typeface.Builder setSourceFromFile(java.io.FileDescriptor);
+ method public android.graphics.Typeface.Builder setSourceFromFilePath(java.lang.String);
+ method public android.graphics.Typeface.Builder setTtcIndex(int);
+ method public android.graphics.Typeface.Builder setWeight(int);
+ field public static final int ITALIC = 1; // 0x1
+ field public static final int NORMAL = 0; // 0x0
+ field public static final int RESOLVE_BY_FONT_TABLE = -1; // 0xffffffff
+ }
+
public static abstract interface Typeface.FontRequestCallback {
method public abstract void onTypefaceRequestFailed(int);
method public abstract void onTypefaceRetrieved(android.graphics.Typeface);
@@ -41024,6 +41049,7 @@
public static final class FontConfig.Axis implements android.os.Parcelable {
ctor public FontConfig.Axis(int, float);
+ ctor public FontConfig.Axis(java.lang.String, float);
method public int describeContents();
method public float getStyleValue();
method public int getTag();
@@ -44592,6 +44618,8 @@
method public default java.lang.CharSequence getContentDescription();
method public abstract int getGroupId();
method public abstract android.graphics.drawable.Drawable getIcon();
+ method public default android.content.res.ColorStateList getIconTintList();
+ method public default android.graphics.PorterDuff.Mode getIconTintMode();
method public abstract android.content.Intent getIntent();
method public abstract int getItemId();
method public abstract android.view.ContextMenu.ContextMenuInfo getMenuInfo();
@@ -44619,6 +44647,8 @@
method public abstract android.view.MenuItem setEnabled(boolean);
method public abstract android.view.MenuItem setIcon(android.graphics.drawable.Drawable);
method public abstract android.view.MenuItem setIcon(int);
+ method public default android.view.MenuItem setIconTintList(android.content.res.ColorStateList);
+ method public default android.view.MenuItem setIconTintMode(android.graphics.PorterDuff.Mode);
method public abstract android.view.MenuItem setIntent(android.content.Intent);
method public abstract android.view.MenuItem setNumericShortcut(char);
method public default android.view.MenuItem setNumericShortcut(char, int);
diff --git a/api/system-current.txt b/api/system-current.txt
index bf39eda..60d7f2f 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -721,6 +721,7 @@
field public static final int fontFamily = 16843692; // 0x10103ac
field public static final int fontFeatureSettings = 16843959; // 0x10104b7
field public static final int fontProviderAuthority = 16844114; // 0x1010552
+ field public static final int fontProviderCerts = 16844128; // 0x1010560
field public static final int fontProviderPackage = 16844122; // 0x101055a
field public static final int fontProviderQuery = 16844115; // 0x1010553
field public static final int fontStyle = 16844095; // 0x101053f
@@ -808,6 +809,8 @@
field public static final int hyphenationFrequency = 16843998; // 0x10104de
field public static final int icon = 16842754; // 0x1010002
field public static final int iconPreview = 16843337; // 0x1010249
+ field public static final int iconTint = 16844129; // 0x1010561
+ field public static final int iconTintMode = 16844130; // 0x1010562
field public static final int iconifiedByDefault = 16843514; // 0x10102fa
field public static final int id = 16842960; // 0x10100d0
field public static final int ignoreGravity = 16843263; // 0x10101ff
@@ -3860,6 +3863,7 @@
method public void setContentView(android.view.View);
method public void setContentView(android.view.View, android.view.ViewGroup.LayoutParams);
method public final void setDefaultKeyMode(int);
+ method public void setDisablePreviewScreenshots(boolean);
method public void setEnterSharedElementCallback(android.app.SharedElementCallback);
method public void setExitSharedElementCallback(android.app.SharedElementCallback);
method public final void setFeatureDrawable(int, android.graphics.drawable.Drawable);
@@ -5691,6 +5695,7 @@
method public void enableLights(boolean);
method public void enableVibration(boolean);
method public android.media.AudioAttributes getAudioAttributes();
+ method public java.lang.String getDescription();
method public java.lang.String getGroup();
method public java.lang.String getId();
method public int getImportance();
@@ -5703,6 +5708,7 @@
method public boolean isDeleted();
method public void populateFromXml(org.xmlpull.v1.XmlPullParser);
method public void setBypassDnd(boolean);
+ method public void setDescription(java.lang.String);
method public void setGroup(java.lang.String);
method public void setImportance(int);
method public void setLightColor(int);
@@ -5718,16 +5724,6 @@
method public void writeXml(org.xmlpull.v1.XmlSerializer) throws java.io.IOException;
field public static final android.os.Parcelable.Creator<android.app.NotificationChannel> CREATOR;
field public static final java.lang.String DEFAULT_CHANNEL_ID = "miscellaneous";
- field public static final int[] LOCKABLE_FIELDS;
- field public static final int USER_LOCKED_ALLOWED = 64; // 0x40
- field public static final int USER_LOCKED_AUDIO_ATTRIBUTES = 256; // 0x100
- field public static final int USER_LOCKED_IMPORTANCE = 4; // 0x4
- field public static final int USER_LOCKED_LIGHTS = 8; // 0x8
- field public static final int USER_LOCKED_PRIORITY = 1; // 0x1
- field public static final int USER_LOCKED_SHOW_BADGE = 128; // 0x80
- field public static final int USER_LOCKED_SOUND = 32; // 0x20
- field public static final int USER_LOCKED_VIBRATION = 16; // 0x10
- field public static final int USER_LOCKED_VISIBILITY = 2; // 0x2
}
public final class NotificationChannelGroup implements android.os.Parcelable {
@@ -14499,6 +14495,26 @@
field public static final android.graphics.Typeface SERIF;
}
+ public static final class Typeface.Builder {
+ ctor public Typeface.Builder();
+ method public android.graphics.Typeface build();
+ method public static android.graphics.Typeface.Builder obtain();
+ method public void recycle();
+ method public void reset();
+ method public android.graphics.Typeface.Builder setFontVariationSettings(java.lang.String);
+ method public android.graphics.Typeface.Builder setFontVariationSettings(android.text.FontConfig.Axis[]);
+ method public android.graphics.Typeface.Builder setItalic(int);
+ method public android.graphics.Typeface.Builder setSourceFromAsset(android.content.res.AssetManager, java.lang.String);
+ method public android.graphics.Typeface.Builder setSourceFromFile(java.io.File);
+ method public android.graphics.Typeface.Builder setSourceFromFile(java.io.FileDescriptor);
+ method public android.graphics.Typeface.Builder setSourceFromFilePath(java.lang.String);
+ method public android.graphics.Typeface.Builder setTtcIndex(int);
+ method public android.graphics.Typeface.Builder setWeight(int);
+ field public static final int ITALIC = 1; // 0x1
+ field public static final int NORMAL = 0; // 0x0
+ field public static final int RESOLVE_BY_FONT_TABLE = -1; // 0xffffffff
+ }
+
public static abstract interface Typeface.FontRequestCallback {
method public abstract void onTypefaceRequestFailed(int);
method public abstract void onTypefaceRetrieved(android.graphics.Typeface);
@@ -44486,6 +44502,7 @@
public static final class FontConfig.Axis implements android.os.Parcelable {
ctor public FontConfig.Axis(int, float);
+ ctor public FontConfig.Axis(java.lang.String, float);
method public int describeContents();
method public float getStyleValue();
method public int getTag();
@@ -48055,6 +48072,8 @@
method public default java.lang.CharSequence getContentDescription();
method public abstract int getGroupId();
method public abstract android.graphics.drawable.Drawable getIcon();
+ method public default android.content.res.ColorStateList getIconTintList();
+ method public default android.graphics.PorterDuff.Mode getIconTintMode();
method public abstract android.content.Intent getIntent();
method public abstract int getItemId();
method public abstract android.view.ContextMenu.ContextMenuInfo getMenuInfo();
@@ -48082,6 +48101,8 @@
method public abstract android.view.MenuItem setEnabled(boolean);
method public abstract android.view.MenuItem setIcon(android.graphics.drawable.Drawable);
method public abstract android.view.MenuItem setIcon(int);
+ method public default android.view.MenuItem setIconTintList(android.content.res.ColorStateList);
+ method public default android.view.MenuItem setIconTintMode(android.graphics.PorterDuff.Mode);
method public abstract android.view.MenuItem setIntent(android.content.Intent);
method public abstract android.view.MenuItem setNumericShortcut(char);
method public default android.view.MenuItem setNumericShortcut(char, int);
diff --git a/api/test-current.txt b/api/test-current.txt
index 40be895..39663f2 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -608,6 +608,7 @@
field public static final int fontFamily = 16843692; // 0x10103ac
field public static final int fontFeatureSettings = 16843959; // 0x10104b7
field public static final int fontProviderAuthority = 16844114; // 0x1010552
+ field public static final int fontProviderCerts = 16844128; // 0x1010560
field public static final int fontProviderPackage = 16844122; // 0x101055a
field public static final int fontProviderQuery = 16844115; // 0x1010553
field public static final int fontStyle = 16844095; // 0x101053f
@@ -695,6 +696,8 @@
field public static final int hyphenationFrequency = 16843998; // 0x10104de
field public static final int icon = 16842754; // 0x1010002
field public static final int iconPreview = 16843337; // 0x1010249
+ field public static final int iconTint = 16844129; // 0x1010561
+ field public static final int iconTintMode = 16844130; // 0x1010562
field public static final int iconifiedByDefault = 16843514; // 0x10102fa
field public static final int id = 16842960; // 0x10100d0
field public static final int ignoreGravity = 16843263; // 0x10101ff
@@ -5516,6 +5519,7 @@
method public void enableLights(boolean);
method public void enableVibration(boolean);
method public android.media.AudioAttributes getAudioAttributes();
+ method public java.lang.String getDescription();
method public java.lang.String getGroup();
method public java.lang.String getId();
method public int getImportance();
@@ -5525,6 +5529,7 @@
method public android.net.Uri getSound();
method public long[] getVibrationPattern();
method public void setBypassDnd(boolean);
+ method public void setDescription(java.lang.String);
method public void setGroup(java.lang.String);
method public void setImportance(int);
method public void setLightColor(int);
@@ -13804,6 +13809,26 @@
field public static final android.graphics.Typeface SERIF;
}
+ public static final class Typeface.Builder {
+ ctor public Typeface.Builder();
+ method public android.graphics.Typeface build();
+ method public static android.graphics.Typeface.Builder obtain();
+ method public void recycle();
+ method public void reset();
+ method public android.graphics.Typeface.Builder setFontVariationSettings(java.lang.String);
+ method public android.graphics.Typeface.Builder setFontVariationSettings(android.text.FontConfig.Axis[]);
+ method public android.graphics.Typeface.Builder setItalic(int);
+ method public android.graphics.Typeface.Builder setSourceFromAsset(android.content.res.AssetManager, java.lang.String);
+ method public android.graphics.Typeface.Builder setSourceFromFile(java.io.File);
+ method public android.graphics.Typeface.Builder setSourceFromFile(java.io.FileDescriptor);
+ method public android.graphics.Typeface.Builder setSourceFromFilePath(java.lang.String);
+ method public android.graphics.Typeface.Builder setTtcIndex(int);
+ method public android.graphics.Typeface.Builder setWeight(int);
+ field public static final int ITALIC = 1; // 0x1
+ field public static final int NORMAL = 0; // 0x0
+ field public static final int RESOLVE_BY_FONT_TABLE = -1; // 0xffffffff
+ }
+
public static abstract interface Typeface.FontRequestCallback {
method public abstract void onTypefaceRequestFailed(int);
method public abstract void onTypefaceRetrieved(android.graphics.Typeface);
@@ -41219,6 +41244,7 @@
public static final class FontConfig.Axis implements android.os.Parcelable {
ctor public FontConfig.Axis(int, float);
+ ctor public FontConfig.Axis(java.lang.String, float);
method public int describeContents();
method public float getStyleValue();
method public int getTag();
@@ -44952,6 +44978,8 @@
method public default java.lang.CharSequence getContentDescription();
method public abstract int getGroupId();
method public abstract android.graphics.drawable.Drawable getIcon();
+ method public default android.content.res.ColorStateList getIconTintList();
+ method public default android.graphics.PorterDuff.Mode getIconTintMode();
method public abstract android.content.Intent getIntent();
method public abstract int getItemId();
method public abstract android.view.ContextMenu.ContextMenuInfo getMenuInfo();
@@ -44979,6 +45007,8 @@
method public abstract android.view.MenuItem setEnabled(boolean);
method public abstract android.view.MenuItem setIcon(android.graphics.drawable.Drawable);
method public abstract android.view.MenuItem setIcon(int);
+ method public default android.view.MenuItem setIconTintList(android.content.res.ColorStateList);
+ method public default android.view.MenuItem setIconTintMode(android.graphics.PorterDuff.Mode);
method public abstract android.view.MenuItem setIntent(android.content.Intent);
method public abstract android.view.MenuItem setNumericShortcut(char);
method public default android.view.MenuItem setNumericShortcut(char, int);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 07540f3..e149868 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -7257,6 +7257,33 @@
mAutoFillResetNeeded = true;
}
+ /**
+ * If set to true, this indicates to the system that it should never take a
+ * screenshot of the activity to be used as a representation while it is not in a started state.
+ * <p>
+ * Note that the system may use the window background of the theme instead to represent
+ * the window when it is not running.
+ * <p>
+ * Also note that in comparison to {@link android.view.WindowManager.LayoutParams#FLAG_SECURE},
+ * this only affects the behavior when the activity's screenshot would be used as a
+ * representation when the activity is not in a started state, i.e. in Overview. The system may
+ * still take screenshots of the activity in other contexts; for example, when the user takes a
+ * screenshot of the entire screen, or when the active
+ * {@link android.service.voice.VoiceInteractionService} requests a screenshot via
+ * {@link android.service.voice.VoiceInteractionSession#SHOW_WITH_SCREENSHOT}.
+ *
+ * @param disable {@code true} to disable preview screenshots; {@code false} otherwise.
+ * @hide
+ */
+ @SystemApi
+ public void setDisablePreviewScreenshots(boolean disable) {
+ try {
+ ActivityManager.getService().setDisablePreviewScreenshots(mToken, disable);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to call setDisablePreviewScreenshots", e);
+ }
+ }
+
class HostCallbacks extends FragmentHostCallback<Activity> {
public HostCallbacks() {
super(Activity.this /*activity*/);
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index d940857..b08ca4f 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -612,6 +612,11 @@
void waitForNetworkStateUpdate(long procStateSeq);
+ /**
+ * See {@link android.app.Activity#setDisablePreviewScreenshots}
+ */
+ void setDisablePreviewScreenshots(IBinder token, boolean disable);
+
// WARNING: when these transactions are updated, check if they are any callers on the native
// side. If so, make sure they are using the correct transaction ids and arguments.
// If a transaction which will also be used on the native side is being inserted, add it
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 29c4520..92216d1 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -20,9 +20,6 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.StringRes;
import android.annotation.SystemApi;
import android.content.Intent;
import android.media.AudioAttributes;
@@ -46,8 +43,15 @@
*/
public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
+ /**
+ * The maximum length for text fields in a NotificationChannel. Fields will be truncated at this
+ * limit.
+ */
+ private static final int MAX_TEXT_LENGTH = 1000;
+
private static final String TAG_CHANNEL = "channel";
private static final String ATT_NAME = "name";
+ private static final String ATT_DESC = "desc";
private static final String ATT_ID = "id";
private static final String ATT_DELETED = "deleted";
private static final String ATT_PRIORITY = "priority";
@@ -69,56 +73,46 @@
/**
* @hide
*/
- @SystemApi
public static final int USER_LOCKED_PRIORITY = 0x00000001;
/**
* @hide
*/
- @SystemApi
public static final int USER_LOCKED_VISIBILITY = 0x00000002;
/**
* @hide
*/
- @SystemApi
public static final int USER_LOCKED_IMPORTANCE = 0x00000004;
/**
* @hide
*/
- @SystemApi
public static final int USER_LOCKED_LIGHTS = 0x00000008;
/**
* @hide
*/
- @SystemApi
public static final int USER_LOCKED_VIBRATION = 0x00000010;
/**
* @hide
*/
- @SystemApi
public static final int USER_LOCKED_SOUND = 0x00000020;
/**
* @hide
*/
- @SystemApi
public static final int USER_LOCKED_ALLOWED = 0x00000040;
/**
* @hide
*/
- @SystemApi
public static final int USER_LOCKED_SHOW_BADGE = 0x00000080;
/**
* @hide
*/
- @SystemApi
public static final int USER_LOCKED_AUDIO_ATTRIBUTES = 0x00000100;
/**
* @hide
*/
- @SystemApi
public static final int[] LOCKABLE_FIELDS = new int[] {
USER_LOCKED_PRIORITY,
USER_LOCKED_VISIBILITY,
@@ -140,7 +134,8 @@
private static final boolean DEFAULT_SHOW_BADGE = true;
private final String mId;
- private CharSequence mName;
+ private String mName;
+ private String mDesc;
private int mImportance = DEFAULT_IMPORTANCE;
private boolean mBypassDnd;
private int mLockscreenVisibility = DEFAULT_VISIBILITY;
@@ -158,19 +153,19 @@
/**
* Creates a notification channel.
*
- * @param id The id of the channel. Must be unique per package.
- * @param name The user visible name of the channel. Unchangeable once created; use this
- * constructor if the channel represents a user-defined category that does not
- * need to be translated. You can rename this channel when the system
+ * @param id The id of the channel. Must be unique per package. The value may be truncated if
+ * it is too long.
+ * @param name The user visible name of the channel. You can rename this channel when the system
* locale changes by listening for the {@link Intent#ACTION_LOCALE_CHANGED}
- * broadcast.
+ * broadcast. The recommended maximum length is 40 characters; the value may be
+ * truncated if it is too long.
* @param importance The importance of the channel. This controls how interruptive notifications
* posted to this channel are. See e.g.
* {@link NotificationManager#IMPORTANCE_DEFAULT}.
*/
public NotificationChannel(String id, CharSequence name, int importance) {
- this.mId = id;
- this.mName = name;
+ this.mId = getTrimmedString(id);
+ this.mName = name != null ? getTrimmedString(name.toString()) : null;
this.mImportance = importance;
}
@@ -180,7 +175,16 @@
} else {
mId = null;
}
- mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ if (in.readByte() != 0) {
+ mName = in.readString();
+ } else {
+ mName = null;
+ }
+ if (in.readByte() != 0) {
+ mDesc = in.readString();
+ } else {
+ mDesc = null;
+ }
mImportance = in.readInt();
mBypassDnd = in.readByte() != 0;
mLockscreenVisibility = in.readInt();
@@ -212,7 +216,18 @@
} else {
dest.writeByte((byte) 0);
}
- TextUtils.writeToParcel(mName, dest, flags);
+ if (mName != null) {
+ dest.writeByte((byte) 1);
+ dest.writeString(mName);
+ } else {
+ dest.writeByte((byte) 0);
+ }
+ if (mDesc != null) {
+ dest.writeByte((byte) 1);
+ dest.writeString(mDesc);
+ } else {
+ dest.writeByte((byte) 0);
+ }
dest.writeInt(mImportance);
dest.writeByte(mBypassDnd ? (byte) 1 : (byte) 0);
dest.writeInt(mLockscreenVisibility);
@@ -257,46 +272,33 @@
mDeleted = deleted;
}
+ // Modifiable by apps post channel creation
+
/**
- * Sets the name of this channel.
+ * Sets the user visible name of this channel.
+ *
+ * <p>The recommended maximum length is 40 characters; the value may be truncated if it is too
+ * long.
*/
public void setName(CharSequence name) {
- mName = name;
- }
-
- // Modifiable by a notification ranker.
-
- /**
- * Sets whether or not notifications posted to this channel can interrupt the user in
- * {@link android.app.NotificationManager.Policy#INTERRUPTION_FILTER_PRIORITY} mode.
- *
- * Only modifiable by the system and notification ranker.
- */
- public void setBypassDnd(boolean bypassDnd) {
- this.mBypassDnd = bypassDnd;
+ mName = name != null ? getTrimmedString(name.toString()) : null;
}
/**
- * Sets whether notifications posted to this channel appear on the lockscreen or not, and if so,
- * whether they appear in a redacted form. See e.g. {@link Notification#VISIBILITY_SECRET}.
+ * Sets the user visible description of this channel.
*
- * Only modifiable by the system and notification ranker.
+ * <p>The recommended maximum length is 300 characters; the value may be truncated if it is too
+ * long.
*/
- public void setLockscreenVisibility(int lockscreenVisibility) {
- this.mLockscreenVisibility = lockscreenVisibility;
+ public void setDescription(String description) {
+ mDesc = getTrimmedString(description);
}
- /**
- * Sets the level of interruption of this notification channel.
- *
- * Only modifiable by the system and notification ranker.
- *
- * @param importance the amount the user should be interrupted by notifications from this
- * channel. See e.g.
- * {@link android.app.NotificationManager#IMPORTANCE_DEFAULT}.
- */
- public void setImportance(int importance) {
- this.mImportance = importance;
+ private String getTrimmedString(String input) {
+ if (input != null && input.length() > MAX_TEXT_LENGTH) {
+ return input.substring(0, MAX_TEXT_LENGTH);
+ }
+ return input;
}
// Modifiable by apps on channel creation.
@@ -385,6 +387,43 @@
}
/**
+ * Sets the level of interruption of this notification channel.
+ *
+ * Only modifiable before the channel is submitted to
+ * {@link NotificationManager#notify(String, int, Notification)}.
+ *
+ * @param importance the amount the user should be interrupted by notifications from this
+ * channel. See e.g.
+ * {@link android.app.NotificationManager#IMPORTANCE_DEFAULT}.
+ */
+ public void setImportance(int importance) {
+ this.mImportance = importance;
+ }
+
+ // Modifiable by a notification ranker.
+
+ /**
+ * Sets whether or not notifications posted to this channel can interrupt the user in
+ * {@link android.app.NotificationManager.Policy#INTERRUPTION_FILTER_PRIORITY} mode.
+ *
+ * Only modifiable by the system and notification ranker.
+ */
+ public void setBypassDnd(boolean bypassDnd) {
+ this.mBypassDnd = bypassDnd;
+ }
+
+ /**
+ * Sets whether notifications posted to this channel appear on the lockscreen or not, and if so,
+ * whether they appear in a redacted form. See e.g. {@link Notification#VISIBILITY_SECRET}.
+ *
+ * Only modifiable by the system and notification ranker.
+ */
+ public void setLockscreenVisibility(int lockscreenVisibility) {
+ this.mLockscreenVisibility = lockscreenVisibility;
+ }
+
+
+ /**
* Returns the id of this channel.
*/
public String getId() {
@@ -394,11 +433,18 @@
/**
* Returns the user visible name of this channel.
*/
- public @Nullable CharSequence getName() {
+ public CharSequence getName() {
return mName;
}
/**
+ * Returns the user visible description of this channel.
+ */
+ public String getDescription() {
+ return mDesc;
+ }
+
+ /**
* Returns the user specified importance {e.g. @link NotificationManager#IMPORTANCE_LOW} for
* notifications posted to this channel.
*/
@@ -507,6 +553,7 @@
@SystemApi
public void populateFromXml(XmlPullParser parser) {
// Name, id, and importance are set in the constructor.
+ setDescription(parser.getAttributeValue(null, ATT_DESC));
setBypassDnd(Notification.PRIORITY_DEFAULT
!= safeInt(parser, ATT_PRIORITY, Notification.PRIORITY_DEFAULT));
setLockscreenVisibility(safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY));
@@ -531,6 +578,9 @@
if (getName() != null) {
out.attribute(null, ATT_NAME, getName().toString());
}
+ if (getDescription() != null) {
+ out.attribute(null, ATT_DESC, getDescription());
+ }
if (getImportance() != DEFAULT_IMPORTANCE) {
out.attribute(
null, ATT_IMPORTANCE, Integer.toString(getImportance()));
@@ -588,6 +638,7 @@
JSONObject record = new JSONObject();
record.put(ATT_ID, getId());
record.put(ATT_NAME, getName());
+ record.put(ATT_DESC, getDescription());
if (getImportance() != DEFAULT_IMPORTANCE) {
record.put(ATT_IMPORTANCE,
NotificationListenerService.Ranking.importanceToString(getImportance()));
@@ -718,6 +769,10 @@
if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
return false;
}
+ if (getDescription() != null ? !getDescription().equals(that.getDescription())
+ : that.getDescription() != null) {
+ return false;
+ }
if (getSound() != null ? !getSound().equals(that.getSound()) : that.getSound() != null) {
return false;
}
@@ -734,6 +789,7 @@
public int hashCode() {
int result = getId() != null ? getId().hashCode() : 0;
result = 31 * result + (getName() != null ? getName().hashCode() : 0);
+ result = 31 * result + (getDescription() != null ? getDescription().hashCode() : 0);
result = 31 * result + getImportance();
result = 31 * result + (mBypassDnd ? 1 : 0);
result = 31 * result + getLockscreenVisibility();
@@ -755,6 +811,7 @@
return "NotificationChannel{" +
"mId='" + mId + '\'' +
", mName=" + mName +
+ ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "") +
", mImportance=" + mImportance +
", mBypassDnd=" + mBypassDnd +
", mLockscreenVisibility=" + mLockscreenVisibility +
diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java
index 2b0cd04..852af8a 100644
--- a/core/java/android/app/NotificationChannelGroup.java
+++ b/core/java/android/app/NotificationChannelGroup.java
@@ -40,6 +40,12 @@
*/
public final class NotificationChannelGroup implements Parcelable {
+ /**
+ * The maximum length for text fields in a NotificationChannelGroup. Fields will be truncated at
+ * this limit.
+ */
+ private static final int MAX_TEXT_LENGTH = 1000;
+
private static final String TAG_GROUP = "channelGroup";
private static final String ATT_NAME = "name";
private static final String ATT_ID = "id";
@@ -51,14 +57,16 @@
/**
* Creates a notification channel group.
*
- * @param id The id of the group. Must be unique per package.
+ * @param id The id of the group. Must be unique per package. the value may be truncated if
+ * it is too long.
* @param name The user visible name of the group. You can rename this group when the system
* locale changes by listening for the {@link Intent#ACTION_LOCALE_CHANGED}
- * broadcast.
+ * broadcast. <p>The recommended maximum length is 40 characters; the value may be
+ * truncated if it is too long.
*/
public NotificationChannelGroup(String id, CharSequence name) {
- this.mId = id;
- this.mName = name;
+ this.mId = getTrimmedString(id);
+ this.mName = name != null ? getTrimmedString(name.toString()) : null;
}
protected NotificationChannelGroup(Parcel in) {
@@ -71,6 +79,13 @@
in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader());
}
+ private String getTrimmedString(String input) {
+ if (input != null && input.length() > MAX_TEXT_LENGTH) {
+ return input.substring(0, MAX_TEXT_LENGTH);
+ }
+ return input;
+ }
+
@Override
public void writeToParcel(Parcel dest, int flags) {
if (mId != null) {
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 0379970..097df31 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -421,8 +421,12 @@
/**
* Creates a notification channel that notifications can be posted to.
*
- * This can also be used to restore a deleted channel and to rename an existing channel. All
- * other fields are ignored for channels that already exist.
+ * This can also be used to restore a deleted channel and to update an existing channel's
+ * name and description. The name and description should only be changed if the locale changes
+ * or in response to the user renaming this channel. For example, if a user has a channel
+ * named 'John Doe' that represents messages from a 'John Doe', and 'John Doe' changes his name
+ * to 'John Smith,' the channel can be renamed to match.
+ * All other fields are ignored for channels that already exist.
*
* @param channel the channel to create. Note that the created channel may differ from this
* value. If the provided channel is malformed, a RemoteException will be
diff --git a/core/java/android/content/res/FontResourcesParser.java b/core/java/android/content/res/FontResourcesParser.java
index 091cc26..0edbc70 100644
--- a/core/java/android/content/res/FontResourcesParser.java
+++ b/core/java/android/content/res/FontResourcesParser.java
@@ -26,6 +26,7 @@
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
/**
@@ -44,12 +45,14 @@
private final @NonNull String mProviderAuthority;
private final @NonNull String mProviderPackage;
private final @NonNull String mQuery;
+ private final @Nullable List<List<String>> mCerts;
public ProviderResourceEntry(@NonNull String authority, @NonNull String pkg,
- @NonNull String query) {
+ @NonNull String query, @Nullable List<List<String>> certs) {
mProviderAuthority = authority;
mProviderPackage = pkg;
mQuery = query;
+ mCerts = certs;
}
public @NonNull String getAuthority() {
@@ -63,6 +66,10 @@
public @NonNull String getQuery() {
return mQuery;
}
+
+ public @Nullable List<List<String>> getCerts() {
+ return mCerts;
+ }
}
// A class represents font element in xml file which points a file in resource.
@@ -144,12 +151,33 @@
String authority = array.getString(R.styleable.FontFamily_fontProviderAuthority);
String providerPackage = array.getString(R.styleable.FontFamily_fontProviderPackage);
String query = array.getString(R.styleable.FontFamily_fontProviderQuery);
+ int certsId = array.getResourceId(R.styleable.FontFamily_fontProviderCerts, 0);
array.recycle();
if (authority != null && providerPackage != null && query != null) {
while (parser.next() != XmlPullParser.END_TAG) {
skip(parser);
}
- return new ProviderResourceEntry(authority, providerPackage, query);
+ List<List<String>> certs = null;
+ if (certsId != 0) {
+ TypedArray typedArray = resources.obtainTypedArray(certsId);
+ if (typedArray.length() > 0) {
+ certs = new ArrayList<>();
+ boolean isArrayOfArrays = typedArray.getResourceId(0, 0) != 0;
+ if (isArrayOfArrays) {
+ for (int i = 0; i < typedArray.length(); i++) {
+ int certId = typedArray.getResourceId(i, 0);
+ String[] certsArray = resources.getStringArray(certId);
+ List<String> certsList = Arrays.asList(certsArray);
+ certs.add(certsList);
+ }
+ } else {
+ String[] certsArray = resources.getStringArray(certsId);
+ List<String> certsList = Arrays.asList(certsArray);
+ certs.add(certsList);
+ }
+ }
+ }
+ return new ProviderResourceEntry(authority, providerPackage, query, certs);
}
List<FontFileResourceEntry> fonts = new ArrayList<>();
while (parser.next() != XmlPullParser.END_TAG) {
diff --git a/core/java/android/provider/FontsContract.java b/core/java/android/provider/FontsContract.java
index 84443e9..fd9d4db 100644
--- a/core/java/android/provider/FontsContract.java
+++ b/core/java/android/provider/FontsContract.java
@@ -15,7 +15,6 @@
*/
package android.provider;
-import android.app.ActivityThread;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
@@ -42,9 +41,10 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.HashSet;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
-import java.util.Set;
/**
* Utility class to deal with Font ContentProviders.
@@ -207,11 +207,12 @@
return info;
}
- Set<byte[]> signatures;
+ List<byte[]> signatures;
try {
PackageInfo packageInfo = mPackageManager.getPackageInfo(info.packageName,
PackageManager.GET_SIGNATURES);
- signatures = convertToSet(packageInfo.signatures);
+ signatures = convertToByteArrayList(packageInfo.signatures);
+ Collections.sort(signatures, sByteArrayComparator);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Can't find content provider " + providerAuthority, e);
receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null);
@@ -219,8 +220,10 @@
}
List<List<byte[]>> requestCertificatesList = request.getCertificates();
for (int i = 0; i < requestCertificatesList.size(); ++i) {
- final Set<byte[]> requestCertificates = convertToSet(requestCertificatesList.get(i));
- if (signatures.equals(requestCertificates)) {
+ // Make a copy so we can sort it without modifying the incoming data.
+ List<byte[]> requestSignatures = new ArrayList<>(requestCertificatesList.get(i));
+ Collections.sort(requestSignatures, sByteArrayComparator);
+ if (equalsByteArrayList(signatures, requestSignatures)) {
return info;
}
}
@@ -229,20 +232,38 @@
return null;
}
- private Set<byte[]> convertToSet(Signature[] signatures) {
- Set<byte[]> shas = new HashSet<>();
+ private static final Comparator<byte[]> sByteArrayComparator = (l, r) -> {
+ if (l.length != r.length) {
+ return l.length - r.length;
+ }
+ for (int i = 0; i < l.length; ++i) {
+ if (l[i] != r[i]) {
+ return l[i] - r[i];
+ }
+ }
+ return 0;
+ };
+
+ private boolean equalsByteArrayList(List<byte[]> signatures, List<byte[]> requestSignatures) {
+ if (signatures.size() != requestSignatures.size()) {
+ return false;
+ }
+ for (int i = 0; i < signatures.size(); ++i) {
+ if (!Arrays.equals(signatures.get(i), requestSignatures.get(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private List<byte[]> convertToByteArrayList(Signature[] signatures) {
+ List<byte[]> shas = new ArrayList<>();
for (int i = 0; i < signatures.length; ++i) {
shas.add(signatures[i].toByteArray());
}
return shas;
}
- private Set<byte[]> convertToSet(List<byte[]> certs) {
- Set<byte[]> shas = new HashSet<>();
- shas.addAll(certs);
- return shas;
- }
-
/** @hide */
@VisibleForTesting
public void getFontFromProvider(FontRequest request, ResultReceiver receiver,
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java
index 9c15e00..70f9bdd 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -111,7 +111,6 @@
mStyleValue = styleValue;
}
- /** @hide */
public Axis(@NonNull String tagString, float styleValue) {
if (!FontListParser.isValidTag(tagString)) {
throw new IllegalArgumentException("Invalid tag pattern: " + tagString);
diff --git a/core/java/android/view/MenuInflater.java b/core/java/android/view/MenuInflater.java
index 6574bc0..a3cd0ba 100644
--- a/core/java/android/view/MenuInflater.java
+++ b/core/java/android/view/MenuInflater.java
@@ -20,8 +20,11 @@
import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;
@@ -304,6 +307,8 @@
private CharSequence itemTitle;
private CharSequence itemTitleCondensed;
private int itemIconResId;
+ private ColorStateList itemIconTintList = null;
+ private PorterDuff.Mode itemIconTintMode = null;
private char itemAlphabeticShortcut;
private int itemAlphabeticModifiers;
private char itemNumericShortcut;
@@ -395,6 +400,22 @@
itemTitle = a.getText(com.android.internal.R.styleable.MenuItem_title);
itemTitleCondensed = a.getText(com.android.internal.R.styleable.MenuItem_titleCondensed);
itemIconResId = a.getResourceId(com.android.internal.R.styleable.MenuItem_icon, 0);
+ if (a.hasValue(com.android.internal.R.styleable.MenuItem_iconTintMode)) {
+ itemIconTintMode = Drawable.parseTintMode(a.getInt(
+ com.android.internal.R.styleable.MenuItem_iconTintMode, -1),
+ itemIconTintMode);
+ } else {
+ // Reset to null so that it's not carried over to the next item
+ itemIconTintMode = null;
+ }
+ if (a.hasValue(com.android.internal.R.styleable.MenuItem_iconTint)) {
+ itemIconTintList = a.getColorStateList(
+ com.android.internal.R.styleable.MenuItem_iconTint);
+ } else {
+ // Reset to null so that it's not carried over to the next item
+ itemIconTintList = null;
+ }
+
itemAlphabeticShortcut =
getShortcut(a.getString(com.android.internal.R.styleable.MenuItem_alphabeticShortcut));
itemAlphabeticModifiers =
@@ -466,6 +487,14 @@
item.setShowAsAction(itemShowAsAction);
}
+ if (itemIconTintMode != null) {
+ item.setIconTintMode(itemIconTintMode);
+ }
+
+ if (itemIconTintList != null) {
+ item.setIconTintList(itemIconTintList);
+ }
+
if (itemListenerMethodName != null) {
if (mContext.isRestricted()) {
throw new IllegalStateException("The android:onClick attribute cannot "
diff --git a/core/java/android/view/MenuItem.java b/core/java/android/view/MenuItem.java
index 6aaaedbe..b171ad0c 100644
--- a/core/java/android/view/MenuItem.java
+++ b/core/java/android/view/MenuItem.java
@@ -18,9 +18,12 @@
import android.annotation.DrawableRes;
import android.annotation.LayoutRes;
+import android.annotation.Nullable;
import android.annotation.StringRes;
import android.app.Activity;
import android.content.Intent;
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnCreateContextMenuListener;
@@ -221,11 +224,62 @@
/**
* Returns the icon for this item as a Drawable (getting it from resources if it hasn't been
- * loaded before).
+ * loaded before). Note that if you call {@link #setIconTintList(ColorStateList)} or
+ * {@link #setIconTintMode(PorterDuff.Mode)} on this item, and you use a custom menu presenter
+ * in your application, you have to apply the tinting explicitly on the {@link Drawable}
+ * returned by this method.
*
* @return The icon as a Drawable.
*/
public Drawable getIcon();
+
+ /**
+ * Applies a tint to this item's icon. Does not modify the
+ * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
+ * <p>
+ * Subsequent calls to {@link #setIcon(Drawable)} or {@link #setIcon(int)} will
+ * automatically mutate the icon and apply the specified tint and
+ * tint mode using
+ * {@link Drawable#setTintList(ColorStateList)}.
+ *
+ * @param tint the tint to apply, may be {@code null} to clear tint
+ *
+ * @attr ref android.R.styleable#MenuItem_iconTint
+ * @see #getIconTintList()
+ * @see Drawable#setTintList(ColorStateList)
+ */
+ public default MenuItem setIconTintList(@Nullable ColorStateList tint) { return this; }
+
+ /**
+ * @return the tint applied to this item's icon
+ * @attr ref android.R.styleable#MenuItem_iconTint
+ * @see #setIconTintList(ColorStateList)
+ */
+ @Nullable
+ public default ColorStateList getIconTintList() { return null; }
+
+ /**
+ * Specifies the blending mode used to apply the tint specified by
+ * {@link #setIconTintList(ColorStateList)} to this item's icon. The default mode is
+ * {@link PorterDuff.Mode#SRC_IN}.
+ *
+ * @param tintMode the blending mode used to apply the tint, may be
+ * {@code null} to clear tint
+ * @attr ref android.R.styleable#MenuItem_iconTintMode
+ * @see #setIconTintList(ColorStateList)
+ * @see Drawable#setTintMode(PorterDuff.Mode)
+ */
+ public default MenuItem setIconTintMode(@Nullable PorterDuff.Mode tintMode) { return this; }
+
+ /**
+ * Returns the blending mode used to apply the tint to this item's icon, if specified.
+ *
+ * @return the blending mode used to apply the tint to this item's icon
+ * @attr ref android.R.styleable#MenuItem_iconTintMode
+ * @see #setIconTintMode(PorterDuff.Mode)
+ */
+ @Nullable
+ public default PorterDuff.Mode getIconTintMode() { return null; }
/**
* Change the Intent associated with this item. By default there is no
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 5269296..6c73b9b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -7677,9 +7677,11 @@
AccessibilityNodeInfo cinfo = provider.createAccessibilityNodeInfo(
AccessibilityNodeInfo.getVirtualDescendantId(info.getChildId(i)));
ViewStructure child = structure.newChild(i);
- // TODO(b/33197203): add CTS test to autofill virtual children based on
- // Accessibility API.
- child.setAutofillId(structure, i);
+ if (forAutofill) {
+ // TODO(b/33197203): add CTS test to autofill virtual children based on
+ // Accessibility API.
+ child.setAutofillId(structure, i);
+ }
populateVirtualStructure(child, provider, cinfo, forAutofill);
cinfo.recycle();
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 9cbbc5a..21e39f6 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -106,6 +106,11 @@
*/
void showTvPictureInPictureMenu();
+ /**
+ * Shows the global actions menu.
+ */
+ void showGlobalActionsMenu();
+
void addQsTile(in ComponentName tile);
void remQsTile(in ComponentName tile);
void clickQsTile(in ComponentName tile);
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 698e387..20db499 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -63,6 +63,15 @@
void onNotificationExpansionChanged(in String key, in boolean userAction, in boolean expanded);
void setSystemUiVisibility(int vis, int mask, String cause);
+ void onGlobalActionsShown();
+ void onGlobalActionsHidden();
+
+ /**
+ * These methods are needed for global actions control which the UI is shown in sysui.
+ */
+ void shutdown();
+ void reboot(boolean safeMode);
+
void addTile(in ComponentName tile);
void remTile(in ComponentName tile);
void clickTile(in ComponentName tile);
diff --git a/core/java/com/android/internal/policy/EmergencyAffordanceManager.java b/core/java/com/android/internal/util/EmergencyAffordanceManager.java
similarity index 85%
rename from core/java/com/android/internal/policy/EmergencyAffordanceManager.java
rename to core/java/com/android/internal/util/EmergencyAffordanceManager.java
index eb75bd4..ba95bfc 100644
--- a/core/java/com/android/internal/policy/EmergencyAffordanceManager.java
+++ b/core/java/com/android/internal/util/EmergencyAffordanceManager.java
@@ -1,20 +1,18 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 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
+ * 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
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
*/
-package com.android.internal.policy;
+package com.android.internal.util;
import android.content.Context;
import android.content.Intent;
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItem.java b/core/java/com/android/internal/view/menu/ActionMenuItem.java
index ac226dd..b807a42 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItem.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItem.java
@@ -16,8 +16,11 @@
package com.android.internal.view.menu;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.view.ActionProvider;
import android.view.ContextMenu.ContextMenuInfo;
@@ -45,6 +48,10 @@
private Drawable mIconDrawable;
private int mIconResId = NO_ICON;
+ private ColorStateList mIconTintList = null;
+ private PorterDuff.Mode mIconTintMode = null;
+ private boolean mHasIconTint = false;
+ private boolean mHasIconTintMode = false;
private Context mContext;
@@ -178,15 +185,65 @@
public MenuItem setIcon(Drawable icon) {
mIconDrawable = icon;
mIconResId = NO_ICON;
+
+ applyIconTint();
return this;
}
public MenuItem setIcon(int iconRes) {
mIconResId = iconRes;
mIconDrawable = mContext.getDrawable(iconRes);
+
+ applyIconTint();
return this;
}
+ @Override
+ public MenuItem setIconTintList(@Nullable ColorStateList iconTintList) {
+ mIconTintList = iconTintList;
+ mHasIconTint = true;
+
+ applyIconTint();
+
+ return this;
+ }
+
+ @Nullable
+ @Override
+ public ColorStateList getIconTintList() {
+ return mIconTintList;
+ }
+
+ @Override
+ public MenuItem setIconTintMode(PorterDuff.Mode iconTintMode) {
+ mIconTintMode = iconTintMode;
+ mHasIconTintMode = true;
+
+ applyIconTint();
+
+ return this;
+ }
+
+ @Nullable
+ @Override
+ public PorterDuff.Mode getIconTintMode() {
+ return mIconTintMode;
+ }
+
+ private void applyIconTint() {
+ if (mIconDrawable != null && (mHasIconTint || mHasIconTintMode)) {
+ mIconDrawable = mIconDrawable.mutate();
+
+ if (mHasIconTint) {
+ mIconDrawable.setTintList(mIconTintList);
+ }
+
+ if (mHasIconTintMode) {
+ mIconDrawable.setTintMode(mIconTintMode);
+ }
+ }
+ }
+
public MenuItem setIntent(Intent intent) {
mIntent = intent;
return this;
diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java
index 9310d14..101623c 100644
--- a/core/java/com/android/internal/view/menu/MenuItemImpl.java
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -18,9 +18,12 @@
import com.android.internal.view.menu.MenuView.ItemView;
+import android.annotation.Nullable;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.ActionProvider;
@@ -38,7 +41,7 @@
*/
public final class MenuItemImpl implements MenuItem {
private static final String TAG = "MenuItemImpl";
-
+
private static final int SHOW_AS_ACTION_MASK = SHOW_AS_ACTION_NEVER |
SHOW_AS_ACTION_IF_ROOM |
SHOW_AS_ACTION_ALWAYS;
@@ -61,14 +64,20 @@
* The icon's resource ID which is used to get the Drawable when it is
* needed (if the Drawable isn't already obtained--only one of the two is
* needed).
- */
+ */
private int mIconResId = NO_ICON;
-
+
+ private ColorStateList mIconTintList = null;
+ private PorterDuff.Mode mIconTintMode = null;
+ private boolean mHasIconTint = false;
+ private boolean mHasIconTintMode = false;
+ private boolean mNeedToApplyIconTint = false;
+
/** The menu to which this item belongs */
private MenuBuilder mMenu;
/** If this item should launch a sub menu, this is the sub menu to launch */
private SubMenuBuilder mSubMenu;
-
+
private Runnable mItemCallback;
private MenuItem.OnMenuItemClickListener mClickListener;
@@ -124,7 +133,7 @@
String lang = menu.getContext().getResources().getConfiguration().locale.toString();
if (sPrependShortcutLabel == null || !lang.equals(sLanguage)) {
sLanguage = lang;
- // This is instantiated from the UI thread, so no chance of sync issues
+ // This is instantiated from the UI thread, so no chance of sync issues
sPrependShortcutLabel = menu.getContext().getResources().getString(
com.android.internal.R.string.prepend_shortcut_label);
sEnterShortcutLabel = menu.getContext().getResources().getString(
@@ -134,7 +143,7 @@
sSpaceShortcutLabel = menu.getContext().getResources().getString(
com.android.internal.R.string.menu_space_shortcut_label);
}
-
+
mMenu = menu;
mId = id;
mGroup = group;
@@ -143,10 +152,10 @@
mTitle = title;
mShowAsAction = showAsAction;
}
-
+
/**
* Invokes the item by calling various listeners or callbacks.
- *
+ *
* @return true if the invocation was handled, false otherwise
*/
public boolean invoke() {
@@ -179,7 +188,7 @@
return false;
}
-
+
public boolean isEnabled() {
return (mFlags & ENABLED) != 0;
}
@@ -192,10 +201,10 @@
}
mMenu.onItemsChanged(false);
-
+
return this;
}
-
+
public int getGroupId() {
return mGroup;
}
@@ -208,11 +217,11 @@
public int getOrder() {
return mCategoryOrder;
}
-
+
public int getOrdering() {
- return mOrdering;
+ return mOrdering;
}
-
+
public Intent getIntent() {
return mIntent;
}
@@ -225,7 +234,7 @@
Runnable getCallback() {
return mItemCallback;
}
-
+
public MenuItem setCallback(Runnable callback) {
mItemCallback = callback;
return this;
@@ -273,11 +282,11 @@
public MenuItem setNumericShortcut(char numericChar) {
if (mShortcutNumericChar == numericChar) return this;
-
+
mShortcutNumericChar = numericChar;
-
+
mMenu.onItemsChanged(false);
-
+
return this;
}
@@ -297,9 +306,9 @@
public MenuItem setShortcut(char numericChar, char alphaChar) {
mShortcutNumericChar = numericChar;
mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
-
+
mMenu.onItemsChanged(false);
-
+
return this;
}
@@ -321,7 +330,7 @@
char getShortcut() {
return (mMenu.isQwertyMode() ? mShortcutAlphabeticChar : mShortcutNumericChar);
}
-
+
/**
* @return The label to show for the shortcut. This includes the chording
* key (for example 'Menu+a'). Also, any non-human readable
@@ -333,30 +342,30 @@
if (shortcut == 0) {
return "";
}
-
+
StringBuilder sb = new StringBuilder(sPrependShortcutLabel);
switch (shortcut) {
-
+
case '\n':
sb.append(sEnterShortcutLabel);
break;
-
+
case '\b':
sb.append(sDeleteShortcutLabel);
break;
-
+
case ' ':
sb.append(sSpaceShortcutLabel);
break;
-
+
default:
sb.append(shortcut);
break;
}
-
+
return sb.toString();
}
-
+
/**
* @return Whether this menu item should be showing shortcuts (depends on
* whether the menu should show shortcuts and whether this item has
@@ -366,7 +375,7 @@
// Show shortcuts if the menu is supposed to show shortcuts AND this item has a shortcut
return mMenu.isShortcutsVisible() && (getShortcut() != 0);
}
-
+
public SubMenu getSubMenu() {
return mSubMenu;
}
@@ -377,10 +386,10 @@
void setSubMenu(SubMenuBuilder subMenu) {
mSubMenu = subMenu;
-
+
subMenu.setHeaderTitle(getTitle());
}
-
+
@ViewDebug.CapturedViewProperty
public CharSequence getTitle() {
return mTitle;
@@ -388,7 +397,7 @@
/**
* Gets the title for a particular {@link ItemView}
- *
+ *
* @param itemView The ItemView that is receiving the title
* @return Either the title or condensed title based on what the ItemView
* prefers
@@ -403,68 +412,122 @@
mTitle = title;
mMenu.onItemsChanged(false);
-
+
if (mSubMenu != null) {
mSubMenu.setHeaderTitle(title);
}
-
+
return this;
}
-
+
public MenuItem setTitle(int title) {
return setTitle(mMenu.getContext().getString(title));
}
-
+
public CharSequence getTitleCondensed() {
return mTitleCondensed != null ? mTitleCondensed : mTitle;
}
-
+
public MenuItem setTitleCondensed(CharSequence title) {
mTitleCondensed = title;
- // Could use getTitle() in the loop below, but just cache what it would do here
+ // Could use getTitle() in the loop below, but just cache what it would do here
if (title == null) {
title = mTitle;
}
-
+
mMenu.onItemsChanged(false);
-
+
return this;
}
public Drawable getIcon() {
if (mIconDrawable != null) {
- return mIconDrawable;
+ return applyIconTintIfNecessary(mIconDrawable);
}
if (mIconResId != NO_ICON) {
Drawable icon = mMenu.getContext().getDrawable(mIconResId);
mIconResId = NO_ICON;
mIconDrawable = icon;
- return icon;
+ return applyIconTintIfNecessary(icon);
}
-
+
return null;
}
-
+
public MenuItem setIcon(Drawable icon) {
mIconResId = NO_ICON;
mIconDrawable = icon;
+ mNeedToApplyIconTint = true;
mMenu.onItemsChanged(false);
-
+
return this;
}
-
+
public MenuItem setIcon(int iconResId) {
mIconDrawable = null;
mIconResId = iconResId;
+ mNeedToApplyIconTint = true;
// If we have a view, we need to push the Drawable to them
mMenu.onItemsChanged(false);
-
+
return this;
}
-
+
+ @Override
+ public MenuItem setIconTintList(@Nullable ColorStateList iconTintList) {
+ mIconTintList = iconTintList;
+ mHasIconTint = true;
+ mNeedToApplyIconTint = true;
+
+ mMenu.onItemsChanged(false);
+
+ return this;
+ }
+
+ @Nullable
+ @Override
+ public ColorStateList getIconTintList() {
+ return mIconTintList;
+ }
+
+ @Override
+ public MenuItem setIconTintMode(PorterDuff.Mode iconTintMode) {
+ mIconTintMode = iconTintMode;
+ mHasIconTintMode = true;
+ mNeedToApplyIconTint = true;
+
+ mMenu.onItemsChanged(false);
+
+ return this;
+ }
+
+ @Nullable
+ @Override
+ public PorterDuff.Mode getIconTintMode() {
+ return mIconTintMode;
+ }
+
+ private Drawable applyIconTintIfNecessary(Drawable icon) {
+ if (icon != null && mNeedToApplyIconTint && (mHasIconTint || mHasIconTintMode)) {
+ icon = icon.mutate();
+
+ if (mHasIconTint) {
+ icon.setTintList(mIconTintList);
+ }
+
+ if (mHasIconTintMode) {
+ icon.setTintMode(mIconTintMode);
+ }
+
+ mNeedToApplyIconTint = false;
+ }
+
+ return icon;
+ }
+
public boolean isCheckable() {
return (mFlags & CHECKABLE) == CHECKABLE;
}
@@ -475,7 +538,7 @@
if (oldFlags != mFlags) {
mMenu.onItemsChanged(false);
}
-
+
return this;
}
@@ -486,7 +549,7 @@
public boolean isExclusiveCheckable() {
return (mFlags & EXCLUSIVE) != 0;
}
-
+
public boolean isChecked() {
return (mFlags & CHECKED) == CHECKED;
}
@@ -499,7 +562,7 @@
} else {
setCheckedInt(checked);
}
-
+
return this;
}
@@ -510,7 +573,7 @@
mMenu.onItemsChanged(false);
}
}
-
+
public boolean isVisible() {
if (mActionProvider != null && mActionProvider.overridesItemVisibility()) {
return (mFlags & HIDDEN) == 0 && mActionProvider.isVisible();
@@ -523,7 +586,7 @@
* parent menu of a change in this item, so this should only be called from
* methods that will eventually trigger this change. If unsure, use {@link #setVisible(boolean)}
* instead.
- *
+ *
* @param shown Whether to show (true) or hide (false).
* @return Whether the item's shown state was changed
*/
@@ -532,13 +595,13 @@
mFlags = (mFlags & ~HIDDEN) | (shown ? 0 : HIDDEN);
return oldFlags != mFlags;
}
-
+
public MenuItem setVisible(boolean shown) {
// Try to set the shown state to the given state. If the shown state was changed
// (i.e. the previous state isn't the same as given state), notify the parent menu that
// the shown state has changed for this item
if (setVisibleInt(shown)) mMenu.onItemVisibleChanged(this);
-
+
return this;
}
@@ -546,7 +609,7 @@
mClickListener = clickListener;
return this;
}
-
+
@Override
public String toString() {
return mTitle != null ? mTitle.toString() : null;
@@ -555,7 +618,7 @@
void setMenuInfo(ContextMenuInfo menuInfo) {
mMenuInfo = menuInfo;
}
-
+
public ContextMenuInfo getMenuInfo() {
return mMenuInfo;
}
@@ -570,15 +633,15 @@
public boolean shouldShowIcon() {
return mMenu.getOptionalIconsVisible();
}
-
+
public boolean isActionButton() {
return (mFlags & IS_ACTION) == IS_ACTION;
}
-
+
public boolean requestsActionButton() {
return (mShowAsAction & SHOW_AS_ACTION_IF_ROOM) == SHOW_AS_ACTION_IF_ROOM;
}
-
+
public boolean requiresActionButton() {
return (mShowAsAction & SHOW_AS_ACTION_ALWAYS) == SHOW_AS_ACTION_ALWAYS;
}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 95b2593..64e1620e 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -210,10 +210,7 @@
$(TOP)/system/media/camera/include \
$(TOP)/system/netd/include \
external/giflib \
- external/pdfium/core/include/fpdfapi \
- external/pdfium/fpdfsdk/include \
external/pdfium/public \
- external/pdfium \
external/skia/include/private \
external/skia/src/core \
external/skia/src/effects \
diff --git a/core/jni/android/graphics/pdf/PdfRenderer.cpp b/core/jni/android/graphics/pdf/PdfRenderer.cpp
index 6e7b6b8..1001c05 100644
--- a/core/jni/android/graphics/pdf/PdfRenderer.cpp
+++ b/core/jni/android/graphics/pdf/PdfRenderer.cpp
@@ -23,11 +23,6 @@
#include "SkMatrix.h"
#include "fpdfview.h"
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
-#include "fsdk_rendercontext.h"
-#pragma GCC diagnostic pop
-
#include "core_jni_helpers.h"
#include <vector>
#include <utils/Log.h>
@@ -80,103 +75,10 @@
HANDLE_PDFIUM_ERROR_STATE(env)
}
-static void DropContext(void* data) {
- delete (CRenderContext*) data;
-}
-
-static void renderPageBitmap(FPDF_BITMAP bitmap, FPDF_PAGE page, int destLeft, int destTop,
- int destRight, int destBottom, SkMatrix* transform, int flags) {
- // Note: this code ignores the currently unused RENDER_NO_NATIVETEXT,
- // FPDF_RENDER_LIMITEDIMAGECACHE, FPDF_RENDER_FORCEHALFTONE, FPDF_GRAYSCALE,
- // and FPDF_ANNOT flags. To add support for that refer to FPDF_RenderPage_Retail
- // in fpdfview.cpp
-
- CRenderContext* pContext = new CRenderContext;
-
- CPDF_Page* pPage = (CPDF_Page*) page;
- pPage->SetPrivateData((void*) 1, pContext, DropContext);
-
- CFX_FxgeDevice* fxgeDevice = new CFX_FxgeDevice;
- pContext->m_pDevice = fxgeDevice;
-
- // Reverse the bytes (last argument TRUE) since the Android
- // format is ARGB while the renderer uses BGRA internally.
- fxgeDevice->Attach((CFX_DIBitmap*) bitmap, 0, TRUE);
-
- CPDF_RenderOptions* renderOptions = pContext->m_pOptions;
-
- if (!renderOptions) {
- renderOptions = new CPDF_RenderOptions;
- pContext->m_pOptions = renderOptions;
- }
-
- if (flags & FPDF_LCD_TEXT) {
- renderOptions->m_Flags |= RENDER_CLEARTYPE;
- } else {
- renderOptions->m_Flags &= ~RENDER_CLEARTYPE;
- }
-
- const CPDF_OCContext::UsageType usage = (flags & FPDF_PRINTING)
- ? CPDF_OCContext::Print : CPDF_OCContext::View;
-
- renderOptions->m_AddFlags = flags >> 8;
- renderOptions->m_pOCContext = new CPDF_OCContext(pPage->m_pDocument, usage);
-
- fxgeDevice->SaveState();
-
- FX_RECT clip;
- clip.left = destLeft;
- clip.right = destRight;
- clip.top = destTop;
- clip.bottom = destBottom;
- fxgeDevice->SetClip_Rect(&clip);
-
- CPDF_RenderContext* pageContext = new CPDF_RenderContext(pPage);
- pContext->m_pContext = pageContext;
-
- CFX_Matrix matrix;
- if (!transform) {
- pPage->GetDisplayMatrix(matrix, destLeft, destTop, destRight - destLeft,
- destBottom - destTop, 0);
- } else {
- // PDF's coordinate system origin is left-bottom while
- // in graphics it is the top-left, so remap the origin.
- SkMatrix reflectOnX = SkMatrix::MakeScale(1, -1);
- SkMatrix moveUp = SkMatrix::MakeTrans(0, FPDF_GetPageHeight(page));
- SkMatrix m = SkMatrix::Concat(moveUp, reflectOnX);
-
- // Concatenate transformation and origin transformation
- m.setConcat(*transform, m);
-
- SkScalar transformValues[6];
- if (!m.asAffine(transformValues)) {
- // Already checked for a return value of false in the caller, so this should never
- // happen.
- ALOGE("Error rendering page!");
- }
-
- matrix = {transformValues[SkMatrix::kAScaleX], transformValues[SkMatrix::kASkewY],
- transformValues[SkMatrix::kASkewX], transformValues[SkMatrix::kAScaleY],
- transformValues[SkMatrix::kATransX], transformValues[SkMatrix::kATransY]};
- }
- pageContext->AppendObjectList(pPage, &matrix);
-
- pContext->m_pRenderer = new CPDF_ProgressiveRenderer(pageContext, fxgeDevice, renderOptions);
- pContext->m_pRenderer->Start(NULL);
-
- fxgeDevice->RestoreState();
-
- pPage->RemovePrivateData((void*) 1);
-
- delete pContext;
-}
-
static void nativeRenderPage(JNIEnv* env, jclass thiz, jlong documentPtr, jlong pagePtr,
- jobject jbitmap, jint destLeft, jint destTop, jint destRight, jint destBottom,
- jlong matrixPtr, jint renderMode) {
-
+ jobject jbitmap, jint clipLeft, jint clipTop, jint clipRight, jint clipBottom,
+ jlong transformPtr, jint renderMode) {
FPDF_PAGE page = reinterpret_cast<FPDF_PAGE>(pagePtr);
- SkMatrix* skMatrix = reinterpret_cast<SkMatrix*>(matrixPtr);
SkBitmap skBitmap;
GraphicsJNI::getSkBitmap(env, jbitmap, &skBitmap);
@@ -187,27 +89,49 @@
FPDF_BITMAP bitmap = FPDFBitmap_CreateEx(skBitmap.width(), skBitmap.height(),
FPDFBitmap_BGRA, skBitmap.getPixels(), stride);
-
- if (!bitmap) {
- ALOGE("Erorr creating bitmap");
+ bool isExceptionPending = forwardPdfiumError(env);
+ if (isExceptionPending || bitmap == NULL) {
+ ALOGE("Error creating bitmap");
return;
}
- int renderFlags = 0;
+ int renderFlags = FPDF_REVERSE_BYTE_ORDER;
if (renderMode == RENDER_MODE_FOR_DISPLAY) {
renderFlags |= FPDF_LCD_TEXT;
} else if (renderMode == RENDER_MODE_FOR_PRINT) {
renderFlags |= FPDF_PRINTING;
}
- if (skMatrix && !skMatrix->asAffine(NULL)) {
+ // PDF's coordinate system origin is left-bottom while in graphics it
+ // is the top-left. So, translate the PDF coordinates to ours.
+ SkMatrix reflectOnX = SkMatrix::MakeScale(1, -1);
+ SkMatrix moveUp = SkMatrix::MakeTrans(0, FPDF_GetPageHeight(page));
+ SkMatrix coordinateChange = SkMatrix::Concat(moveUp, reflectOnX);
+
+ // Apply the transformation
+ SkMatrix matrix;
+ if (transformPtr == 0) {
+ matrix = coordinateChange;
+ } else {
+ matrix = SkMatrix::Concat(*reinterpret_cast<SkMatrix*>(transformPtr), coordinateChange);
+ }
+
+ SkScalar transformValues[6];
+ if (!matrix.asAffine(transformValues)) {
jniThrowException(env, "java/lang/IllegalArgumentException",
"transform matrix has perspective. Only affine matrices are allowed.");
return;
}
- renderPageBitmap(bitmap, page, destLeft, destTop, destRight,
- destBottom, skMatrix, renderFlags);
+ FS_MATRIX transform = {transformValues[SkMatrix::kAScaleX], transformValues[SkMatrix::kASkewY],
+ transformValues[SkMatrix::kASkewX], transformValues[SkMatrix::kAScaleY],
+ transformValues[SkMatrix::kATransX],
+ transformValues[SkMatrix::kATransY]};
+
+ FS_RECTF clip = {(float) clipLeft, (float) clipTop, (float) clipRight, (float) clipBottom};
+
+ FPDF_RenderPageBitmapWithMatrix(bitmap, page, &transform, &clip, renderFlags);
+ HANDLE_PDFIUM_ERROR_STATE(env);
skBitmap.notifyPixelsChanged();
}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index eecb021..3a1cf77 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -7010,6 +7010,30 @@
the title should be sufficient in describing this item. -->
<attr name="icon" />
+ <!-- Tint to apply to the icon. -->
+ <attr name="iconTint" format="color" />
+
+ <!-- Blending mode used to apply the icon tint. -->
+ <attr name="iconTintMode">
+ <!-- The tint is drawn on top of the icon.
+ [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->
+ <enum name="src_over" value="3" />
+ <!-- The tint is masked by the alpha channel of the icon. The icon’s
+ color channels are thrown out. [Sa * Da, Sc * Da] -->
+ <enum name="src_in" value="5" />
+ <!-- The tint is drawn above the icon, but with the icon’s alpha
+ channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->
+ <enum name="src_atop" value="9" />
+ <!-- Multiplies the color and alpha channels of the icon with those of
+ the tint. [Sa * Da, Sc * Dc] -->
+ <enum name="multiply" value="14" />
+ <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->
+ <enum name="screen" value="15" />
+ <!-- Combines the tint and icon color and alpha channels, clamping the
+ result to valid color values. Saturate(S + D) -->
+ <enum name="add" value="16" />
+ </attr>
+
<!-- The alphabetic shortcut key. This is the shortcut when using a keyboard
with alphabetic keys. -->
<attr name="alphabeticShortcut" format="string" />
@@ -8569,6 +8593,7 @@
<attr name="fontProviderAuthority" format="string" />
<attr name="fontProviderPackage" format="string" />
<attr name="fontProviderQuery" format="string" />
+ <attr name="fontProviderCerts" format="reference" />
</declare-styleable>
<!-- @hide -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 8b1b9d3..d6b5527 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2809,6 +2809,9 @@
<public name="isStatic" />
<public name="isFeatureSplit" />
<public name="singleLineTitle" />
+ <public name="fontProviderCerts" />
+ <public name="iconTint" />
+ <public name="iconTintMode" />
</public-group>
<public-group type="style" first-id="0x010302e0">
diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml
new file mode 100644
index 0000000..ed96bc1
--- /dev/null
+++ b/core/tests/coretests/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<configuration description="Runs Frameworks Core Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="FrameworksCoreTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="FrameworksCoreTests" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.frameworks.coretests" />
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/core/tests/coretests/res/font/samplexmldownloadedfontmulticerts.xml b/core/tests/coretests/res/font/samplexmldownloadedfontmulticerts.xml
new file mode 100644
index 0000000..7a753c3
--- /dev/null
+++ b/core/tests/coretests/res/font/samplexmldownloadedfontmulticerts.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:android="http://schemas.android.com/apk/res/android"
+ android:fontProviderAuthority="com.example.test.fontprovider"
+ android:fontProviderQuery="MyRequestedFont"
+ android:fontProviderPackage="com.example.test.fontprovider.package"
+ android:fontProviderCerts="@array/certarray">
+</font-family>
\ No newline at end of file
diff --git a/core/tests/coretests/res/font/samplexmldownloadedfontsinglecerts.xml b/core/tests/coretests/res/font/samplexmldownloadedfontsinglecerts.xml
new file mode 100644
index 0000000..b834771
--- /dev/null
+++ b/core/tests/coretests/res/font/samplexmldownloadedfontsinglecerts.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:android="http://schemas.android.com/apk/res/android"
+ android:fontProviderAuthority="com.example.test.fontprovider"
+ android:fontProviderQuery="MyRequestedFont"
+ android:fontProviderPackage="com.example.test.fontprovider.package"
+ android:fontProviderCerts="@array/certs1">
+</font-family>
\ No newline at end of file
diff --git a/core/tests/coretests/res/values/arrays.xml b/core/tests/coretests/res/values/arrays.xml
index f76da85..7a25707 100644
--- a/core/tests/coretests/res/values/arrays.xml
+++ b/core/tests/coretests/res/values/arrays.xml
@@ -31,4 +31,19 @@
<item>2 days</item>
<item>1 week</item>
</string-array>
+
+ <string-array name="certs1">
+ <item>123456789</item>
+ <item>987654321</item>
+ </string-array>
+
+ <string-array name="certs2">
+ <item>abcdefg</item>
+ <item>gfedcba</item>
+ </string-array>
+
+ <array name="certarray">
+ <item>@array/certs1</item>
+ <item>@array/certs2</item>
+ </array>
</resources>
diff --git a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
index 82f4690..5e426e8 100644
--- a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
+++ b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
@@ -15,21 +15,20 @@
*/
package android.content.res;
-import static junit.framework.Assert.assertNull;
+import static android.content.res.FontResourcesParser.FamilyResourceEntry;
+import static android.content.res.FontResourcesParser.FontFamilyFilesResourceEntry;
+import static android.content.res.FontResourcesParser.FontFileResourceEntry;
+import static android.content.res.FontResourcesParser.ProviderResourceEntry;
+
+import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static android.content.res.FontResourcesParser.FamilyResourceEntry;
-import static android.content.res.FontResourcesParser.ProviderResourceEntry;
-import static android.content.res.FontResourcesParser.FontFileResourceEntry;
-import static android.content.res.FontResourcesParser.FontFamilyFilesResourceEntry;
-
import android.app.Instrumentation;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import android.text.FontConfig;
import com.android.frameworks.coretests.R;
@@ -97,4 +96,50 @@
assertEquals("com.example.test.fontprovider.package", providerEntry.getPackage());
assertEquals("MyRequestedFont", providerEntry.getQuery());
}
+
+ @Test
+ public void testParseDownloadableFont_singleCerts() throws IOException, XmlPullParserException {
+ XmlResourceParser parser = mResources.getXml(R.font.samplexmldownloadedfontsinglecerts);
+
+ FamilyResourceEntry result = FontResourcesParser.parse(parser, mResources);
+
+ assertNotNull(result);
+ assertTrue(result instanceof ProviderResourceEntry);
+ ProviderResourceEntry providerResourceEntry = (ProviderResourceEntry) result;
+ assertEquals("com.example.test.fontprovider", providerResourceEntry.getAuthority());
+ assertEquals("MyRequestedFont", providerResourceEntry.getQuery());
+ assertEquals("com.example.test.fontprovider.package", providerResourceEntry.getPackage());
+ List<List<String>> certList = providerResourceEntry.getCerts();
+ assertNotNull(certList);
+ assertEquals(1, certList.size());
+ List<String> certs = certList.get(0);
+ assertEquals(2, certs.size());
+ assertEquals("123456789", certs.get(0));
+ assertEquals("987654321", certs.get(1));
+ }
+
+ @Test
+ public void testParseDownloadableFont_multipleCerts() throws IOException, XmlPullParserException {
+ XmlResourceParser parser = mResources.getXml(R.font.samplexmldownloadedfontmulticerts);
+
+ FamilyResourceEntry result = FontResourcesParser.parse(parser, mResources);
+
+ assertNotNull(result);
+ assertTrue(result instanceof ProviderResourceEntry);
+ ProviderResourceEntry providerResourceEntry = (ProviderResourceEntry) result;
+ assertEquals("com.example.test.fontprovider", providerResourceEntry.getAuthority());
+ assertEquals("MyRequestedFont", providerResourceEntry.getQuery());
+ assertEquals("com.example.test.fontprovider.package", providerResourceEntry.getPackage());
+ List<List<String>> certList = providerResourceEntry.getCerts();
+ assertNotNull(certList);
+ assertEquals(2, certList.size());
+ List<String> certs1 = certList.get(0);
+ assertEquals(2, certs1.size());
+ assertEquals("123456789", certs1.get(0));
+ assertEquals("987654321", certs1.get(1));
+ List<String> certs2 = certList.get(1);
+ assertEquals(2, certs2.size());
+ assertEquals("abcdefg", certs2.get(0));
+ assertEquals("gfedcba", certs2.get(1));
+ }
}
diff --git a/core/tests/coretests/src/android/provider/FontsContractTest.java b/core/tests/coretests/src/android/provider/FontsContractTest.java
index d90fc2b..6820e92 100644
--- a/core/tests/coretests/src/android/provider/FontsContractTest.java
+++ b/core/tests/coretests/src/android/provider/FontsContractTest.java
@@ -51,6 +51,11 @@
public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> {
private static final byte[] BYTE_ARRAY =
Base64.decode("e04fd020ea3a6910a2d808002b30", Base64.DEFAULT);
+ // Use a different instance to test byte array comparison
+ private static final byte[] BYTE_ARRAY_COPY =
+ Base64.decode("e04fd020ea3a6910a2d808002b30", Base64.DEFAULT);
+ private static final byte[] BYTE_ARRAY_2 =
+ Base64.decode("e04fd020ea3a6910a2d808002b32", Base64.DEFAULT);
private static final String PACKAGE_NAME = "com.my.font.provider.package";
private final FontRequest request = new FontRequest(
@@ -268,6 +273,34 @@
assertNull(result);
}
+ public void testGetProvider_providerIsNonSystemAppDuplicateCerts()
+ throws PackageManager.NameNotFoundException {
+ ProviderInfo info = new ProviderInfo();
+ info.packageName = PACKAGE_NAME;
+ info.applicationInfo = new ApplicationInfo();
+ when(mPackageManager.resolveContentProvider(anyString(), anyInt())).thenReturn(info);
+ PackageInfo packageInfo = new PackageInfo();
+ Signature signature = mock(Signature.class);
+ when(signature.toByteArray()).thenReturn(BYTE_ARRAY_COPY);
+ Signature signature2 = mock(Signature.class);
+ when(signature2.toByteArray()).thenReturn(BYTE_ARRAY_COPY);
+ packageInfo.packageName = PACKAGE_NAME;
+ packageInfo.signatures = new Signature[] { signature, signature2 };
+ when(mPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(packageInfo);
+
+ // The provider has {BYTE_ARRAY_COPY, BYTE_ARRAY_COPY}, the request has
+ // {BYTE_ARRAY_2, BYTE_ARRAY_COPY}.
+ List<byte[]> certList = Arrays.asList(BYTE_ARRAY_2, BYTE_ARRAY_COPY);
+ FontRequest requestRightCerts = new FontRequest(
+ TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", Arrays.asList(certList));
+ ProviderInfo result = mContract.getProvider(requestRightCerts, mResultReceiver);
+
+ // The given list includes an extra cert and doesn't have a second copy of the cert like
+ // the provider does, so it should have failed.
+ verify(mResultReceiver).send(FontsContract.RESULT_CODE_WRONG_CERTIFICATES, null);
+ assertNull(result);
+ }
+
public void testGetProvider_providerIsNonSystemAppCorrectCertsSeveralSets()
throws PackageManager.NameNotFoundException {
ProviderInfo info = setupPackageManager();
@@ -306,7 +339,7 @@
when(mPackageManager.resolveContentProvider(anyString(), anyInt())).thenReturn(info);
PackageInfo packageInfo = new PackageInfo();
Signature signature = mock(Signature.class);
- when(signature.toByteArray()).thenReturn(BYTE_ARRAY);
+ when(signature.toByteArray()).thenReturn(BYTE_ARRAY_COPY);
packageInfo.packageName = PACKAGE_NAME;
packageInfo.signatures = new Signature[] { signature };
when(mPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(packageInfo);
diff --git a/core/tests/utiltests/Android.mk b/core/tests/utiltests/Android.mk
index 46a0d9b..a4b0916 100644
--- a/core/tests/utiltests/Android.mk
+++ b/core/tests/utiltests/Android.mk
@@ -25,4 +25,6 @@
LOCAL_CERTIFICATE := platform
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
include $(BUILD_PACKAGE)
diff --git a/core/tests/utiltests/AndroidTest.xml b/core/tests/utiltests/AndroidTest.xml
new file mode 100644
index 0000000..9ebf568
--- /dev/null
+++ b/core/tests/utiltests/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<configuration description="Runs Frameworks Utility Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="FrameworksUtilTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="FrameworksUtilTests" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.frameworks.utiltests" />
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 8a8d027..bc1d88b 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -254,6 +254,7 @@
<permission name="android.permission.BIND_APPWIDGET"/>
<permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
<permission name="android.permission.CHANGE_CONFIGURATION"/>
+ <permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST" />
<permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
<permission name="android.permission.CONNECTIVITY_INTERNAL"/>
<permission name="android.permission.DELETE_CACHE_FILES"/>
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index 1b25a62..6214ba6 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -142,6 +142,11 @@
isItalic);
}
+ // TODO: Remove once internal user stop using private API.
+ private static boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex) {
+ return nAddFont(builderPtr, font, ttcIndex, -1, -1);
+ }
+
private static native long nInitBuilder(String lang, int variant);
@CriticalNative
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 2afe375..042bac6 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -38,6 +38,7 @@
import android.os.ResultReceiver;
import android.provider.FontsContract;
import android.text.FontConfig;
+import android.util.Base64;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.LruCache;
@@ -196,10 +197,22 @@
if (typeface != null) {
return typeface;
}
+ List<List<String>> givenCerts = providerEntry.getCerts();
+ List<List<byte[]>> certs = new ArrayList<>();
+ if (givenCerts != null) {
+ for (int i = 0; i < givenCerts.size(); i++) {
+ List<String> certSet = givenCerts.get(i);
+ List<byte[]> byteArraySet = new ArrayList<>();
+ for (int j = 0; j < certSet.size(); j++) {
+ byteArraySet.add(Base64.decode(certSet.get(j), Base64.DEFAULT));
+ }
+ certs.add(byteArraySet);
+ }
+ }
// Downloaded font and it wasn't cached, request it again and return a
// default font instead (nothing we can do now).
create(new FontRequest(providerEntry.getAuthority(), providerEntry.getPackage(),
- providerEntry.getQuery()), NO_OP_REQUEST_CALLBACK);
+ providerEntry.getQuery(), certs), NO_OP_REQUEST_CALLBACK);
return DEFAULT;
}
@@ -477,7 +490,6 @@
*
* <p>Note that only one source can be specified for the single Typeface.</p>
*/
- /** @hide TODO: Make this API public. */
public static final class Builder {
/**
* Value for weight and italic.
diff --git a/graphics/java/android/graphics/pdf/PdfRenderer.java b/graphics/java/android/graphics/pdf/PdfRenderer.java
index 99a0422..7b7a290 100644
--- a/graphics/java/android/graphics/pdf/PdfRenderer.java
+++ b/graphics/java/android/graphics/pdf/PdfRenderer.java
@@ -411,7 +411,18 @@
final int contentBottom = (destClip != null) ? destClip.bottom
: destination.getHeight();
- final long transformPtr = (transform != null) ? transform.native_instance : 0;
+ // If transform is not set, stretch page to whole clipped area
+ if (transform == null) {
+ int clipWidth = contentRight - contentLeft;
+ int clipHeight = contentBottom - contentTop;
+
+ transform = new Matrix();
+ transform.postScale((float)clipWidth / getWidth(),
+ (float)clipHeight / getHeight());
+ transform.postTranslate(contentLeft, contentTop);
+ }
+
+ final long transformPtr = transform.native_instance;
synchronized (sPdfiumLock) {
nativeRenderPage(mNativeDocument, mNativePage, destination, contentLeft,
@@ -463,7 +474,8 @@
private static native int nativeGetPageCount(long documentPtr);
private static native boolean nativeScaleForPrinting(long documentPtr);
private static native void nativeRenderPage(long documentPtr, long pagePtr, Bitmap dest,
- int destLeft, int destTop, int destRight, int destBottom, long matrixPtr, int renderMode);
+ int clipLeft, int clipTop, int clipRight, int clipBottom, long transformPtr,
+ int renderMode);
private static native long nativeOpenPageAndGetSize(long documentPtr, int pageIndex,
Point outSize);
private static native void nativeClosePage(long pagePtr);
diff --git a/packages/MtpDocumentsProvider/tests/AndroidTest.xml b/packages/MtpDocumentsProvider/tests/AndroidTest.xml
new file mode 100644
index 0000000..d0b4c6e
--- /dev/null
+++ b/packages/MtpDocumentsProvider/tests/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<configuration description="Runs Tests for MtpDocumentsProvider with the UI for output.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="MtpDocumentsProviderTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="MtpDocumentsProviderTests" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.mtp" />
+ <option name="runner" value="android.test.InstrumentationTestRunner" />
+ </test>
+</configuration>
diff --git a/packages/PrintSpooler/tests/outofprocess/AndroidTest.xml b/packages/PrintSpooler/tests/outofprocess/AndroidTest.xml
new file mode 100644
index 0000000..7716992
--- /dev/null
+++ b/packages/PrintSpooler/tests/outofprocess/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<configuration description="Runs PrintSpooler Out of Process Test Cases.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="PrintSpoolerOutOfProcessTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="PrintSpoolerOutOfProcessTests" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.printspooler.outofprocess.tests" />
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/packages/SettingsLib/tests/integ/AndroidTest.xml b/packages/SettingsLib/tests/integ/AndroidTest.xml
new file mode 100644
index 0000000..8581acb
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<configuration description="Runs Tests for SettingsLib.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="SettingsLibTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="SettingsLibTests" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.settingslib" />
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 3cce299..6bfab78 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -103,6 +103,7 @@
<uses-permission android:name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"/>
<uses-permission android:name="android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS" />
<uses-permission android:name="android.permission.CHANGE_APP_IDLE_STATE" />
+ <uses-permission android:name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.MOUNT_FORMAT_FILESYSTEMS" />
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
diff --git a/packages/Shell/tests/AndroidTest.xml b/packages/Shell/tests/AndroidTest.xml
new file mode 100644
index 0000000..ed9cb2b
--- /dev/null
+++ b/packages/Shell/tests/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<configuration description="Runs Tests for Shell.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="ShellTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="ShellTests" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.shell.tests" />
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/GlobalActions.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/GlobalActions.java
new file mode 100644
index 0000000..bb21fb3
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/GlobalActions.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 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.plugins;
+
+import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
+import com.android.systemui.plugins.annotations.DependsOn;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+@ProvidesInterface(action = GlobalActions.ACTION, version = GlobalActions.VERSION)
+@DependsOn(target = GlobalActionsManager.class)
+public interface GlobalActions extends Plugin {
+
+ String ACTION = "com.android.systemui.action.PLUGIN_GLOBAL_ACTIONS";
+ int VERSION = 1;
+
+ void showGlobalActions(GlobalActionsManager manager);
+
+ @ProvidesInterface(version = GlobalActionsManager.VERSION)
+ public interface GlobalActionsManager {
+ int VERSION = 1;
+
+ void onGlobalActionsShown();
+ void onGlobalActionsHidden();
+
+ void shutdown();
+ void reboot(boolean safeMode);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
index e3ab05d..9435589 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
@@ -37,7 +37,7 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.telephony.IccCardConstants.State;
import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.policy.EmergencyAffordanceManager;
+import com.android.internal.util.EmergencyAffordanceManager;
/**
* This class implements a smart emergency button that updates itself based
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index be69867..51fa425 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -31,10 +31,12 @@
import android.util.Log;
import com.android.systemui.fragments.FragmentService;
+import com.android.systemui.globalactions.GlobalActionsComponent;
import com.android.systemui.keyboard.KeyboardUI;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.media.RingtonePlayer;
import com.android.systemui.pip.PipUI;
+import com.android.systemui.plugins.GlobalActions;
import com.android.systemui.plugins.OverlayPlugin;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
@@ -84,6 +86,7 @@
VendorServices.class,
GarbageMonitor.Service.class,
LatencyTester.class,
+ GlobalActionsComponent.class,
};
/**
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
new file mode 100644
index 0000000..f07027e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 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.globalactions;
+
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.Dependency;
+import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.SystemUI;
+import com.android.systemui.plugins.GlobalActions;
+import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.CommandQueue.Callbacks;
+import com.android.systemui.statusbar.policy.ExtensionController;
+import com.android.systemui.statusbar.policy.ExtensionController.Extension;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+public class GlobalActionsComponent extends SystemUI implements Callbacks, GlobalActionsManager {
+
+ private Extension<GlobalActions> mExtension;
+ private IStatusBarService mBarService;
+
+ @Override
+ public void start() {
+ mBarService = IStatusBarService.Stub.asInterface(
+ ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+ mExtension = Dependency.get(ExtensionController.class).newExtension(GlobalActions.class)
+ .withPlugin(GlobalActions.class)
+ .withDefault(() -> new GlobalActionsImpl(mContext))
+ .build();
+ SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallbacks(this);
+ }
+
+ @Override
+ public void handleShowGlobalActionsMenu() {
+ mExtension.get().showGlobalActions(this);
+ }
+
+ @Override
+ public void onGlobalActionsShown() {
+ try {
+ mBarService.onGlobalActionsShown();
+ } catch (RemoteException e) {
+ }
+ }
+
+ @Override
+ public void onGlobalActionsHidden() {
+ try {
+ mBarService.onGlobalActionsHidden();
+ } catch (RemoteException e) {
+ }
+ }
+
+ @Override
+ public void shutdown() {
+ try {
+ mBarService.shutdown();
+ } catch (RemoteException e) {
+ }
+ }
+
+ @Override
+ public void reboot(boolean safeMode) {
+ try {
+ mBarService.reboot(safeMode);
+ } catch (RemoteException e) {
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
new file mode 100644
index 0000000..206342e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -0,0 +1,1260 @@
+/*
+ * Copyright (C) 2017 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.globalactions;
+
+import com.android.internal.R;
+import com.android.internal.app.AlertController;
+import com.android.internal.app.AlertController.AlertParams;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.EmergencyAffordanceManager;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
+
+import android.app.ActivityManager;
+import android.app.Dialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.UserInfo;
+import android.database.ContentObserver;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.net.ConnectivityManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.Vibrator;
+import android.provider.Settings;
+import android.service.dreams.DreamService;
+import android.service.dreams.IDreamManager;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper to show the global actions dialog. Each item is an {@link Action} that
+ * may show depending on whether the keyguard is showing, and whether the device
+ * is provisioned.
+ */
+class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
+
+ static public final String SYSTEM_DIALOG_REASON_KEY = "reason";
+ static public final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
+
+ private static final String TAG = "GlobalActionsDialog";
+
+ private static final boolean SHOW_SILENT_TOGGLE = true;
+
+ /* Valid settings for global actions keys.
+ * see config.xml config_globalActionList */
+ private static final String GLOBAL_ACTION_KEY_POWER = "power";
+ private static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane";
+ private static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport";
+ private static final String GLOBAL_ACTION_KEY_SILENT = "silent";
+ private static final String GLOBAL_ACTION_KEY_USERS = "users";
+ private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings";
+ private static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown";
+ private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist";
+ private static final String GLOBAL_ACTION_KEY_ASSIST = "assist";
+ private static final String GLOBAL_ACTION_KEY_RESTART = "restart";
+
+ private final Context mContext;
+ private final GlobalActionsManager mWindowManagerFuncs;
+ private final AudioManager mAudioManager;
+ private final IDreamManager mDreamManager;
+
+ private ArrayList<Action> mItems;
+ private ActionsDialog mDialog;
+
+ private Action mSilentModeAction;
+ private ToggleAction mAirplaneModeOn;
+
+ private MyAdapter mAdapter;
+
+ private boolean mKeyguardShowing = false;
+ private boolean mDeviceProvisioned = false;
+ private ToggleAction.State mAirplaneState = ToggleAction.State.Off;
+ private boolean mIsWaitingForEcmExit = false;
+ private boolean mHasTelephony;
+ private boolean mHasVibrator;
+ private final boolean mShowSilentToggle;
+ private final EmergencyAffordanceManager mEmergencyAffordanceManager;
+
+ /**
+ * @param context everything needs a context :(
+ */
+ public GlobalActionsDialog(Context context, GlobalActionsManager windowManagerFuncs) {
+ mContext = context;
+ mWindowManagerFuncs = windowManagerFuncs;
+ mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ mDreamManager = IDreamManager.Stub.asInterface(
+ ServiceManager.getService(DreamService.DREAM_SERVICE));
+
+ // receive broadcasts
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
+ context.registerReceiver(mBroadcastReceiver, filter);
+
+ ConnectivityManager cm = (ConnectivityManager)
+ context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ mHasTelephony = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+
+ // get notified of phone state changes
+ TelephonyManager telephonyManager =
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
+ mAirplaneModeObserver);
+ Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
+ mHasVibrator = vibrator != null && vibrator.hasVibrator();
+
+ mShowSilentToggle = SHOW_SILENT_TOGGLE && !mContext.getResources().getBoolean(
+ R.bool.config_useFixedVolume);
+
+ mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
+ }
+
+ /**
+ * Show the global actions dialog (creating if necessary)
+ * @param keyguardShowing True if keyguard is showing
+ */
+ public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
+ mKeyguardShowing = keyguardShowing;
+ mDeviceProvisioned = isDeviceProvisioned;
+ if (mDialog != null) {
+ mDialog.dismiss();
+ mDialog = null;
+ // Show delayed, so that the dismiss of the previous dialog completes
+ mHandler.sendEmptyMessage(MESSAGE_SHOW);
+ } else {
+ handleShow();
+ }
+ }
+
+ private void awakenIfNecessary() {
+ if (mDreamManager != null) {
+ try {
+ if (mDreamManager.isDreaming()) {
+ mDreamManager.awaken();
+ }
+ } catch (RemoteException e) {
+ // we tried
+ }
+ }
+ }
+
+ private void handleShow() {
+ awakenIfNecessary();
+ mDialog = createDialog();
+ prepareDialog();
+
+ // If we only have 1 item and it's a simple press action, just do this action.
+ if (mAdapter.getCount() == 1
+ && mAdapter.getItem(0) instanceof SinglePressAction
+ && !(mAdapter.getItem(0) instanceof LongPressAction)) {
+ ((SinglePressAction) mAdapter.getItem(0)).onPress();
+ } else {
+ WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
+ attrs.setTitle("ActionsDialog");
+ mDialog.getWindow().setAttributes(attrs);
+ mDialog.show();
+ mWindowManagerFuncs.onGlobalActionsShown();
+ mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND);
+ }
+ }
+
+ /**
+ * Create the global actions dialog.
+ * @return A new dialog.
+ */
+ private ActionsDialog createDialog() {
+ // Simple toggle style if there's no vibrator, otherwise use a tri-state
+ if (!mHasVibrator) {
+ mSilentModeAction = new SilentModeToggleAction();
+ } else {
+ mSilentModeAction = new SilentModeTriStateAction(mContext, mAudioManager, mHandler);
+ }
+ mAirplaneModeOn = new ToggleAction(
+ R.drawable.ic_lock_airplane_mode,
+ R.drawable.ic_lock_airplane_mode_off,
+ R.string.global_actions_toggle_airplane_mode,
+ R.string.global_actions_airplane_mode_on_status,
+ R.string.global_actions_airplane_mode_off_status) {
+
+ void onToggle(boolean on) {
+ if (mHasTelephony && Boolean.parseBoolean(
+ SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) {
+ mIsWaitingForEcmExit = true;
+ // Launch ECM exit dialog
+ Intent ecmDialogIntent =
+ new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
+ ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(ecmDialogIntent);
+ } else {
+ changeAirplaneModeSystemSetting(on);
+ }
+ }
+
+ @Override
+ protected void changeStateFromPress(boolean buttonOn) {
+ if (!mHasTelephony) return;
+
+ // In ECM mode airplane state cannot be changed
+ if (!(Boolean.parseBoolean(
+ SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)))) {
+ mState = buttonOn ? State.TurningOn : State.TurningOff;
+ mAirplaneState = mState;
+ }
+ }
+
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+ };
+ onAirplaneModeChanged();
+
+ mItems = new ArrayList<Action>();
+ String[] defaultActions = mContext.getResources().getStringArray(
+ R.array.config_globalActionsList);
+
+ ArraySet<String> addedKeys = new ArraySet<String>();
+ for (int i = 0; i < defaultActions.length; i++) {
+ String actionKey = defaultActions[i];
+ if (addedKeys.contains(actionKey)) {
+ // If we already have added this, don't add it again.
+ continue;
+ }
+ if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) {
+ mItems.add(new PowerAction());
+ } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
+ mItems.add(mAirplaneModeOn);
+ } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
+ if (Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) {
+ mItems.add(new BugReportAction());
+ }
+ } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) {
+ if (mShowSilentToggle) {
+ mItems.add(mSilentModeAction);
+ }
+ } else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) {
+ if (SystemProperties.getBoolean("fw.power_user_switcher", false)) {
+ addUsersToMenu(mItems);
+ }
+ } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
+ mItems.add(getSettingsAction());
+ } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
+ mItems.add(getLockdownAction());
+ } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {
+ mItems.add(getVoiceAssistAction());
+ } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) {
+ mItems.add(getAssistAction());
+ } else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) {
+ mItems.add(new RestartAction());
+ } else {
+ Log.e(TAG, "Invalid global action key " + actionKey);
+ }
+ // Add here so we don't add more than one.
+ addedKeys.add(actionKey);
+ }
+
+ if (mEmergencyAffordanceManager.needsEmergencyAffordance()) {
+ mItems.add(getEmergencyAction());
+ }
+
+ mAdapter = new MyAdapter();
+
+ AlertParams params = new AlertParams(mContext);
+ params.mAdapter = mAdapter;
+ params.mOnClickListener = this;
+ params.mForceInverseBackground = true;
+
+ ActionsDialog dialog = new ActionsDialog(mContext, params);
+ dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
+
+ dialog.getListView().setItemsCanFocus(true);
+ dialog.getListView().setLongClickable(true);
+ dialog.getListView().setOnItemLongClickListener(
+ new AdapterView.OnItemLongClickListener() {
+ @Override
+ public boolean onItemLongClick(AdapterView<?> parent, View view, int position,
+ long id) {
+ final Action action = mAdapter.getItem(position);
+ if (action instanceof LongPressAction) {
+ return ((LongPressAction) action).onLongPress();
+ }
+ return false;
+ }
+ });
+ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+
+ dialog.setOnDismissListener(this);
+
+ return dialog;
+ }
+
+ private final class PowerAction extends SinglePressAction implements LongPressAction {
+ private PowerAction() {
+ super(R.drawable.ic_lock_power_off,
+ R.string.global_action_power_off);
+ }
+
+ @Override
+ public boolean onLongPress() {
+ UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
+ mWindowManagerFuncs.reboot(true);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return true;
+ }
+
+ @Override
+ public void onPress() {
+ // shutdown by making sure radio and power are handled accordingly.
+ mWindowManagerFuncs.shutdown();
+ }
+ }
+
+ private final class RestartAction extends SinglePressAction implements LongPressAction {
+ private RestartAction() {
+ super(R.drawable.ic_restart, R.string.global_action_restart);
+ }
+
+ @Override
+ public boolean onLongPress() {
+ UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
+ mWindowManagerFuncs.reboot(true);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return true;
+ }
+
+ @Override
+ public void onPress() {
+ mWindowManagerFuncs.reboot(false);
+ }
+ }
+
+
+ private class BugReportAction extends SinglePressAction implements LongPressAction {
+
+ public BugReportAction() {
+ super(R.drawable.ic_lock_bugreport, R.string.bugreport_title);
+ }
+
+ @Override
+ public void onPress() {
+ // don't actually trigger the bugreport if we are running stability
+ // tests via monkey
+ if (ActivityManager.isUserAMonkey()) {
+ return;
+ }
+ // Add a little delay before executing, to give the
+ // dialog a chance to go away before it takes a
+ // screenshot.
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ // Take an "interactive" bugreport.
+ MetricsLogger.action(mContext,
+ MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE);
+ ActivityManager.getService().requestBugReport(
+ ActivityManager.BUGREPORT_OPTION_INTERACTIVE);
+ } catch (RemoteException e) {
+ }
+ }
+ }, 500);
+ }
+
+ @Override
+ public boolean onLongPress() {
+ // don't actually trigger the bugreport if we are running stability
+ // tests via monkey
+ if (ActivityManager.isUserAMonkey()) {
+ return false;
+ }
+ try {
+ // Take a "full" bugreport.
+ MetricsLogger.action(mContext, MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
+ ActivityManager.getService().requestBugReport(
+ ActivityManager.BUGREPORT_OPTION_FULL);
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+
+ @Override
+ public String getStatus() {
+ return mContext.getString(
+ R.string.bugreport_status,
+ Build.VERSION.RELEASE,
+ Build.ID);
+ }
+ }
+
+ private Action getSettingsAction() {
+ return new SinglePressAction(R.drawable.ic_settings,
+ R.string.global_action_settings) {
+
+ @Override
+ public void onPress() {
+ Intent intent = new Intent(Settings.ACTION_SETTINGS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mContext.startActivity(intent);
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return true;
+ }
+ };
+ }
+
+ private Action getEmergencyAction() {
+ return new SinglePressAction(R.drawable.emergency_icon,
+ R.string.global_action_emergency) {
+ @Override
+ public void onPress() {
+ mEmergencyAffordanceManager.performEmergencyCall();
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return true;
+ }
+ };
+ }
+
+ private Action getAssistAction() {
+ return new SinglePressAction(R.drawable.ic_action_assist_focused,
+ R.string.global_action_assist) {
+ @Override
+ public void onPress() {
+ Intent intent = new Intent(Intent.ACTION_ASSIST);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mContext.startActivity(intent);
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return true;
+ }
+ };
+ }
+
+ private Action getVoiceAssistAction() {
+ return new SinglePressAction(R.drawable.ic_voice_search,
+ R.string.global_action_voice_assist) {
+ @Override
+ public void onPress() {
+ Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mContext.startActivity(intent);
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return true;
+ }
+ };
+ }
+
+ private Action getLockdownAction() {
+ return new SinglePressAction(R.drawable.ic_lock_lock,
+ R.string.global_action_lockdown) {
+
+ @Override
+ public void onPress() {
+ new LockPatternUtils(mContext).requireCredentialEntry(UserHandle.USER_ALL);
+ try {
+ WindowManagerGlobal.getWindowManagerService().lockNow(null);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error while trying to lock device.", e);
+ }
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+ };
+ }
+
+ private UserInfo getCurrentUser() {
+ try {
+ return ActivityManager.getService().getCurrentUser();
+ } catch (RemoteException re) {
+ return null;
+ }
+ }
+
+ private boolean isCurrentUserOwner() {
+ UserInfo currentUser = getCurrentUser();
+ return currentUser == null || currentUser.isPrimary();
+ }
+
+ private void addUsersToMenu(ArrayList<Action> items) {
+ UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ if (um.isUserSwitcherEnabled()) {
+ List<UserInfo> users = um.getUsers();
+ UserInfo currentUser = getCurrentUser();
+ for (final UserInfo user : users) {
+ if (user.supportsSwitchToByUser()) {
+ boolean isCurrentUser = currentUser == null
+ ? user.id == 0 : (currentUser.id == user.id);
+ Drawable icon = user.iconPath != null ? Drawable.createFromPath(user.iconPath)
+ : null;
+ SinglePressAction switchToUser = new SinglePressAction(
+ R.drawable.ic_menu_cc, icon,
+ (user.name != null ? user.name : "Primary")
+ + (isCurrentUser ? " \u2714" : "")) {
+ public void onPress() {
+ try {
+ ActivityManager.getService().switchUser(user.id);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Couldn't switch user " + re);
+ }
+ }
+
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+ };
+ items.add(switchToUser);
+ }
+ }
+ }
+ }
+
+ private void prepareDialog() {
+ refreshSilentMode();
+ mAirplaneModeOn.updateState(mAirplaneState);
+ mAdapter.notifyDataSetChanged();
+ mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ if (mShowSilentToggle) {
+ IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
+ mContext.registerReceiver(mRingerModeReceiver, filter);
+ }
+ }
+
+ private void refreshSilentMode() {
+ if (!mHasVibrator) {
+ final boolean silentModeOn =
+ mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
+ ((ToggleAction)mSilentModeAction).updateState(
+ silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void onDismiss(DialogInterface dialog) {
+ mWindowManagerFuncs.onGlobalActionsHidden();
+ if (mShowSilentToggle) {
+ try {
+ mContext.unregisterReceiver(mRingerModeReceiver);
+ } catch (IllegalArgumentException ie) {
+ // ignore this
+ Log.w(TAG, ie);
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void onClick(DialogInterface dialog, int which) {
+ if (!(mAdapter.getItem(which) instanceof SilentModeTriStateAction)) {
+ dialog.dismiss();
+ }
+ mAdapter.getItem(which).onPress();
+ }
+
+ /**
+ * The adapter used for the list within the global actions dialog, taking
+ * into account whether the keyguard is showing via
+ * {@link com.android.systemui.globalactions.GlobalActionsDialog#mKeyguardShowing} and whether the device is provisioned
+ * via {@link com.android.systemui.globalactions.GlobalActionsDialog#mDeviceProvisioned}.
+ */
+ private class MyAdapter extends BaseAdapter {
+
+ public int getCount() {
+ int count = 0;
+
+ for (int i = 0; i < mItems.size(); i++) {
+ final Action action = mItems.get(i);
+
+ if (mKeyguardShowing && !action.showDuringKeyguard()) {
+ continue;
+ }
+ if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
+ continue;
+ }
+ count++;
+ }
+ return count;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return getItem(position).isEnabled();
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return false;
+ }
+
+ public Action getItem(int position) {
+
+ int filteredPos = 0;
+ for (int i = 0; i < mItems.size(); i++) {
+ final Action action = mItems.get(i);
+ if (mKeyguardShowing && !action.showDuringKeyguard()) {
+ continue;
+ }
+ if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
+ continue;
+ }
+ if (filteredPos == position) {
+ return action;
+ }
+ filteredPos++;
+ }
+
+ throw new IllegalArgumentException("position " + position
+ + " out of range of showable actions"
+ + ", filtered count=" + getCount()
+ + ", keyguardshowing=" + mKeyguardShowing
+ + ", provisioned=" + mDeviceProvisioned);
+ }
+
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ Action action = getItem(position);
+ return action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
+ }
+ }
+
+ // note: the scheme below made more sense when we were planning on having
+ // 8 different things in the global actions dialog. seems overkill with
+ // only 3 items now, but may as well keep this flexible approach so it will
+ // be easy should someone decide at the last minute to include something
+ // else, such as 'enable wifi', or 'enable bluetooth'
+
+ /**
+ * What each item in the global actions dialog must be able to support.
+ */
+ private interface Action {
+ /**
+ * @return Text that will be announced when dialog is created. null
+ * for none.
+ */
+ CharSequence getLabelForAccessibility(Context context);
+
+ View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater);
+
+ void onPress();
+
+ /**
+ * @return whether this action should appear in the dialog when the keygaurd
+ * is showing.
+ */
+ boolean showDuringKeyguard();
+
+ /**
+ * @return whether this action should appear in the dialog before the
+ * device is provisioned.
+ */
+ boolean showBeforeProvisioning();
+
+ boolean isEnabled();
+ }
+
+ /**
+ * An action that also supports long press.
+ */
+ private interface LongPressAction extends Action {
+ boolean onLongPress();
+ }
+
+ /**
+ * A single press action maintains no state, just responds to a press
+ * and takes an action.
+ */
+ private static abstract class SinglePressAction implements Action {
+ private final int mIconResId;
+ private final Drawable mIcon;
+ private final int mMessageResId;
+ private final CharSequence mMessage;
+
+ protected SinglePressAction(int iconResId, int messageResId) {
+ mIconResId = iconResId;
+ mMessageResId = messageResId;
+ mMessage = null;
+ mIcon = null;
+ }
+
+ protected SinglePressAction(int iconResId, Drawable icon, CharSequence message) {
+ mIconResId = iconResId;
+ mMessageResId = 0;
+ mMessage = message;
+ mIcon = icon;
+ }
+
+ public boolean isEnabled() {
+ return true;
+ }
+
+ public String getStatus() {
+ return null;
+ }
+
+ abstract public void onPress();
+
+ public CharSequence getLabelForAccessibility(Context context) {
+ if (mMessage != null) {
+ return mMessage;
+ } else {
+ return context.getString(mMessageResId);
+ }
+ }
+
+ public View create(
+ Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
+ View v = inflater.inflate(R.layout.global_actions_item, parent, false);
+
+ ImageView icon = (ImageView) v.findViewById(R.id.icon);
+ TextView messageView = (TextView) v.findViewById(R.id.message);
+
+ TextView statusView = (TextView) v.findViewById(R.id.status);
+ final String status = getStatus();
+ if (!TextUtils.isEmpty(status)) {
+ statusView.setText(status);
+ } else {
+ statusView.setVisibility(View.GONE);
+ }
+ if (mIcon != null) {
+ icon.setImageDrawable(mIcon);
+ icon.setScaleType(ScaleType.CENTER_CROP);
+ } else if (mIconResId != 0) {
+ icon.setImageDrawable(context.getDrawable(mIconResId));
+ }
+ if (mMessage != null) {
+ messageView.setText(mMessage);
+ } else {
+ messageView.setText(mMessageResId);
+ }
+
+ return v;
+ }
+ }
+
+ /**
+ * A toggle action knows whether it is on or off, and displays an icon
+ * and status message accordingly.
+ */
+ private static abstract class ToggleAction implements Action {
+
+ enum State {
+ Off(false),
+ TurningOn(true),
+ TurningOff(true),
+ On(false);
+
+ private final boolean inTransition;
+
+ State(boolean intermediate) {
+ inTransition = intermediate;
+ }
+
+ public boolean inTransition() {
+ return inTransition;
+ }
+ }
+
+ protected State mState = State.Off;
+
+ // prefs
+ protected int mEnabledIconResId;
+ protected int mDisabledIconResid;
+ protected int mMessageResId;
+ protected int mEnabledStatusMessageResId;
+ protected int mDisabledStatusMessageResId;
+
+ /**
+ * @param enabledIconResId The icon for when this action is on.
+ * @param disabledIconResid The icon for when this action is off.
+ * @param message The general information message, e.g 'Silent Mode'
+ * @param enabledStatusMessageResId The on status message, e.g 'sound disabled'
+ * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled'
+ */
+ public ToggleAction(int enabledIconResId,
+ int disabledIconResid,
+ int message,
+ int enabledStatusMessageResId,
+ int disabledStatusMessageResId) {
+ mEnabledIconResId = enabledIconResId;
+ mDisabledIconResid = disabledIconResid;
+ mMessageResId = message;
+ mEnabledStatusMessageResId = enabledStatusMessageResId;
+ mDisabledStatusMessageResId = disabledStatusMessageResId;
+ }
+
+ /**
+ * Override to make changes to resource IDs just before creating the
+ * View.
+ */
+ void willCreate() {
+
+ }
+
+ @Override
+ public CharSequence getLabelForAccessibility(Context context) {
+ return context.getString(mMessageResId);
+ }
+
+ public View create(Context context, View convertView, ViewGroup parent,
+ LayoutInflater inflater) {
+ willCreate();
+
+ View v = inflater.inflate(R
+ .layout.global_actions_item, parent, false);
+
+ ImageView icon = (ImageView) v.findViewById(R.id.icon);
+ TextView messageView = (TextView) v.findViewById(R.id.message);
+ TextView statusView = (TextView) v.findViewById(R.id.status);
+ final boolean enabled = isEnabled();
+
+ if (messageView != null) {
+ messageView.setText(mMessageResId);
+ messageView.setEnabled(enabled);
+ }
+
+ boolean on = ((mState == State.On) || (mState == State.TurningOn));
+ if (icon != null) {
+ icon.setImageDrawable(context.getDrawable(
+ (on ? mEnabledIconResId : mDisabledIconResid)));
+ icon.setEnabled(enabled);
+ }
+
+ if (statusView != null) {
+ statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
+ statusView.setVisibility(View.VISIBLE);
+ statusView.setEnabled(enabled);
+ }
+ v.setEnabled(enabled);
+
+ return v;
+ }
+
+ public final void onPress() {
+ if (mState.inTransition()) {
+ Log.w(TAG, "shouldn't be able to toggle when in transition");
+ return;
+ }
+
+ final boolean nowOn = !(mState == State.On);
+ onToggle(nowOn);
+ changeStateFromPress(nowOn);
+ }
+
+ public boolean isEnabled() {
+ return !mState.inTransition();
+ }
+
+ /**
+ * Implementations may override this if their state can be in on of the intermediate
+ * states until some notification is received (e.g airplane mode is 'turning off' until
+ * we know the wireless connections are back online
+ * @param buttonOn Whether the button was turned on or off
+ */
+ protected void changeStateFromPress(boolean buttonOn) {
+ mState = buttonOn ? State.On : State.Off;
+ }
+
+ abstract void onToggle(boolean on);
+
+ public void updateState(State state) {
+ mState = state;
+ }
+ }
+
+ private class SilentModeToggleAction extends ToggleAction {
+ public SilentModeToggleAction() {
+ super(R.drawable.ic_audio_vol_mute,
+ R.drawable.ic_audio_vol,
+ R.string.global_action_toggle_silent_mode,
+ R.string.global_action_silent_mode_on_status,
+ R.string.global_action_silent_mode_off_status);
+ }
+
+ void onToggle(boolean on) {
+ if (on) {
+ mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
+ } else {
+ mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+ }
+ }
+
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+ }
+
+ private static class SilentModeTriStateAction implements Action, View.OnClickListener {
+
+ private final int[] ITEM_IDS = { R.id.option1, R.id.option2, R.id.option3 };
+
+ private final AudioManager mAudioManager;
+ private final Handler mHandler;
+ private final Context mContext;
+
+ SilentModeTriStateAction(Context context, AudioManager audioManager, Handler handler) {
+ mAudioManager = audioManager;
+ mHandler = handler;
+ mContext = context;
+ }
+
+ private int ringerModeToIndex(int ringerMode) {
+ // They just happen to coincide
+ return ringerMode;
+ }
+
+ private int indexToRingerMode(int index) {
+ // They just happen to coincide
+ return index;
+ }
+
+ @Override
+ public CharSequence getLabelForAccessibility(Context context) {
+ return null;
+ }
+
+ public View create(Context context, View convertView, ViewGroup parent,
+ LayoutInflater inflater) {
+ View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false);
+
+ int selectedIndex = ringerModeToIndex(mAudioManager.getRingerMode());
+ for (int i = 0; i < 3; i++) {
+ View itemView = v.findViewById(ITEM_IDS[i]);
+ itemView.setSelected(selectedIndex == i);
+ // Set up click handler
+ itemView.setTag(i);
+ itemView.setOnClickListener(this);
+ }
+ return v;
+ }
+
+ public void onPress() {
+ }
+
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+
+ public boolean isEnabled() {
+ return true;
+ }
+
+ void willCreate() {
+ }
+
+ public void onClick(View v) {
+ if (!(v.getTag() instanceof Integer)) return;
+
+ int index = (Integer) v.getTag();
+ mAudioManager.setRingerMode(indexToRingerMode(index));
+ mHandler.sendEmptyMessageDelayed(MESSAGE_DISMISS, DIALOG_DISMISS_DELAY);
+ }
+ }
+
+ private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
+ || Intent.ACTION_SCREEN_OFF.equals(action)) {
+ String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
+ if (!SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) {
+ mHandler.sendEmptyMessage(MESSAGE_DISMISS);
+ }
+ } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) {
+ // Airplane mode can be changed after ECM exits if airplane toggle button
+ // is pressed during ECM mode
+ if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) &&
+ mIsWaitingForEcmExit) {
+ mIsWaitingForEcmExit = false;
+ changeAirplaneModeSystemSetting(true);
+ }
+ }
+ }
+ };
+
+ PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+ @Override
+ public void onServiceStateChanged(ServiceState serviceState) {
+ if (!mHasTelephony) return;
+ final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF;
+ mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off;
+ mAirplaneModeOn.updateState(mAirplaneState);
+ mAdapter.notifyDataSetChanged();
+ }
+ };
+
+ private BroadcastReceiver mRingerModeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
+ mHandler.sendEmptyMessage(MESSAGE_REFRESH);
+ }
+ }
+ };
+
+ private ContentObserver mAirplaneModeObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ onAirplaneModeChanged();
+ }
+ };
+
+ private static final int MESSAGE_DISMISS = 0;
+ private static final int MESSAGE_REFRESH = 1;
+ private static final int MESSAGE_SHOW = 2;
+ private static final int DIALOG_DISMISS_DELAY = 300; // ms
+
+ private Handler mHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_DISMISS:
+ if (mDialog != null) {
+ mDialog.dismiss();
+ mDialog = null;
+ }
+ break;
+ case MESSAGE_REFRESH:
+ refreshSilentMode();
+ mAdapter.notifyDataSetChanged();
+ break;
+ case MESSAGE_SHOW:
+ handleShow();
+ break;
+ }
+ }
+ };
+
+ private void onAirplaneModeChanged() {
+ // Let the service state callbacks handle the state.
+ if (mHasTelephony) return;
+
+ boolean airplaneModeOn = Settings.Global.getInt(
+ mContext.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON,
+ 0) == 1;
+ mAirplaneState = airplaneModeOn ? ToggleAction.State.On : ToggleAction.State.Off;
+ mAirplaneModeOn.updateState(mAirplaneState);
+ }
+
+ /**
+ * Change the airplane mode system setting
+ */
+ private void changeAirplaneModeSystemSetting(boolean on) {
+ Settings.Global.putInt(
+ mContext.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON,
+ on ? 1 : 0);
+ Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.putExtra("state", on);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ if (!mHasTelephony) {
+ mAirplaneState = on ? ToggleAction.State.On : ToggleAction.State.Off;
+ }
+ }
+
+ private static final class ActionsDialog extends Dialog implements DialogInterface {
+ private final Context mContext;
+ private final AlertController mAlert;
+ private final MyAdapter mAdapter;
+
+ public ActionsDialog(Context context, AlertParams params) {
+ super(context, getDialogTheme(context));
+ mContext = getContext();
+ mAlert = AlertController.create(mContext, this, getWindow());
+ mAdapter = (MyAdapter) params.mAdapter;
+ params.apply(mAlert);
+ }
+
+ private static int getDialogTheme(Context context) {
+ TypedValue outValue = new TypedValue();
+ context.getTheme().resolveAttribute(R.attr.alertDialogTheme,
+ outValue, true);
+ return outValue.resourceId;
+ }
+
+ @Override
+ protected void onStart() {
+ super.setCanceledOnTouchOutside(true);
+ super.onStart();
+ }
+
+ public ListView getListView() {
+ return mAlert.getListView();
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mAlert.installContent();
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+ for (int i = 0; i < mAdapter.getCount(); ++i) {
+ CharSequence label =
+ mAdapter.getItem(i).getLabelForAccessibility(getContext());
+ if (label != null) {
+ event.getText().add(label);
+ }
+ }
+ }
+ return super.dispatchPopulateAccessibilityEvent(event);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (mAlert.onKeyDown(keyCode, event)) {
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (mAlert.onKeyUp(keyCode, event)) {
+ return true;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
new file mode 100644
index 0000000..c1e51b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 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.globalactions;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.plugins.GlobalActions;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+
+import android.content.Context;
+import android.support.v7.view.ContextThemeWrapper;
+
+public class GlobalActionsImpl implements GlobalActions {
+
+ private final Context mContext;
+ private final KeyguardMonitor mKeyguardMonitor;
+ private final DeviceProvisionedController mDeviceProvisionedController;
+ private GlobalActionsDialog mGlobalActions;
+
+ public GlobalActionsImpl(Context context) {
+ mContext = context;
+ mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
+ mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
+ }
+
+ @Override
+ public void showGlobalActions(GlobalActionsManager manager) {
+ if (mGlobalActions == null) {
+ final ContextThemeWrapper context = new ContextThemeWrapper(mContext,
+ android.R.style.Theme_Material_Light);
+ mGlobalActions = new GlobalActionsDialog(context, manager);
+ }
+ mGlobalActions.showDialog(mKeyguardMonitor.isShowing(),
+ mDeviceProvisionedController.isDeviceProvisioned());
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 09b7bec..73bf454 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -78,6 +78,7 @@
private static final int MSG_APP_TRANSITION_FINISHED = 31 << MSG_SHIFT;
private static final int MSG_DISMISS_KEYBOARD_SHORTCUTS = 32 << MSG_SHIFT;
private static final int MSG_HANDLE_SYSNAV_KEY = 33 << MSG_SHIFT;
+ private static final int MSG_SHOW_GLOBAL_ACTIONS = 34 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -134,6 +135,7 @@
default void clickTile(ComponentName tile) { }
default void handleSystemNavigationKey(int arg1) { }
+ default void handleShowGlobalActionsMenu() { }
}
@VisibleForTesting
@@ -414,6 +416,14 @@
}
}
+ @Override
+ public void showGlobalActionsMenu() {
+ synchronized (mLock) {
+ mHandler.removeMessages(MSG_SHOW_GLOBAL_ACTIONS);
+ mHandler.obtainMessage(MSG_SHOW_GLOBAL_ACTIONS).sendToTarget();
+ }
+ }
+
private final class H extends Handler {
private H(Looper l) {
super(l);
@@ -590,6 +600,11 @@
mCallbacks.get(i).handleSystemNavigationKey(msg.arg1);
}
break;
+ case MSG_SHOW_GLOBAL_ACTIONS:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).handleShowGlobalActionsMenu();
+ }
+ break;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index f53dad5..7162b31 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -28,11 +28,11 @@
import android.os.UserHandle;
import android.util.Log;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewStub;
import android.view.WindowManager;
import android.widget.LinearLayout;
-
import com.android.systemui.BatteryMeterView;
import com.android.systemui.Dependency;
import com.android.systemui.R;
@@ -136,8 +136,14 @@
return;
}
- mNavigationBarView = (CarNavigationBarView) View.inflate(mContext,
- R.layout.car_navigation_bar, null);
+ // SystemUI requires that the navigation bar view have a parent. Since the regular
+ // StatusBar inflates navigation_bar_window as this parent view, use the same view for the
+ // CarNavigationBarView.
+ ViewGroup navigationBarWindow = (ViewGroup) View.inflate(mContext,
+ R.layout.navigation_bar_window, null);
+ View.inflate(mContext, R.layout.car_navigation_bar, navigationBarWindow);
+ mNavigationBarView = (CarNavigationBarView) navigationBarWindow.getChildAt(0);
+
mController = new CarNavigationBarController(mContext, mNavigationBarView,
this /* ActivityStarter*/);
mNavigationBarView.getBarTransitions().setAlwaysOpaque(true);
@@ -153,7 +159,8 @@
PixelFormat.TRANSLUCENT);
lp.setTitle("CarNavigationBar");
lp.windowAnimations = 0;
- mWindowManager.addView(mNavigationBarView, lp);
+
+ mWindowManager.addView(navigationBarWindow, lp);
}
@Override
diff --git a/packages/SystemUI/tests/AndroidTest.xml b/packages/SystemUI/tests/AndroidTest.xml
new file mode 100644
index 0000000..ee78bcd
--- /dev/null
+++ b/packages/SystemUI/tests/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<configuration description="Runs Tests for SystemUI.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="SystemUITests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="SystemUITests" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.systemui.tests" />
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index e0b7b47..d13cf77 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -23,6 +23,7 @@
import static android.view.autofill.AutofillManager.FLAG_VIEW_EXITED;
import static android.view.autofill.AutofillManager.FLAG_START_SESSION;
import static android.view.autofill.AutofillManager.FLAG_VALUE_CHANGED;
+import static android.view.autofill.AutofillManager.FLAG_MANUAL_REQUEST;
import static com.android.server.autofill.Helper.DEBUG;
import static com.android.server.autofill.Helper.VERBOSE;
@@ -1128,6 +1129,13 @@
return;
}
+ if ((mFlags & FLAG_MANUAL_REQUEST) != 0 && response.getDatasets() != null
+ && response.getDatasets().size() == 1) {
+ Slog.d(TAG, "autofilling manual request directly");
+ autoFill(response.getDatasets().get(0));
+ return;
+ }
+
mCurrentViewState.setResponse(mCurrentResponse);
}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 8f2b428..30d06db 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -1295,7 +1295,7 @@
if (DEBUG) Slog.v(TAG, "Starting with transport " + currentTransport);
mTransportManager = new TransportManager(context, transportWhitelist, currentTransport,
- mTransportBoundListener);
+ mTransportBoundListener, mHandlerThread.getLooper());
mTransportManager.registerAllTransports();
// Now that we know about valid backup participants, parse any
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index 619ddb1..67f105e 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -27,9 +27,13 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
@@ -55,10 +59,15 @@
private static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
+ private static final long REBINDING_TIMEOUT_UNPROVISIONED_MS = 30 * 1000; // 30 sec
+ private static final long REBINDING_TIMEOUT_PROVISIONED_MS = 5 * 60 * 1000; // 5 mins
+ private static final int REBINDING_TIMEOUT_MSG = 1;
+
private final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST);
private final Context mContext;
private final PackageManager mPackageManager;
private final Set<ComponentName> mTransportWhitelist;
+ private final Handler mHandler;
/**
* This listener is called after we bind to any transport. If it returns true, this is a valid
@@ -83,12 +92,13 @@
private final Map<String, ComponentName> mBoundTransports = new ArrayMap<>();
TransportManager(Context context, Set<ComponentName> whitelist, String defaultTransport,
- TransportBoundListener listener) {
+ TransportBoundListener listener, Looper looper) {
mContext = context;
mPackageManager = context.getPackageManager();
mTransportWhitelist = (whitelist != null) ? whitelist : new ArraySet<>();
mCurrentTransportName = defaultTransport;
mTransportBoundListener = listener;
+ mHandler = new RebindOnTimeoutHandler(looper);
}
void onPackageAdded(String packageName) {
@@ -242,12 +252,12 @@
intent, 0, UserHandle.USER_SYSTEM);
if (hosts != null) {
for (ResolveInfo host : hosts) {
- final ServiceInfo info = host.serviceInfo;
+ final ComponentName infoComponentName = host.serviceInfo.getComponentName();
boolean shouldBind = false;
if (components != null && packageName != null) {
for (String component : components) {
ComponentName cn = new ComponentName(pkgInfo.packageName, component);
- if (info.getComponentName().equals(cn)) {
+ if (infoComponentName.equals(cn)) {
shouldBind = true;
break;
}
@@ -255,8 +265,8 @@
} else {
shouldBind = true;
}
- if (shouldBind && isTransportTrusted(info.getComponentName())) {
- tryBindTransport(info);
+ if (shouldBind && isTransportTrusted(infoComponentName)) {
+ tryBindTransport(infoComponentName);
}
}
}
@@ -283,8 +293,7 @@
return true;
}
- private void tryBindTransport(ServiceInfo transport) {
- final ComponentName transportComponentName = transport.getComponentName();
+ private void tryBindTransport(ComponentName transportComponentName) {
Slog.d(TAG, "Binding to transport: " + transportComponentName.flattenToShortString());
// TODO: b/22388012 (Multi user backup and restore)
TransportConnection connection = new TransportConnection(transportComponentName);
@@ -335,17 +344,22 @@
success = false;
Slog.e(TAG, "Couldn't get transport name.", e);
} finally {
+ // we need to intern() the String of the component, so that we can use it with
+ // Handler's removeMessages(), which uses == operator to compare the tokens
+ String componentShortString = component.flattenToShortString().intern();
if (success) {
- Slog.d(TAG, "Bound to transport: " + component.flattenToShortString());
+ Slog.d(TAG, "Bound to transport: " + componentShortString);
mBoundTransports.put(mTransportName, component);
for (SelectBackupTransportCallback listener : mListeners) {
listener.onSuccess(mTransportName);
}
+ // cancel rebinding on timeout for this component as we've already connected
+ mHandler.removeMessages(REBINDING_TIMEOUT_MSG, componentShortString);
} else {
- Slog.w(TAG, "Bound to transport " + component.flattenToShortString() +
+ Slog.w(TAG, "Bound to transport " + componentShortString +
" but it is invalid");
EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE,
- component.flattenToShortString(), 0);
+ componentShortString, 0);
mContext.unbindService(this);
mValidTransports.remove(component);
mBinder = null;
@@ -364,9 +378,27 @@
mBinder = null;
mBoundTransports.remove(mTransportName);
}
- EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE,
- component.flattenToShortString(), 0);
- Slog.w(TAG, "Disconnected from transport " + component.flattenToShortString());
+ String componentShortString = component.flattenToShortString();
+ EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, componentShortString, 0);
+ Slog.w(TAG, "Disconnected from transport " + componentShortString);
+ scheduleRebindTimeout(component);
+ }
+
+ /**
+ * We'll attempt to explicitly rebind to a transport if it hasn't happened automatically
+ * for a few minutes after the binding went away.
+ */
+ private void scheduleRebindTimeout(ComponentName component) {
+ // we need to intern() the String of the component, so that we can use it with Handler's
+ // removeMessages(), which uses == operator to compare the tokens
+ final String componentShortString = component.flattenToShortString().intern();
+ final long rebindTimeout = getRebindTimeout();
+ mHandler.removeMessages(REBINDING_TIMEOUT_MSG, componentShortString);
+ Message msg = mHandler.obtainMessage(REBINDING_TIMEOUT_MSG);
+ msg.obj = componentShortString;
+ mHandler.sendMessageDelayed(msg, rebindTimeout);
+ Slog.d(TAG, "Scheduled explicit rebinding for " + componentShortString + " in "
+ + rebindTimeout + "ms");
}
private IBackupTransport getBinder() {
@@ -403,6 +435,14 @@
}
}
}
+
+ private long getRebindTimeout() {
+ final boolean isDeviceProvisioned = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+ return isDeviceProvisioned
+ ? REBINDING_TIMEOUT_PROVISIONED_MS
+ : REBINDING_TIMEOUT_UNPROVISIONED_MS;
+ }
}
interface TransportBoundListener {
@@ -410,6 +450,43 @@
boolean onTransportBound(IBackupTransport binder);
}
+ private class RebindOnTimeoutHandler extends Handler {
+
+ RebindOnTimeoutHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == REBINDING_TIMEOUT_MSG) {
+ String componentShortString = (String) msg.obj;
+ ComponentName transportComponent =
+ ComponentName.unflattenFromString(componentShortString);
+ synchronized (mTransportLock) {
+ if (mBoundTransports.containsValue(transportComponent)) {
+ Slog.d(TAG, "Explicit rebinding timeout passed, but already bound to "
+ + componentShortString + " so not attempting to rebind");
+ return;
+ }
+ Slog.d(TAG, "Explicit rebinding timeout passed, attempting rebinding to: "
+ + componentShortString);
+ // unbind the existing (broken) connection
+ TransportConnection conn = mValidTransports.get(transportComponent);
+ if (conn != null) {
+ mContext.unbindService(conn);
+ Slog.d(TAG, "Unbinding the existing (broken) connection to transport: "
+ + componentShortString);
+ }
+ }
+ // rebind to transport
+ tryBindTransport(transportComponent);
+ } else {
+ Slog.e(TAG, "Unknown message sent to RebindOnTimeoutHandler, msg.what: "
+ + msg.what);
+ }
+ }
+ }
+
private static void log_verbose(String message) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Slog.v(TAG, message);
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 3e2dae5..26b15d8 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -2865,8 +2865,9 @@
if (arg != null) {
try {
addPowerSaveTempWhitelistAppChecked(arg, duration, shell.userId, "shell");
- } catch (RemoteException re) {
- pw.println("Failed: " + re);
+ } catch (Exception e) {
+ pw.println("Failed: " + e);
+ return -1;
}
} else {
dumpTempWhitelistSchedule(pw, false);
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index 2067620..c0d1107 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -957,7 +957,9 @@
// Unlock managed profile with unified lock
if (pi.isManagedProfile()
&& !mLockPatternUtils.isSeparateProfileChallengeEnabled(pi.id)
- && mStorage.hasChildProfileLock(pi.id)) {
+ && mStorage.hasChildProfileLock(pi.id)
+ && mUserManager.isUserRunning(pi.id)
+ && !mUserManager.isUserUnlocked(pi.id)) {
unlockChildProfile(pi.id);
}
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 1a7f016..490e63d 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -4891,7 +4891,6 @@
private void installNotification(int notificationId, final Notification notification,
String packageName, int userId) {
- SystemNotificationChannels.createAccountChannelForPackage(packageName, mContext);
final long token = clearCallingIdentity();
try {
INotificationManager notificationManager = mInjector.getNotificationManager();
@@ -5678,6 +5677,7 @@
synchronized (mUsers) {
userAccounts = mUsers.get(userId);
}
+ SystemNotificationChannels.createAccountChannelForPackage(packageName, mContext);
doNotification(userAccounts, account, null, intent, packageName, userId);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 05a59d3..2be5e77 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -128,7 +128,6 @@
import static com.android.server.am.ActivityStackSupervisor.ActivityContainer.FORCE_NEW_TASK_FLAGS;
import static com.android.server.am.ActivityStackSupervisor.CREATE_IF_NEEDED;
import static com.android.server.am.ActivityStackSupervisor.DEFER_RESUME;
-import static com.android.server.am.ActivityStackSupervisor.FORCE_FOCUS;
import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_ONLY;
import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS;
import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
@@ -149,8 +148,6 @@
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
-import static java.lang.Integer.MAX_VALUE;
-
import android.Manifest;
import android.Manifest.permission;
import android.annotation.NonNull;
@@ -10851,6 +10848,25 @@
}
}
+ @Override
+ public void setDisablePreviewScreenshots(IBinder token, boolean disable)
+ throws RemoteException {
+ synchronized (this) {
+ final ActivityRecord r = ActivityRecord.isInStackLocked(token);
+ if (r == null) {
+ Slog.w(TAG, "setDisablePreviewScreenshots: Unable to find activity for token="
+ + token);
+ return;
+ }
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ r.setDisablePreviewScreenshots(disable);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+ }
+
// =========================================================
// CONTENT PROVIDERS
// =========================================================
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index b9bb106..6cea483 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -1973,6 +1973,10 @@
task.taskId, requestedOrientation);
}
+ void setDisablePreviewScreenshots(boolean disable) {
+ mWindowContainerController.setDisablePreviewScreenshots(disable);
+ }
+
/**
* Set the last reported global configuration to the client. Should be called whenever a new
* global configuration is sent to the client for this activity.
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index fa73b6b..7e10a09 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3109,7 +3109,8 @@
+ " - notification=" + notification);
return;
}
- throw new IllegalArgumentException("No Channel found for notification=" + notification);
+ throw new IllegalArgumentException("No Channel found for channelId=" + channelId
+ + ", notification=" + notification);
}
final StatusBarNotification n = new StatusBarNotification(
pkg, opPkg, id, tag, notificationUid, callingPid, notification,
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index ce79465..73afaa0 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -201,7 +201,7 @@
// Channels
if (TAG_CHANNEL.equals(tagName)) {
String id = parser.getAttributeValue(null, ATT_ID);
- CharSequence channelName = parser.getAttributeValue(null, ATT_NAME);
+ String channelName = parser.getAttributeValue(null, ATT_NAME);
int channelImportance =
safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
@@ -546,7 +546,8 @@
existing.setDeleted(false);
}
- existing.setName(channel.getName());
+ existing.setName(channel.getName().toString());
+ existing.setDescription(channel.getDescription());
MetricsLogger.action(getChannelLog(channel, pkg));
updateConfig();
@@ -683,11 +684,11 @@
NotificationChannel channel = r.channels.get(channelId);
if (channel != null) {
channel.setDeleted(true);
+ LogMaker lm = getChannelLog(channel, pkg);
+ lm.setType(MetricsProto.MetricsEvent.TYPE_CLOSE);
+ MetricsLogger.action(lm);
+ updateConfig();
}
- LogMaker lm = getChannelLog(channel, pkg);
- lm.setType(MetricsProto.MetricsEvent.TYPE_CLOSE);
- MetricsLogger.action(lm);
- updateConfig();
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 16c8a15..96e2626 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2879,24 +2879,17 @@
}
mInstallerService = new PackageInstallerService(context, this);
-
final ComponentName ephemeralResolverComponent = getEphemeralResolverLPr();
if (ephemeralResolverComponent != null) {
if (DEBUG_EPHEMERAL) {
- Slog.i(TAG, "Ephemeral resolver: " + ephemeralResolverComponent);
+ Slog.d(TAG, "Set ephemeral resolver: " + ephemeralResolverComponent);
}
mInstantAppResolverConnection =
new EphemeralResolverConnection(mContext, ephemeralResolverComponent);
} else {
mInstantAppResolverConnection = null;
}
- mInstantAppInstallerComponent = getEphemeralInstallerLPr();
- if (mInstantAppInstallerComponent != null) {
- if (DEBUG_EPHEMERAL) {
- Slog.i(TAG, "Ephemeral installer: " + mInstantAppInstallerComponent);
- }
- setUpInstantAppInstallerActivityLP(mInstantAppInstallerComponent);
- }
+ updateInstantAppInstallerLocked();
// Read and update the usage of dex files.
// Do this at the end of PM init so that all the packages have their
@@ -2936,6 +2929,21 @@
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
+ private void updateInstantAppInstallerLocked() {
+ final ComponentName oldInstantAppInstallerComponent = mInstantAppInstallerComponent;
+ final ComponentName newInstantAppInstallerComponent = getEphemeralInstallerLPr();
+ if (newInstantAppInstallerComponent != null
+ && !newInstantAppInstallerComponent.equals(oldInstantAppInstallerComponent)) {
+ if (DEBUG_EPHEMERAL) {
+ Slog.d(TAG, "Set ephemeral installer: " + newInstantAppInstallerComponent);
+ }
+ setUpInstantAppInstallerActivityLP(newInstantAppInstallerComponent);
+ } else if (DEBUG_EPHEMERAL && newInstantAppInstallerComponent == null) {
+ Slog.d(TAG, "Unset ephemeral installer; none available");
+ }
+ mInstantAppInstallerComponent = newInstantAppInstallerComponent;
+ }
+
private static File preparePackageParserCache(boolean isUpgrade) {
if (!DEFAULT_PACKAGE_PARSER_CACHE_ENABLED) {
return null;
@@ -16918,6 +16926,7 @@
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
updateSequenceNumberLP(pkgName, res.newUsers);
+ updateInstantAppInstallerLocked();
}
}
}
@@ -17493,6 +17502,7 @@
mInstantAppRegistry.onPackageUninstalledLPw(pkg, info.removedUsers);
}
updateSequenceNumberLP(packageName, info.removedUsers);
+ updateInstantAppInstallerLocked();
}
}
}
@@ -19838,6 +19848,7 @@
}
scheduleWritePackageRestrictionsLocked(userId);
updateSequenceNumberLP(packageName, new int[] { userId });
+ updateInstantAppInstallerLocked();
components = mPendingBroadcasts.get(userId, packageName);
final boolean newPackage = components == null;
if (newPackage) {
diff --git a/services/core/java/com/android/server/policy/GlobalActions.java b/services/core/java/com/android/server/policy/GlobalActions.java
index 335a230..17e5e9f 100644
--- a/services/core/java/com/android/server/policy/GlobalActions.java
+++ b/services/core/java/com/android/server/policy/GlobalActions.java
@@ -1,1257 +1,94 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2017 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
+ * 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.
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
*/
package com.android.server.policy;
-import com.android.internal.app.AlertController;
-import com.android.internal.app.AlertController.AlertParams;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.policy.EmergencyAffordanceManager;
-import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.telephony.TelephonyProperties;
-import com.android.internal.R;
-import com.android.internal.widget.LockPatternUtils;
+import com.android.server.LocalServices;
+import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.statusbar.StatusBarManagerInternal.GlobalActionsListener;
-import android.app.ActivityManager;
-import android.app.Dialog;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.UserInfo;
-import android.database.ContentObserver;
-import android.graphics.drawable.Drawable;
-import android.media.AudioManager;
-import android.net.ConnectivityManager;
-import android.os.Build;
-import android.os.Bundle;
import android.os.Handler;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.os.Vibrator;
-import android.provider.Settings;
-import android.service.dreams.DreamService;
-import android.service.dreams.IDreamManager;
-import android.telephony.PhoneStateListener;
-import android.telephony.ServiceState;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
+import android.util.Slog;
import android.view.WindowManagerPolicy.WindowManagerFuncs;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.AdapterView;
-import android.widget.BaseAdapter;
-import android.widget.ImageView;
-import android.widget.ImageView.ScaleType;
-import android.widget.ListView;
-import android.widget.TextView;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Helper to show the global actions dialog. Each item is an {@link Action} that
- * may show depending on whether the keyguard is showing, and whether the device
- * is provisioned.
- */
-class GlobalActions implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
+class GlobalActions implements GlobalActionsListener {
private static final String TAG = "GlobalActions";
-
- private static final boolean SHOW_SILENT_TOGGLE = true;
-
- /* Valid settings for global actions keys.
- * see config.xml config_globalActionList */
- private static final String GLOBAL_ACTION_KEY_POWER = "power";
- private static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane";
- private static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport";
- private static final String GLOBAL_ACTION_KEY_SILENT = "silent";
- private static final String GLOBAL_ACTION_KEY_USERS = "users";
- private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings";
- private static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown";
- private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist";
- private static final String GLOBAL_ACTION_KEY_ASSIST = "assist";
- private static final String GLOBAL_ACTION_KEY_RESTART = "restart";
+ private static final boolean DEBUG = false;
private final Context mContext;
- private final WindowManagerFuncs mWindowManagerFuncs;
- private final AudioManager mAudioManager;
- private final IDreamManager mDreamManager;
+ private final LegacyGlobalActions mLegacyGlobalActions;
+ private final StatusBarManagerInternal mStatusBarInternal;
+ private final Handler mHandler;
+ private boolean mKeyguardShowing;
+ private boolean mDeviceProvisioned;
+ private boolean mStatusBarConnected;
+ private boolean mShowing;
- private ArrayList<Action> mItems;
- private GlobalActionsDialog mDialog;
-
- private Action mSilentModeAction;
- private ToggleAction mAirplaneModeOn;
-
- private MyAdapter mAdapter;
-
- private boolean mKeyguardShowing = false;
- private boolean mDeviceProvisioned = false;
- private ToggleAction.State mAirplaneState = ToggleAction.State.Off;
- private boolean mIsWaitingForEcmExit = false;
- private boolean mHasTelephony;
- private boolean mHasVibrator;
- private final boolean mShowSilentToggle;
- private final EmergencyAffordanceManager mEmergencyAffordanceManager;
-
- /**
- * @param context everything needs a context :(
- */
public GlobalActions(Context context, WindowManagerFuncs windowManagerFuncs) {
mContext = context;
- mWindowManagerFuncs = windowManagerFuncs;
- mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
- mDreamManager = IDreamManager.Stub.asInterface(
- ServiceManager.getService(DreamService.DREAM_SERVICE));
-
- // receive broadcasts
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- filter.addAction(Intent.ACTION_SCREEN_OFF);
- filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
- context.registerReceiver(mBroadcastReceiver, filter);
-
- ConnectivityManager cm = (ConnectivityManager)
- context.getSystemService(Context.CONNECTIVITY_SERVICE);
- mHasTelephony = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
-
- // get notified of phone state changes
- TelephonyManager telephonyManager =
- (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
- telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
- mAirplaneModeObserver);
- Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
- mHasVibrator = vibrator != null && vibrator.hasVibrator();
-
- mShowSilentToggle = SHOW_SILENT_TOGGLE && !mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_useFixedVolume);
-
- mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
+ mHandler = new Handler();
+ mLegacyGlobalActions = new LegacyGlobalActions(context, windowManagerFuncs,
+ this::onGlobalActionsDismissed);
+ mStatusBarInternal = LocalServices.getService(StatusBarManagerInternal.class);
+ mStatusBarInternal.setGlobalActionsListener(this);
}
- /**
- * Show the global actions dialog (creating if necessary)
- * @param keyguardShowing True if keyguard is showing
- */
- public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
+ public void showDialog(boolean keyguardShowing, boolean deviceProvisioned) {
+ if (DEBUG) Slog.d(TAG, "showDialog " + keyguardShowing + " " + deviceProvisioned);
mKeyguardShowing = keyguardShowing;
- mDeviceProvisioned = isDeviceProvisioned;
- if (mDialog != null) {
- mDialog.dismiss();
- mDialog = null;
- // Show delayed, so that the dismiss of the previous dialog completes
- mHandler.sendEmptyMessage(MESSAGE_SHOW);
+ mDeviceProvisioned = deviceProvisioned;
+ mShowing = true;
+ if (mStatusBarConnected) {
+ mStatusBarInternal.showGlobalActions();
+ mHandler.postDelayed(mShowTimeout, 5000);
} else {
- handleShow();
+ // SysUI isn't alive, show legacy menu.
+ mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned);
}
}
- private void awakenIfNecessary() {
- if (mDreamManager != null) {
- try {
- if (mDreamManager.isDreaming()) {
- mDreamManager.awaken();
- }
- } catch (RemoteException e) {
- // we tried
- }
+ @Override
+ public void onGlobalActionsShown() {
+ if (DEBUG) Slog.d(TAG, "onGlobalActionsShown");
+ // SysUI is showing, remove timeout callbacks.
+ mHandler.removeCallbacks(mShowTimeout);
+ }
+
+ @Override
+ public void onGlobalActionsDismissed() {
+ if (DEBUG) Slog.d(TAG, "onGlobalActionsDismissed");
+ mShowing = false;
+ }
+
+ @Override
+ public void onStatusBarConnectedChanged(boolean connected) {
+ if (DEBUG) Slog.d(TAG, "onStatusBarConnectedChanged " + connected);
+ mStatusBarConnected = connected;
+ if (mShowing && !mStatusBarConnected) {
+ // Status bar died but we need to be showing global actions still, show the legacy.
+ mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned);
}
}
- private void handleShow() {
- awakenIfNecessary();
- mDialog = createDialog();
- prepareDialog();
-
- // If we only have 1 item and it's a simple press action, just do this action.
- if (mAdapter.getCount() == 1
- && mAdapter.getItem(0) instanceof SinglePressAction
- && !(mAdapter.getItem(0) instanceof LongPressAction)) {
- ((SinglePressAction) mAdapter.getItem(0)).onPress();
- } else {
- WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
- attrs.setTitle("GlobalActions");
- mDialog.getWindow().setAttributes(attrs);
- mDialog.show();
- mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND);
- }
- }
-
- /**
- * Create the global actions dialog.
- * @return A new dialog.
- */
- private GlobalActionsDialog createDialog() {
- // Simple toggle style if there's no vibrator, otherwise use a tri-state
- if (!mHasVibrator) {
- mSilentModeAction = new SilentModeToggleAction();
- } else {
- mSilentModeAction = new SilentModeTriStateAction(mContext, mAudioManager, mHandler);
- }
- mAirplaneModeOn = new ToggleAction(
- R.drawable.ic_lock_airplane_mode,
- R.drawable.ic_lock_airplane_mode_off,
- R.string.global_actions_toggle_airplane_mode,
- R.string.global_actions_airplane_mode_on_status,
- R.string.global_actions_airplane_mode_off_status) {
-
- void onToggle(boolean on) {
- if (mHasTelephony && Boolean.parseBoolean(
- SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) {
- mIsWaitingForEcmExit = true;
- // Launch ECM exit dialog
- Intent ecmDialogIntent =
- new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
- ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(ecmDialogIntent);
- } else {
- changeAirplaneModeSystemSetting(on);
- }
- }
-
- @Override
- protected void changeStateFromPress(boolean buttonOn) {
- if (!mHasTelephony) return;
-
- // In ECM mode airplane state cannot be changed
- if (!(Boolean.parseBoolean(
- SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)))) {
- mState = buttonOn ? State.TurningOn : State.TurningOff;
- mAirplaneState = mState;
- }
- }
-
- public boolean showDuringKeyguard() {
- return true;
- }
-
- public boolean showBeforeProvisioning() {
- return false;
- }
- };
- onAirplaneModeChanged();
-
- mItems = new ArrayList<Action>();
- String[] defaultActions = mContext.getResources().getStringArray(
- com.android.internal.R.array.config_globalActionsList);
-
- ArraySet<String> addedKeys = new ArraySet<String>();
- for (int i = 0; i < defaultActions.length; i++) {
- String actionKey = defaultActions[i];
- if (addedKeys.contains(actionKey)) {
- // If we already have added this, don't add it again.
- continue;
- }
- if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) {
- mItems.add(new PowerAction());
- } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
- mItems.add(mAirplaneModeOn);
- } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
- if (Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) {
- mItems.add(new BugReportAction());
- }
- } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) {
- if (mShowSilentToggle) {
- mItems.add(mSilentModeAction);
- }
- } else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) {
- if (SystemProperties.getBoolean("fw.power_user_switcher", false)) {
- addUsersToMenu(mItems);
- }
- } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
- mItems.add(getSettingsAction());
- } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
- mItems.add(getLockdownAction());
- } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {
- mItems.add(getVoiceAssistAction());
- } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) {
- mItems.add(getAssistAction());
- } else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) {
- mItems.add(new RestartAction());
- } else {
- Log.e(TAG, "Invalid global action key " + actionKey);
- }
- // Add here so we don't add more than one.
- addedKeys.add(actionKey);
- }
-
- if (mEmergencyAffordanceManager.needsEmergencyAffordance()) {
- mItems.add(getEmergencyAction());
- }
-
- mAdapter = new MyAdapter();
-
- AlertParams params = new AlertParams(mContext);
- params.mAdapter = mAdapter;
- params.mOnClickListener = this;
- params.mForceInverseBackground = true;
-
- GlobalActionsDialog dialog = new GlobalActionsDialog(mContext, params);
- dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
-
- dialog.getListView().setItemsCanFocus(true);
- dialog.getListView().setLongClickable(true);
- dialog.getListView().setOnItemLongClickListener(
- new AdapterView.OnItemLongClickListener() {
- @Override
- public boolean onItemLongClick(AdapterView<?> parent, View view, int position,
- long id) {
- final Action action = mAdapter.getItem(position);
- if (action instanceof LongPressAction) {
- return ((LongPressAction) action).onLongPress();
- }
- return false;
- }
- });
- dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
-
- dialog.setOnDismissListener(this);
-
- return dialog;
- }
-
- private final class PowerAction extends SinglePressAction implements LongPressAction {
- private PowerAction() {
- super(com.android.internal.R.drawable.ic_lock_power_off,
- R.string.global_action_power_off);
- }
-
+ private final Runnable mShowTimeout = new Runnable() {
@Override
- public boolean onLongPress() {
- UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
- mWindowManagerFuncs.rebootSafeMode(true);
- return true;
- }
- return false;
- }
-
- @Override
- public boolean showDuringKeyguard() {
- return true;
- }
-
- @Override
- public boolean showBeforeProvisioning() {
- return true;
- }
-
- @Override
- public void onPress() {
- // shutdown by making sure radio and power are handled accordingly.
- mWindowManagerFuncs.shutdown(false /* confirm */);
- }
- }
-
- private final class RestartAction extends SinglePressAction implements LongPressAction {
- private RestartAction() {
- super(R.drawable.ic_restart, R.string.global_action_restart);
- }
-
- @Override
- public boolean onLongPress() {
- UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
- mWindowManagerFuncs.rebootSafeMode(true);
- return true;
- }
- return false;
- }
-
- @Override
- public boolean showDuringKeyguard() {
- return true;
- }
-
- @Override
- public boolean showBeforeProvisioning() {
- return true;
- }
-
- @Override
- public void onPress() {
- mWindowManagerFuncs.reboot(false /* confirm */);
- }
- }
-
-
- private class BugReportAction extends SinglePressAction implements LongPressAction {
-
- public BugReportAction() {
- super(com.android.internal.R.drawable.ic_lock_bugreport, R.string.bugreport_title);
- }
-
- @Override
- public void onPress() {
- // don't actually trigger the bugreport if we are running stability
- // tests via monkey
- if (ActivityManager.isUserAMonkey()) {
- return;
- }
- // Add a little delay before executing, to give the
- // dialog a chance to go away before it takes a
- // screenshot.
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- try {
- // Take an "interactive" bugreport.
- MetricsLogger.action(mContext,
- MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE);
- ActivityManager.getService().requestBugReport(
- ActivityManager.BUGREPORT_OPTION_INTERACTIVE);
- } catch (RemoteException e) {
- }
- }
- }, 500);
- }
-
- @Override
- public boolean onLongPress() {
- // don't actually trigger the bugreport if we are running stability
- // tests via monkey
- if (ActivityManager.isUserAMonkey()) {
- return false;
- }
- try {
- // Take a "full" bugreport.
- MetricsLogger.action(mContext, MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
- ActivityManager.getService().requestBugReport(
- ActivityManager.BUGREPORT_OPTION_FULL);
- } catch (RemoteException e) {
- }
- return false;
- }
-
- public boolean showDuringKeyguard() {
- return true;
- }
-
- @Override
- public boolean showBeforeProvisioning() {
- return false;
- }
-
- @Override
- public String getStatus() {
- return mContext.getString(
- com.android.internal.R.string.bugreport_status,
- Build.VERSION.RELEASE,
- Build.ID);
- }
- }
-
- private Action getSettingsAction() {
- return new SinglePressAction(com.android.internal.R.drawable.ic_settings,
- R.string.global_action_settings) {
-
- @Override
- public void onPress() {
- Intent intent = new Intent(Settings.ACTION_SETTINGS);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- mContext.startActivity(intent);
- }
-
- @Override
- public boolean showDuringKeyguard() {
- return true;
- }
-
- @Override
- public boolean showBeforeProvisioning() {
- return true;
- }
- };
- }
-
- private Action getEmergencyAction() {
- return new SinglePressAction(com.android.internal.R.drawable.emergency_icon,
- R.string.global_action_emergency) {
- @Override
- public void onPress() {
- mEmergencyAffordanceManager.performEmergencyCall();
- }
-
- @Override
- public boolean showDuringKeyguard() {
- return true;
- }
-
- @Override
- public boolean showBeforeProvisioning() {
- return true;
- }
- };
- }
-
- private Action getAssistAction() {
- return new SinglePressAction(com.android.internal.R.drawable.ic_action_assist_focused,
- R.string.global_action_assist) {
- @Override
- public void onPress() {
- Intent intent = new Intent(Intent.ACTION_ASSIST);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- mContext.startActivity(intent);
- }
-
- @Override
- public boolean showDuringKeyguard() {
- return true;
- }
-
- @Override
- public boolean showBeforeProvisioning() {
- return true;
- }
- };
- }
-
- private Action getVoiceAssistAction() {
- return new SinglePressAction(com.android.internal.R.drawable.ic_voice_search,
- R.string.global_action_voice_assist) {
- @Override
- public void onPress() {
- Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- mContext.startActivity(intent);
- }
-
- @Override
- public boolean showDuringKeyguard() {
- return true;
- }
-
- @Override
- public boolean showBeforeProvisioning() {
- return true;
- }
- };
- }
-
- private Action getLockdownAction() {
- return new SinglePressAction(com.android.internal.R.drawable.ic_lock_lock,
- R.string.global_action_lockdown) {
-
- @Override
- public void onPress() {
- new LockPatternUtils(mContext).requireCredentialEntry(UserHandle.USER_ALL);
- try {
- WindowManagerGlobal.getWindowManagerService().lockNow(null);
- } catch (RemoteException e) {
- Log.e(TAG, "Error while trying to lock device.", e);
- }
- }
-
- @Override
- public boolean showDuringKeyguard() {
- return true;
- }
-
- @Override
- public boolean showBeforeProvisioning() {
- return false;
- }
- };
- }
-
- private UserInfo getCurrentUser() {
- try {
- return ActivityManager.getService().getCurrentUser();
- } catch (RemoteException re) {
- return null;
- }
- }
-
- private boolean isCurrentUserOwner() {
- UserInfo currentUser = getCurrentUser();
- return currentUser == null || currentUser.isPrimary();
- }
-
- private void addUsersToMenu(ArrayList<Action> items) {
- UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- if (um.isUserSwitcherEnabled()) {
- List<UserInfo> users = um.getUsers();
- UserInfo currentUser = getCurrentUser();
- for (final UserInfo user : users) {
- if (user.supportsSwitchToByUser()) {
- boolean isCurrentUser = currentUser == null
- ? user.id == 0 : (currentUser.id == user.id);
- Drawable icon = user.iconPath != null ? Drawable.createFromPath(user.iconPath)
- : null;
- SinglePressAction switchToUser = new SinglePressAction(
- com.android.internal.R.drawable.ic_menu_cc, icon,
- (user.name != null ? user.name : "Primary")
- + (isCurrentUser ? " \u2714" : "")) {
- public void onPress() {
- try {
- ActivityManager.getService().switchUser(user.id);
- } catch (RemoteException re) {
- Log.e(TAG, "Couldn't switch user " + re);
- }
- }
-
- public boolean showDuringKeyguard() {
- return true;
- }
-
- public boolean showBeforeProvisioning() {
- return false;
- }
- };
- items.add(switchToUser);
- }
- }
- }
- }
-
- private void prepareDialog() {
- refreshSilentMode();
- mAirplaneModeOn.updateState(mAirplaneState);
- mAdapter.notifyDataSetChanged();
- mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
- if (mShowSilentToggle) {
- IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
- mContext.registerReceiver(mRingerModeReceiver, filter);
- }
- }
-
- private void refreshSilentMode() {
- if (!mHasVibrator) {
- final boolean silentModeOn =
- mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
- ((ToggleAction)mSilentModeAction).updateState(
- silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off);
- }
- }
-
- /** {@inheritDoc} */
- public void onDismiss(DialogInterface dialog) {
- if (mShowSilentToggle) {
- try {
- mContext.unregisterReceiver(mRingerModeReceiver);
- } catch (IllegalArgumentException ie) {
- // ignore this
- Log.w(TAG, ie);
- }
- }
- }
-
- /** {@inheritDoc} */
- public void onClick(DialogInterface dialog, int which) {
- if (!(mAdapter.getItem(which) instanceof SilentModeTriStateAction)) {
- dialog.dismiss();
- }
- mAdapter.getItem(which).onPress();
- }
-
- /**
- * The adapter used for the list within the global actions dialog, taking
- * into account whether the keyguard is showing via
- * {@link GlobalActions#mKeyguardShowing} and whether the device is provisioned
- * via {@link GlobalActions#mDeviceProvisioned}.
- */
- private class MyAdapter extends BaseAdapter {
-
- public int getCount() {
- int count = 0;
-
- for (int i = 0; i < mItems.size(); i++) {
- final Action action = mItems.get(i);
-
- if (mKeyguardShowing && !action.showDuringKeyguard()) {
- continue;
- }
- if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
- continue;
- }
- count++;
- }
- return count;
- }
-
- @Override
- public boolean isEnabled(int position) {
- return getItem(position).isEnabled();
- }
-
- @Override
- public boolean areAllItemsEnabled() {
- return false;
- }
-
- public Action getItem(int position) {
-
- int filteredPos = 0;
- for (int i = 0; i < mItems.size(); i++) {
- final Action action = mItems.get(i);
- if (mKeyguardShowing && !action.showDuringKeyguard()) {
- continue;
- }
- if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
- continue;
- }
- if (filteredPos == position) {
- return action;
- }
- filteredPos++;
- }
-
- throw new IllegalArgumentException("position " + position
- + " out of range of showable actions"
- + ", filtered count=" + getCount()
- + ", keyguardshowing=" + mKeyguardShowing
- + ", provisioned=" + mDeviceProvisioned);
- }
-
-
- public long getItemId(int position) {
- return position;
- }
-
- public View getView(int position, View convertView, ViewGroup parent) {
- Action action = getItem(position);
- return action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
- }
- }
-
- // note: the scheme below made more sense when we were planning on having
- // 8 different things in the global actions dialog. seems overkill with
- // only 3 items now, but may as well keep this flexible approach so it will
- // be easy should someone decide at the last minute to include something
- // else, such as 'enable wifi', or 'enable bluetooth'
-
- /**
- * What each item in the global actions dialog must be able to support.
- */
- private interface Action {
- /**
- * @return Text that will be announced when dialog is created. null
- * for none.
- */
- CharSequence getLabelForAccessibility(Context context);
-
- View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater);
-
- void onPress();
-
- /**
- * @return whether this action should appear in the dialog when the keygaurd
- * is showing.
- */
- boolean showDuringKeyguard();
-
- /**
- * @return whether this action should appear in the dialog before the
- * device is provisioned.
- */
- boolean showBeforeProvisioning();
-
- boolean isEnabled();
- }
-
- /**
- * An action that also supports long press.
- */
- private interface LongPressAction extends Action {
- boolean onLongPress();
- }
-
- /**
- * A single press action maintains no state, just responds to a press
- * and takes an action.
- */
- private static abstract class SinglePressAction implements Action {
- private final int mIconResId;
- private final Drawable mIcon;
- private final int mMessageResId;
- private final CharSequence mMessage;
-
- protected SinglePressAction(int iconResId, int messageResId) {
- mIconResId = iconResId;
- mMessageResId = messageResId;
- mMessage = null;
- mIcon = null;
- }
-
- protected SinglePressAction(int iconResId, Drawable icon, CharSequence message) {
- mIconResId = iconResId;
- mMessageResId = 0;
- mMessage = message;
- mIcon = icon;
- }
-
- public boolean isEnabled() {
- return true;
- }
-
- public String getStatus() {
- return null;
- }
-
- abstract public void onPress();
-
- public CharSequence getLabelForAccessibility(Context context) {
- if (mMessage != null) {
- return mMessage;
- } else {
- return context.getString(mMessageResId);
- }
- }
-
- public View create(
- Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
- View v = inflater.inflate(R.layout.global_actions_item, parent, false);
-
- ImageView icon = (ImageView) v.findViewById(R.id.icon);
- TextView messageView = (TextView) v.findViewById(R.id.message);
-
- TextView statusView = (TextView) v.findViewById(R.id.status);
- final String status = getStatus();
- if (!TextUtils.isEmpty(status)) {
- statusView.setText(status);
- } else {
- statusView.setVisibility(View.GONE);
- }
- if (mIcon != null) {
- icon.setImageDrawable(mIcon);
- icon.setScaleType(ScaleType.CENTER_CROP);
- } else if (mIconResId != 0) {
- icon.setImageDrawable(context.getDrawable(mIconResId));
- }
- if (mMessage != null) {
- messageView.setText(mMessage);
- } else {
- messageView.setText(mMessageResId);
- }
-
- return v;
- }
- }
-
- /**
- * A toggle action knows whether it is on or off, and displays an icon
- * and status message accordingly.
- */
- private static abstract class ToggleAction implements Action {
-
- enum State {
- Off(false),
- TurningOn(true),
- TurningOff(true),
- On(false);
-
- private final boolean inTransition;
-
- State(boolean intermediate) {
- inTransition = intermediate;
- }
-
- public boolean inTransition() {
- return inTransition;
- }
- }
-
- protected State mState = State.Off;
-
- // prefs
- protected int mEnabledIconResId;
- protected int mDisabledIconResid;
- protected int mMessageResId;
- protected int mEnabledStatusMessageResId;
- protected int mDisabledStatusMessageResId;
-
- /**
- * @param enabledIconResId The icon for when this action is on.
- * @param disabledIconResid The icon for when this action is off.
- * @param essage The general information message, e.g 'Silent Mode'
- * @param enabledStatusMessageResId The on status message, e.g 'sound disabled'
- * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled'
- */
- public ToggleAction(int enabledIconResId,
- int disabledIconResid,
- int message,
- int enabledStatusMessageResId,
- int disabledStatusMessageResId) {
- mEnabledIconResId = enabledIconResId;
- mDisabledIconResid = disabledIconResid;
- mMessageResId = message;
- mEnabledStatusMessageResId = enabledStatusMessageResId;
- mDisabledStatusMessageResId = disabledStatusMessageResId;
- }
-
- /**
- * Override to make changes to resource IDs just before creating the
- * View.
- */
- void willCreate() {
-
- }
-
- @Override
- public CharSequence getLabelForAccessibility(Context context) {
- return context.getString(mMessageResId);
- }
-
- public View create(Context context, View convertView, ViewGroup parent,
- LayoutInflater inflater) {
- willCreate();
-
- View v = inflater.inflate(R
- .layout.global_actions_item, parent, false);
-
- ImageView icon = (ImageView) v.findViewById(R.id.icon);
- TextView messageView = (TextView) v.findViewById(R.id.message);
- TextView statusView = (TextView) v.findViewById(R.id.status);
- final boolean enabled = isEnabled();
-
- if (messageView != null) {
- messageView.setText(mMessageResId);
- messageView.setEnabled(enabled);
- }
-
- boolean on = ((mState == State.On) || (mState == State.TurningOn));
- if (icon != null) {
- icon.setImageDrawable(context.getDrawable(
- (on ? mEnabledIconResId : mDisabledIconResid)));
- icon.setEnabled(enabled);
- }
-
- if (statusView != null) {
- statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
- statusView.setVisibility(View.VISIBLE);
- statusView.setEnabled(enabled);
- }
- v.setEnabled(enabled);
-
- return v;
- }
-
- public final void onPress() {
- if (mState.inTransition()) {
- Log.w(TAG, "shouldn't be able to toggle when in transition");
- return;
- }
-
- final boolean nowOn = !(mState == State.On);
- onToggle(nowOn);
- changeStateFromPress(nowOn);
- }
-
- public boolean isEnabled() {
- return !mState.inTransition();
- }
-
- /**
- * Implementations may override this if their state can be in on of the intermediate
- * states until some notification is received (e.g airplane mode is 'turning off' until
- * we know the wireless connections are back online
- * @param buttonOn Whether the button was turned on or off
- */
- protected void changeStateFromPress(boolean buttonOn) {
- mState = buttonOn ? State.On : State.Off;
- }
-
- abstract void onToggle(boolean on);
-
- public void updateState(State state) {
- mState = state;
- }
- }
-
- private class SilentModeToggleAction extends ToggleAction {
- public SilentModeToggleAction() {
- super(R.drawable.ic_audio_vol_mute,
- R.drawable.ic_audio_vol,
- R.string.global_action_toggle_silent_mode,
- R.string.global_action_silent_mode_on_status,
- R.string.global_action_silent_mode_off_status);
- }
-
- void onToggle(boolean on) {
- if (on) {
- mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
- } else {
- mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
- }
- }
-
- public boolean showDuringKeyguard() {
- return true;
- }
-
- public boolean showBeforeProvisioning() {
- return false;
- }
- }
-
- private static class SilentModeTriStateAction implements Action, View.OnClickListener {
-
- private final int[] ITEM_IDS = { R.id.option1, R.id.option2, R.id.option3 };
-
- private final AudioManager mAudioManager;
- private final Handler mHandler;
- private final Context mContext;
-
- SilentModeTriStateAction(Context context, AudioManager audioManager, Handler handler) {
- mAudioManager = audioManager;
- mHandler = handler;
- mContext = context;
- }
-
- private int ringerModeToIndex(int ringerMode) {
- // They just happen to coincide
- return ringerMode;
- }
-
- private int indexToRingerMode(int index) {
- // They just happen to coincide
- return index;
- }
-
- @Override
- public CharSequence getLabelForAccessibility(Context context) {
- return null;
- }
-
- public View create(Context context, View convertView, ViewGroup parent,
- LayoutInflater inflater) {
- View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false);
-
- int selectedIndex = ringerModeToIndex(mAudioManager.getRingerMode());
- for (int i = 0; i < 3; i++) {
- View itemView = v.findViewById(ITEM_IDS[i]);
- itemView.setSelected(selectedIndex == i);
- // Set up click handler
- itemView.setTag(i);
- itemView.setOnClickListener(this);
- }
- return v;
- }
-
- public void onPress() {
- }
-
- public boolean showDuringKeyguard() {
- return true;
- }
-
- public boolean showBeforeProvisioning() {
- return false;
- }
-
- public boolean isEnabled() {
- return true;
- }
-
- void willCreate() {
- }
-
- public void onClick(View v) {
- if (!(v.getTag() instanceof Integer)) return;
-
- int index = (Integer) v.getTag();
- mAudioManager.setRingerMode(indexToRingerMode(index));
- mHandler.sendEmptyMessageDelayed(MESSAGE_DISMISS, DIALOG_DISMISS_DELAY);
- }
- }
-
- private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
- || Intent.ACTION_SCREEN_OFF.equals(action)) {
- String reason = intent.getStringExtra(PhoneWindowManager.SYSTEM_DIALOG_REASON_KEY);
- if (!PhoneWindowManager.SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) {
- mHandler.sendEmptyMessage(MESSAGE_DISMISS);
- }
- } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) {
- // Airplane mode can be changed after ECM exits if airplane toggle button
- // is pressed during ECM mode
- if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) &&
- mIsWaitingForEcmExit) {
- mIsWaitingForEcmExit = false;
- changeAirplaneModeSystemSetting(true);
- }
- }
+ public void run() {
+ if (DEBUG) Slog.d(TAG, "Global actions timeout");
+ // We haven't heard from sysui, show the legacy dialog.
+ mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned);
}
};
-
- PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
- @Override
- public void onServiceStateChanged(ServiceState serviceState) {
- if (!mHasTelephony) return;
- final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF;
- mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off;
- mAirplaneModeOn.updateState(mAirplaneState);
- mAdapter.notifyDataSetChanged();
- }
- };
-
- private BroadcastReceiver mRingerModeReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
- mHandler.sendEmptyMessage(MESSAGE_REFRESH);
- }
- }
- };
-
- private ContentObserver mAirplaneModeObserver = new ContentObserver(new Handler()) {
- @Override
- public void onChange(boolean selfChange) {
- onAirplaneModeChanged();
- }
- };
-
- private static final int MESSAGE_DISMISS = 0;
- private static final int MESSAGE_REFRESH = 1;
- private static final int MESSAGE_SHOW = 2;
- private static final int DIALOG_DISMISS_DELAY = 300; // ms
-
- private Handler mHandler = new Handler() {
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MESSAGE_DISMISS:
- if (mDialog != null) {
- mDialog.dismiss();
- mDialog = null;
- }
- break;
- case MESSAGE_REFRESH:
- refreshSilentMode();
- mAdapter.notifyDataSetChanged();
- break;
- case MESSAGE_SHOW:
- handleShow();
- break;
- }
- }
- };
-
- private void onAirplaneModeChanged() {
- // Let the service state callbacks handle the state.
- if (mHasTelephony) return;
-
- boolean airplaneModeOn = Settings.Global.getInt(
- mContext.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_ON,
- 0) == 1;
- mAirplaneState = airplaneModeOn ? ToggleAction.State.On : ToggleAction.State.Off;
- mAirplaneModeOn.updateState(mAirplaneState);
- }
-
- /**
- * Change the airplane mode system setting
- */
- private void changeAirplaneModeSystemSetting(boolean on) {
- Settings.Global.putInt(
- mContext.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_ON,
- on ? 1 : 0);
- Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
- intent.putExtra("state", on);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
- if (!mHasTelephony) {
- mAirplaneState = on ? ToggleAction.State.On : ToggleAction.State.Off;
- }
- }
-
- private static final class GlobalActionsDialog extends Dialog implements DialogInterface {
- private final Context mContext;
- private final AlertController mAlert;
- private final MyAdapter mAdapter;
-
- public GlobalActionsDialog(Context context, AlertParams params) {
- super(context, getDialogTheme(context));
- mContext = getContext();
- mAlert = AlertController.create(mContext, this, getWindow());
- mAdapter = (MyAdapter) params.mAdapter;
- params.apply(mAlert);
- }
-
- private static int getDialogTheme(Context context) {
- TypedValue outValue = new TypedValue();
- context.getTheme().resolveAttribute(com.android.internal.R.attr.alertDialogTheme,
- outValue, true);
- return outValue.resourceId;
- }
-
- @Override
- protected void onStart() {
- super.setCanceledOnTouchOutside(true);
- super.onStart();
- }
-
- public ListView getListView() {
- return mAlert.getListView();
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mAlert.installContent();
- }
-
- @Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
- for (int i = 0; i < mAdapter.getCount(); ++i) {
- CharSequence label =
- mAdapter.getItem(i).getLabelForAccessibility(getContext());
- if (label != null) {
- event.getText().add(label);
- }
- }
- }
- return super.dispatchPopulateAccessibilityEvent(event);
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (mAlert.onKeyDown(keyCode, event)) {
- return true;
- }
- return super.onKeyDown(keyCode, event);
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (mAlert.onKeyUp(keyCode, event)) {
- return true;
- }
- return super.onKeyUp(keyCode, event);
- }
- }
}
diff --git a/services/core/java/com/android/server/policy/LegacyGlobalActions.java b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
new file mode 100644
index 0000000..a71bc4c
--- /dev/null
+++ b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
@@ -0,0 +1,1263 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import com.android.internal.app.AlertController;
+import com.android.internal.app.AlertController.AlertParams;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.EmergencyAffordanceManager;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.R;
+import com.android.internal.widget.LockPatternUtils;
+
+import android.app.ActivityManager;
+import android.app.Dialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.UserInfo;
+import android.database.ContentObserver;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.net.ConnectivityManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.Vibrator;
+import android.provider.Settings;
+import android.service.dreams.DreamService;
+import android.service.dreams.IDreamManager;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.WindowManagerPolicy.WindowManagerFuncs;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper to show the global actions dialog. Each item is an {@link Action} that
+ * may show depending on whether the keyguard is showing, and whether the device
+ * is provisioned.
+ */
+class LegacyGlobalActions implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
+
+ private static final String TAG = "LegacyGlobalActions";
+
+ private static final boolean SHOW_SILENT_TOGGLE = true;
+
+ /* Valid settings for global actions keys.
+ * see config.xml config_globalActionList */
+ private static final String GLOBAL_ACTION_KEY_POWER = "power";
+ private static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane";
+ private static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport";
+ private static final String GLOBAL_ACTION_KEY_SILENT = "silent";
+ private static final String GLOBAL_ACTION_KEY_USERS = "users";
+ private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings";
+ private static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown";
+ private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist";
+ private static final String GLOBAL_ACTION_KEY_ASSIST = "assist";
+ private static final String GLOBAL_ACTION_KEY_RESTART = "restart";
+
+ private final Context mContext;
+ private final WindowManagerFuncs mWindowManagerFuncs;
+ private final AudioManager mAudioManager;
+ private final IDreamManager mDreamManager;
+ private final Runnable mOnDismiss;
+
+ private ArrayList<Action> mItems;
+ private GlobalActionsDialog mDialog;
+
+ private Action mSilentModeAction;
+ private ToggleAction mAirplaneModeOn;
+
+ private MyAdapter mAdapter;
+
+ private boolean mKeyguardShowing = false;
+ private boolean mDeviceProvisioned = false;
+ private ToggleAction.State mAirplaneState = ToggleAction.State.Off;
+ private boolean mIsWaitingForEcmExit = false;
+ private boolean mHasTelephony;
+ private boolean mHasVibrator;
+ private final boolean mShowSilentToggle;
+ private final EmergencyAffordanceManager mEmergencyAffordanceManager;
+
+ /**
+ * @param context everything needs a context :(
+ */
+ public LegacyGlobalActions(Context context, WindowManagerFuncs windowManagerFuncs,
+ Runnable onDismiss) {
+ mContext = context;
+ mWindowManagerFuncs = windowManagerFuncs;
+ mOnDismiss = onDismiss;
+ mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ mDreamManager = IDreamManager.Stub.asInterface(
+ ServiceManager.getService(DreamService.DREAM_SERVICE));
+
+ // receive broadcasts
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
+ context.registerReceiver(mBroadcastReceiver, filter);
+
+ ConnectivityManager cm = (ConnectivityManager)
+ context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ mHasTelephony = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+
+ // get notified of phone state changes
+ TelephonyManager telephonyManager =
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
+ mAirplaneModeObserver);
+ Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
+ mHasVibrator = vibrator != null && vibrator.hasVibrator();
+
+ mShowSilentToggle = SHOW_SILENT_TOGGLE && !mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_useFixedVolume);
+
+ mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
+ }
+
+ /**
+ * Show the global actions dialog (creating if necessary)
+ * @param keyguardShowing True if keyguard is showing
+ */
+ public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
+ mKeyguardShowing = keyguardShowing;
+ mDeviceProvisioned = isDeviceProvisioned;
+ if (mDialog != null) {
+ mDialog.dismiss();
+ mDialog = null;
+ // Show delayed, so that the dismiss of the previous dialog completes
+ mHandler.sendEmptyMessage(MESSAGE_SHOW);
+ } else {
+ handleShow();
+ }
+ }
+
+ private void awakenIfNecessary() {
+ if (mDreamManager != null) {
+ try {
+ if (mDreamManager.isDreaming()) {
+ mDreamManager.awaken();
+ }
+ } catch (RemoteException e) {
+ // we tried
+ }
+ }
+ }
+
+ private void handleShow() {
+ awakenIfNecessary();
+ mDialog = createDialog();
+ prepareDialog();
+
+ // If we only have 1 item and it's a simple press action, just do this action.
+ if (mAdapter.getCount() == 1
+ && mAdapter.getItem(0) instanceof SinglePressAction
+ && !(mAdapter.getItem(0) instanceof LongPressAction)) {
+ ((SinglePressAction) mAdapter.getItem(0)).onPress();
+ } else {
+ WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
+ attrs.setTitle("LegacyGlobalActions");
+ mDialog.getWindow().setAttributes(attrs);
+ mDialog.show();
+ mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND);
+ }
+ }
+
+ /**
+ * Create the global actions dialog.
+ * @return A new dialog.
+ */
+ private GlobalActionsDialog createDialog() {
+ // Simple toggle style if there's no vibrator, otherwise use a tri-state
+ if (!mHasVibrator) {
+ mSilentModeAction = new SilentModeToggleAction();
+ } else {
+ mSilentModeAction = new SilentModeTriStateAction(mContext, mAudioManager, mHandler);
+ }
+ mAirplaneModeOn = new ToggleAction(
+ R.drawable.ic_lock_airplane_mode,
+ R.drawable.ic_lock_airplane_mode_off,
+ R.string.global_actions_toggle_airplane_mode,
+ R.string.global_actions_airplane_mode_on_status,
+ R.string.global_actions_airplane_mode_off_status) {
+
+ void onToggle(boolean on) {
+ if (mHasTelephony && Boolean.parseBoolean(
+ SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) {
+ mIsWaitingForEcmExit = true;
+ // Launch ECM exit dialog
+ Intent ecmDialogIntent =
+ new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
+ ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(ecmDialogIntent);
+ } else {
+ changeAirplaneModeSystemSetting(on);
+ }
+ }
+
+ @Override
+ protected void changeStateFromPress(boolean buttonOn) {
+ if (!mHasTelephony) return;
+
+ // In ECM mode airplane state cannot be changed
+ if (!(Boolean.parseBoolean(
+ SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)))) {
+ mState = buttonOn ? State.TurningOn : State.TurningOff;
+ mAirplaneState = mState;
+ }
+ }
+
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+ };
+ onAirplaneModeChanged();
+
+ mItems = new ArrayList<Action>();
+ String[] defaultActions = mContext.getResources().getStringArray(
+ com.android.internal.R.array.config_globalActionsList);
+
+ ArraySet<String> addedKeys = new ArraySet<String>();
+ for (int i = 0; i < defaultActions.length; i++) {
+ String actionKey = defaultActions[i];
+ if (addedKeys.contains(actionKey)) {
+ // If we already have added this, don't add it again.
+ continue;
+ }
+ if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) {
+ mItems.add(new PowerAction());
+ } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
+ mItems.add(mAirplaneModeOn);
+ } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
+ if (Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) {
+ mItems.add(new BugReportAction());
+ }
+ } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) {
+ if (mShowSilentToggle) {
+ mItems.add(mSilentModeAction);
+ }
+ } else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) {
+ if (SystemProperties.getBoolean("fw.power_user_switcher", false)) {
+ addUsersToMenu(mItems);
+ }
+ } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
+ mItems.add(getSettingsAction());
+ } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
+ mItems.add(getLockdownAction());
+ } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {
+ mItems.add(getVoiceAssistAction());
+ } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) {
+ mItems.add(getAssistAction());
+ } else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) {
+ mItems.add(new RestartAction());
+ } else {
+ Log.e(TAG, "Invalid global action key " + actionKey);
+ }
+ // Add here so we don't add more than one.
+ addedKeys.add(actionKey);
+ }
+
+ if (mEmergencyAffordanceManager.needsEmergencyAffordance()) {
+ mItems.add(getEmergencyAction());
+ }
+
+ mAdapter = new MyAdapter();
+
+ AlertParams params = new AlertParams(mContext);
+ params.mAdapter = mAdapter;
+ params.mOnClickListener = this;
+ params.mForceInverseBackground = true;
+
+ GlobalActionsDialog dialog = new GlobalActionsDialog(mContext, params);
+ dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
+
+ dialog.getListView().setItemsCanFocus(true);
+ dialog.getListView().setLongClickable(true);
+ dialog.getListView().setOnItemLongClickListener(
+ new AdapterView.OnItemLongClickListener() {
+ @Override
+ public boolean onItemLongClick(AdapterView<?> parent, View view, int position,
+ long id) {
+ final Action action = mAdapter.getItem(position);
+ if (action instanceof LongPressAction) {
+ return ((LongPressAction) action).onLongPress();
+ }
+ return false;
+ }
+ });
+ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+
+ dialog.setOnDismissListener(this);
+
+ return dialog;
+ }
+
+ private final class PowerAction extends SinglePressAction implements LongPressAction {
+ private PowerAction() {
+ super(com.android.internal.R.drawable.ic_lock_power_off,
+ R.string.global_action_power_off);
+ }
+
+ @Override
+ public boolean onLongPress() {
+ UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
+ mWindowManagerFuncs.rebootSafeMode(true);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return true;
+ }
+
+ @Override
+ public void onPress() {
+ // shutdown by making sure radio and power are handled accordingly.
+ mWindowManagerFuncs.shutdown(false /* confirm */);
+ }
+ }
+
+ private final class RestartAction extends SinglePressAction implements LongPressAction {
+ private RestartAction() {
+ super(R.drawable.ic_restart, R.string.global_action_restart);
+ }
+
+ @Override
+ public boolean onLongPress() {
+ UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
+ mWindowManagerFuncs.rebootSafeMode(true);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return true;
+ }
+
+ @Override
+ public void onPress() {
+ mWindowManagerFuncs.reboot(false /* confirm */);
+ }
+ }
+
+
+ private class BugReportAction extends SinglePressAction implements LongPressAction {
+
+ public BugReportAction() {
+ super(com.android.internal.R.drawable.ic_lock_bugreport, R.string.bugreport_title);
+ }
+
+ @Override
+ public void onPress() {
+ // don't actually trigger the bugreport if we are running stability
+ // tests via monkey
+ if (ActivityManager.isUserAMonkey()) {
+ return;
+ }
+ // Add a little delay before executing, to give the
+ // dialog a chance to go away before it takes a
+ // screenshot.
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ // Take an "interactive" bugreport.
+ MetricsLogger.action(mContext,
+ MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE);
+ ActivityManager.getService().requestBugReport(
+ ActivityManager.BUGREPORT_OPTION_INTERACTIVE);
+ } catch (RemoteException e) {
+ }
+ }
+ }, 500);
+ }
+
+ @Override
+ public boolean onLongPress() {
+ // don't actually trigger the bugreport if we are running stability
+ // tests via monkey
+ if (ActivityManager.isUserAMonkey()) {
+ return false;
+ }
+ try {
+ // Take a "full" bugreport.
+ MetricsLogger.action(mContext, MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
+ ActivityManager.getService().requestBugReport(
+ ActivityManager.BUGREPORT_OPTION_FULL);
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+
+ @Override
+ public String getStatus() {
+ return mContext.getString(
+ com.android.internal.R.string.bugreport_status,
+ Build.VERSION.RELEASE,
+ Build.ID);
+ }
+ }
+
+ private Action getSettingsAction() {
+ return new SinglePressAction(com.android.internal.R.drawable.ic_settings,
+ R.string.global_action_settings) {
+
+ @Override
+ public void onPress() {
+ Intent intent = new Intent(Settings.ACTION_SETTINGS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mContext.startActivity(intent);
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return true;
+ }
+ };
+ }
+
+ private Action getEmergencyAction() {
+ return new SinglePressAction(com.android.internal.R.drawable.emergency_icon,
+ R.string.global_action_emergency) {
+ @Override
+ public void onPress() {
+ mEmergencyAffordanceManager.performEmergencyCall();
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return true;
+ }
+ };
+ }
+
+ private Action getAssistAction() {
+ return new SinglePressAction(com.android.internal.R.drawable.ic_action_assist_focused,
+ R.string.global_action_assist) {
+ @Override
+ public void onPress() {
+ Intent intent = new Intent(Intent.ACTION_ASSIST);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mContext.startActivity(intent);
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return true;
+ }
+ };
+ }
+
+ private Action getVoiceAssistAction() {
+ return new SinglePressAction(com.android.internal.R.drawable.ic_voice_search,
+ R.string.global_action_voice_assist) {
+ @Override
+ public void onPress() {
+ Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mContext.startActivity(intent);
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return true;
+ }
+ };
+ }
+
+ private Action getLockdownAction() {
+ return new SinglePressAction(com.android.internal.R.drawable.ic_lock_lock,
+ R.string.global_action_lockdown) {
+
+ @Override
+ public void onPress() {
+ new LockPatternUtils(mContext).requireCredentialEntry(UserHandle.USER_ALL);
+ try {
+ WindowManagerGlobal.getWindowManagerService().lockNow(null);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error while trying to lock device.", e);
+ }
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+ };
+ }
+
+ private UserInfo getCurrentUser() {
+ try {
+ return ActivityManager.getService().getCurrentUser();
+ } catch (RemoteException re) {
+ return null;
+ }
+ }
+
+ private boolean isCurrentUserOwner() {
+ UserInfo currentUser = getCurrentUser();
+ return currentUser == null || currentUser.isPrimary();
+ }
+
+ private void addUsersToMenu(ArrayList<Action> items) {
+ UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ if (um.isUserSwitcherEnabled()) {
+ List<UserInfo> users = um.getUsers();
+ UserInfo currentUser = getCurrentUser();
+ for (final UserInfo user : users) {
+ if (user.supportsSwitchToByUser()) {
+ boolean isCurrentUser = currentUser == null
+ ? user.id == 0 : (currentUser.id == user.id);
+ Drawable icon = user.iconPath != null ? Drawable.createFromPath(user.iconPath)
+ : null;
+ SinglePressAction switchToUser = new SinglePressAction(
+ com.android.internal.R.drawable.ic_menu_cc, icon,
+ (user.name != null ? user.name : "Primary")
+ + (isCurrentUser ? " \u2714" : "")) {
+ public void onPress() {
+ try {
+ ActivityManager.getService().switchUser(user.id);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Couldn't switch user " + re);
+ }
+ }
+
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+ };
+ items.add(switchToUser);
+ }
+ }
+ }
+ }
+
+ private void prepareDialog() {
+ refreshSilentMode();
+ mAirplaneModeOn.updateState(mAirplaneState);
+ mAdapter.notifyDataSetChanged();
+ mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ if (mShowSilentToggle) {
+ IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
+ mContext.registerReceiver(mRingerModeReceiver, filter);
+ }
+ }
+
+ private void refreshSilentMode() {
+ if (!mHasVibrator) {
+ final boolean silentModeOn =
+ mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
+ ((ToggleAction)mSilentModeAction).updateState(
+ silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void onDismiss(DialogInterface dialog) {
+ if (mOnDismiss != null) {
+ mOnDismiss.run();
+ }
+ if (mShowSilentToggle) {
+ try {
+ mContext.unregisterReceiver(mRingerModeReceiver);
+ } catch (IllegalArgumentException ie) {
+ // ignore this
+ Log.w(TAG, ie);
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void onClick(DialogInterface dialog, int which) {
+ if (!(mAdapter.getItem(which) instanceof SilentModeTriStateAction)) {
+ dialog.dismiss();
+ }
+ mAdapter.getItem(which).onPress();
+ }
+
+ /**
+ * The adapter used for the list within the global actions dialog, taking
+ * into account whether the keyguard is showing via
+ * {@link LegacyGlobalActions#mKeyguardShowing} and whether the device is provisioned
+ * via {@link LegacyGlobalActions#mDeviceProvisioned}.
+ */
+ private class MyAdapter extends BaseAdapter {
+
+ public int getCount() {
+ int count = 0;
+
+ for (int i = 0; i < mItems.size(); i++) {
+ final Action action = mItems.get(i);
+
+ if (mKeyguardShowing && !action.showDuringKeyguard()) {
+ continue;
+ }
+ if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
+ continue;
+ }
+ count++;
+ }
+ return count;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return getItem(position).isEnabled();
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return false;
+ }
+
+ public Action getItem(int position) {
+
+ int filteredPos = 0;
+ for (int i = 0; i < mItems.size(); i++) {
+ final Action action = mItems.get(i);
+ if (mKeyguardShowing && !action.showDuringKeyguard()) {
+ continue;
+ }
+ if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
+ continue;
+ }
+ if (filteredPos == position) {
+ return action;
+ }
+ filteredPos++;
+ }
+
+ throw new IllegalArgumentException("position " + position
+ + " out of range of showable actions"
+ + ", filtered count=" + getCount()
+ + ", keyguardshowing=" + mKeyguardShowing
+ + ", provisioned=" + mDeviceProvisioned);
+ }
+
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ Action action = getItem(position);
+ return action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
+ }
+ }
+
+ // note: the scheme below made more sense when we were planning on having
+ // 8 different things in the global actions dialog. seems overkill with
+ // only 3 items now, but may as well keep this flexible approach so it will
+ // be easy should someone decide at the last minute to include something
+ // else, such as 'enable wifi', or 'enable bluetooth'
+
+ /**
+ * What each item in the global actions dialog must be able to support.
+ */
+ private interface Action {
+ /**
+ * @return Text that will be announced when dialog is created. null
+ * for none.
+ */
+ CharSequence getLabelForAccessibility(Context context);
+
+ View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater);
+
+ void onPress();
+
+ /**
+ * @return whether this action should appear in the dialog when the keygaurd
+ * is showing.
+ */
+ boolean showDuringKeyguard();
+
+ /**
+ * @return whether this action should appear in the dialog before the
+ * device is provisioned.
+ */
+ boolean showBeforeProvisioning();
+
+ boolean isEnabled();
+ }
+
+ /**
+ * An action that also supports long press.
+ */
+ private interface LongPressAction extends Action {
+ boolean onLongPress();
+ }
+
+ /**
+ * A single press action maintains no state, just responds to a press
+ * and takes an action.
+ */
+ private static abstract class SinglePressAction implements Action {
+ private final int mIconResId;
+ private final Drawable mIcon;
+ private final int mMessageResId;
+ private final CharSequence mMessage;
+
+ protected SinglePressAction(int iconResId, int messageResId) {
+ mIconResId = iconResId;
+ mMessageResId = messageResId;
+ mMessage = null;
+ mIcon = null;
+ }
+
+ protected SinglePressAction(int iconResId, Drawable icon, CharSequence message) {
+ mIconResId = iconResId;
+ mMessageResId = 0;
+ mMessage = message;
+ mIcon = icon;
+ }
+
+ public boolean isEnabled() {
+ return true;
+ }
+
+ public String getStatus() {
+ return null;
+ }
+
+ abstract public void onPress();
+
+ public CharSequence getLabelForAccessibility(Context context) {
+ if (mMessage != null) {
+ return mMessage;
+ } else {
+ return context.getString(mMessageResId);
+ }
+ }
+
+ public View create(
+ Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
+ View v = inflater.inflate(R.layout.global_actions_item, parent, false);
+
+ ImageView icon = (ImageView) v.findViewById(R.id.icon);
+ TextView messageView = (TextView) v.findViewById(R.id.message);
+
+ TextView statusView = (TextView) v.findViewById(R.id.status);
+ final String status = getStatus();
+ if (!TextUtils.isEmpty(status)) {
+ statusView.setText(status);
+ } else {
+ statusView.setVisibility(View.GONE);
+ }
+ if (mIcon != null) {
+ icon.setImageDrawable(mIcon);
+ icon.setScaleType(ScaleType.CENTER_CROP);
+ } else if (mIconResId != 0) {
+ icon.setImageDrawable(context.getDrawable(mIconResId));
+ }
+ if (mMessage != null) {
+ messageView.setText(mMessage);
+ } else {
+ messageView.setText(mMessageResId);
+ }
+
+ return v;
+ }
+ }
+
+ /**
+ * A toggle action knows whether it is on or off, and displays an icon
+ * and status message accordingly.
+ */
+ private static abstract class ToggleAction implements Action {
+
+ enum State {
+ Off(false),
+ TurningOn(true),
+ TurningOff(true),
+ On(false);
+
+ private final boolean inTransition;
+
+ State(boolean intermediate) {
+ inTransition = intermediate;
+ }
+
+ public boolean inTransition() {
+ return inTransition;
+ }
+ }
+
+ protected State mState = State.Off;
+
+ // prefs
+ protected int mEnabledIconResId;
+ protected int mDisabledIconResid;
+ protected int mMessageResId;
+ protected int mEnabledStatusMessageResId;
+ protected int mDisabledStatusMessageResId;
+
+ /**
+ * @param enabledIconResId The icon for when this action is on.
+ * @param disabledIconResid The icon for when this action is off.
+ * @param essage The general information message, e.g 'Silent Mode'
+ * @param enabledStatusMessageResId The on status message, e.g 'sound disabled'
+ * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled'
+ */
+ public ToggleAction(int enabledIconResId,
+ int disabledIconResid,
+ int message,
+ int enabledStatusMessageResId,
+ int disabledStatusMessageResId) {
+ mEnabledIconResId = enabledIconResId;
+ mDisabledIconResid = disabledIconResid;
+ mMessageResId = message;
+ mEnabledStatusMessageResId = enabledStatusMessageResId;
+ mDisabledStatusMessageResId = disabledStatusMessageResId;
+ }
+
+ /**
+ * Override to make changes to resource IDs just before creating the
+ * View.
+ */
+ void willCreate() {
+
+ }
+
+ @Override
+ public CharSequence getLabelForAccessibility(Context context) {
+ return context.getString(mMessageResId);
+ }
+
+ public View create(Context context, View convertView, ViewGroup parent,
+ LayoutInflater inflater) {
+ willCreate();
+
+ View v = inflater.inflate(R
+ .layout.global_actions_item, parent, false);
+
+ ImageView icon = (ImageView) v.findViewById(R.id.icon);
+ TextView messageView = (TextView) v.findViewById(R.id.message);
+ TextView statusView = (TextView) v.findViewById(R.id.status);
+ final boolean enabled = isEnabled();
+
+ if (messageView != null) {
+ messageView.setText(mMessageResId);
+ messageView.setEnabled(enabled);
+ }
+
+ boolean on = ((mState == State.On) || (mState == State.TurningOn));
+ if (icon != null) {
+ icon.setImageDrawable(context.getDrawable(
+ (on ? mEnabledIconResId : mDisabledIconResid)));
+ icon.setEnabled(enabled);
+ }
+
+ if (statusView != null) {
+ statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
+ statusView.setVisibility(View.VISIBLE);
+ statusView.setEnabled(enabled);
+ }
+ v.setEnabled(enabled);
+
+ return v;
+ }
+
+ public final void onPress() {
+ if (mState.inTransition()) {
+ Log.w(TAG, "shouldn't be able to toggle when in transition");
+ return;
+ }
+
+ final boolean nowOn = !(mState == State.On);
+ onToggle(nowOn);
+ changeStateFromPress(nowOn);
+ }
+
+ public boolean isEnabled() {
+ return !mState.inTransition();
+ }
+
+ /**
+ * Implementations may override this if their state can be in on of the intermediate
+ * states until some notification is received (e.g airplane mode is 'turning off' until
+ * we know the wireless connections are back online
+ * @param buttonOn Whether the button was turned on or off
+ */
+ protected void changeStateFromPress(boolean buttonOn) {
+ mState = buttonOn ? State.On : State.Off;
+ }
+
+ abstract void onToggle(boolean on);
+
+ public void updateState(State state) {
+ mState = state;
+ }
+ }
+
+ private class SilentModeToggleAction extends ToggleAction {
+ public SilentModeToggleAction() {
+ super(R.drawable.ic_audio_vol_mute,
+ R.drawable.ic_audio_vol,
+ R.string.global_action_toggle_silent_mode,
+ R.string.global_action_silent_mode_on_status,
+ R.string.global_action_silent_mode_off_status);
+ }
+
+ void onToggle(boolean on) {
+ if (on) {
+ mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
+ } else {
+ mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+ }
+ }
+
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+ }
+
+ private static class SilentModeTriStateAction implements Action, View.OnClickListener {
+
+ private final int[] ITEM_IDS = { R.id.option1, R.id.option2, R.id.option3 };
+
+ private final AudioManager mAudioManager;
+ private final Handler mHandler;
+ private final Context mContext;
+
+ SilentModeTriStateAction(Context context, AudioManager audioManager, Handler handler) {
+ mAudioManager = audioManager;
+ mHandler = handler;
+ mContext = context;
+ }
+
+ private int ringerModeToIndex(int ringerMode) {
+ // They just happen to coincide
+ return ringerMode;
+ }
+
+ private int indexToRingerMode(int index) {
+ // They just happen to coincide
+ return index;
+ }
+
+ @Override
+ public CharSequence getLabelForAccessibility(Context context) {
+ return null;
+ }
+
+ public View create(Context context, View convertView, ViewGroup parent,
+ LayoutInflater inflater) {
+ View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false);
+
+ int selectedIndex = ringerModeToIndex(mAudioManager.getRingerMode());
+ for (int i = 0; i < 3; i++) {
+ View itemView = v.findViewById(ITEM_IDS[i]);
+ itemView.setSelected(selectedIndex == i);
+ // Set up click handler
+ itemView.setTag(i);
+ itemView.setOnClickListener(this);
+ }
+ return v;
+ }
+
+ public void onPress() {
+ }
+
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+
+ public boolean isEnabled() {
+ return true;
+ }
+
+ void willCreate() {
+ }
+
+ public void onClick(View v) {
+ if (!(v.getTag() instanceof Integer)) return;
+
+ int index = (Integer) v.getTag();
+ mAudioManager.setRingerMode(indexToRingerMode(index));
+ mHandler.sendEmptyMessageDelayed(MESSAGE_DISMISS, DIALOG_DISMISS_DELAY);
+ }
+ }
+
+ private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
+ || Intent.ACTION_SCREEN_OFF.equals(action)) {
+ String reason = intent.getStringExtra(PhoneWindowManager.SYSTEM_DIALOG_REASON_KEY);
+ if (!PhoneWindowManager.SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) {
+ mHandler.sendEmptyMessage(MESSAGE_DISMISS);
+ }
+ } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) {
+ // Airplane mode can be changed after ECM exits if airplane toggle button
+ // is pressed during ECM mode
+ if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) &&
+ mIsWaitingForEcmExit) {
+ mIsWaitingForEcmExit = false;
+ changeAirplaneModeSystemSetting(true);
+ }
+ }
+ }
+ };
+
+ PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+ @Override
+ public void onServiceStateChanged(ServiceState serviceState) {
+ if (!mHasTelephony) return;
+ final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF;
+ mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off;
+ mAirplaneModeOn.updateState(mAirplaneState);
+ mAdapter.notifyDataSetChanged();
+ }
+ };
+
+ private BroadcastReceiver mRingerModeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
+ mHandler.sendEmptyMessage(MESSAGE_REFRESH);
+ }
+ }
+ };
+
+ private ContentObserver mAirplaneModeObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ onAirplaneModeChanged();
+ }
+ };
+
+ private static final int MESSAGE_DISMISS = 0;
+ private static final int MESSAGE_REFRESH = 1;
+ private static final int MESSAGE_SHOW = 2;
+ private static final int DIALOG_DISMISS_DELAY = 300; // ms
+
+ private Handler mHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_DISMISS:
+ if (mDialog != null) {
+ mDialog.dismiss();
+ mDialog = null;
+ }
+ break;
+ case MESSAGE_REFRESH:
+ refreshSilentMode();
+ mAdapter.notifyDataSetChanged();
+ break;
+ case MESSAGE_SHOW:
+ handleShow();
+ break;
+ }
+ }
+ };
+
+ private void onAirplaneModeChanged() {
+ // Let the service state callbacks handle the state.
+ if (mHasTelephony) return;
+
+ boolean airplaneModeOn = Settings.Global.getInt(
+ mContext.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON,
+ 0) == 1;
+ mAirplaneState = airplaneModeOn ? ToggleAction.State.On : ToggleAction.State.Off;
+ mAirplaneModeOn.updateState(mAirplaneState);
+ }
+
+ /**
+ * Change the airplane mode system setting
+ */
+ private void changeAirplaneModeSystemSetting(boolean on) {
+ Settings.Global.putInt(
+ mContext.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON,
+ on ? 1 : 0);
+ Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.putExtra("state", on);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ if (!mHasTelephony) {
+ mAirplaneState = on ? ToggleAction.State.On : ToggleAction.State.Off;
+ }
+ }
+
+ private static final class GlobalActionsDialog extends Dialog implements DialogInterface {
+ private final Context mContext;
+ private final AlertController mAlert;
+ private final MyAdapter mAdapter;
+
+ public GlobalActionsDialog(Context context, AlertParams params) {
+ super(context, getDialogTheme(context));
+ mContext = getContext();
+ mAlert = AlertController.create(mContext, this, getWindow());
+ mAdapter = (MyAdapter) params.mAdapter;
+ params.apply(mAlert);
+ }
+
+ private static int getDialogTheme(Context context) {
+ TypedValue outValue = new TypedValue();
+ context.getTheme().resolveAttribute(com.android.internal.R.attr.alertDialogTheme,
+ outValue, true);
+ return outValue.resourceId;
+ }
+
+ @Override
+ protected void onStart() {
+ super.setCanceledOnTouchOutside(true);
+ super.onStart();
+ }
+
+ public ListView getListView() {
+ return mAlert.getListView();
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mAlert.installContent();
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+ for (int i = 0; i < mAdapter.getCount(); ++i) {
+ CharSequence label =
+ mAdapter.getItem(i).getLabelForAccessibility(getContext());
+ if (label != null) {
+ event.getText().add(label);
+ }
+ }
+ }
+ return super.dispatchPopulateAccessibilityEvent(event);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (mAlert.onKeyDown(keyCode, event)) {
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (mAlert.onKeyUp(keyCode, event)) {
+ return true;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index b4467af..135b20d 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -76,4 +76,26 @@
void toggleRecentApps();
void setCurrentUser(int newUserId);
+
+ void setGlobalActionsListener(GlobalActionsListener listener);
+ void showGlobalActions();
+
+ public interface GlobalActionsListener {
+ /**
+ * Called when sysui starts and connects its status bar, or when the status bar binder
+ * dies indicating sysui is no longer alive.
+ */
+ void onStatusBarConnectedChanged(boolean connected);
+
+ /**
+ * Callback from sysui to notify system that global actions has been successfully shown.
+ */
+ void onGlobalActionsShown();
+
+ /**
+ * Callback from sysui to notify system that the user has dismissed global actions and
+ * it no longer needs to be displayed (even if sysui dies).
+ */
+ void onGlobalActionsDismissed();
+ }
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 2dfe20a8..aaaa080 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -25,6 +25,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -40,6 +41,8 @@
import com.android.internal.statusbar.StatusBarIcon;
import com.android.server.LocalServices;
import com.android.server.notification.NotificationDelegate;
+import com.android.server.power.ShutdownThread;
+import com.android.server.statusbar.StatusBarManagerInternal.GlobalActionsListener;
import com.android.server.wm.WindowManagerService;
import java.io.FileDescriptor;
@@ -65,6 +68,7 @@
// for disabling the status bar
private final ArrayList<DisableRecord> mDisableRecords = new ArrayList<DisableRecord>();
+ private GlobalActionsListener mGlobalActionListener;
private IBinder mSysUiVisToken = new Binder();
private int mDisabled1 = 0;
private int mDisabled2 = 0;
@@ -307,6 +311,21 @@
} catch (RemoteException ex) {}
}
}
+
+ @Override
+ public void setGlobalActionsListener(GlobalActionsListener listener) {
+ mGlobalActionListener = listener;
+ mGlobalActionListener.onStatusBarConnectedChanged(mBar != null);
+ }
+
+ @Override
+ public void showGlobalActions() {
+ if (mBar != null) {
+ try {
+ mBar.showGlobalActionsMenu();
+ } catch (RemoteException ex) {}
+ }
+ }
};
// ================================================================================
@@ -656,6 +675,17 @@
Slog.i(TAG, "registerStatusBar bar=" + bar);
mBar = bar;
+ try {
+ mBar.asBinder().linkToDeath(new DeathRecipient() {
+ @Override
+ public void binderDied() {
+ mBar = null;
+ notifyBarAttachChanged();
+ }
+ }, 0);
+ } catch (RemoteException e) {
+ }
+ notifyBarAttachChanged();
synchronized (mIcons) {
for (String slot : mIcons.keySet()) {
iconSlots.add(slot);
@@ -678,6 +708,13 @@
}
}
+ private void notifyBarAttachChanged() {
+ mHandler.post(() -> {
+ if (mGlobalActionListener == null) return;
+ mGlobalActionListener.onStatusBarConnectedChanged(mBar != null);
+ });
+ }
+
/**
* @param clearNotificationEffects whether to consider notifications as "shown" and stop
* LED, vibration, and ringing
@@ -715,6 +752,65 @@
}
}
+ /**
+ * Allows the status bar to shutdown the device.
+ */
+ @Override
+ public void shutdown() {
+ enforceStatusBarService();
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mHandler.post(() ->
+ ShutdownThread.shutdown(mContext, PowerManager.SHUTDOWN_USER_REQUESTED, false));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Allows the status bar to reboot the device.
+ */
+ @Override
+ public void reboot(boolean safeMode) {
+ enforceStatusBarService();
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mHandler.post(() -> {
+ if (safeMode) {
+ ShutdownThread.rebootSafeMode(mContext, false);
+ } else {
+ ShutdownThread.reboot(mContext, PowerManager.SHUTDOWN_USER_REQUESTED, false);
+ }
+ });
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void onGlobalActionsShown() {
+ enforceStatusBarService();
+ long identity = Binder.clearCallingIdentity();
+ try {
+ if (mGlobalActionListener == null) return;
+ mGlobalActionListener.onGlobalActionsShown();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void onGlobalActionsHidden() {
+ enforceStatusBarService();
+ long identity = Binder.clearCallingIdentity();
+ try {
+ if (mGlobalActionListener == null) return;
+ mGlobalActionListener.onGlobalActionsDismissed();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
@Override
public void onNotificationClick(String key) {
enforceStatusBarService();
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index bd38be4..ef3d87c 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -35,7 +35,6 @@
import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.Trace;
import android.util.Slog;
import android.view.IApplicationToken;
@@ -228,7 +227,7 @@
boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation,
int rotationAnimationHint, int configChanges, boolean launchTaskBehind,
boolean alwaysFocusable, AppWindowContainerController controller) {
- return new AppWindowToken(service, token, voiceInteraction, dc,
+ return new AppWindowToken(service, token, voiceInteraction, dc,
inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdk, orientation,
rotationAnimationHint, configChanges, launchTaskBehind, alwaysFocusable,
controller);
@@ -298,6 +297,17 @@
}
}
+ public void setDisablePreviewScreenshots(boolean disable) {
+ synchronized (mWindowMap) {
+ if (mContainer == null) {
+ Slog.w(TAG_WM, "Attempted to set disable screenshots of non-existing app"
+ + " token: " + mToken);
+ return;
+ }
+ mContainer.setDisablePreviewSnapshots(disable);
+ }
+ }
+
public void setVisibility(boolean visible) {
synchronized(mWindowMap) {
if (mContainer == null) {
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index a474316..111fbd3f 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -167,6 +167,8 @@
ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>();
ArrayDeque<Configuration> mFrozenMergedConfig = new ArrayDeque<>();
+ private boolean mDisbalePreviewScreenshots;
+
AppWindowToken(WindowManagerService service, IApplicationToken token, boolean voiceInteraction,
DisplayContent dc, long inputDispatchingTimeoutNanos, boolean fullscreen,
boolean showForAllUsers, int targetSdk, int orientation, int rotationAnimationHint,
@@ -1170,7 +1172,12 @@
*/
@Override
int getOrientation() {
- if (fillsParent() && (isVisible() || mService.mOpeningApps.contains(this))) {
+ // The {@link AppWindowToken} should only specify an orientation when it is not closing or
+ // going to the bottom. Allowing closing {@link AppWindowToken} to participate can lead to
+ // an Activity in another task being started in the wrong orientation during the transition.
+ if (fillsParent()
+ && !(sendingToBottom || mService.mClosingApps.contains(this))
+ && (isVisible() || mService.mOpeningApps.contains(this))) {
return mOrientation;
}
@@ -1428,6 +1435,14 @@
return candidate;
}
+ void setDisablePreviewSnapshots(boolean disable) {
+ mDisbalePreviewScreenshots = disable;
+ }
+
+ boolean shouldDisablePreviewScreenshots() {
+ return mDisbalePreviewScreenshots;
+ }
+
@Override
int getAnimLayerAdjustment() {
return mAppAnimator.animLayerAdjustment;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9e4d60a..3ffb093 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -22,6 +22,7 @@
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static com.android.server.EventLogTags.WM_TASK_REMOVED;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
@@ -107,6 +108,9 @@
setController(controller);
setBounds(bounds, overrideConfig);
mTaskDescription = taskDescription;
+
+ // Tasks have no set orientation value (including SCREEN_ORIENTATION_UNSPECIFIED).
+ setOrientation(SCREEN_ORIENTATION_UNSET);
}
DisplayContent getDisplayContent() {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 469a8a7..b8d0b8c 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -17,11 +17,16 @@
package com.android.server.wm;
import static android.app.ActivityManager.ENABLE_TASK_SNAPSHOTS;
+import static android.graphics.GraphicBuffer.USAGE_HW_TEXTURE;
+import static android.graphics.GraphicBuffer.USAGE_SW_READ_NEVER;
+import static android.graphics.GraphicBuffer.USAGE_SW_WRITE_RARELY;
+import static android.graphics.PixelFormat.RGBA_8888;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManager.StackId;
import android.app.ActivityManager.TaskSnapshot;
+import android.graphics.Canvas;
import android.graphics.GraphicBuffer;
import android.os.Environment;
import android.util.ArraySet;
@@ -48,6 +53,26 @@
*/
class TaskSnapshotController {
+ /**
+ * Return value for {@link #getSnapshotMode}: We are allowed to take a real screenshot to be
+ * used as the snapshot.
+ */
+ @VisibleForTesting
+ static final int SNAPSHOT_MODE_REAL = 0;
+
+ /**
+ * Return value for {@link #getSnapshotMode}: We are not allowed to take a real screenshot but
+ * we should try to use the app theme to create a dummy representation of the app.
+ */
+ @VisibleForTesting
+ static final int SNAPSHOT_MODE_APP_THEME = 1;
+
+ /**
+ * Return value for {@link #getSnapshotMode}: We aren't allowed to take any snapshot.
+ */
+ @VisibleForTesting
+ static final int SNAPSHOT_MODE_NONE = 2;
+
private final WindowManagerService mService;
private final TaskSnapshotCache mCache;
@@ -88,10 +113,21 @@
getClosingTasks(closingApps, mTmpTasks);
for (int i = mTmpTasks.size() - 1; i >= 0; i--) {
final Task task = mTmpTasks.valueAt(i);
- if (!canSnapshotTask(task)) {
- continue;
+ final int mode = getSnapshotMode(task);
+ final TaskSnapshot snapshot;
+ switch (mode) {
+ case SNAPSHOT_MODE_NONE:
+ continue;
+ case SNAPSHOT_MODE_APP_THEME:
+ snapshot = drawAppThemeSnapshot(task);
+ break;
+ case SNAPSHOT_MODE_REAL:
+ snapshot = snapshotTask(task);
+ break;
+ default:
+ snapshot = null;
+ break;
}
- final TaskSnapshot snapshot = snapshotTask(task);
if (snapshot != null) {
mCache.putSnapshot(task, snapshot);
mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
@@ -152,8 +188,43 @@
}
}
- private boolean canSnapshotTask(Task task) {
- return !StackId.isHomeOrRecentsStack(task.mStack.mStackId);
+ @VisibleForTesting
+ int getSnapshotMode(Task task) {
+ final AppWindowToken topChild = task.getTopChild();
+ if (StackId.isHomeOrRecentsStack(task.mStack.mStackId)) {
+ return SNAPSHOT_MODE_NONE;
+ } else if (topChild != null && topChild.shouldDisablePreviewScreenshots()) {
+ return SNAPSHOT_MODE_APP_THEME;
+ } else {
+ return SNAPSHOT_MODE_REAL;
+ }
+ }
+
+ /**
+ * If we are not allowed to take a real screenshot, this attempts to represent the app as best
+ * as possible by using the theme's window background.
+ */
+ private TaskSnapshot drawAppThemeSnapshot(Task task) {
+ final AppWindowToken topChild = task.getTopChild();
+ if (topChild == null) {
+ return null;
+ }
+ final WindowState mainWindow = topChild.findMainWindow();
+ if (mainWindow == null) {
+ return null;
+ }
+ final int color = task.getTaskDescription().getBackgroundColor();
+ final GraphicBuffer buffer = GraphicBuffer.create(mainWindow.getFrameLw().width(),
+ mainWindow.getFrameLw().height(),
+ RGBA_8888, USAGE_HW_TEXTURE | USAGE_SW_WRITE_RARELY | USAGE_SW_READ_NEVER);
+ if (buffer == null) {
+ return null;
+ }
+ final Canvas c = buffer.lockCanvas();
+ c.drawColor(color);
+ buffer.unlockCanvasAndPost(c);
+ return new TaskSnapshot(buffer, topChild.getConfiguration().orientation,
+ mainWindow.mStableInsets, false /* reduced */, 1.0f /* scale */);
}
/**
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b77000b..9ed7c93 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -56,7 +56,7 @@
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.SamplingProfilerIntegration;
-import com.android.internal.policy.EmergencyAffordanceManager;
+import com.android.internal.util.EmergencyAffordanceManager;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.widget.ILockSettings;
import com.android.server.accessibility.AccessibilityManagerService;
diff --git a/services/tests/notification/AndroidTest.xml b/services/tests/notification/AndroidTest.xml
new file mode 100644
index 0000000..46fdccc
--- /dev/null
+++ b/services/tests/notification/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<configuration description="Runs Frameworks Notification Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="FrameworksNotificationTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="FrameworksNotificationTests" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.frameworks.tests.notification" />
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
index 5a94018..40af2f8 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -208,6 +208,7 @@
private void compareChannels(NotificationChannel expected, NotificationChannel actual) {
assertEquals(expected.getId(), actual.getId());
assertEquals(expected.getName(), actual.getName());
+ assertEquals(expected.getDescription(), actual.getDescription());
assertEquals(expected.shouldVibrate(), actual.shouldVibrate());
assertEquals(expected.shouldShowLights(), actual.shouldShowLights());
assertEquals(expected.getImportance(), actual.getImportance());
@@ -283,6 +284,7 @@
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
NotificationChannel channel2 =
new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ channel2.setDescription("descriptions for all");
channel2.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
channel2.enableLights(true);
channel2.setBypassDnd(true);
@@ -725,6 +727,11 @@
}
@Test
+ public void testDeleteNonExistentChannel() throws Exception {
+ mHelper.deleteNotificationChannelGroup(PKG, UID, "does not exist");
+ }
+
+ @Test
public void testGetDeletedChannel() throws Exception {
NotificationChannel channel =
new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
new file mode 100644
index 0000000..4622d50
--- /dev/null
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<configuration description="Runs Frameworks Services Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="FrameworksServicesTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="FrameworksServicesTests" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.frameworks.servicestests" />
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java
index a2a4019..9343449 100644
--- a/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java
@@ -53,20 +53,22 @@
import java.io.File;
import java.util.Arrays;
+import java.util.ArrayList;
public class BaseLockSettingsServiceTests extends AndroidTestCase {
protected static final int PRIMARY_USER_ID = 0;
protected static final int MANAGED_PROFILE_USER_ID = 12;
+ protected static final int TURNED_OFF_PROFILE_USER_ID = 17;
protected static final int SECONDARY_USER_ID = 20;
private static final UserInfo PRIMARY_USER_INFO = new UserInfo(PRIMARY_USER_ID, null, null,
UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
- private static final UserInfo MANAGED_PROFILE_INFO = new UserInfo(MANAGED_PROFILE_USER_ID, null,
- null, UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_MANAGED_PROFILE);
private static final UserInfo SECONDARY_USER_INFO = new UserInfo(SECONDARY_USER_ID, null, null,
UserInfo.FLAG_INITIALIZED);
+ private ArrayList<UserInfo> mPrimaryUserProfiles = new ArrayList<>();
+
LockSettingsService mService;
MockLockSettingsContext mContext;
@@ -106,13 +108,12 @@
mService = new LockSettingsServiceTestable(mContext, mLockPatternUtils,
mStorage, mGateKeeperService, mKeyStore, mStorageManager, mActivityManager);
when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(PRIMARY_USER_INFO);
- when(mUserManager.getProfiles(eq(PRIMARY_USER_ID))).thenReturn(Arrays.asList(
- new UserInfo[] {PRIMARY_USER_INFO, MANAGED_PROFILE_INFO}));
- when(mUserManager.getUserInfo(eq(MANAGED_PROFILE_USER_ID))).thenReturn(
- MANAGED_PROFILE_INFO);
- when(mUserManager.getProfileParent(eq(MANAGED_PROFILE_USER_ID))).thenReturn(
- PRIMARY_USER_INFO);
+ mPrimaryUserProfiles.add(PRIMARY_USER_INFO);
+ installChildProfile(MANAGED_PROFILE_USER_ID);
+ installQuietModeChildProfile(TURNED_OFF_PROFILE_USER_ID);
+ when(mUserManager.getProfiles(eq(PRIMARY_USER_ID))).thenReturn(mPrimaryUserProfiles);
when(mUserManager.getUserInfo(eq(SECONDARY_USER_ID))).thenReturn(SECONDARY_USER_INFO);
+ when(mUserManager.isUserRunning(eq(MANAGED_PROFILE_USER_ID))).thenReturn(true);
when(mActivityManager.unlockUser(anyInt(), any(), any(), any())).thenAnswer(
new Answer<Boolean>() {
@@ -132,6 +133,21 @@
new ComponentName("com.dummy.package", ".FakeDeviceOwner"));
}
+ private UserInfo installChildProfile(int profileId) {
+ final UserInfo userInfo = new UserInfo(
+ profileId, null, null, UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_MANAGED_PROFILE);
+ mPrimaryUserProfiles.add(userInfo);
+ when(mUserManager.getUserInfo(eq(profileId))).thenReturn(userInfo);
+ when(mUserManager.getProfileParent(eq(profileId))).thenReturn(PRIMARY_USER_INFO);
+ return userInfo;
+ }
+
+ private UserInfo installQuietModeChildProfile(int profileId) {
+ final UserInfo userInfo = installChildProfile(profileId);
+ userInfo.flags |= UserInfo.FLAG_QUIET_MODE;
+ return userInfo;
+ }
+
@Override
protected void tearDown() throws Exception {
super.tearDown();
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java
index ae9762a..cfc3962 100644
--- a/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java
@@ -98,12 +98,18 @@
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
+ final long turnedOffprofileSid =
+ mGateKeeperService.getSecureUserId(TURNED_OFF_PROFILE_USER_ID);
assertTrue(primarySid != 0);
assertTrue(profileSid != 0);
assertTrue(profileSid != primarySid);
+ assertTrue(turnedOffprofileSid != 0);
+ assertTrue(turnedOffprofileSid != primarySid);
+ assertTrue(turnedOffprofileSid != profileSid);
// clear auth token and wait for verify challenge from primary user to re-generate it.
mGateKeeperService.clearAuthToken(MANAGED_PROFILE_USER_ID);
+ mGateKeeperService.clearAuthToken(TURNED_OFF_PROFILE_USER_ID);
// verify credential
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
@@ -113,6 +119,9 @@
assertNotNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+ // Verify that profile which arent't running (e.g. turn off work) don't get unlocked
+ assertNull(mGateKeeperService.getAuthToken(TURNED_OFF_PROFILE_USER_ID));
+
/* Currently in LockSettingsService.setLockCredential, unlockUser() is called with the new
* credential as part of verifyCredential() before the new credential is committed in
* StorageManager. So we relax the check in our mock StorageManager to allow that.
@@ -123,12 +132,14 @@
UnifiedPassword, PRIMARY_USER_ID);
mStorageManager.setIgnoreBadUnlock(false);
assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+ assertNull(mGateKeeperService.getAuthToken(TURNED_OFF_PROFILE_USER_ID));
- //Clear unified challenge
+ // Clear unified challenge
mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, UnifiedPassword,
PRIMARY_USER_ID);
assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
assertEquals(0, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+ assertEquals(0, mGateKeeperService.getSecureUserId(TURNED_OFF_PROFILE_USER_ID));
}
public void testManagedProfileSeparateChallenge() throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotControllerTest.java
index 58d277b..2752340 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotControllerTest.java
@@ -18,7 +18,10 @@
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
+import static com.android.server.wm.TaskSnapshotController.*;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
@@ -68,4 +71,17 @@
sWm.mTaskSnapshotController.getClosingTasks(closingApps, closingTasks);
assertEquals(0, closingTasks.size());
}
+
+ @Test
+ public void testGetSnapshotMode() throws Exception {
+ final WindowState disabledWindow = createWindow(null,
+ FIRST_APPLICATION_WINDOW, sDisplayContent, "disabledWindow");
+ disabledWindow.mAppToken.setDisablePreviewSnapshots(true);
+ assertEquals(SNAPSHOT_MODE_APP_THEME,
+ sWm.mTaskSnapshotController.getSnapshotMode(disabledWindow.getTask()));
+ final WindowState normalWindow = createWindow(null,
+ FIRST_APPLICATION_WINDOW, sDisplayContent, "normalWindow");
+ assertEquals(SNAPSHOT_MODE_REAL,
+ sWm.mTaskSnapshotController.getSnapshotMode(normalWindow.getTask()));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java
index 1c69033..3ce3df1 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java
@@ -16,6 +16,9 @@
package com.android.server.wm;
+import android.content.pm.ActivityInfo;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -23,6 +26,9 @@
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -58,6 +64,42 @@
}
@Test
+ public void testClosingAppDifferentStackOrientation() throws Exception {
+ final TaskStack stack = createTaskStackOnDisplay(sDisplayContent);
+ final Task task1 = createTaskInStack(stack, 0 /* userId */);
+ TestAppWindowToken appWindowToken1 = new TestAppWindowToken(sDisplayContent);
+ task1.addChild(appWindowToken1, 0);
+ appWindowToken1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+
+ final Task task2 = createTaskInStack(stack, 1 /* userId */);
+ TestAppWindowToken appWindowToken2 = new TestAppWindowToken(sDisplayContent);
+ task2.addChild(appWindowToken2, 0);
+ appWindowToken2.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
+
+ assertEquals(stack.getOrientation(), SCREEN_ORIENTATION_PORTRAIT);
+ sWm.mClosingApps.add(appWindowToken2);
+ assertEquals(stack.getOrientation(), SCREEN_ORIENTATION_LANDSCAPE);
+ }
+
+ @Test
+ public void testMoveTaskToBackDifferentStackOrientation() throws Exception {
+ final TaskStack stack = createTaskStackOnDisplay(sDisplayContent);
+ final Task task1 = createTaskInStack(stack, 0 /* userId */);
+ TestAppWindowToken appWindowToken1 = new TestAppWindowToken(sDisplayContent);
+ task1.addChild(appWindowToken1, 0);
+ appWindowToken1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+
+ final Task task2 = createTaskInStack(stack, 1 /* userId */);
+ TestAppWindowToken appWindowToken2 = new TestAppWindowToken(sDisplayContent);
+ task2.addChild(appWindowToken2, 0);
+ appWindowToken2.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
+
+ assertEquals(stack.getOrientation(), SCREEN_ORIENTATION_PORTRAIT);
+ task2.setSendingToBottom(true);
+ assertEquals(stack.getOrientation(), SCREEN_ORIENTATION_LANDSCAPE);
+ }
+
+ @Test
public void testStackRemoveImmediately() throws Exception {
final TaskStack stack = createTaskStackOnDisplay(sDisplayContent);
final Task task = createTaskInStack(stack, 0 /* userId */);
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index ce632ae..6f78245 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -42,6 +42,7 @@
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.EMPTY;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
diff --git a/tests/ActivityTests/AndroidManifest.xml b/tests/ActivityTests/AndroidManifest.xml
index 7b9c9f1..64cdcf7 100644
--- a/tests/ActivityTests/AndroidManifest.xml
+++ b/tests/ActivityTests/AndroidManifest.xml
@@ -79,5 +79,13 @@
android:singleUser="true" android:exported="true" />
<receiver android:name="TrackTimeReceiver" />
<receiver android:name="AlarmSpamReceiver" />
+ <activity android:name="DisableScreenshotsActivity"
+ android:label="DisableScreenshots"
+ android:theme="@style/DisableScreenshots">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/tests/ActivityTests/res/values/colors.xml b/tests/ActivityTests/res/values/colors.xml
new file mode 100644
index 0000000..9925722
--- /dev/null
+++ b/tests/ActivityTests/res/values/colors.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright (C) 2017 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.
+-->
+<resources>
+ <color name="blue">#0000ff</color>
+</resources>
\ No newline at end of file
diff --git a/tests/ActivityTests/res/values/themes.xml b/tests/ActivityTests/res/values/themes.xml
index 67f5938..b8dd830 100644
--- a/tests/ActivityTests/res/values/themes.xml
+++ b/tests/ActivityTests/res/values/themes.xml
@@ -22,4 +22,8 @@
<item name="android:windowEnterAnimation">@anim/slow_enter</item>
<item name="android:windowExitAnimation">@anim/slow_exit</item>
</style>
+ <style name="DisableScreenshots" parent="@android:style/Theme.Material">
+ <item name="android:colorBackground">@color/blue</item>
+ <item name="android:windowBackground">@color/blue</item>
+ </style>
</resources>
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/DisableScreenshotsActivity.java b/tests/ActivityTests/src/com/google/android/test/activity/DisableScreenshotsActivity.java
new file mode 100644
index 0000000..fa5724e
--- /dev/null
+++ b/tests/ActivityTests/src/com/google/android/test/activity/DisableScreenshotsActivity.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 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.annotation.Nullable;
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.SystemClock;
+
+/**
+ * Activity for which screenshotting is disabled.
+ */
+public class DisableScreenshotsActivity extends Activity {
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setDisablePreviewScreenshots(true);
+ getWindow().getDecorView().setBackgroundColor(Color.RED);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ // This is to simulate slowness over resuming the app, such that we have plenty of time to
+ // see the starting window.
+ SystemClock.sleep(500);
+ }
+}
diff --git a/tests/TouchLatency/Android.mk b/tests/TouchLatency/Android.mk
index 73b5b6c..6ad4705 100644
--- a/tests/TouchLatency/Android.mk
+++ b/tests/TouchLatency/Android.mk
@@ -24,4 +24,6 @@
LOCAL_PACKAGE_NAME := TouchLatency
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
include $(BUILD_PACKAGE)
diff --git a/tests/UiBench/Android.mk b/tests/UiBench/Android.mk
index be9a541..36ebc90 100644
--- a/tests/UiBench/Android.mk
+++ b/tests/UiBench/Android.mk
@@ -33,4 +33,6 @@
LOCAL_PACKAGE_NAME := UiBench
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
include $(BUILD_PACKAGE)
diff --git a/tests/net/AndroidTest.xml b/tests/net/AndroidTest.xml
new file mode 100644
index 0000000..6c0a6d0
--- /dev/null
+++ b/tests/net/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<configuration description="Runs Frameworks Networking Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="FrameworksNetTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="FrameworksNetTests" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.frameworks.tests.net" />
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 60f0d56..391aa47 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -4765,6 +4765,7 @@
const String16 vector16("vector");
const String16 animatedVector16("animated-vector");
const String16 pathInterpolator16("pathInterpolator");
+ const String16 objectAnimator16("objectAnimator");
const int minSdk = getMinSdkVersion(bundle);
if (minSdk >= SDK_LOLLIPOP_MR1) {
@@ -4791,6 +4792,7 @@
if (bundle->getNoVersionVectors() && (node->getElementName() == vector16 ||
node->getElementName() == animatedVector16 ||
+ node->getElementName() == objectAnimator16 ||
node->getElementName() == pathInterpolator16)) {
// We were told not to version vector tags, so skip the children here.
continue;
diff --git a/wifi/tests/AndroidTest.xml b/wifi/tests/AndroidTest.xml
new file mode 100644
index 0000000..c30dcea
--- /dev/null
+++ b/wifi/tests/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<configuration description="Runs Frameworks Wifi API Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="FrameworksWifiApiTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="FrameworksWifiApiTests" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="android.net.wifi.test" />
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>