Merge "Add API to disable snapshotting of activities"
diff --git a/Android.mk b/Android.mk
index 03b2533..e324a75 100644
--- a/Android.mk
+++ b/Android.mk
@@ -282,6 +282,7 @@
core/java/android/service/notification/IStatusBarNotificationHolder.aidl \
core/java/android/service/notification/IConditionListener.aidl \
core/java/android/service/notification/IConditionProvider.aidl \
+ core/java/android/service/vr/IPersistentVrStateCallbacks.aidl \
core/java/android/service/vr/IVrListener.aidl \
core/java/android/service/vr/IVrManager.aidl \
core/java/android/service/vr/IVrStateCallbacks.aidl \
@@ -568,6 +569,10 @@
android.hardware.thermal@1.0-java-constants \
android.hardware.health@1.0-java-constants \
android.hardware.usb@1.0-java-constants \
+ android.hardware.vibrator@1.0-java-constants \
+
+# Loaded with System.loadLibrary by android.view.textclassifier
+LOCAL_REQUIRED_MODULES += libtextclassifier
LOCAL_PROTOC_OPTIMIZE_TYPE := stream
LOCAL_PROTOC_FLAGS := \
@@ -988,11 +993,13 @@
framework_docs_LOCAL_DROIDDOC_OPTIONS += \
-hdf dac true \
- -hdf sdk.codename N \
- -hdf sdk.preview.version 5 \
+ -hdf sdk.codename O \
+ -hdf sdk.preview.version 1 \
-hdf sdk.version $(framework_docs_SDK_VERSION) \
-hdf sdk.rel.id $(framework_docs_SDK_REL_ID) \
- -hdf sdk.preview 0
+ -hdf sdk.preview 0 \
+ -resourcesdir $(LOCAL_PATH)/docs/html/reference/images/ \
+ -resourcesoutdir reference/android/images/
# ==== the api stubs and current.xml ===========================
include $(CLEAR_VARS)
@@ -1189,9 +1196,7 @@
-proofread $(OUT_DOCS)/$(LOCAL_MODULE)-proofread.txt \
-sdkvalues $(OUT_DOCS) \
-hdf android.whichdoc offline \
- -referenceonly \
- -resourcesdir $(LOCAL_PATH)/docs/html/reference/images/ \
- -resourcesoutdir reference/android/images/
+ -referenceonly
LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=external/doclava/res/assets/templates-sdk
diff --git a/api/current.txt b/api/current.txt
index e6bacc0..f646832 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
@@ -1179,6 +1180,7 @@
field public static final deprecated int shownWeekCount = 16843585; // 0x1010341
field public static final int shrinkColumns = 16843082; // 0x101014a
field public static final deprecated int singleLine = 16843101; // 0x101015d
+ field public static final int singleLineTitle = 16844127; // 0x101055f
field public static final int singleUser = 16843711; // 0x10103bf
field public static final int slideEdge = 16843824; // 0x1010430
field public static final int smallIcon = 16843422; // 0x101029e
@@ -4844,6 +4846,7 @@
public class Instrumentation {
ctor public Instrumentation();
+ method public android.os.TestLooperManager acquireLooperManager(android.os.Looper);
method public void addMonitor(android.app.Instrumentation.ActivityMonitor);
method public android.app.Instrumentation.ActivityMonitor addMonitor(android.content.IntentFilter, android.app.Instrumentation.ActivityResult, boolean);
method public android.app.Instrumentation.ActivityMonitor addMonitor(java.lang.String, android.app.Instrumentation.ActivityResult, boolean);
@@ -5504,6 +5507,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();
@@ -5513,6 +5517,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);
@@ -6749,6 +6754,7 @@
method public boolean isRequireBatteryNotLow();
method public boolean isRequireCharging();
method public boolean isRequireDeviceIdle();
+ method public boolean isRequireStorageNotLow();
method public void writeToParcel(android.os.Parcel, int);
field public static final int BACKOFF_POLICY_EXPONENTIAL = 1; // 0x1
field public static final int BACKOFF_POLICY_LINEAR = 0; // 0x0
@@ -6776,6 +6782,7 @@
method public android.app.job.JobInfo.Builder setRequiresBatteryNotLow(boolean);
method public android.app.job.JobInfo.Builder setRequiresCharging(boolean);
method public android.app.job.JobInfo.Builder setRequiresDeviceIdle(boolean);
+ method public android.app.job.JobInfo.Builder setRequiresStorageNotLow(boolean);
method public android.app.job.JobInfo.Builder setTransientExtras(android.os.Bundle);
method public android.app.job.JobInfo.Builder setTriggerContentMaxDelay(long);
method public android.app.job.JobInfo.Builder setTriggerContentUpdateDelay(long);
@@ -7977,6 +7984,7 @@
method public boolean isAnonymous();
method public boolean isConnectable();
method public boolean isLegacy();
+ method public boolean isScannable();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.bluetooth.le.AdvertisingSetParameters> CREATOR;
field public static final int INTERVAL_HIGH = 160; // 0xa0
@@ -8004,6 +8012,7 @@
method public android.bluetooth.le.AdvertisingSetParameters.Builder setInterval(int);
method public android.bluetooth.le.AdvertisingSetParameters.Builder setLegacyMode(boolean);
method public android.bluetooth.le.AdvertisingSetParameters.Builder setPrimaryPhy(int);
+ method public android.bluetooth.le.AdvertisingSetParameters.Builder setScannable(boolean);
method public android.bluetooth.le.AdvertisingSetParameters.Builder setSecondaryPhy(int);
method public android.bluetooth.le.AdvertisingSetParameters.Builder setTxPowerLevel(int);
}
@@ -9479,7 +9488,7 @@
field public static final deprecated java.lang.String EXTRA_SHORTCUT_INTENT = "android.intent.extra.shortcut.INTENT";
field public static final deprecated java.lang.String EXTRA_SHORTCUT_NAME = "android.intent.extra.shortcut.NAME";
field public static final java.lang.String EXTRA_SHUTDOWN_USERSPACE_ONLY = "android.intent.extra.SHUTDOWN_USERSPACE_ONLY";
- field public static final java.lang.String EXTRA_STREAM = "android.intent.extra.STREAM";
+ field public static final deprecated java.lang.String EXTRA_STREAM = "android.intent.extra.STREAM";
field public static final java.lang.String EXTRA_SUBJECT = "android.intent.extra.SUBJECT";
field public static final java.lang.String EXTRA_TEMPLATE = "android.intent.extra.TEMPLATE";
field public static final java.lang.String EXTRA_TEXT = "android.intent.extra.TEXT";
@@ -10621,6 +10630,7 @@
field public static final java.lang.String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash";
field public static final java.lang.String FEATURE_CAMERA_FRONT = "android.hardware.camera.front";
field public static final java.lang.String FEATURE_CAMERA_LEVEL_FULL = "android.hardware.camera.level.full";
+ field public static final java.lang.String FEATURE_COMPANION_DEVICE_SETUP = "android.software.companion_device_setup";
field public static final java.lang.String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
field public static final java.lang.String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
field public static final java.lang.String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
@@ -13759,6 +13769,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);
@@ -31556,6 +31586,16 @@
method public static long uptimeMillis();
}
+ public class TestLooperManager {
+ method public void execute(android.os.Message);
+ method public android.os.MessageQueue getQueue();
+ method public boolean hasMessages(android.os.Handler, java.lang.Object, int);
+ method public boolean hasMessages(android.os.Handler, java.lang.Object, java.lang.Runnable);
+ method public android.os.Message next();
+ method public void recycle(android.os.Message);
+ method public void release();
+ }
+
public abstract class TokenWatcher {
ctor public TokenWatcher(android.os.Handler, java.lang.String);
method public void acquire(android.os.IBinder, java.lang.String);
@@ -31655,13 +31695,25 @@
field public static final int USER_CREATION_FAILED_NO_MORE_USERS = 2; // 0x2
}
+ public abstract class VibrationEffect implements android.os.Parcelable {
+ method public static android.os.VibrationEffect createOneShot(long, int);
+ method public static android.os.VibrationEffect createWaveform(long[], int);
+ method public static android.os.VibrationEffect createWaveform(long[], int[], int);
+ method public int describeContents();
+ field public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
+ field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
+ }
+
public abstract class Vibrator {
method public abstract void cancel();
+ method public abstract boolean hasAmplitudeControl();
method public abstract boolean hasVibrator();
- method public void vibrate(long);
- method public void vibrate(long, android.media.AudioAttributes);
- method public void vibrate(long[], int);
- method public void vibrate(long[], int, android.media.AudioAttributes);
+ method public deprecated void vibrate(long);
+ method public deprecated void vibrate(long, android.media.AudioAttributes);
+ method public deprecated void vibrate(long[], int);
+ method public deprecated void vibrate(long[], int, android.media.AudioAttributes);
+ method public void vibrate(android.os.VibrationEffect);
+ method public void vibrate(android.os.VibrationEffect, android.media.AudioAttributes);
}
public class WorkSource implements android.os.Parcelable {
@@ -32001,6 +32053,7 @@
method public boolean isPersistent();
method public boolean isRecycleEnabled();
method public boolean isSelectable();
+ method public boolean isSingleLineTitle();
method protected void notifyChanged();
method public void notifyDependencyChange(boolean);
method protected void notifyHierarchyChanged();
@@ -32042,6 +32095,7 @@
method public void setRecycleEnabled(boolean);
method public void setSelectable(boolean);
method public void setShouldDisableView(boolean);
+ method public void setSingleLineTitle(boolean);
method public void setSummary(java.lang.CharSequence);
method public void setSummary(int);
method public void setTitle(java.lang.CharSequence);
@@ -36838,9 +36892,11 @@
public static final class Dataset.Builder {
ctor public Dataset.Builder(android.widget.RemoteViews);
+ ctor public Dataset.Builder();
method public android.service.autofill.Dataset build();
method public android.service.autofill.Dataset.Builder setAuthentication(android.content.IntentSender);
method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue);
+ method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, android.widget.RemoteViews);
}
public final class FillCallback {
@@ -40991,6 +41047,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();
@@ -46308,7 +46365,6 @@
method public abstract int addChildCount(int);
method public abstract void asyncCommit();
method public abstract android.view.ViewStructure asyncNewChild(int);
- method public abstract android.view.ViewStructure asyncNewChild(int, int, int);
method public abstract int getChildCount();
method public abstract android.os.Bundle getExtras();
method public abstract java.lang.CharSequence getHint();
@@ -46317,11 +46373,11 @@
method public abstract int getTextSelectionStart();
method public abstract boolean hasExtras();
method public abstract android.view.ViewStructure newChild(int);
- method public abstract android.view.ViewStructure newChild(int, int, int);
method public abstract void setAccessibilityFocused(boolean);
method public abstract void setActivated(boolean);
method public abstract void setAlpha(float);
method public abstract void setAutofillHint(java.lang.String[]);
+ method public abstract void setAutofillId(android.view.ViewStructure, int);
method public abstract void setAutofillOptions(java.lang.String[]);
method public abstract void setAutofillType(int);
method public abstract void setAutofillValue(android.view.autofill.AutofillValue);
@@ -46340,6 +46396,7 @@
method public abstract void setFocused(boolean);
method public abstract void setHint(java.lang.CharSequence);
method public abstract void setId(int, java.lang.String, java.lang.String, java.lang.String);
+ method public abstract void setIdEntry(java.lang.String);
method public abstract void setInputType(int);
method public abstract void setLongClickable(boolean);
method public abstract void setOpaque(boolean);
diff --git a/api/system-current.txt b/api/system-current.txt
index 445ae14..eef7839 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
@@ -1296,6 +1297,7 @@
field public static final deprecated int shownWeekCount = 16843585; // 0x1010341
field public static final int shrinkColumns = 16843082; // 0x101014a
field public static final deprecated int singleLine = 16843101; // 0x101015d
+ field public static final int singleLineTitle = 16844127; // 0x101055f
field public static final int singleUser = 16843711; // 0x10103bf
field public static final int slideEdge = 16843824; // 0x1010430
field public static final int smallIcon = 16843422; // 0x101029e
@@ -5014,6 +5016,7 @@
public class Instrumentation {
ctor public Instrumentation();
+ method public android.os.TestLooperManager acquireLooperManager(android.os.Looper);
method public void addMonitor(android.app.Instrumentation.ActivityMonitor);
method public android.app.Instrumentation.ActivityMonitor addMonitor(android.content.IntentFilter, android.app.Instrumentation.ActivityResult, boolean);
method public android.app.Instrumentation.ActivityMonitor addMonitor(java.lang.String, android.app.Instrumentation.ActivityResult, boolean);
@@ -5690,6 +5693,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();
@@ -5702,6 +5706,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);
@@ -5717,16 +5722,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 {
@@ -7184,6 +7179,7 @@
method public boolean isRequireBatteryNotLow();
method public boolean isRequireCharging();
method public boolean isRequireDeviceIdle();
+ method public boolean isRequireStorageNotLow();
method public void writeToParcel(android.os.Parcel, int);
field public static final int BACKOFF_POLICY_EXPONENTIAL = 1; // 0x1
field public static final int BACKOFF_POLICY_LINEAR = 0; // 0x0
@@ -7211,6 +7207,7 @@
method public android.app.job.JobInfo.Builder setRequiresBatteryNotLow(boolean);
method public android.app.job.JobInfo.Builder setRequiresCharging(boolean);
method public android.app.job.JobInfo.Builder setRequiresDeviceIdle(boolean);
+ method public android.app.job.JobInfo.Builder setRequiresStorageNotLow(boolean);
method public android.app.job.JobInfo.Builder setTransientExtras(android.os.Bundle);
method public android.app.job.JobInfo.Builder setTriggerContentMaxDelay(long);
method public android.app.job.JobInfo.Builder setTriggerContentUpdateDelay(long);
@@ -8451,6 +8448,7 @@
method public boolean isAnonymous();
method public boolean isConnectable();
method public boolean isLegacy();
+ method public boolean isScannable();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.bluetooth.le.AdvertisingSetParameters> CREATOR;
field public static final int INTERVAL_HIGH = 160; // 0xa0
@@ -8478,6 +8476,7 @@
method public android.bluetooth.le.AdvertisingSetParameters.Builder setInterval(int);
method public android.bluetooth.le.AdvertisingSetParameters.Builder setLegacyMode(boolean);
method public android.bluetooth.le.AdvertisingSetParameters.Builder setPrimaryPhy(int);
+ method public android.bluetooth.le.AdvertisingSetParameters.Builder setScannable(boolean);
method public android.bluetooth.le.AdvertisingSetParameters.Builder setSecondaryPhy(int);
method public android.bluetooth.le.AdvertisingSetParameters.Builder setTxPowerLevel(int);
}
@@ -10015,7 +10014,7 @@
field public static final deprecated java.lang.String EXTRA_SHORTCUT_NAME = "android.intent.extra.shortcut.NAME";
field public static final java.lang.String EXTRA_SHUTDOWN_USERSPACE_ONLY = "android.intent.extra.SHUTDOWN_USERSPACE_ONLY";
field public static final java.lang.String EXTRA_SPLIT_NAME = "android.intent.extra.SPLIT_NAME";
- field public static final java.lang.String EXTRA_STREAM = "android.intent.extra.STREAM";
+ field public static final deprecated java.lang.String EXTRA_STREAM = "android.intent.extra.STREAM";
field public static final java.lang.String EXTRA_SUBJECT = "android.intent.extra.SUBJECT";
field public static final java.lang.String EXTRA_TEMPLATE = "android.intent.extra.TEMPLATE";
field public static final java.lang.String EXTRA_TEXT = "android.intent.extra.TEXT";
@@ -11278,6 +11277,7 @@
field public static final java.lang.String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash";
field public static final java.lang.String FEATURE_CAMERA_FRONT = "android.hardware.camera.front";
field public static final java.lang.String FEATURE_CAMERA_LEVEL_FULL = "android.hardware.camera.level.full";
+ field public static final java.lang.String FEATURE_COMPANION_DEVICE_SETUP = "android.software.companion_device_setup";
field public static final java.lang.String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
field public static final java.lang.String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
field public static final java.lang.String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
@@ -14493,6 +14493,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);
@@ -34318,6 +34338,16 @@
method public static long uptimeMillis();
}
+ public class TestLooperManager {
+ method public void execute(android.os.Message);
+ method public android.os.MessageQueue getQueue();
+ method public boolean hasMessages(android.os.Handler, java.lang.Object, int);
+ method public boolean hasMessages(android.os.Handler, java.lang.Object, java.lang.Runnable);
+ method public android.os.Message next();
+ method public void recycle(android.os.Message);
+ method public void release();
+ }
+
public abstract class TokenWatcher {
ctor public TokenWatcher(android.os.Handler, java.lang.String);
method public void acquire(android.os.IBinder, java.lang.String);
@@ -34494,13 +34524,25 @@
public static abstract class UserManager.UserRestrictionSource implements java.lang.annotation.Annotation {
}
+ public abstract class VibrationEffect implements android.os.Parcelable {
+ method public static android.os.VibrationEffect createOneShot(long, int);
+ method public static android.os.VibrationEffect createWaveform(long[], int);
+ method public static android.os.VibrationEffect createWaveform(long[], int[], int);
+ method public int describeContents();
+ field public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
+ field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
+ }
+
public abstract class Vibrator {
method public abstract void cancel();
+ method public abstract boolean hasAmplitudeControl();
method public abstract boolean hasVibrator();
- method public void vibrate(long);
- method public void vibrate(long, android.media.AudioAttributes);
- method public void vibrate(long[], int);
- method public void vibrate(long[], int, android.media.AudioAttributes);
+ method public deprecated void vibrate(long);
+ method public deprecated void vibrate(long, android.media.AudioAttributes);
+ method public deprecated void vibrate(long[], int);
+ method public deprecated void vibrate(long[], int, android.media.AudioAttributes);
+ method public void vibrate(android.os.VibrationEffect);
+ method public void vibrate(android.os.VibrationEffect, android.media.AudioAttributes);
}
public class WorkSource implements android.os.Parcelable {
@@ -34852,6 +34894,7 @@
method public boolean isPersistent();
method public boolean isRecycleEnabled();
method public boolean isSelectable();
+ method public boolean isSingleLineTitle();
method protected void notifyChanged();
method public void notifyDependencyChange(boolean);
method protected void notifyHierarchyChanged();
@@ -34893,6 +34936,7 @@
method public void setRecycleEnabled(boolean);
method public void setSelectable(boolean);
method public void setShouldDisableView(boolean);
+ method public void setSingleLineTitle(boolean);
method public void setSummary(java.lang.CharSequence);
method public void setSummary(int);
method public void setTitle(java.lang.CharSequence);
@@ -39873,9 +39917,11 @@
public static final class Dataset.Builder {
ctor public Dataset.Builder(android.widget.RemoteViews);
+ ctor public Dataset.Builder();
method public android.service.autofill.Dataset build();
method public android.service.autofill.Dataset.Builder setAuthentication(android.content.IntentSender);
method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue);
+ method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, android.widget.RemoteViews);
}
public final class FillCallback {
@@ -44454,6 +44500,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();
@@ -49772,7 +49819,6 @@
method public abstract int addChildCount(int);
method public abstract void asyncCommit();
method public abstract android.view.ViewStructure asyncNewChild(int);
- method public abstract android.view.ViewStructure asyncNewChild(int, int, int);
method public abstract int getChildCount();
method public abstract android.os.Bundle getExtras();
method public abstract java.lang.CharSequence getHint();
@@ -49781,11 +49827,11 @@
method public abstract int getTextSelectionStart();
method public abstract boolean hasExtras();
method public abstract android.view.ViewStructure newChild(int);
- method public abstract android.view.ViewStructure newChild(int, int, int);
method public abstract void setAccessibilityFocused(boolean);
method public abstract void setActivated(boolean);
method public abstract void setAlpha(float);
method public abstract void setAutofillHint(java.lang.String[]);
+ method public abstract void setAutofillId(android.view.ViewStructure, int);
method public abstract void setAutofillOptions(java.lang.String[]);
method public abstract void setAutofillType(int);
method public abstract void setAutofillValue(android.view.autofill.AutofillValue);
@@ -49804,6 +49850,7 @@
method public abstract void setFocused(boolean);
method public abstract void setHint(java.lang.CharSequence);
method public abstract void setId(int, java.lang.String, java.lang.String, java.lang.String);
+ method public abstract void setIdEntry(java.lang.String);
method public abstract void setInputType(int);
method public abstract void setLongClickable(boolean);
method public abstract void setOpaque(boolean);
diff --git a/api/test-current.txt b/api/test-current.txt
index 99d960b..4e350ee 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
@@ -1179,6 +1180,7 @@
field public static final deprecated int shownWeekCount = 16843585; // 0x1010341
field public static final int shrinkColumns = 16843082; // 0x101014a
field public static final deprecated int singleLine = 16843101; // 0x101015d
+ field public static final int singleLineTitle = 16844127; // 0x101055f
field public static final int singleUser = 16843711; // 0x10103bf
field public static final int slideEdge = 16843824; // 0x1010430
field public static final int smallIcon = 16843422; // 0x101029e
@@ -4854,6 +4856,7 @@
public class Instrumentation {
ctor public Instrumentation();
+ method public android.os.TestLooperManager acquireLooperManager(android.os.Looper);
method public void addMonitor(android.app.Instrumentation.ActivityMonitor);
method public android.app.Instrumentation.ActivityMonitor addMonitor(android.content.IntentFilter, android.app.Instrumentation.ActivityResult, boolean);
method public android.app.Instrumentation.ActivityMonitor addMonitor(java.lang.String, android.app.Instrumentation.ActivityResult, boolean);
@@ -5514,6 +5517,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();
@@ -5523,6 +5527,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);
@@ -6776,6 +6781,7 @@
method public boolean isRequireBatteryNotLow();
method public boolean isRequireCharging();
method public boolean isRequireDeviceIdle();
+ method public boolean isRequireStorageNotLow();
method public void writeToParcel(android.os.Parcel, int);
field public static final int BACKOFF_POLICY_EXPONENTIAL = 1; // 0x1
field public static final int BACKOFF_POLICY_LINEAR = 0; // 0x0
@@ -6803,6 +6809,7 @@
method public android.app.job.JobInfo.Builder setRequiresBatteryNotLow(boolean);
method public android.app.job.JobInfo.Builder setRequiresCharging(boolean);
method public android.app.job.JobInfo.Builder setRequiresDeviceIdle(boolean);
+ method public android.app.job.JobInfo.Builder setRequiresStorageNotLow(boolean);
method public android.app.job.JobInfo.Builder setTransientExtras(android.os.Bundle);
method public android.app.job.JobInfo.Builder setTriggerContentMaxDelay(long);
method public android.app.job.JobInfo.Builder setTriggerContentUpdateDelay(long);
@@ -8004,6 +8011,7 @@
method public boolean isAnonymous();
method public boolean isConnectable();
method public boolean isLegacy();
+ method public boolean isScannable();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.bluetooth.le.AdvertisingSetParameters> CREATOR;
field public static final int INTERVAL_HIGH = 160; // 0xa0
@@ -8031,6 +8039,7 @@
method public android.bluetooth.le.AdvertisingSetParameters.Builder setInterval(int);
method public android.bluetooth.le.AdvertisingSetParameters.Builder setLegacyMode(boolean);
method public android.bluetooth.le.AdvertisingSetParameters.Builder setPrimaryPhy(int);
+ method public android.bluetooth.le.AdvertisingSetParameters.Builder setScannable(boolean);
method public android.bluetooth.le.AdvertisingSetParameters.Builder setSecondaryPhy(int);
method public android.bluetooth.le.AdvertisingSetParameters.Builder setTxPowerLevel(int);
}
@@ -9509,7 +9518,7 @@
field public static final deprecated java.lang.String EXTRA_SHORTCUT_INTENT = "android.intent.extra.shortcut.INTENT";
field public static final deprecated java.lang.String EXTRA_SHORTCUT_NAME = "android.intent.extra.shortcut.NAME";
field public static final java.lang.String EXTRA_SHUTDOWN_USERSPACE_ONLY = "android.intent.extra.SHUTDOWN_USERSPACE_ONLY";
- field public static final java.lang.String EXTRA_STREAM = "android.intent.extra.STREAM";
+ field public static final deprecated java.lang.String EXTRA_STREAM = "android.intent.extra.STREAM";
field public static final java.lang.String EXTRA_SUBJECT = "android.intent.extra.SUBJECT";
field public static final java.lang.String EXTRA_TEMPLATE = "android.intent.extra.TEMPLATE";
field public static final java.lang.String EXTRA_TEXT = "android.intent.extra.TEXT";
@@ -10657,6 +10666,7 @@
field public static final java.lang.String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash";
field public static final java.lang.String FEATURE_CAMERA_FRONT = "android.hardware.camera.front";
field public static final java.lang.String FEATURE_CAMERA_LEVEL_FULL = "android.hardware.camera.level.full";
+ field public static final java.lang.String FEATURE_COMPANION_DEVICE_SETUP = "android.software.companion_device_setup";
field public static final java.lang.String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
field public static final java.lang.String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
field public static final java.lang.String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
@@ -13797,6 +13807,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);
@@ -31679,6 +31709,16 @@
method public static long uptimeMillis();
}
+ public class TestLooperManager {
+ method public void execute(android.os.Message);
+ method public android.os.MessageQueue getQueue();
+ method public boolean hasMessages(android.os.Handler, java.lang.Object, int);
+ method public boolean hasMessages(android.os.Handler, java.lang.Object, java.lang.Runnable);
+ method public android.os.Message next();
+ method public void recycle(android.os.Message);
+ method public void release();
+ }
+
public abstract class TokenWatcher {
ctor public TokenWatcher(android.os.Handler, java.lang.String);
method public void acquire(android.os.IBinder, java.lang.String);
@@ -31780,13 +31820,25 @@
field public static final int USER_CREATION_FAILED_NO_MORE_USERS = 2; // 0x2
}
+ public abstract class VibrationEffect implements android.os.Parcelable {
+ method public static android.os.VibrationEffect createOneShot(long, int);
+ method public static android.os.VibrationEffect createWaveform(long[], int);
+ method public static android.os.VibrationEffect createWaveform(long[], int[], int);
+ method public int describeContents();
+ field public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
+ field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
+ }
+
public abstract class Vibrator {
method public abstract void cancel();
+ method public abstract boolean hasAmplitudeControl();
method public abstract boolean hasVibrator();
- method public void vibrate(long);
- method public void vibrate(long, android.media.AudioAttributes);
- method public void vibrate(long[], int);
- method public void vibrate(long[], int, android.media.AudioAttributes);
+ method public deprecated void vibrate(long);
+ method public deprecated void vibrate(long, android.media.AudioAttributes);
+ method public deprecated void vibrate(long[], int);
+ method public deprecated void vibrate(long[], int, android.media.AudioAttributes);
+ method public void vibrate(android.os.VibrationEffect);
+ method public void vibrate(android.os.VibrationEffect, android.media.AudioAttributes);
}
public class WorkSource implements android.os.Parcelable {
@@ -32126,6 +32178,7 @@
method public boolean isPersistent();
method public boolean isRecycleEnabled();
method public boolean isSelectable();
+ method public boolean isSingleLineTitle();
method protected void notifyChanged();
method public void notifyDependencyChange(boolean);
method protected void notifyHierarchyChanged();
@@ -32167,6 +32220,7 @@
method public void setRecycleEnabled(boolean);
method public void setSelectable(boolean);
method public void setShouldDisableView(boolean);
+ method public void setSingleLineTitle(boolean);
method public void setSummary(java.lang.CharSequence);
method public void setSummary(int);
method public void setTitle(java.lang.CharSequence);
@@ -36984,9 +37038,11 @@
public static final class Dataset.Builder {
ctor public Dataset.Builder(android.widget.RemoteViews);
+ ctor public Dataset.Builder();
method public android.service.autofill.Dataset build();
method public android.service.autofill.Dataset.Builder setAuthentication(android.content.IntentSender);
method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue);
+ method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, android.widget.RemoteViews);
}
public final class FillCallback {
@@ -41186,6 +41242,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();
@@ -46677,7 +46734,6 @@
method public abstract int addChildCount(int);
method public abstract void asyncCommit();
method public abstract android.view.ViewStructure asyncNewChild(int);
- method public abstract android.view.ViewStructure asyncNewChild(int, int, int);
method public abstract int getChildCount();
method public abstract android.os.Bundle getExtras();
method public abstract java.lang.CharSequence getHint();
@@ -46686,11 +46742,11 @@
method public abstract int getTextSelectionStart();
method public abstract boolean hasExtras();
method public abstract android.view.ViewStructure newChild(int);
- method public abstract android.view.ViewStructure newChild(int, int, int);
method public abstract void setAccessibilityFocused(boolean);
method public abstract void setActivated(boolean);
method public abstract void setAlpha(float);
method public abstract void setAutofillHint(java.lang.String[]);
+ method public abstract void setAutofillId(android.view.ViewStructure, int);
method public abstract void setAutofillOptions(java.lang.String[]);
method public abstract void setAutofillType(int);
method public abstract void setAutofillValue(android.view.autofill.AutofillValue);
@@ -46709,6 +46765,7 @@
method public abstract void setFocused(boolean);
method public abstract void setHint(java.lang.CharSequence);
method public abstract void setId(int, java.lang.String, java.lang.String, java.lang.String);
+ method public abstract void setIdEntry(java.lang.String);
method public abstract void setInputType(int);
method public abstract void setLongClickable(boolean);
method public abstract void setOpaque(boolean);
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 75d4f32..6d4b812 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -344,6 +344,17 @@
"android.accounts.LOGIN_ACCOUNTS_CHANGED";
/**
+ * Action sent as a broadcast Intent to specific package by the AccountsService
+ * when account visibility or account's credentials (saved password, etc) are changed.
+ *
+ * @see #addOnAccountsUpdatedListener
+ *
+ * @hide
+ */
+ public static final String ACTION_VISIBLE_ACCOUNTS_CHANGED =
+ "android.accounts.action.VISIBLE_ACCOUNTS_CHANGED";
+
+ /**
* Key to set default visibility for applications targeting API level
* {@link android.os.Build.VERSION_CODES#O} or above and don't have the same signature as
* authenticator See {@link #getAccountVisibility}. If the value was not set by authenticator
@@ -1057,8 +1068,8 @@
/**
* Gets the previous name associated with the account or {@code null}, if
- * none. This is intended so that clients of {@link
- * #LOGIN_ACCOUNTS_CHANGED_ACTION} broadcasts can determine if an
+ * none. This is intended so that clients of
+ * {@link OnAccountsUpdateListener} can determine if an
* authenticator has renamed an account.
*
* <p>It is safe to call this method from the main thread.
@@ -1555,7 +1566,8 @@
* <p>In that case, you may need to wait until the user responds, which
* could take hours or days or forever. When the user does respond and
* supply a new password, the account manager will broadcast the
- * {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} Intent, which applications can
+ * {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} Intent and
+ * notify {@link OnAccountsUpdateListener} which applications can
* use to try again.
*
* <p>If notifyAuthFailure is not set, it is the application's
@@ -1631,7 +1643,8 @@
* <p>In that case, you may need to wait until the user responds, which
* could take hours or days or forever. When the user does respond and
* supply a new password, the account manager will broadcast the
- * {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} Intent, which applications can
+ * {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} Intent and
+ * notify {@link OnAccountsUpdateListener} which applications can
* use to try again.
*
* <p>If notifyAuthFailure is not set, it is the application's
@@ -2811,7 +2824,7 @@
Maps.newHashMap();
/**
- * BroadcastReceiver that listens for the LOGIN_ACCOUNTS_CHANGED_ACTION intent
+ * BroadcastReceiver that listens for the ACTION_VISIBLE_ACCOUNTS_CHANGED intent
* so that it can read the updated list of accounts and send them to the listener
* in mAccountsUpdatedListeners.
*/
@@ -2881,22 +2894,27 @@
mAccountsUpdatedListeners.put(listener, handler);
if (accountTypes != null) {
mAccountsUpdatedListenersTypes.put(listener,
- new HashSet<String>(Arrays.asList(accountTypes)));
+ new HashSet<String>(Arrays.asList(accountTypes)));
+ } else {
+ mAccountsUpdatedListenersTypes.put(listener, null);
}
if (wasEmpty) {
// Register a broadcast receiver to monitor account changes
IntentFilter intentFilter = new IntentFilter();
- // TODO get rid of the broadcast receiver
- // create android.os.ResultReceiver
- // send it to the service via aidl
- // handle onReceiveResult
- intentFilter.addAction(LOGIN_ACCOUNTS_CHANGED_ACTION);
+ intentFilter.addAction(ACTION_VISIBLE_ACCOUNTS_CHANGED);
// To recover from disk-full.
intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
- // Register a broadcast receiver to monitor account changes
mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter);
}
+
+ try {
+ // Notify AccountManagedService about new receiver.
+ // The receiver must be unregistered later exactly one time
+ mService.registerAccountListener(accountTypes, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
if (updateImmediately) {
postToHandler(handler, listener, getAccounts());
@@ -2923,11 +2941,23 @@
Log.e(TAG, "Listener was not previously added");
return;
}
+ Set<String> accountTypes = mAccountsUpdatedListenersTypes.get(listener);
+ String[] accountsArray;
+ if (accountTypes != null) {
+ accountsArray = accountTypes.toArray(new String[accountTypes.size()]);
+ } else {
+ accountsArray = null;
+ }
mAccountsUpdatedListeners.remove(listener);
mAccountsUpdatedListenersTypes.remove(listener);
if (mAccountsUpdatedListeners.isEmpty()) {
mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver);
}
+ try {
+ mService.unregisterAccountListener(accountsArray, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index 49cd2c6..7494cfc 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -117,6 +117,9 @@
/* Type may be null returns Map <Account, Integer>*/
Map getAccountsAndVisibilityForPackage(in String packageName, in String accountType);
+ void registerAccountListener(in String[] accountTypes, String opPackageName);
+ void unregisterAccountListener(in String[] accountTypes, String opPackageName);
+
/* Check if the package in a user can access an account */
boolean hasAccountAccess(in Account account, String packageName, in UserHandle userHandle);
/* Crate an intent to request account access for package and a given user id */
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 55407e6..286f8570 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2139,11 +2139,12 @@
@Override
public void getPackageSizeInfoAsUser(String packageName, int userHandle,
IPackageStatsObserver observer) {
+ final String msg = "Shame on you for calling the hidden API "
+ + "getPackageSizeInfoAsUser(). Shame!";
if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O) {
- throw new UnsupportedOperationException(
- "Shame on you for calling a hidden API. Shame!");
+ throw new UnsupportedOperationException(msg);
} else if (observer != null) {
- Log.d(TAG, "Shame on you for calling a hidden API. Shame!");
+ Log.d(TAG, msg);
try {
observer.onGetStatsCompleted(null, false);
} catch (RemoteException ignored) {
diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java
index c88448a..f564e8d 100644
--- a/core/java/android/app/BackStackRecord.java
+++ b/core/java/android/app/BackStackRecord.java
@@ -370,7 +370,7 @@
public BackStackRecord(FragmentManagerImpl manager) {
mManager = manager;
- mAllowOptimization = getTargetSdk() > Build.VERSION_CODES.N_MR1;
+ mAllowOptimization = mManager.getTargetSdk() > Build.VERSION_CODES.N_MR1;
}
public int getId() {
@@ -423,7 +423,7 @@
}
private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
- if (getTargetSdk() > Build.VERSION_CODES.N_MR1) {
+ if (mManager.getTargetSdk() > Build.VERSION_CODES.N_MR1) {
final Class fragmentClass = fragment.getClass();
final int modifiers = fragmentClass.getModifiers();
if ((fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers)
@@ -1022,22 +1022,4 @@
public boolean isEmpty() {
return mOps.isEmpty();
}
-
- /**
- * @return the target SDK of the FragmentManager's application info. If the
- * FragmentManager has been torn down, then 0 is returned.
- */
- private int getTargetSdk() {
- FragmentHostCallback host = mManager.mHost;
- if (host != null) {
- Context context = host.getContext();
- if (context != null) {
- ApplicationInfo info = context.getApplicationInfo();
- if (info != null) {
- return info.targetSdkVersion;
- }
- }
- }
- return 0;
- }
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 8a3d9b1..4232617 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -63,6 +63,7 @@
import android.os.ServiceManager;
import android.os.Trace;
import android.os.UserHandle;
+import android.os.UserManager;
import android.os.storage.IStorageManager;
import android.system.ErrnoException;
import android.system.Os;
@@ -365,6 +366,13 @@
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
checkMode(mode);
+ if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
+ if (isCredentialProtectedStorage()
+ && !getSystemService(UserManager.class).isUserUnlocked()) {
+ throw new IllegalStateException("SharedPreferences in credential encrypted "
+ + "storage are not available until after user is unlocked");
+ }
+ }
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 0672e3b..0d859a1 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -23,9 +23,11 @@
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.content.res.Resources.NotFoundException;
import android.content.res.TypedArray;
+import android.os.Build;
import android.os.Bundle;
import android.os.Debug;
import android.os.Looper;
@@ -674,6 +676,10 @@
// Postponed transactions.
ArrayList<StartEnterTransitionListener> mPostponedTransactions;
+ // Prior to O, we allowed executing transactions during fragment manager state changes.
+ // This is dangerous, but we want to keep from breaking old applications.
+ boolean mAllowOldReentrantBehavior;
+
Runnable mExecCommit = new Runnable() {
@Override
public void run() {
@@ -2815,69 +2821,92 @@
mHost = host;
mContainer = container;
mParent = parent;
+ mAllowOldReentrantBehavior = getTargetSdk() <= Build.VERSION_CODES.N_MR1;
}
-
+
+ /**
+ * @return the target SDK of the FragmentManager's application info. If the
+ * FragmentManager has been torn down, then 0 is returned.
+ */
+ int getTargetSdk() {
+ if (mHost != null) {
+ Context context = mHost.getContext();
+ if (context != null) {
+ ApplicationInfo info = context.getApplicationInfo();
+ if (info != null) {
+ return info.targetSdkVersion;
+ }
+ }
+ }
+ return 0;
+ }
+
public void noteStateNotSaved() {
mStateSaved = false;
}
public void dispatchCreate() {
mStateSaved = false;
- mExecutingActions = true;
- moveToState(Fragment.CREATED, false);
- mExecutingActions = false;
+ dispatchMoveToState(Fragment.CREATED);
}
public void dispatchActivityCreated() {
mStateSaved = false;
- mExecutingActions = true;
- moveToState(Fragment.ACTIVITY_CREATED, false);
- mExecutingActions = false;
+ dispatchMoveToState(Fragment.ACTIVITY_CREATED);
}
public void dispatchStart() {
mStateSaved = false;
- mExecutingActions = true;
- moveToState(Fragment.STARTED, false);
- mExecutingActions = false;
+ dispatchMoveToState(Fragment.STARTED);
}
public void dispatchResume() {
mStateSaved = false;
- mExecutingActions = true;
- moveToState(Fragment.RESUMED, false);
- mExecutingActions = false;
+ dispatchMoveToState(Fragment.RESUMED);
}
public void dispatchPause() {
- mExecutingActions = true;
- moveToState(Fragment.STARTED, false);
- mExecutingActions = false;
+ dispatchMoveToState(Fragment.STARTED);
}
public void dispatchStop() {
- mExecutingActions = true;
- moveToState(Fragment.STOPPED, false);
- mExecutingActions = false;
+ dispatchMoveToState(Fragment.STOPPED);
}
public void dispatchDestroyView() {
- mExecutingActions = true;
- moveToState(Fragment.CREATED, false);
- mExecutingActions = false;
+ dispatchMoveToState(Fragment.CREATED);
}
public void dispatchDestroy() {
mDestroyed = true;
execPendingActions();
- mExecutingActions = true;
- moveToState(Fragment.INITIALIZING, false);
- mExecutingActions = false;
+ dispatchMoveToState(Fragment.INITIALIZING);
mHost = null;
mContainer = null;
mParent = null;
}
+ /**
+ * This method is called by dispatch* methods to change the FragmentManager's state.
+ * It calls moveToState directly if the target SDK is older than O. Otherwise, it sets and
+ * clears mExecutingActions to ensure that there is no reentrancy while the
+ * FragmentManager is changing state.
+ *
+ * @param state The new state of the FragmentManager.
+ */
+ private void dispatchMoveToState(int state) {
+ if (mAllowOldReentrantBehavior) {
+ moveToState(state, false);
+ } else {
+ try {
+ mExecutingActions = true;
+ moveToState(state, false);
+ } finally {
+ mExecutingActions = false;
+ }
+ }
+ }
+
public void dispatchMultiWindowModeChanged(boolean isInMultiWindowMode) {
if (mAdded == null) {
return;
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 4db29fb..f9a3ea7 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -37,6 +37,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.TestLooperManager;
import android.os.UserHandle;
import android.util.AndroidRuntimeException;
import android.util.Log;
@@ -110,6 +111,22 @@
}
/**
+ * Called for methods that shouldn't be called by standard apps and
+ * should only be used in instrumentation environments. This is not
+ * security feature as these classes will still be accessible through
+ * reflection, but it will serve as noticeable discouragement from
+ * doing such a thing.
+ */
+ private void checkInstrumenting(String method) {
+ // Check if we have an instrumentation context, as init should only get called by
+ // the system in startup processes that are being instrumented.
+ if (mInstrContext == null) {
+ throw new RuntimeException(method +
+ " cannot be called outside of instrumented processes");
+ }
+ }
+
+ /**
* Called when the instrumentation is starting, before any application code
* has been loaded. Usually this will be implemented to simply call
* {@link #start} to begin the instrumentation thread, which will then
@@ -2024,6 +2041,15 @@
return null;
}
+ /**
+ * Takes control of the execution of messages on the specified looper until
+ * {@link TestLooperManager#release} is called.
+ */
+ public TestLooperManager acquireLooperManager(Looper looper) {
+ checkInstrumenting("acquireLooperManager");
+ return new TestLooperManager(looper);
+ }
+
private final class InstrumentationThread extends Thread {
public InstrumentationThread(String name) {
super(name);
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/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index 91b87d7..790a952 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -172,6 +172,25 @@
return new SecurityEvent[size];
}
};
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ SecurityEvent other = (SecurityEvent) o;
+ return mEvent.equals(other.mEvent);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public int hashCode() {
+ return mEvent.hashCode();
+ }
}
/**
* Retrieve all security logs and return immediately.
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 27bfb51..d5436b7 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -1,5 +1,6 @@
package android.app.assist;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
import android.content.ComponentName;
@@ -639,6 +640,7 @@
static final int FLAGS_HAS_CHILDREN = 0x00100000;
static final int FLAGS_HAS_URL = 0x00080000;
static final int FLAGS_HAS_INPUT_TYPE = 0x00040000;
+ static final int FLAGS_HAS_ENTRY_ID = 0x00020000;
static final int FLAGS_ALL_CONTROL = 0xfff00000;
int mFlags;
@@ -672,7 +674,10 @@
mIdPackage = preader.readString();
}
}
+ } else if ((flags&FLAGS_HAS_ENTRY_ID) != 0) {
+ mIdEntry = preader.readString();
}
+
if ((flags&FLAGS_HAS_AUTOFILL_DATA) != 0) {
mSanitized = in.readInt() == 1;
mAutofillId = in.readParcelable(null);
@@ -745,6 +750,8 @@
int flags = mFlags & ~FLAGS_ALL_CONTROL;
if (mId != View.NO_ID) {
flags |= FLAGS_HAS_ID;
+ } else if (mIdEntry != null ){
+ flags |= FLAGS_HAS_ENTRY_ID;
}
if (mAutofillId != null) {
flags |= FLAGS_HAS_AUTOFILL_DATA;
@@ -805,7 +812,10 @@
pwriter.writeString(mIdPackage);
}
}
+ } else if ((flags&FLAGS_HAS_ENTRY_ID) != 0) {
+ pwriter.writeString(mIdEntry);
}
+
if ((flags&FLAGS_HAS_AUTOFILL_DATA) != 0) {
writeSensitive = mSanitized || !sanitizeOnWrite;
out.writeInt(mSanitized ? 1 : 0);
@@ -887,6 +897,10 @@
* If {@link #getId()} is a resource identifier, this is the entry name of that
* identifier. See {@link android.view.ViewStructure#setId ViewStructure.setId}
* for more information.
+ *
+ * <p>If the node represents a virtual view, it could also represent the entry id set by
+ * {@link android.view.ViewStructure#setIdEntry ViewStructure.setIdEntry}
+ *
*/
public String getIdEntry() {
return mIdEntry;
@@ -1361,6 +1375,11 @@
}
@Override
+ public void setIdEntry(String entryName) {
+ mNode.mIdEntry = entryName;
+ }
+
+ @Override
public void setDimens(int left, int top, int scrollX, int scrollY, int width, int height) {
mNode.mX = left;
mNode.mY = top;
@@ -1583,23 +1602,22 @@
return mNode.mChildren != null ? mNode.mChildren.length : 0;
}
- private void setAutofillId(ViewNode child, boolean forAutoFill, int virtualId) {
- if (forAutoFill) {
- child.mAutofillId = new AutofillId(mNode.mAutofillId, virtualId);
- }
+ @Override
+ public void setAutofillId(@NonNull ViewStructure parent, int virtualId) {
+ mNode.mAutofillId = new AutofillId(parent.getAutofillId(), virtualId);
}
- private ViewStructure newChild(int index, boolean forAutoFill, int virtualId, int flags) {
+ @Override
+ public ViewStructure newChild(int index) {
ViewNode node = new ViewNode();
- setAutofillId(node, forAutoFill, virtualId);
mNode.mChildren[index] = node;
return new ViewNodeBuilder(mAssist, node, false);
}
- private ViewStructure asyncNewChild(int index, boolean forAutoFill, int virtualId) {
+ @Override
+ public ViewStructure asyncNewChild(int index) {
synchronized (mAssist) {
ViewNode node = new ViewNode();
- setAutofillId(node, forAutoFill, virtualId);
mNode.mChildren[index] = node;
ViewNodeBuilder builder = new ViewNodeBuilder(mAssist, node, true);
mAssist.mPendingAsyncChildren.add(builder);
@@ -1608,26 +1626,6 @@
}
@Override
- public ViewStructure newChild(int index) {
- return newChild(index, false, 0, 0);
- }
-
- @Override
- public ViewStructure newChild(int index, int virtualId, int flags) {
- return newChild(index, true, virtualId, flags);
- }
-
- @Override
- public ViewStructure asyncNewChild(int index) {
- return asyncNewChild(index, false, 0);
- }
-
- @Override
- public ViewStructure asyncNewChild(int index, int virtualId, int flags) {
- return asyncNewChild(index, true, virtualId);
- }
-
- @Override
public void asyncCommit() {
synchronized (mAssist) {
if (!mAsync) {
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index 6652eee..78e4c0d 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -189,6 +189,11 @@
*/
public static final int CONSTRAINT_FLAG_DEVICE_IDLE = 1 << 2;
+ /**
+ * @hide
+ */
+ public static final int CONSTRAINT_FLAG_STORAGE_NOT_LOW = 1 << 3;
+
private final int jobId;
private final PersistableBundle extras;
private final Bundle transientExtras;
@@ -273,6 +278,13 @@
}
/**
+ * Whether this job needs the device's storage to not be low.
+ */
+ public boolean isRequireStorageNotLow() {
+ return (constraintFlags & CONSTRAINT_FLAG_STORAGE_NOT_LOW) != 0;
+ }
+
+ /**
* @hide
*/
public int getConstraintFlags() {
@@ -710,15 +722,33 @@
}
/**
+ * Specify that to run this job, the device's available storage must not be low.
+ * This defaults to false. If true, the job will only run when the device is not
+ * in a low storage state, which is generally the point where the user is given a
+ * "low storage" warning.
+ * @param storageNotLow Whether or not the device's available storage must not be low.
+ */
+ public Builder setRequiresStorageNotLow(boolean storageNotLow) {
+ mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_STORAGE_NOT_LOW)
+ | (storageNotLow ? CONSTRAINT_FLAG_STORAGE_NOT_LOW : 0);
+ return this;
+ }
+
+ /**
* Add a new content: URI that will be monitored with a
* {@link android.database.ContentObserver}, and will cause the job to execute if changed.
* If you have any trigger content URIs associated with a job, it will not execute until
* there has been a change report for one or more of them.
+ *
* <p>Note that trigger URIs can not be used in combination with
* {@link #setPeriodic(long)} or {@link #setPersisted(boolean)}. To continually monitor
* for content changes, you need to schedule a new JobInfo observing the same URIs
- * before you finish execution of the JobService handling the most recent changes.</p>
- * <p>Because because setting this property is not compatible with periodic or
+ * before you finish execution of the JobService handling the most recent changes.
+ * Following this pattern will ensure you do not lost any content changes: while your
+ * job is running, the system will continue monitoring for content changes, and propagate
+ * any it sees over to the next job you schedule.</p>
+ *
+ * <p>Because setting this property is not compatible with periodic or
* persisted jobs, doing so will throw an {@link java.lang.IllegalArgumentException} when
* {@link android.app.job.JobInfo.Builder#build()} is called.</p>
*
diff --git a/core/java/android/app/job/JobService.java b/core/java/android/app/job/JobService.java
index 77307b7..f4019ce 100644
--- a/core/java/android/app/job/JobService.java
+++ b/core/java/android/app/job/JobService.java
@@ -250,7 +250,7 @@
public abstract boolean onStopJob(JobParameters params);
/**
- * Callback to inform the JobManager you've finished executing. This can be called from any
+ * Call this to inform the JobManager you've finished executing. This can be called from any
* thread, as it will ultimately be run on your application's main thread. When the system
* receives this message it will release the wakelock being held.
* <p>
diff --git a/core/java/android/bluetooth/le/AdvertisingSetParameters.java b/core/java/android/bluetooth/le/AdvertisingSetParameters.java
index 59fef8d..fe1f425 100644
--- a/core/java/android/bluetooth/le/AdvertisingSetParameters.java
+++ b/core/java/android/bluetooth/le/AdvertisingSetParameters.java
@@ -116,14 +116,16 @@
private final int primaryPhy;
private final int secondaryPhy;
private final boolean connectable;
+ private final boolean scannable;
private final int interval;
private final int txPowerLevel;
- private AdvertisingSetParameters(boolean connectable, boolean isLegacy,
+ private AdvertisingSetParameters(boolean connectable, boolean scannable, boolean isLegacy,
boolean isAnonymous, boolean includeTxPower,
int primaryPhy, int secondaryPhy,
int interval, int txPowerLevel) {
this.connectable = connectable;
+ this.scannable = scannable;
this.isLegacy = isLegacy;
this.isAnonymous = isAnonymous;
this.includeTxPower = includeTxPower;
@@ -135,6 +137,7 @@
private AdvertisingSetParameters(Parcel in) {
connectable = in.readInt() != 0 ? true : false;
+ scannable = in.readInt() != 0 ? true : false;
isLegacy = in.readInt() != 0 ? true : false;
isAnonymous = in.readInt() != 0 ? true : false;
includeTxPower = in.readInt() != 0 ? true : false;
@@ -150,6 +153,11 @@
public boolean isConnectable() { return connectable; }
/**
+ * Returns whether the advertisement will be scannable.
+ */
+ public boolean isScannable() { return scannable; }
+
+ /**
* Returns whether the legacy advertisement will be used.
*/
public boolean isLegacy() { return isLegacy; }
@@ -204,6 +212,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(connectable ? 1 : 0);
+ dest.writeInt(scannable ? 1 : 0);
dest.writeInt(isLegacy ? 1 : 0);
dest.writeInt(isAnonymous ? 1 : 0);
dest.writeInt(includeTxPower ? 1 : 0);
@@ -232,6 +241,7 @@
public static final class Builder {
private boolean connectable = true;
+ private boolean scannable = true;
private boolean isLegacy = false;
private boolean isAnonymous = false;
private boolean includeTxPower = false;
@@ -254,6 +264,18 @@
}
/**
+ * Set whether the advertisement type should be scannable
+ * Legacy advertisements can be both connectable and scannable. Other
+ * advertisements can be scannable only if not connectable.
+ * @param scannable Controls whether the advertisment type will be
+ * scannable (true) or non-scannable (false).
+ */
+ public Builder setScannable(boolean scannable) {
+ this.scannable = scannable;
+ return this;
+ }
+
+ /**
* When set to true, advertising set will advertise 4.x Spec compliant
* advertisements.
*
@@ -371,7 +393,7 @@
* Build the {@link AdvertisingSetParameters} object.
*/
public AdvertisingSetParameters build() {
- return new AdvertisingSetParameters(connectable, isLegacy, isAnonymous,
+ return new AdvertisingSetParameters(connectable, scannable, isLegacy, isAnonymous,
includeTxPower, primaryPhy,
secondaryPhy, interval, txPowerLevel);
}
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
index 4457bdd..ae012d9 100644
--- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -130,6 +130,7 @@
AdvertisingSetParameters.Builder parameters = new AdvertisingSetParameters.Builder();
parameters.setLegacyMode(true);
parameters.setConnectable(isConnectable);
+ parameters.setScannable(true); // legacy advertisements we support are always scannable
if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_POWER) {
parameters.setInterval(1600); // 1s
} else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_BALANCED) {
@@ -157,7 +158,9 @@
AdvertisingSetCallback wrapOldCallback(AdvertiseCallback callback, AdvertiseSettings settings) {
return new AdvertisingSetCallback() {
- public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int status) {
+ @Override
+ public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower,
+ int status) {
if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) {
postStartFailure(callback, status);
return;
@@ -167,7 +170,9 @@
}
/* Legacy advertiser is disabled on timeout */
- public void onAdvertisingEnabled(int advertiserId, boolean enabled, int status) {
+ @Override
+ public void onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enabled,
+ int status) {
if (enabled == true) {
Log.e(TAG, "Legacy advertiser should be only disabled on timeout," +
" but was enabled!");
@@ -400,6 +405,7 @@
IAdvertisingSetCallback wrap(AdvertisingSetCallback callback, Handler handler) {
return new IAdvertisingSetCallback.Stub() {
+ @Override
public void onAdvertisingSetStarted(int advertiserId, int txPower, int status) {
handler.post(new Runnable() {
@Override
@@ -418,6 +424,7 @@
});
}
+ @Override
public void onAdvertisingSetStopped(int advertiserId) {
handler.post(new Runnable() {
@Override
@@ -430,6 +437,7 @@
});
}
+ @Override
public void onAdvertisingEnabled(int advertiserId, boolean enabled, int status) {
handler.post(new Runnable() {
@Override
@@ -440,6 +448,7 @@
});
}
+ @Override
public void onAdvertisingDataSet(int advertiserId, int status) {
handler.post(new Runnable() {
@Override
@@ -450,6 +459,7 @@
});
}
+ @Override
public void onScanResponseDataSet(int advertiserId, int status) {
handler.post(new Runnable() {
@Override
@@ -460,6 +470,7 @@
});
}
+ @Override
public void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status) {
handler.post(new Runnable() {
@Override
@@ -470,6 +481,7 @@
});
}
+ @Override
public void onPeriodicAdvertisingParametersUpdated(int advertiserId, int status) {
handler.post(new Runnable() {
@Override
@@ -480,6 +492,7 @@
});
}
+ @Override
public void onPeriodicAdvertisingDataSet(int advertiserId, int status) {
handler.post(new Runnable() {
@Override
@@ -490,6 +503,7 @@
});
}
+ @Override
public void onPeriodicAdvertisingEnable(int advertiserId, boolean enable, int status) {
handler.post(new Runnable() {
@Override
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 5710ad1..ecdc0ce 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -22,10 +22,13 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.IntentSender;
+import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
+import android.util.Log;
+import java.util.Collections;
import java.util.List;
/**
@@ -40,6 +43,9 @@
*/
public final class CompanionDeviceManager {
+ private static final boolean DEBUG = false; //TODO
+ private static final String LOG_TAG = "CompanionDeviceManager";
+
/**
* A device, returned in the activity result of the {@link IntentSender} received in
* {@link Callback#onDeviceFound}
@@ -81,7 +87,7 @@
/** @hide */
public CompanionDeviceManager(
- @NonNull ICompanionDeviceManager service, @NonNull Context context) {
+ @Nullable ICompanionDeviceManager service, @NonNull Context context) {
mService = service;
mContext = context;
}
@@ -120,6 +126,10 @@
@NonNull AssociationRequest request,
@NonNull Callback callback,
@Nullable Handler handler) {
+ if (!checkFeaturePresent()) {
+ return;
+ }
+
final Handler finalHandler = handler != null
? handler
: new Handler(Looper.getMainLooper());
@@ -153,6 +163,9 @@
*/
@NonNull
public List<String> getAssociations() {
+ if (!checkFeaturePresent()) {
+ return Collections.emptyList();
+ }
try {
return mService.getAssociations(mContext.getPackageName());
} catch (RemoteException e) {
@@ -172,6 +185,9 @@
* @param deviceMacAddress the MAC address of device to disassociate from this app
*/
public void disassociate(@NonNull String deviceMacAddress) {
+ if (!checkFeaturePresent()) {
+ return;
+ }
try {
mService.disassociate(deviceMacAddress, mContext.getPackageName());
} catch (RemoteException e) {
@@ -181,14 +197,28 @@
/** @hide */
public void requestNotificationAccess() {
+ if (!checkFeaturePresent()) {
+ return;
+ }
//TODO implement
throw new UnsupportedOperationException("Not yet implemented");
}
/** @hide */
public boolean haveNotificationAccess() {
+ if (!checkFeaturePresent()) {
+ return false;
+ }
//TODO implement
throw new UnsupportedOperationException("Not yet implemented");
}
+ private boolean checkFeaturePresent() {
+ boolean featurePresent = mService == null;
+ if (!featurePresent && DEBUG) {
+ Log.d(LOG_TAG, "Feature " + PackageManager.FEATURE_COMPANION_DEVICE_SETUP
+ + " not available");
+ }
+ return featurePresent;
+ }
}
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 4ffc6f9..d75c2ee0 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -620,12 +620,17 @@
return MODE_IGNORED;
}
- final String failReason = mExported
- ? " requires " + missingPerm + ", or grantUriPermission()"
- : " requires the provider be exported, or grantUriPermission()";
+ final String suffix;
+ if (android.Manifest.permission.MANAGE_DOCUMENTS.equals(mReadPermission)) {
+ suffix = " requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs";
+ } else if (mExported) {
+ suffix = " requires " + missingPerm + ", or grantUriPermission()";
+ } else {
+ suffix = " requires the provider be exported, or grantUriPermission()";
+ }
throw new SecurityException("Permission Denial: reading "
+ ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + pid
- + ", uid=" + uid + failReason);
+ + ", uid=" + uid + suffix);
}
/** {@hide} */
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index bd31b03..fb86791 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -16,6 +16,8 @@
package android.content;
+import static android.content.ContentProvider.maybeAddUserId;
+
import android.annotation.AnyRes;
import android.annotation.BroadcastBehavior;
import android.annotation.IntDef;
@@ -41,7 +43,6 @@
import android.os.ShellCommand;
import android.os.StrictMode;
import android.os.UserHandle;
-import android.os.storage.StorageManager;
import android.provider.DocumentsContract;
import android.provider.DocumentsProvider;
import android.provider.MediaStore;
@@ -49,7 +50,9 @@
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Log;
+
import com.android.internal.util.XmlUtils;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -67,8 +70,6 @@
import java.util.Objects;
import java.util.Set;
-import static android.content.ContentProvider.maybeAddUserId;
-
/**
* An intent is an abstract description of an operation to be performed. It
* can be used with {@link Context#startActivity(Intent) startActivity} to
@@ -3854,9 +3855,23 @@
public static final String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT";
/**
- * A content: URI holding a stream of data associated with the Intent,
- * used with {@link #ACTION_SEND} to supply the data being sent.
+ * A content: URI holding a stream of data associated with the Intent, used
+ * with {@link #ACTION_SEND} to supply the data being sent.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN} this value
+ * will be automatically promoted to {@link Intent#setClipData(ClipData)}
+ * when that value is not already defined.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#O} this value will be
+ * automatically demoted from {@link Intent#getClipData()} when this value
+ * is not already defined.
+ *
+ * @deprecated apps should use {@link Intent#setClipData(ClipData)} and
+ * {@link Intent#getClipData()} instead of this extra, since
+ * only those APIs can extend temporary permission grants to the
+ * underlying resource.
*/
+ @Deprecated
public static final String EXTRA_STREAM = "android.intent.extra.STREAM";
/**
@@ -5123,6 +5138,8 @@
private Intent mSelector;
private ClipData mClipData;
private int mContentUserHint = UserHandle.USER_CURRENT;
+ /** Token to track instant app launches. Local only; do not copy cross-process. */
+ private String mLaunchToken;
// ---------------------------------------------------------------------
@@ -5143,6 +5160,7 @@
this.mComponent = o.mComponent;
this.mFlags = o.mFlags;
this.mContentUserHint = o.mContentUserHint;
+ this.mLaunchToken = o.mLaunchToken;
if (o.mCategories != null) {
this.mCategories = new ArraySet<String>(o.mCategories);
}
@@ -6379,6 +6397,16 @@
return mContentUserHint;
}
+ /** @hide */
+ public String getLaunchToken() {
+ return mLaunchToken;
+ }
+
+ /** @hide */
+ public void setLaunchToken(String launchToken) {
+ mLaunchToken = launchToken;
+ }
+
/**
* Sets the ClassLoader that will be used when unmarshalling
* any Parcelable values from the extras of this Intent.
@@ -9362,6 +9390,21 @@
mContentUserHint = UserHandle.USER_CURRENT;
}
}
+
+ // If someone is sending us ClipData, but not EXTRA_STREAM, offer to
+ // downgrade that content for older apps to find
+ if (mClipData != null && mClipData.getItemCount() > 0 && !hasExtra(EXTRA_STREAM)) {
+ final String action = getAction();
+ if (ACTION_SEND.equals(action)) {
+ putExtra(EXTRA_STREAM, mClipData.getItemAt(0).getUri());
+ } else if (ACTION_SEND_MULTIPLE.equals(action)) {
+ final ArrayList<Uri> list = new ArrayList<>();
+ for (int i = 0; i < mClipData.getItemCount(); i++) {
+ list.add(mClipData.getItemAt(i).getUri());
+ }
+ putExtra(EXTRA_STREAM, list);
+ }
+ }
}
/**
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 0b3742f..ffc7719 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -920,7 +920,7 @@
/**
* Category for apps which are primarily social apps, such as messaging,
- * communication, or social network apps.
+ * communication, email, or social network apps.
*
* @see #category
*/
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 6dd1833..bb35928 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -46,6 +46,7 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
@@ -58,6 +59,8 @@
import com.android.internal.util.ArrayUtils;
+import dalvik.system.VMRuntime;
+
import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -2233,6 +2236,15 @@
/**
* Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device supports {@link android.companion.CompanionDeviceManager#associate associating}
+ * with devices via {@link android.companion.CompanionDeviceManager}.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_COMPANION_DEVICE_SETUP
+ = "android.software.companion_device_setup";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
* The device can perform backup and restore operations on installed applications.
*/
@SdkConstant(SdkConstantType.FEATURE)
@@ -4252,8 +4264,14 @@
@Deprecated
public List<ResolveInfo> queryBroadcastReceivers(Intent intent,
@ResolveInfoFlags int flags, @UserIdInt int userId) {
- Log.w(TAG, "STAHP USING HIDDEN APIS KTHX");
- return queryBroadcastReceiversAsUser(intent, flags, userId);
+ final String msg = "Shame on you for calling the hidden API "
+ + "queryBroadcastReceivers(). Shame!";
+ if (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.O) {
+ throw new UnsupportedOperationException(msg);
+ } else {
+ Log.d(TAG, msg);
+ return queryBroadcastReceiversAsUser(intent, flags, userId);
+ }
}
/**
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/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 949d644..ccf30ac 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -74,8 +74,6 @@
private static final boolean TRACE_FOR_PRELOAD = false;
private static final boolean TRACE_FOR_MISS_PRELOAD = false;
- private static final int LAYOUT_DIR_CONFIG = ActivityInfo.activityInfoConfigJavaToNative(
- ActivityInfo.CONFIG_LAYOUT_DIRECTION);
private static final int ID_OTHER = 0x01000004;
@@ -636,8 +634,8 @@
}
} else {
if (verifyPreloadConfig(
- changingConfigs, LAYOUT_DIR_CONFIG, value.resourceId, "drawable")) {
- if ((changingConfigs & LAYOUT_DIR_CONFIG) == 0) {
+ changingConfigs, ActivityInfo.CONFIG_LAYOUT_DIRECTION, value.resourceId, "drawable")) {
+ if ((changingConfigs & ActivityInfo.CONFIG_LAYOUT_DIRECTION) == 0) {
// If this resource does not vary based on layout direction,
// we can put it in all of the preload maps.
sPreloadedDrawables[0].put(key, cs);
diff --git a/core/java/android/database/PageViewCursor.java b/core/java/android/database/PageViewCursor.java
index fbd039d..5f42f30 100644
--- a/core/java/android/database/PageViewCursor.java
+++ b/core/java/android/database/PageViewCursor.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
import android.content.ContentResolver;
+import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.util.MathUtils;
@@ -33,22 +34,24 @@
*
* @hide
*/
-public final class PageViewCursor extends CrossProcessCursorWrapper {
+public final class PageViewCursor extends CursorWrapper implements CrossProcessCursor {
/**
- * An extra added to results that are auto-paged using the wrapper.
+ * An in internal extra added to results that are auto-paged using the wrapper.
*/
public static final String EXTRA_AUTO_PAGED = "android.content.extra.AUTO_PAGED";
private static final String TAG = "PageViewCursor";
- private static final boolean DEBUG = false;
- private static final boolean VERBOSE = false;
+ private static final boolean DEBUG = Build.IS_DEBUGGABLE;
+ private static final boolean VERBOSE = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE);
private final int mOffset; // aka first index
private final int mCount;
private final Bundle mExtras;
+ private @Nullable CursorWindow mWindow;
private int mPos = -1;
+ private int mWindowFillCount = 0;
/**
* @see PageViewCursor#wrap(Cursor, Bundle)
@@ -195,6 +198,33 @@
return mCount;
}
+ @Override
+ public boolean getWantsAllOnMoveCalls() {
+ return false; // we want bulk cursor adapter to lift data into a CursorWindow.
+ }
+
+ @Override
+ public CursorWindow getWindow() {
+ assert(mPos == -1 || mPos == 0);
+ if (mWindow == null) {
+ mWindow = new CursorWindow("PageViewCursorWindow");
+ fillWindow(0, mWindow);
+ }
+
+ return mWindow;
+ }
+
+ @Override
+ public void fillWindow(int position, CursorWindow window) {
+ assert(window == mWindow);
+
+ if (mWindowFillCount++ > 0) {
+ Log.w(TAG, "Re-filling window on paged cursor! Reduce ContentResolver.QUERY_ARG_LIMIT");
+ }
+
+ DatabaseUtils.cursorFillWindow(this, position, window);
+ }
+
/**
* Wraps the cursor such that it will honor paging args (if present), AND if the cursor
* does not report paging size.
@@ -209,12 +239,19 @@
|| queryArgs.containsKey(ContentResolver.QUERY_ARG_LIMIT));
if (!hasPagingArgs) {
- if (VERBOSE) Log.d(TAG, "No-wrap: No paging args in request.");
+ if (VERBOSE) Log.v(TAG, "No-wrap: No paging args in request.");
return cursor;
}
if (hasPagedResponseDetails(cursor.getExtras())) {
- if (VERBOSE) Log.d(TAG, "No-wrap. Cursor has paging details.");
+ if (VERBOSE) Log.v(TAG, "No-wrap. Cursor has paging details.");
+ return cursor;
+ }
+
+ // Cursors that want all calls aren't compatible with our way
+ // of doing business. TODO: Cover this case in CTS.
+ if (cursor.getWantsAllOnMoveCalls()) {
+ Log.w(TAG, "Unable to wrap cursor that wants to hear about move calls.");
return cursor;
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 6e202b0..631b77d 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -33,6 +33,7 @@
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.Vibrator;
+import android.os.VibrationEffect;
import android.os.ServiceManager.ServiceNotFoundException;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
@@ -1154,23 +1155,33 @@
return true;
}
- /**
- * @hide
- */
@Override
- public void vibrate(int uid, String opPkg, long milliseconds, AudioAttributes attributes) {
- vibrate(new long[] { 0, milliseconds}, -1);
+ public boolean hasAmplitudeControl() {
+ return false;
}
/**
* @hide
*/
@Override
- public void vibrate(int uid, String opPkg, long[] pattern, int repeat,
- AudioAttributes attributes) {
- if (repeat >= pattern.length) {
- throw new ArrayIndexOutOfBoundsException();
+ public void vibrate(int uid, String opPkg,
+ VibrationEffect effect, AudioAttributes attributes) {
+ long[] pattern;
+ int repeat;
+ if (effect instanceof VibrationEffect.OneShot) {
+ VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect;
+ pattern = new long[] { 0, oneShot.getTiming() };
+ repeat = -1;
+ } else if (effect instanceof VibrationEffect.Waveform) {
+ VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
+ pattern = waveform.getTimings();
+ repeat = waveform.getRepeatIndex();
+ } else {
+ // TODO: Add support for prebaked effects
+ Log.w(TAG, "Pre-baked effects aren't supported on input devices");
+ return;
}
+
try {
mIm.vibrate(mDeviceId, pattern, repeat, mToken);
} catch (RemoteException ex) {
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
index 6f2857d..e59c3ae 100644
--- a/core/java/android/os/IVibratorService.aidl
+++ b/core/java/android/os/IVibratorService.aidl
@@ -16,12 +16,14 @@
package android.os;
+import android.os.VibrationEffect;
+
/** {@hide} */
interface IVibratorService
{
boolean hasVibrator();
- void vibrate(int uid, String opPkg, long milliseconds, int usageHint, IBinder token);
- void vibratePattern(int uid, String opPkg, in long[] pattern, int repeat, int usageHint, IBinder token);
+ boolean hasAmplitudeControl();
+ void vibrate(int uid, String opPkg, in VibrationEffect effect, int usageHint, IBinder token);
void cancelVibrate(IBinder token);
}
diff --git a/core/java/android/os/NullVibrator.java b/core/java/android/os/NullVibrator.java
index 19b452f..b8bdc89 100644
--- a/core/java/android/os/NullVibrator.java
+++ b/core/java/android/os/NullVibrator.java
@@ -38,22 +38,14 @@
return false;
}
- /**
- * @hide
- */
@Override
- public void vibrate(int uid, String opPkg, long milliseconds, AudioAttributes attributes) {
+ public boolean hasAmplitudeControl() {
+ return false;
}
- /**
- * @hide
- */
@Override
- public void vibrate(int uid, String opPkg, long[] pattern, int repeat,
- AudioAttributes attributes) {
- if (repeat >= pattern.length) {
- throw new ArrayIndexOutOfBoundsException();
- }
+ public void vibrate(int uid, String opPkg,
+ VibrationEffect effect, AudioAttributes attributes) {
}
@Override
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index d6688e3..f69c996 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -140,6 +140,12 @@
public static final int WEBVIEW_ZYGOTE_UID = 1051;
/**
+ * Defines the UID used for resource tracking for OTA updates.
+ * @hide
+ */
+ public static final int OTA_UPDATE_UID = 1061;
+
+ /**
* Defines the start of a range of UIDs (and GIDs), going from this
* number to {@link #LAST_APPLICATION_UID} that are reserved for assigning
* to applications.
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index c488811..f776c17 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -32,14 +32,12 @@
private final Binder mToken = new Binder();
public SystemVibrator() {
- mService = IVibratorService.Stub.asInterface(
- ServiceManager.getService("vibrator"));
+ mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
}
public SystemVibrator(Context context) {
super(context);
- mService = IVibratorService.Stub.asInterface(
- ServiceManager.getService("vibrator"));
+ mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
}
@Override
@@ -55,47 +53,33 @@
return false;
}
- /**
- * @hide
- */
@Override
- public void vibrate(int uid, String opPkg, long milliseconds, AudioAttributes attributes) {
+ public boolean hasAmplitudeControl() {
+ if (mService == null) {
+ Log.w(TAG, "Failed to check amplitude control; no vibrator service.");
+ return false;
+ }
+ try {
+ return mService.hasAmplitudeControl();
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ @Override
+ public void vibrate(int uid, String opPkg,
+ VibrationEffect effect, AudioAttributes attributes) {
if (mService == null) {
Log.w(TAG, "Failed to vibrate; no vibrator service.");
return;
}
try {
- mService.vibrate(uid, opPkg, milliseconds, usageForAttributes(attributes), mToken);
+ mService.vibrate(uid, opPkg, effect, usageForAttributes(attributes), mToken);
} catch (RemoteException e) {
Log.w(TAG, "Failed to vibrate.", e);
}
}
- /**
- * @hide
- */
- @Override
- public void vibrate(int uid, String opPkg, long[] pattern, int repeat,
- AudioAttributes attributes) {
- if (mService == null) {
- Log.w(TAG, "Failed to vibrate; no vibrator service.");
- return;
- }
- // catch this here because the server will do nothing. pattern may
- // not be null, let that be checked, because the server will drop it
- // anyway
- if (repeat < pattern.length) {
- try {
- mService.vibratePattern(uid, opPkg, pattern, repeat, usageForAttributes(attributes),
- mToken);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to vibrate.", e);
- }
- } else {
- throw new ArrayIndexOutOfBoundsException();
- }
- }
-
private static int usageForAttributes(AudioAttributes attributes) {
return attributes != null ? attributes.getUsage() : AudioAttributes.USAGE_UNKNOWN;
}
diff --git a/core/java/android/os/TestLooperManager.java b/core/java/android/os/TestLooperManager.java
new file mode 100644
index 0000000..745642e
--- /dev/null
+++ b/core/java/android/os/TestLooperManager.java
@@ -0,0 +1,209 @@
+/*
+ * 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 android.os;
+
+import android.util.ArraySet;
+
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * Blocks a looper from executing any messages, and allows the holder of this object
+ * to control when and which messages get executed until it is released.
+ * <p>
+ * A TestLooperManager should be acquired using
+ * {@link android.app.Instrumentation#acquireLooperManager}. Until {@link #release()} is called,
+ * the Looper thread will not execute any messages except when {@link #execute(Message)} is called.
+ * The test code may use {@link #next()} to acquire messages that have been queued to this
+ * {@link Looper}/{@link MessageQueue} and then {@link #execute} to run any that desires.
+ */
+public class TestLooperManager {
+
+ private static final ArraySet<Looper> sHeldLoopers = new ArraySet<>();
+
+ private final MessageQueue mQueue;
+ private final Looper mLooper;
+ private final LinkedBlockingQueue<MessageExecution> mExecuteQueue = new LinkedBlockingQueue<>();
+
+ private boolean mReleased;
+ private boolean mLooperBlocked;
+
+ /**
+ * @hide
+ */
+ public TestLooperManager(Looper looper) {
+ synchronized (sHeldLoopers) {
+ if (sHeldLoopers.contains(looper)) {
+ throw new RuntimeException("TestLooperManager already held for this looper");
+ }
+ sHeldLoopers.add(looper);
+ }
+ mLooper = looper;
+ mQueue = mLooper.getQueue();
+ // Post a message that will keep the looper blocked as long as we are dispatching.
+ new Handler(looper).post(new LooperHolder());
+ }
+
+ /**
+ * Returns the {@link MessageQueue} this object is wrapping.
+ */
+ public MessageQueue getQueue() {
+ checkReleased();
+ return mQueue;
+ }
+
+ /**
+ * Returns the next message that should be executed by this queue, may block
+ * if no messages are ready.
+ * <p>
+ * Callers should always call {@link #recycle(Message)} on the message when all
+ * interactions with it have completed.
+ */
+ public Message next() {
+ // Wait for the looper block to come up, to make sure we don't accidentally get
+ // the message for the block.
+ while (!mLooperBlocked) {
+ synchronized (this) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ checkReleased();
+ return mQueue.next();
+ }
+
+ /**
+ * Releases the looper to continue standard looping and processing of messages,
+ * no further interactions with TestLooperManager will be allowed after
+ * release() has been called.
+ */
+ public void release() {
+ synchronized (sHeldLoopers) {
+ sHeldLoopers.remove(mLooper);
+ }
+ checkReleased();
+ mReleased = true;
+ mExecuteQueue.add(new MessageExecution());
+ }
+
+ /**
+ * Executes the given message on the Looper thread this wrapper is
+ * attached to.
+ * <p>
+ * Execution will happen on the Looper's thread (whether it is the current thread
+ * or not), but all RuntimeExceptions encountered while executing the message will
+ * be thrown on the calling thread.
+ */
+ public void execute(Message message) {
+ checkReleased();
+ if (Looper.myLooper() == mLooper) {
+ // This is being called from the thread it should be executed on, we can just dispatch.
+ message.target.dispatchMessage(message);
+ } else {
+ MessageExecution execution = new MessageExecution();
+ execution.m = message;
+ synchronized (execution) {
+ mExecuteQueue.add(execution);
+ // Wait for the message to be executed.
+ try {
+ execution.wait();
+ } catch (InterruptedException e) {
+ }
+ if (execution.response != null) {
+ throw new RuntimeException(execution.response);
+ }
+ }
+ }
+ }
+
+ /**
+ * Called to indicate that a Message returned by {@link #next()} has been parsed
+ * and should be recycled.
+ */
+ public void recycle(Message msg) {
+ checkReleased();
+ msg.recycleUnchecked();
+ }
+
+ /**
+ * Returns true if there are any queued messages that match the parameters.
+ *
+ * @param h the value of {@link Message#getTarget()}
+ * @param what the value of {@link Message#what}
+ * @param object the value of {@link Message#obj}, null for any
+ */
+ public boolean hasMessages(Handler h, Object object, int what) {
+ checkReleased();
+ return mQueue.hasMessages(h, what, object);
+ }
+
+ /**
+ * Returns true if there are any queued messages that match the parameters.
+ *
+ * @param h the value of {@link Message#getTarget()}
+ * @param r the value of {@link Message#getCallback()}
+ * @param object the value of {@link Message#obj}, null for any
+ */
+ public boolean hasMessages(Handler h, Object object, Runnable r) {
+ checkReleased();
+ return mQueue.hasMessages(h, r, object);
+ }
+
+ private void checkReleased() {
+ if (mReleased) {
+ throw new RuntimeException("release() has already be called");
+ }
+ }
+
+ private class LooperHolder implements Runnable {
+ @Override
+ public void run() {
+ synchronized (TestLooperManager.this) {
+ mLooperBlocked = true;
+ TestLooperManager.this.notify();
+ }
+ while (!mReleased) {
+ try {
+ final MessageExecution take = mExecuteQueue.take();
+ if (take.m != null) {
+ processMessage(take);
+ }
+ } catch (InterruptedException e) {
+ }
+ }
+ synchronized (TestLooperManager.this) {
+ mLooperBlocked = false;
+ }
+ }
+
+ private void processMessage(MessageExecution mex) {
+ synchronized (mex) {
+ try {
+ mex.m.target.dispatchMessage(mex.m);
+ mex.response = null;
+ } catch (Throwable t) {
+ mex.response = t;
+ }
+ mex.notifyAll();
+ }
+ }
+ }
+
+ private static class MessageExecution {
+ private Message m;
+ private Throwable response;
+ }
+}
diff --git a/core/java/android/os/VibrationEffect.aidl b/core/java/android/os/VibrationEffect.aidl
new file mode 100644
index 0000000..dcc79d7
--- /dev/null
+++ b/core/java/android/os/VibrationEffect.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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 android.os;
+
+parcelable VibrationEffect;
+parcelable VibrationEffect.OneShotVibration;
+parcelable VibrationEffect.WaveformVibration;
+parcelable VibrationEffect.EffectVibration;
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
new file mode 100644
index 0000000..eceaa31
--- /dev/null
+++ b/core/java/android/os/VibrationEffect.java
@@ -0,0 +1,444 @@
+/*
+ * 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 android.os;
+
+import android.hardware.vibrator.V1_0.Constants.Effect;
+
+import java.util.Arrays;
+
+/**
+ * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
+ *
+ * These effects may be any number of things, from single shot vibrations to complex waveforms.
+ */
+public abstract class VibrationEffect implements Parcelable {
+ private static final int PARCEL_TOKEN_ONE_SHOT = 1;
+ private static final int PARCEL_TOKEN_WAVEFORM = 2;
+ private static final int PARCEL_TOKEN_EFFECT = 3;
+
+ /**
+ * The default vibration strength of the device.
+ */
+ public static final int DEFAULT_AMPLITUDE = -1;
+
+ /**
+ * A click effect.
+ *
+ * @see #get(int)
+ * @hide
+ */
+ public static final int EFFECT_CLICK = Effect.CLICK;
+
+ /**
+ * A double click effect.
+ *
+ * @see #get(int)
+ * @hide
+ */
+ public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
+
+ /** @hide to prevent subclassing from outside of the framework */
+ public VibrationEffect() { }
+
+ /**
+ * Create a one shot vibration.
+ *
+ * One shot vibrations will vibrate constantly for the specified period of time at the
+ * specified amplitude, and then stop.
+ *
+ * @param milliseconds The number of milliseconds to vibrate. This must be a positive number.
+ * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or
+ * {@link #DEFAULT_AMPLITUDE}.
+ *
+ * @return The desired effect.
+ */
+ public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
+ VibrationEffect effect = new OneShot(milliseconds, amplitude);
+ effect.validate();
+ return effect;
+ }
+
+ /**
+ * Create a waveform vibration.
+ *
+ * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
+ * each pair, the value in the amplitude array determines the strength of the vibration and the
+ * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
+ * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
+ * <p>
+ * The amplitude array of the generated waveform will be the same size as the given
+ * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
+ * starting with 0. Therefore the first timing value will be the period to wait before turning
+ * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
+ * strength, etc.
+ * </p><p>
+ * To cause the pattern to repeat, pass the index into the timings array at which to start the
+ * repetition, or -1 to disable repeating.
+ * </p>
+ *
+ * @param timings The pattern of alternating on-off timings, starting with off. Timing values
+ * of 0 will cause the timing / amplitude pair to be ignored.
+ * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
+ * want to repeat.
+ *
+ * @return The desired effect.
+ */
+ public static VibrationEffect createWaveform(long[] timings, int repeat) {
+ int[] amplitudes = new int[timings.length];
+ for (int i = 0; i < (timings.length / 2); i++) {
+ amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE;
+ }
+ return createWaveform(timings, amplitudes, repeat);
+ }
+
+ /**
+ * Create a waveform vibration.
+ *
+ * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
+ * each pair, the value in the amplitude array determines the strength of the vibration and the
+ * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
+ * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
+ * </p><p>
+ * To cause the pattern to repeat, pass the index into the timings array at which to start the
+ * repetition, or -1 to disable repeating.
+ * </p>
+ *
+ * @param timings The timing values of the timing / amplitude pairs. Timing values of 0
+ * will cause the pair to be ignored.
+ * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
+ * must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
+ * amplitude value of 0 implies the motor is off.
+ * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
+ * want to repeat.
+ *
+ * @return The desired effect.
+ */
+ public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
+ VibrationEffect effect = new Waveform(timings, amplitudes, repeat);
+ effect.validate();
+ return effect;
+ }
+
+ /**
+ * Get a predefined vibration effect.
+ *
+ * Predefined effects are a set of common vibration effects that should be identical, regardless
+ * of the app they come from, in order to provide a cohesive experience for users across
+ * the entire device. They also may be custom tailored to the device hardware in order to
+ * provide a better experience than you could otherwise build using the generic building
+ * blocks.
+ *
+ * @param effectId The ID of the effect to perform:
+ * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}.
+ *
+ * @return The desired effect.
+ * @hide
+ */
+ public static VibrationEffect get(int effectId) {
+ VibrationEffect effect = new Prebaked(effectId);
+ effect.validate();
+ return effect;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ public abstract void validate();
+
+ /** @hide */
+ public static class OneShot extends VibrationEffect implements Parcelable {
+ private long mTiming;
+ private int mAmplitude;
+
+ public OneShot(Parcel in) {
+ this(in.readLong(), in.readInt());
+ }
+
+ public OneShot(long milliseconds, int amplitude) {
+ mTiming = milliseconds;
+ mAmplitude = amplitude;
+ }
+
+ public long getTiming() {
+ return mTiming;
+ }
+
+ public int getAmplitude() {
+ return mAmplitude;
+ }
+
+ @Override
+ public void validate() {
+ if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) {
+ throw new IllegalArgumentException(
+ "amplitude must either be DEFAULT_AMPLITUDE, " +
+ "or between 1 and 255 inclusive");
+ }
+ if (mTiming <= 0) {
+ throw new IllegalArgumentException("timing must be positive");
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof VibrationEffect.OneShot)) {
+ return false;
+ }
+ VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
+ return other.mTiming == mTiming && other.mAmplitude == mAmplitude;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 37 * (int) mTiming;
+ result = 37 * mAmplitude;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "OneShot{mTiming=" + mTiming +", mAmplitude=" + mAmplitude + "}";
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_ONE_SHOT);
+ out.writeLong(mTiming);
+ out.writeInt(mAmplitude);
+ }
+
+ public static final Parcelable.Creator<OneShot> CREATOR =
+ new Parcelable.Creator<OneShot>() {
+ @Override
+ public OneShot createFromParcel(Parcel in) {
+ // Skip the type token
+ in.readInt();
+ return new OneShot(in);
+ }
+ @Override
+ public OneShot[] newArray(int size) {
+ return new OneShot[size];
+ }
+ };
+ }
+
+ /** @hide */
+ public static class Waveform extends VibrationEffect implements Parcelable {
+ private long[] mTimings;
+ private int[] mAmplitudes;
+ private int mRepeat;
+
+ public Waveform(Parcel in) {
+ this(in.createLongArray(), in.createIntArray(), in.readInt());
+ }
+
+ public Waveform(long[] timings, int[] amplitudes, int repeat) {
+ mTimings = new long[timings.length];
+ System.arraycopy(timings, 0, mTimings, 0, timings.length);
+ mAmplitudes = new int[amplitudes.length];
+ System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length);
+ mRepeat = repeat;
+ }
+
+ public long[] getTimings() {
+ return mTimings;
+ }
+
+ public int[] getAmplitudes() {
+ return mAmplitudes;
+ }
+
+ public int getRepeatIndex() {
+ return mRepeat;
+ }
+
+ @Override
+ public void validate() {
+ if (mTimings.length != mAmplitudes.length) {
+ throw new IllegalArgumentException(
+ "timing and amplitude arrays must be of equal length");
+ }
+ if (!hasNonZeroEntry(mTimings)) {
+ throw new IllegalArgumentException("at least one timing must be non-zero");
+ }
+ for (long timing : mTimings) {
+ if (timing < 0) {
+ throw new IllegalArgumentException("timings must all be >= 0");
+ }
+ }
+ for (int amplitude : mAmplitudes) {
+ if (amplitude < -1 || amplitude > 255) {
+ throw new IllegalArgumentException(
+ "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255");
+ }
+ }
+ if (mRepeat < -1 || mRepeat >= mTimings.length) {
+ throw new IllegalArgumentException("repeat index must be >= -1");
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof VibrationEffect.Waveform)) {
+ return false;
+ }
+ VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
+ return Arrays.equals(mTimings, other.mTimings) &&
+ Arrays.equals(mAmplitudes, other.mAmplitudes) &&
+ mRepeat == other.mRepeat;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 37 * Arrays.hashCode(mTimings);
+ result = 37 * Arrays.hashCode(mAmplitudes);
+ result = 37 * mRepeat;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "Waveform{mTimings=" + Arrays.toString(mTimings) +
+ ", mAmplitudes=" + Arrays.toString(mAmplitudes) +
+ ", mRepeat=" + mRepeat +
+ "}";
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_WAVEFORM);
+ out.writeLongArray(mTimings);
+ out.writeIntArray(mAmplitudes);
+ out.writeInt(mRepeat);
+ }
+
+ private static boolean hasNonZeroEntry(long[] vals) {
+ for (long val : vals) {
+ if (val != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ public static final Parcelable.Creator<Waveform> CREATOR =
+ new Parcelable.Creator<Waveform>() {
+ @Override
+ public Waveform createFromParcel(Parcel in) {
+ // Skip the type token
+ in.readInt();
+ return new Waveform(in);
+ }
+ @Override
+ public Waveform[] newArray(int size) {
+ return new Waveform[size];
+ }
+ };
+ }
+
+ /** @hide */
+ public static class Prebaked extends VibrationEffect implements Parcelable {
+ private int mEffectId;
+
+ public Prebaked(Parcel in) {
+ this(in.readInt());
+ }
+
+ public Prebaked(int effectId) {
+ mEffectId = effectId;
+ }
+
+ public int getId() {
+ return mEffectId;
+ }
+
+ @Override
+ public void validate() {
+ if (mEffectId != EFFECT_CLICK) {
+ throw new IllegalArgumentException("Unknown prebaked effect type");
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof VibrationEffect.Prebaked)) {
+ return false;
+ }
+ VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
+ return mEffectId == other.mEffectId;
+ }
+
+ @Override
+ public int hashCode() {
+ return mEffectId;
+ }
+
+ @Override
+ public String toString() {
+ return "Prebaked{mEffectId=" + mEffectId + "}";
+ }
+
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_EFFECT);
+ out.writeInt(mEffectId);
+ }
+
+ public static final Parcelable.Creator<Prebaked> CREATOR =
+ new Parcelable.Creator<Prebaked>() {
+ @Override
+ public Prebaked createFromParcel(Parcel in) {
+ // Skip the type token
+ in.readInt();
+ return new Prebaked(in);
+ }
+ @Override
+ public Prebaked[] newArray(int size) {
+ return new Prebaked[size];
+ }
+ };
+ }
+
+ public static final Parcelable.Creator<VibrationEffect> CREATOR =
+ new Parcelable.Creator<VibrationEffect>() {
+ @Override
+ public VibrationEffect createFromParcel(Parcel in) {
+ int token = in.readInt();
+ if (token == PARCEL_TOKEN_ONE_SHOT) {
+ return new OneShot(in);
+ } else if (token == PARCEL_TOKEN_WAVEFORM) {
+ return new Waveform(in);
+ } else if (token == PARCEL_TOKEN_EFFECT) {
+ return new Prebaked(in);
+ } else {
+ throw new IllegalStateException(
+ "Unexpected vibration event type token in parcel.");
+ }
+ }
+ @Override
+ public VibrationEffect[] newArray(int size) {
+ return new VibrationEffect[size];
+ }
+ };
+}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index f9b7666..b1f6421 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -55,12 +55,22 @@
public abstract boolean hasVibrator();
/**
+ * Check whether the vibrator has amplitude control.
+ *
+ * @return True if the hardware can control the amplitude of the vibrations, otherwise false.
+ */
+ public abstract boolean hasAmplitudeControl();
+
+ /**
* Vibrate constantly for the specified period of time.
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#VIBRATE}.
*
* @param milliseconds The number of milliseconds to vibrate.
+ *
+ * @deprecated Use {@link #vibrate(VibrationEffect)} instead.
*/
+ @Deprecated
public void vibrate(long milliseconds) {
vibrate(milliseconds, null);
}
@@ -75,9 +85,14 @@
* specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or
* {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for
* vibrations associated with incoming calls.
+ *
+ * @deprecated Use {@link #vibrate(VibrationEffect, AudioAttributes)} instead.
*/
+ @Deprecated
public void vibrate(long milliseconds, AudioAttributes attributes) {
- vibrate(Process.myUid(), mPackageName, milliseconds, attributes);
+ VibrationEffect effect =
+ VibrationEffect.createOneShot(milliseconds, VibrationEffect.DEFAULT_AMPLITUDE);
+ vibrate(effect, attributes);
}
/**
@@ -99,7 +114,10 @@
* @param pattern an array of longs of times for which to turn the vibrator on or off.
* @param repeat the index into pattern at which to repeat, or -1 if
* you don't want to repeat.
+ *
+ * @deprecated Use {@link #vibrate(VibrationEffect)} instead.
*/
+ @Deprecated
public void vibrate(long[] pattern, int repeat) {
vibrate(pattern, repeat, null);
}
@@ -127,26 +145,34 @@
* specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or
* {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for
* vibrations associated with incoming calls.
+ *
+ * @deprecated Use {@link #vibrate(VibrationEffect, AudioAttributes)} instead.
*/
+ @Deprecated
public void vibrate(long[] pattern, int repeat, AudioAttributes attributes) {
- vibrate(Process.myUid(), mPackageName, pattern, repeat, attributes);
+ // This call needs to continue throwing ArrayIndexOutOfBoundsException for compatibility
+ // purposes, whereas VibrationEffect throws an IllegalArgumentException.
+ if (repeat < -1 || repeat >= pattern.length) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ vibrate(VibrationEffect.createWaveform(pattern, repeat), attributes);
+ }
+
+ public void vibrate(VibrationEffect vibe) {
+ vibrate(vibe, null);
+ }
+
+ public void vibrate(VibrationEffect vibe, AudioAttributes attributes) {
+ vibrate(Process.myUid(), mPackageName, vibe, attributes);
}
/**
+ * Like {@link #vibrate(VibrationEffect, AudioAttributes)}, but allowing the caller to specify
+ * that the vibration is owned by someone else.
* @hide
- * Like {@link #vibrate(long, AudioAttributes)}, but allowing the caller to specify that
- * the vibration is owned by someone else.
*/
- public abstract void vibrate(int uid, String opPkg, long milliseconds,
- AudioAttributes attributes);
-
- /**
- * @hide
- * Like {@link #vibrate(long[], int, AudioAttributes)}, but allowing the caller to specify that
- * the vibration is owned by someone else.
- */
- public abstract void vibrate(int uid, String opPkg, long[] pattern, int repeat,
- AudioAttributes attributes);
+ public abstract void vibrate(int uid, String opPkg,
+ VibrationEffect vibe, AudioAttributes attributes);
/**
* Turn the vibrator off.
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index d9c749a..4d14277 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -82,6 +82,7 @@
* @attr ref android.R.styleable#Preference_defaultValue
* @attr ref android.R.styleable#Preference_shouldDisableView
* @attr ref android.R.styleable#Preference_recycleEnabled
+ * @attr ref android.R.styleable#Preference_singleLineTitle
*/
public class Preference implements Comparable<Preference> {
/**
@@ -133,6 +134,7 @@
private boolean mDependencyMet = true;
private boolean mParentDependencyMet = true;
private boolean mRecycleEnabled = true;
+ private boolean mSingleLineTitle = true;
/**
* @see #setShouldDisableView(boolean)
@@ -296,6 +298,10 @@
case com.android.internal.R.styleable.Preference_recycleEnabled:
mRecycleEnabled = a.getBoolean(attr, mRecycleEnabled);
break;
+
+ case com.android.internal.R.styleable.Preference_singleLineTitle:
+ mSingleLineTitle = a.getBoolean(attr, mSingleLineTitle);
+ break;
}
}
a.recycle();
@@ -597,6 +603,7 @@
if (!TextUtils.isEmpty(title)) {
titleView.setText(title);
titleView.setVisibility(View.VISIBLE);
+ titleView.setSingleLine(mSingleLineTitle);
} else {
titleView.setVisibility(View.GONE);
}
@@ -903,6 +910,27 @@
}
/**
+ * Sets whether to constrain the title of this Preference to a single line instead of
+ * letting it wrap onto multiple lines.
+ *
+ * @param singleLineTitle set {@code true} if the title should be constrained to one line
+ */
+ public void setSingleLineTitle(boolean singleLineTitle) {
+ mSingleLineTitle = singleLineTitle;
+ notifyChanged();
+ }
+
+ /**
+ * Gets whether the title of this preference is constrained to a single line.
+ *
+ * @see #setSingleLineTitle(boolean)
+ * @return {@code true} if the title of this preference is constrained to a single line
+ */
+ public boolean isSingleLineTitle() {
+ return mSingleLineTitle;
+ }
+
+ /**
* Returns a unique ID for this Preference. This ID should be unique across all
* Preference objects in a hierarchy.
*
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index dac8354..b4f19d8 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -8983,6 +8983,11 @@
* value of this extra is a {@code String} and should be the value of {@link
* android.accounts.Account#hashCode()} for some account returned by {@link
* android.accounts.AccountManager#getAccounts()}.
+ * <p>
+ * If the extra is not specified, the app can decide which account to use.
+ * <p>
+ * If the account specified in the extra cannot be used for any reason (account missing, not
+ * usable by the app, etc), the message should not be sent.
*/
public static final String EXTRA_SENDER_ACCOUNT_HASH =
"android.provider.extra.SENDER_ACCOUNT_HASH";
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/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index c4684e7..93adf83 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -534,6 +534,14 @@
}
/**
+ * Used to trigger special logic for directories.
+ * @hide
+ */
+ public static final Uri getDirectoryUri(String volumeName) {
+ return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + "/dir");
+ }
+
+ /**
* Fields for master table for all media files.
* Table also contains MediaColumns._ID, DATA, SIZE and DATE_MODIFIED.
*/
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index ebe02c2..47e7803 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -49,12 +49,14 @@
private final ArrayList<AutofillId> mFieldIds;
private final ArrayList<AutofillValue> mFieldValues;
+ private final ArrayList<RemoteViews> mFieldPresentations;
private final RemoteViews mPresentation;
private final IntentSender mAuthentication;
private Dataset(Builder builder) {
mFieldIds = builder.mFieldIds;
mFieldValues = builder.mFieldValues;
+ mFieldPresentations = builder.mFieldPresentations;
mPresentation = builder.mPresentation;
mAuthentication = builder.mAuthentication;
}
@@ -70,6 +72,12 @@
}
/** @hide */
+ public RemoteViews getFieldPresentation(int index) {
+ final RemoteViews customPresentation = mFieldPresentations.get(index);
+ return customPresentation != null ? customPresentation : mPresentation;
+ }
+
+ /** @hide */
public @Nullable RemoteViews getPresentation() {
return mPresentation;
}
@@ -91,6 +99,8 @@
return new StringBuilder("Dataset [")
.append(", fieldIds=").append(mFieldIds)
.append(", fieldValues=").append(mFieldValues)
+ .append(", fieldPresentations=")
+ .append(mFieldPresentations == null ? 0 : mFieldPresentations.size())
.append(", hasPresentation=").append(mPresentation != null)
.append(", hasAuthentication=").append(mAuthentication != null)
.append(']').toString();
@@ -103,6 +113,7 @@
public static final class Builder {
private ArrayList<AutofillId> mFieldIds;
private ArrayList<AutofillValue> mFieldValues;
+ private ArrayList<RemoteViews> mFieldPresentations;
private RemoteViews mPresentation;
private IntentSender mAuthentication;
private boolean mDestroyed;
@@ -118,6 +129,15 @@
}
/**
+ * Creates a new builder for a dataset where each field will be visualized independently.
+ *
+ * <p>When using this constructor, fields must be set through
+ * {@link #setValue(AutofillId, AutofillValue, RemoteViews)}.
+ */
+ public Builder() {
+ }
+
+ /**
* Requires a dataset authentication before autofilling the activity with this dataset.
*
* <p>This method is called when you need to provide an authentication
@@ -175,24 +195,54 @@
* android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
* @param value value to be auto filled.
* @return This builder.
+ * @throws IllegalStateException if the builder was constructed without a presentation
+ * ({@link RemoteViews}).
*/
public @NonNull Builder setValue(@NonNull AutofillId id, @NonNull AutofillValue value) {
throwIfDestroyed();
+ if (mPresentation == null) {
+ throw new IllegalStateException("Dataset presentation not set on constructor");
+ }
+ setValueAndPresentation(id, value, null);
+ return this;
+ }
+
+ /**
+ * Sets the value of a field, usin a custom presentation to visualize it.
+ *
+ * @param id id returned by {@link
+ * android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
+ * @param value value to be auto filled.
+ * @param presentation The presentation used to visualize this field.
+ * @return This builder.
+ */
+ public @NonNull Builder setValue(@NonNull AutofillId id, @NonNull AutofillValue value,
+ @NonNull RemoteViews presentation) {
+ throwIfDestroyed();
+ Preconditions.checkNotNull(presentation, "presentation cannot be null");
+ setValueAndPresentation(id, value, presentation);
+ return this;
+ }
+
+ private void setValueAndPresentation(AutofillId id, AutofillValue value,
+ RemoteViews presentation) {
Preconditions.checkNotNull(id, "id cannot be null");
Preconditions.checkNotNull(value, "value cannot be null");
if (mFieldIds != null) {
final int existingIdx = mFieldIds.indexOf(id);
if (existingIdx >= 0) {
mFieldValues.set(existingIdx, value);
- return this;
+ mFieldPresentations.set(existingIdx, presentation);
+ return;
}
} else {
mFieldIds = new ArrayList<>();
mFieldValues = new ArrayList<>();
+ mFieldPresentations = new ArrayList<>();
}
mFieldIds.add(id);
mFieldValues.add(value);
- return this;
+ mFieldPresentations.add(presentation);
}
/**
@@ -234,6 +284,7 @@
parcel.writeParcelable(mPresentation, flags);
parcel.writeTypedArrayList(mFieldIds, flags);
parcel.writeTypedArrayList(mFieldValues, flags);
+ parcel.writeParcelableList(mFieldPresentations, flags);
parcel.writeParcelable(mAuthentication, flags);
}
@@ -243,15 +294,22 @@
// Always go through the builder to ensure the data ingested by
// the system obeys the contract of the builder to avoid attacks
// using specially crafted parcels.
- final Builder builder = new Builder(parcel.readParcelable(null));
+ final RemoteViews presentation = parcel.readParcelable(null);
+ final Builder builder = (presentation == null)
+ ? new Builder()
+ : new Builder(presentation);
final ArrayList<AutofillId> ids = parcel.readTypedArrayList(null);
final ArrayList<AutofillValue> values = parcel.readTypedArrayList(null);
+ final ArrayList<RemoteViews> presentations = new ArrayList<>();
+ parcel.readParcelableList(presentations, null);
final int idCount = (ids != null) ? ids.size() : 0;
final int valueCount = (values != null) ? values.size() : 0;
for (int i = 0; i < idCount; i++) {
final AutofillId id = ids.get(i);
final AutofillValue value = (valueCount > i) ? values.get(i) : null;
- builder.setValue(id, value);
+ final RemoteViews fieldPresentation = presentations.isEmpty() ? null
+ : presentations.get(i);
+ builder.setValueAndPresentation(id, value, fieldPresentation);
}
builder.setAuthentication(parcel.readParcelable(null));
return builder.build();
diff --git a/core/java/android/service/vr/IPersistentVrStateCallbacks.aidl b/core/java/android/service/vr/IPersistentVrStateCallbacks.aidl
new file mode 100644
index 0000000..7de8b63
--- /dev/null
+++ b/core/java/android/service/vr/IPersistentVrStateCallbacks.aidl
@@ -0,0 +1,24 @@
+/**
+ * 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 android.service.vr;
+
+/** @hide */
+oneway interface IPersistentVrStateCallbacks {
+
+ void onPersistentVrStateChanged(in boolean enabled);
+
+}
diff --git a/core/java/android/service/vr/IVrManager.aidl b/core/java/android/service/vr/IVrManager.aidl
index 6034c18..fce06d6 100644
--- a/core/java/android/service/vr/IVrManager.aidl
+++ b/core/java/android/service/vr/IVrManager.aidl
@@ -17,6 +17,7 @@
package android.service.vr;
import android.service.vr.IVrStateCallbacks;
+import android.service.vr.IPersistentVrStateCallbacks;
/** @hide */
interface IVrManager {
@@ -36,6 +37,20 @@
void unregisterListener(in IVrStateCallbacks cb);
/**
+ * Add a callback to be notified when persistent VR mode state changes.
+ *
+ * @param cb the callback instance to add.
+ */
+ void registerPersistentVrStateListener(in IPersistentVrStateCallbacks cb);
+
+ /**
+ * Remove the callack from the current set of registered callbacks.
+ *
+ * @param cb the callback to remove.
+ */
+ void unregisterPersistentVrStateListener(in IPersistentVrStateCallbacks cb);
+
+ /**
* Return current VR mode state.
*
* @return {@code true} if VR mode is enabled.
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java
index 04596fa..70f9bdd 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -21,6 +21,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.FontListParser;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
@@ -106,8 +107,16 @@
private final float mStyleValue;
public Axis(int tag, float styleValue) {
- this.mTag = tag;
- this.mStyleValue = styleValue;
+ mTag = tag;
+ mStyleValue = styleValue;
+ }
+
+ public Axis(@NonNull String tagString, float styleValue) {
+ if (!FontListParser.isValidTag(tagString)) {
+ throw new IllegalArgumentException("Invalid tag pattern: " + tagString);
+ }
+ mTag = FontListParser.makeTag(tagString);
+ mStyleValue = styleValue;
}
/**
diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java
index 92c70bd..6d4281b 100644
--- a/core/java/android/util/EventLog.java
+++ b/core/java/android/util/EventLog.java
@@ -201,6 +201,29 @@
public void clearError() {
mLastWtf = null;
}
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean equals(Object o) {
+ // Not using ByteBuffer.equals since it takes buffer position into account and we
+ // always use absolute positions here.
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Event other = (Event) o;
+ return Arrays.equals(mBuffer.array(), other.mBuffer.array());
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public int hashCode() {
+ // Not using ByteBuffer.hashCode since it takes buffer position into account and we
+ // always use absolute positions here.
+ return Arrays.hashCode(mBuffer.array());
+ }
}
// We assume that the native methods deal with any concurrency issues.
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 5494377..6dedbde 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -50,7 +50,7 @@
* <li>The real display area specifies the part of the display that contains content
* including the system decorations. Even so, the real display area may be smaller than the
* physical size of the display if the window manager is emulating a smaller display
- * using (adb shell am display-size). Use the following methods to query the
+ * using (adb shell wm size). Use the following methods to query the
* real display area: {@link #getRealSize}, {@link #getRealMetrics}.</li>
* </ul>
* </p><p>
@@ -947,7 +947,7 @@
* The size is adjusted based on the current rotation of the display.
* </p><p>
* The real size may be smaller than the physical size of the screen when the
- * window manager is emulating a smaller display (using adb shell am display-size).
+ * window manager is emulating a smaller display (using adb shell wm size).
* </p>
*
* @param outSize Set to the real size of the display.
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 350675f..5269296 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -7275,11 +7275,16 @@
* fills in all data that can be inferred from the view itself.
* @param flags optional flags (currently {@code 0}).
*/
- @CallSuper
public void onProvideAutofillStructure(ViewStructure structure, int flags) {
onProvideStructureForAssistOrAutofill(structure, true);
}
+ private void setAutofillId(ViewStructure structure) {
+ // The autofill id needs to be unique, but its value doesn't matter,
+ // so it's better to reuse the accessibility id to save space.
+ structure.setAutofillId(getAccessibilityViewId());
+ }
+
private void onProvideStructureForAssistOrAutofill(ViewStructure structure,
boolean forAutofill) {
final int id = mID;
@@ -7299,13 +7304,11 @@
}
if (forAutofill) {
+ setAutofillId(structure);
final @AutofillType int autofillType = getAutofillType();
// Don't need to fill autofill info if view does not support it.
// For example, only TextViews that are editable support autofill
if (autofillType != AUTOFILL_TYPE_NONE) {
- // The autofill id needs to be unique, but its value doesn't matter, so it's better
- // to reuse the accessibility id to save space.
- structure.setAutofillId(getAccessibilityViewId());
structure.setAutofillType(autofillType);
structure.setAutofillHint(getAutofillHint());
structure.setAutofillValue(getAutofillValue());
@@ -7404,6 +7407,9 @@
private void onProvideVirtualStructureForAssistOrAutofill(ViewStructure structure,
boolean forAutofill) {
+ if (forAutofill) {
+ setAutofillId(structure);
+ }
// NOTE: currently flags are only used for AutoFill; if they're used for Assist as well,
// this method should take a boolean with the type of request.
AccessibilityNodeProvider provider = getAccessibilityNodeProvider();
@@ -7671,6 +7677,9 @@
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);
populateVirtualStructure(child, provider, cinfo, forAutofill);
cinfo.recycle();
}
@@ -7707,9 +7716,7 @@
boolean blocked = forAutofill ? isAutofillBlocked() : isAssistBlocked();
if (!blocked) {
if (forAutofill) {
- // The autofill id needs to be unique, but its value doesn't matter,
- // so it's better to reuse the accessibility id to save space.
- structure.setAutofillId(getAccessibilityViewId());
+ setAutofillId(structure);
// NOTE: flags are not currently supported, hence 0
onProvideAutofillStructure(structure, 0);
onProvideAutofillVirtualStructure(structure, 0);
@@ -17892,7 +17899,8 @@
// This case should hopefully never or seldom happen
canvas = new Canvas(bitmap);
}
-
+ boolean enabledHwBitmapsInSwMode = canvas.isHwBitmapsInSwModeEnabled();
+ canvas.setHwBitmapsInSwModeEnabled(true);
if ((backgroundColor & 0xff000000) != 0) {
bitmap.eraseColor(backgroundColor);
}
@@ -17920,6 +17928,7 @@
canvas.restoreToCount(restoreCount);
canvas.setBitmap(null);
+ canvas.setHwBitmapsInSwModeEnabled(enabledHwBitmapsInSwMode);
if (attachInfo != null) {
// Restore the cached Canvas for our siblings
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 989cb13d..0c669f3 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -16,6 +16,7 @@
package android.view;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Matrix;
import android.graphics.Rect;
@@ -41,6 +42,17 @@
public abstract void setId(int id, String packageName, String typeName, String entryName);
/**
+ * Sets the name of the identifier for this view.
+ *
+ * <p>Typically used when adding virtual children (through
+ * {@link #asyncNewChild(int)}) that does not map to Android {@link View}
+ * - otherwise, it's better to call {@link #setId(int, String, String, String)}.
+ *
+ * @param entryName The entry name of the view's identifier, or {@code null} if there is none.
+ */
+ public abstract void setIdEntry(String entryName);
+
+ /**
* Set the basic dimensions of this view.
*
* @param left The view's left position, in pixels relative to its parent's left edge.
@@ -269,20 +281,6 @@
public abstract ViewStructure newChild(int index);
/**
- * Create a new child {@link ViewStructure} in this view for autofill purposes.
- *
- * @param index the index (in the list of children) to put the new child at (see
- * {@link #addChildCount(int)} and {@link #setChildCount(int)}.
- * @param virtualId an opaque ID to the Android System (although it could be meaningful to the
- * {@link View} creating the {@link ViewStructure}), but it's the same id used on
- * {@link View#autofill(android.util.SparseArray)}.
- * @param flags currently {@code 0}.
- *
- * @return Returns an fresh {@link ViewStructure} ready to be filled in.
- */
- public abstract ViewStructure newChild(int index, int virtualId, int flags);
-
- /**
* Like {@link #newChild}, but allows the caller to asynchronously populate the returned
* child. It can transfer the returned {@link ViewStructure} to another thread for it
* to build its content (and children etc). Once done, some thread must call
@@ -293,25 +291,13 @@
public abstract ViewStructure asyncNewChild(int index);
/**
- * Like {@link #newChild(int, int, int)}, but allows the caller to asynchronously
- * populate the returned child.
+ * Sets the {@link AutofillId} for this virtual node.
*
- * <p>It can transfer the returned {@link ViewStructure} to another thread for it to build its
- * content (and children etc).
- *
- * <p>Once done, some thread must call {@link #asyncCommit()} to tell the containing
- * {@link ViewStructure} that the async population is done.
- *
- * @param index the index (in the list of children) to put the new child at (see
- * {@link #addChildCount(int)} and {@link #setChildCount(int)}.
- * @param virtualId an opaque ID to the Android System (although it could be meaningful to the
- * {@link View} creating the {@link ViewStructure}), but it's the same id used on
+ * @param parent parent node.
+ * @param virtualId an opaque ID to the Android System; it's the same id used on
* {@link View#autofill(android.util.SparseArray)}.
- * @param flags currently {@code 0}.
- *
- * @return Returns an fresh {@link ViewStructure} ready to be filled in.
*/
- public abstract ViewStructure asyncNewChild(int index, int virtualId, int flags);
+ public abstract void setAutofillId(@NonNull ViewStructure parent, int virtualId);
/**
* Sets the {@link View#getAutofillType()} that can be used to autofill this node.
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 9a931c2..c2b4138 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -41,6 +41,7 @@
import android.os.RemoteException;
import android.print.PrintDocumentAdapter;
import android.security.KeyChain;
+import android.text.InputType;
import android.util.AttributeSet;
import android.util.Log;
import android.view.DragEvent;
@@ -2611,6 +2612,76 @@
mProvider.getViewDelegate().onProvideVirtualStructure(structure);
}
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The {@link ViewStructure} traditionally represents a {@link View}, while for web pages
+ * it represent HTML nodes. Hence, it's necessary to "map" the HTML properties in a way that is
+ * understood by the {@link android.service.autofill.AutofillService} implementations:
+ *
+ * <ol>
+ * <li>{@link ViewStructure#setClassName(String)} should be use to describe the type of node:
+ * <ol>
+ * <li>If the Android SDK provides a similar View, the full-qualified class name of that
+ * view should be used.
+ * <li>Otherwise, the class name should be {@code HTML.iframe}.
+ * </ol>
+ * <li>The W3C autofill field ({@code autocomplete} tag attribute) maps to
+ * {@link ViewStructure#setAutofillHint(String[])}.
+ * <li>The {@code type} attribute of {@code INPUT} tags maps to
+ * {@link ViewStructure#setInputType(int)}.
+ * <li>The {@code name} attribute maps to {@link ViewStructure#setIdEntry(String)}.
+ * <li>The {@code value} attribute maps to {@link ViewStructure#setText(CharSequence)}.
+ * <li>The {@code placeholder} attribute maps to {@link ViewStructure#setHint(CharSequence)}.
+ * <li>{@link ViewStructure#setDataIsSensitive(boolean)} whould only be called with
+ * {@code true} for form fields whose {@code value} attribute was not pre-loaded.
+ * </ol>
+ *
+ * <p>Example1: an HTML form with 2 fields for username and password.
+ *
+ * <pre class="prettyprint">
+ * <input type="text" name="username" value="mr.sparkle" autocomplete="username" placeholder="Email or username">
+ * <input type="password" name="password" autocomplete="current-password" placeholder="Password">
+ * </pre>
+ *
+ * <p>Would map to:
+ *
+ * <pre class="prettyprint">
+ * ViewStructure username = //structure.newChildForAutofill(...);
+ * username.setClassName("input");
+ * username.setInputType("android.widget.EditText");
+ * username.setAutofillHints("username");
+ * username.setIdEntry("username");
+ * username.setHint("Email or username");
+ * username.setAutofillType(View.AUTOFILL_TYPE_TEXT);
+ * username.setAutofillValue(AutofillValue.forText("mr.sparkle"));
+ * username.setText("mr.sparkle");
+ * username.setDataIsSensitive(true); // Contains real username, which is sensitive
+ *
+ * ViewStructure password = //structure.newChildForAutofill(...);
+ * password.setInputType("android.widget.EditText");
+ * password.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ * password.setAutofillHints("current-password");
+ * password.setIdEntry("password");
+ * password.setHint("Password");
+ * password.setAutofillType(View.AUTOFILL_TYPE_TEXT);
+ * password.setDataIsSensitive(false); // Value is not set
+ * </pre>
+ *
+ * <p>Example2: an IFRAME tag.
+ *
+ * <pre class="prettyprint">
+ * <iframe src="http://example.com/login"/>
+ * </pre>
+ *
+ * <p>Would map to:
+ *
+ * <pre class="prettyprint">
+ * ViewStructure iframe = //structure.newChildForAutofill(...);
+ * iframe.setClassName("HTML.iframe");
+ * iframe.setUrl("http://example.com/login");
+ * </pre>
+ */
@Override
public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {
mProvider.getViewDelegate().onProvideAutofillVirtualStructure(structure, flags);
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 0906d1a..81c2f5d 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -198,7 +198,9 @@
if (sProviderInstance != null) return sProviderInstance;
final int uid = android.os.Process.myUid();
- if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID) {
+ if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID
+ || uid == android.os.Process.PHONE_UID || uid == android.os.Process.NFC_UID
+ || uid == android.os.Process.BLUETOOTH_UID) {
throw new UnsupportedOperationException(
"For security reasons, WebView is not allowed in privileged processes");
}
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index 59881b5..a6a9db4 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -132,7 +132,7 @@
private CharSequence mDescFormat;
- private boolean mAttached;
+ private boolean mRegistered;
private Calendar mTime;
private String mTimeZone;
@@ -252,7 +252,7 @@
}
createTime(mTimeZone);
- // Wait until onAttachedToWindow() to handle the ticker
+ // Wait until registering for events to handle the ticker
chooseFormat(false);
}
@@ -503,12 +503,9 @@
boolean hadSeconds = mHasSeconds;
mHasSeconds = DateFormat.hasSeconds(mFormat);
- if (handleTicker && mAttached && hadSeconds != mHasSeconds) {
- if (hadSeconds) {
- getHandler().removeCallbacks(mTicker);
- } else if (getVisibility() == VISIBLE) {
- mTicker.run();
- }
+ if (handleTicker && mRegistered && hadSeconds != mHasSeconds) {
+ if (hadSeconds) getHandler().removeCallbacks(mTicker);
+ else mTicker.run();
}
}
@@ -520,50 +517,27 @@
}
@Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- if (!mAttached) {
- mAttached = true;
+ public void onVisibilityAggregated(boolean isVisible) {
+ if (!mRegistered && isVisible) {
+ mRegistered = true;
registerReceiver();
registerObserver();
createTime(mTimeZone);
- if (getVisibility() == VISIBLE) {
- if (mHasSeconds) {
- mTicker.run();
- } else {
- onTimeChanged();
- }
+ if (mHasSeconds) {
+ mTicker.run();
+ } else {
+ onTimeChanged();
}
- }
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- if (mAttached) {
+ } else if (mRegistered && !isVisible) {
unregisterReceiver();
unregisterObserver();
getHandler().removeCallbacks(mTicker);
- mAttached = false;
- }
- }
-
- @Override
- public void onVisibilityAggregated(boolean isVisible) {
- if (mAttached) {
- if (isVisible && mHasSeconds) {
- mTicker.run();
- } else {
- getHandler().removeCallbacks(mTicker);
- }
- onTimeChanged();
+ mRegistered = false;
}
}
@@ -586,7 +560,7 @@
}
private void registerObserver() {
- if (mAttached) {
+ if (mRegistered) {
if (mFormatChangeObserver == null) {
mFormatChangeObserver = new FormatChangeObserver(getHandler());
}
@@ -613,11 +587,9 @@
}
private void onTimeChanged() {
- if (getVisibility() == VISIBLE) {
- mTime.setTimeInMillis(System.currentTimeMillis());
- setText(DateFormat.format(mFormat, mTime));
- setContentDescription(DateFormat.format(mDescFormat, mTime));
- }
+ mTime.setTimeInMillis(System.currentTimeMillis());
+ setText(DateFormat.format(mFormat, mTime));
+ setContentDescription(DateFormat.format(mDescFormat, mTime));
}
/** @hide */
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index 83cc9f0..6e9e350 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -129,21 +129,35 @@
}
final File file = FileUtils.buildUniqueFile(parent, mimeType, displayName);
+ final String childId;
if (Document.MIME_TYPE_DIR.equals(mimeType)) {
if (!file.mkdir()) {
throw new IllegalStateException("Failed to mkdir " + file);
}
+ childId = getDocIdForFile(file);
+ addFolderToMediaStore(getFileForDocId(childId, true));
} else {
try {
if (!file.createNewFile()) {
throw new IllegalStateException("Failed to touch " + file);
}
+ childId = getDocIdForFile(file);
} catch (IOException e) {
throw new IllegalStateException("Failed to touch " + file + ": " + e);
}
}
- return getDocIdForFile(file);
+ return childId;
+ }
+
+ private void addFolderToMediaStore(File visibleFolder) {
+ assert(visibleFolder.isDirectory());
+
+ final ContentResolver resolver = getContext().getContentResolver();
+ final Uri uri = MediaStore.Files.getDirectoryUri("external");
+ ContentValues values = new ContentValues();
+ values.put(MediaStore.Files.FileColumns.DATA, visibleFolder.getAbsolutePath());
+ resolver.insert(uri, values);
}
@Override
@@ -193,7 +207,9 @@
private void moveInMediaStore(File oldVisibleFile, File newVisibleFile) {
if (newVisibleFile != null) {
final ContentResolver resolver = getContext().getContentResolver();
- final Uri externalUri = MediaStore.Files.getContentUri("external");
+ final Uri externalUri = newVisibleFile.isDirectory()
+ ? MediaStore.Files.getDirectoryUri("external")
+ : MediaStore.Files.getContentUri("external");
ContentValues values = new ContentValues();
values.put(MediaStore.Files.FileColumns.DATA, newVisibleFile.getAbsolutePath());
diff --git a/core/java/com/android/internal/os/WrapperInit.java b/core/java/com/android/internal/os/WrapperInit.java
index 0a9faa1..b245678 100644
--- a/core/java/com/android/internal/os/WrapperInit.java
+++ b/core/java/com/android/internal/os/WrapperInit.java
@@ -18,6 +18,11 @@
import android.os.Process;
import android.os.Trace;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.system.StructCapUserData;
+import android.system.StructCapUserHeader;
import android.util.BootTimingsTraceLog;
import android.util.Slog;
import com.android.internal.os.Zygote.MethodAndArgsCaller;
@@ -122,6 +127,7 @@
command.append(' ');
command.append(targetSdkVersion);
Zygote.appendQuotedShellArgs(command, args);
+ preserveCapabilities();
Zygote.execShell(command.toString());
}
@@ -159,4 +165,57 @@
RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
}
+
+ /**
+ * Copy current capabilities to ambient capabilities. This is required for apps using
+ * capabilities, as execv will re-evaluate the capability set, and the set of sh is
+ * empty. Ambient capabilities have to be set to inherit them effectively.
+ *
+ * Note: This is BEST EFFORT ONLY. In case capabilities can't be raised, this function
+ * will silently return. In THIS CASE ONLY, as this is a development feature, it
+ * is better to return and try to run anyways, instead of blocking the wrapped app.
+ * This is acceptable here as failure will leave the wrapped app with strictly less
+ * capabilities, which may make it crash, but not exceed its allowances.
+ */
+ private static void preserveCapabilities() {
+ StructCapUserHeader header = new StructCapUserHeader(
+ OsConstants._LINUX_CAPABILITY_VERSION_3, 0);
+ StructCapUserData[] data;
+ try {
+ data = Os.capget(header);
+ } catch (ErrnoException e) {
+ Slog.e(RuntimeInit.TAG, "RuntimeInit: Failed capget", e);
+ return;
+ }
+
+ if (data[0].permitted != data[0].inheritable ||
+ data[1].permitted != data[1].inheritable) {
+ data[0] = new StructCapUserData(data[0].effective, data[0].permitted,
+ data[0].permitted);
+ data[1] = new StructCapUserData(data[1].effective, data[1].permitted,
+ data[1].permitted);
+ try {
+ Os.capset(header, data);
+ } catch (ErrnoException e) {
+ Slog.e(RuntimeInit.TAG, "RuntimeInit: Failed capset", e);
+ return;
+ }
+ }
+
+ for (int i = 0; i < 64; i++) {
+ int dataIndex = OsConstants.CAP_TO_INDEX(i);
+ int capMask = OsConstants.CAP_TO_MASK(i);
+ if ((data[dataIndex].inheritable & capMask) != 0) {
+ try {
+ Os.prctl(OsConstants.PR_CAP_AMBIENT, OsConstants.PR_CAP_AMBIENT_RAISE, i, 0,
+ 0);
+ } catch (ErrnoException ex) {
+ // Only log here. Try to run the wrapped application even without this
+ // ambient capability. It may crash after fork, but at least we'll try.
+ Slog.e(RuntimeInit.TAG, "RuntimeInit: Failed to raise ambient capability "
+ + i, ex);
+ }
+ }
+ }
+ }
}
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/jni/Android.mk b/core/jni/Android.mk
index af5fca2..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 \
@@ -286,7 +283,6 @@
libhwbinder \
libvintf \
libnativewindow \
- libtextclassifier \
LOCAL_SHARED_LIBRARIES += \
libhwui \
diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index fb7c5c4..4e68602 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -34,12 +34,16 @@
#include <hwui/MinikinSkia.h>
#include <hwui/Typeface.h>
+#include <utils/FatVector.h>
#include <minikin/FontFamily.h>
#include <memory>
namespace android {
+// Must be same with Java constant in Typeface.Builder. See Typeface.java
+constexpr jint RESOLVE_BY_FONT_TABLE = -1;
+
struct NativeFamilyBuilder {
uint32_t langId;
int variant;
@@ -81,24 +85,53 @@
delete family;
}
-static void addSkTypeface(jlong builderPtr, sk_sp<SkTypeface> face, const void* fontData,
- size_t fontSize, int ttcIndex, jint givenWeight, jboolean givenItalic) {
+static bool addSkTypeface(NativeFamilyBuilder* builder, sk_sp<SkData>&& data, int ttcIndex,
+ jint givenWeight, jint givenItalic) {
+ uirenderer::FatVector<SkFontMgr::FontParameters::Axis, 2> skiaAxes;
+ for (const auto& axis : builder->axes) {
+ skiaAxes.emplace_back(SkFontMgr::FontParameters::Axis{axis.axisTag, axis.value});
+ }
+
+ const size_t fontSize = data->size();
+ const void* fontPtr = data->data();
+ std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(std::move(data)));
+
+ SkFontMgr::FontParameters params;
+ params.setCollectionIndex(ttcIndex);
+ params.setAxes(skiaAxes.data(), skiaAxes.size());
+
+ sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
+ sk_sp<SkTypeface> face(fm->createFromStream(fontData.release(), params));
+ if (face == NULL) {
+ ALOGE("addFont failed to create font, invalid request");
+ builder->axes.clear();
+ return false;
+ }
std::shared_ptr<minikin::MinikinFont> minikinFont =
- std::make_shared<MinikinFontSkia>(std::move(face), fontData, fontSize, ttcIndex,
- std::vector<minikin::FontVariation>());
- NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr);
+ std::make_shared<MinikinFontSkia>(std::move(face), fontPtr, fontSize, ttcIndex,
+ builder->axes);
+
int weight = givenWeight / 100;
- bool italic = givenItalic;
- if (weight == 0) {
- if (!minikin::FontFamily::analyzeStyle(minikinFont, &weight, &italic)) {
+ bool italic = givenItalic == 1;
+ if (givenWeight == RESOLVE_BY_FONT_TABLE || givenItalic == RESOLVE_BY_FONT_TABLE) {
+ int os2Weight;
+ bool os2Italic;
+ if (!minikin::FontFamily::analyzeStyle(minikinFont, &os2Weight, &os2Italic)) {
ALOGE("analyzeStyle failed. Using default style");
- weight = 4;
- italic = false;
+ os2Weight = 4;
+ os2Italic = false;
+ }
+ if (givenWeight == RESOLVE_BY_FONT_TABLE) {
+ weight = os2Weight;
+ }
+ if (givenItalic == RESOLVE_BY_FONT_TABLE) {
+ italic = os2Italic;
}
}
- builder->fonts.push_back(minikin::Font(
- std::move(minikinFont), minikin::FontStyle(weight, italic)));
+ builder->fonts.push_back(minikin::Font(minikinFont, minikin::FontStyle(weight, italic)));
+ builder->axes.clear();
+ return true;
}
static void release_global_ref(const void* /*data*/, void* context) {
@@ -125,80 +158,47 @@
}
static jboolean FontFamily_addFont(JNIEnv* env, jobject clazz, jlong builderPtr, jobject bytebuf,
- jint ttcIndex) {
+ jint ttcIndex, jint weight, jint isItalic) {
NPE_CHECK_RETURN_ZERO(env, bytebuf);
+ NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr);
const void* fontPtr = env->GetDirectBufferAddress(bytebuf);
if (fontPtr == NULL) {
ALOGE("addFont failed to create font, buffer invalid");
+ builder->axes.clear();
return false;
}
jlong fontSize = env->GetDirectBufferCapacity(bytebuf);
if (fontSize < 0) {
ALOGE("addFont failed to create font, buffer size invalid");
+ builder->axes.clear();
return false;
}
jobject fontRef = MakeGlobalRefOrDie(env, bytebuf);
sk_sp<SkData> data(SkData::MakeWithProc(fontPtr, fontSize,
release_global_ref, reinterpret_cast<void*>(fontRef)));
- std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(std::move(data)));
-
- SkFontMgr::FontParameters params;
- params.setCollectionIndex(ttcIndex);
-
- sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
- sk_sp<SkTypeface> face(fm->createFromStream(fontData.release(), params));
- if (face == NULL) {
- ALOGE("addFont failed to create font");
- return false;
- }
- addSkTypeface(builderPtr, std::move(face), fontPtr, (size_t)fontSize, ttcIndex, 0, false);
- return true;
+ return addSkTypeface(builder, std::move(data), ttcIndex, weight, isItalic);
}
static jboolean FontFamily_addFontWeightStyle(JNIEnv* env, jobject clazz, jlong builderPtr,
- jobject font, jint ttcIndex, jint weight, jboolean isItalic) {
+ jobject font, jint ttcIndex, jint weight, jint isItalic) {
NPE_CHECK_RETURN_ZERO(env, font);
-
NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr);
-
- // Declare axis native type.
- std::vector<SkFontMgr::FontParameters::Axis> skiaAxes;
- skiaAxes.reserve(builder->axes.size());
- for (const minikin::FontVariation& minikinAxis : builder->axes) {
- skiaAxes.push_back({minikinAxis.axisTag, minikinAxis.value});
- }
-
const void* fontPtr = env->GetDirectBufferAddress(font);
if (fontPtr == NULL) {
ALOGE("addFont failed to create font, buffer invalid");
+ builder->axes.clear();
return false;
}
jlong fontSize = env->GetDirectBufferCapacity(font);
if (fontSize < 0) {
ALOGE("addFont failed to create font, buffer size invalid");
+ builder->axes.clear();
return false;
}
jobject fontRef = MakeGlobalRefOrDie(env, font);
sk_sp<SkData> data(SkData::MakeWithProc(fontPtr, fontSize,
release_global_ref, reinterpret_cast<void*>(fontRef)));
- std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(std::move(data)));
-
- SkFontMgr::FontParameters params;
- params.setCollectionIndex(ttcIndex);
- params.setAxes(skiaAxes.data(), skiaAxes.size());
-
- sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
- sk_sp<SkTypeface> face(fm->createFromStream(fontData.release(), params));
- if (face == NULL) {
- ALOGE("addFont failed to create font, invalid request");
- return false;
- }
- std::shared_ptr<minikin::MinikinFont> minikinFont =
- std::make_shared<MinikinFontSkia>(std::move(face), fontPtr, fontSize, ttcIndex,
- std::vector<minikin::FontVariation>());
- builder->fonts.push_back(minikin::Font(std::move(minikinFont),
- minikin::FontStyle(weight / 100, isItalic)));
- return true;
+ return addSkTypeface(builder, std::move(data), ttcIndex, weight, isItalic);
}
static void releaseAsset(const void* ptr, void* context) {
@@ -206,18 +206,21 @@
}
static jboolean FontFamily_addFontFromAssetManager(JNIEnv* env, jobject, jlong builderPtr,
- jobject jassetMgr, jstring jpath, jint cookie, jboolean isAsset, jint weight,
- jboolean isItalic) {
+ jobject jassetMgr, jstring jpath, jint cookie, jboolean isAsset, jint ttcIndex,
+ jint weight, jint isItalic) {
NPE_CHECK_RETURN_ZERO(env, jassetMgr);
NPE_CHECK_RETURN_ZERO(env, jpath);
+ NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr);
AssetManager* mgr = assetManagerForJavaObject(env, jassetMgr);
if (NULL == mgr) {
+ builder->axes.clear();
return false;
}
ScopedUtfChars str(env, jpath);
if (str.c_str() == nullptr) {
+ builder->axes.clear();
return false;
}
@@ -230,27 +233,19 @@
}
if (NULL == asset) {
+ builder->axes.clear();
return false;
}
const void* buf = asset->getBuffer(false);
if (NULL == buf) {
delete asset;
+ builder->axes.clear();
return false;
}
- size_t bufSize = asset->getLength();
sk_sp<SkData> data(SkData::MakeWithProc(buf, asset->getLength(), releaseAsset, asset));
- std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(std::move(data)));
-
- sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
- sk_sp<SkTypeface> face(fm->createFromStream(fontData.release(), SkFontMgr::FontParameters()));
- if (face == NULL) {
- ALOGE("addFontFromAsset failed to create font %s", str.c_str());
- return false;
- }
-
- addSkTypeface(builderPtr, std::move(face), buf, bufSize, 0 /* ttc index */, weight, isItalic);
+ addSkTypeface(builder, std::move(data), ttcIndex, weight, isItalic);
return true;
}
@@ -266,10 +261,10 @@
{ "nCreateFamily", "(J)J", (void*)FontFamily_create },
{ "nAbort", "(J)V", (void*)FontFamily_abort },
{ "nUnrefFamily", "(J)V", (void*)FontFamily_unref },
- { "nAddFont", "(JLjava/nio/ByteBuffer;I)Z", (void*)FontFamily_addFont },
- { "nAddFontWeightStyle", "(JLjava/nio/ByteBuffer;IIZ)Z",
+ { "nAddFont", "(JLjava/nio/ByteBuffer;III)Z", (void*)FontFamily_addFont },
+ { "nAddFontWeightStyle", "(JLjava/nio/ByteBuffer;III)Z",
(void*)FontFamily_addFontWeightStyle },
- { "nAddFontFromAssetManager", "(JLandroid/content/res/AssetManager;Ljava/lang/String;IZIZ)Z",
+ { "nAddFontFromAssetManager", "(JLandroid/content/res/AssetManager;Ljava/lang/String;IZIII)Z",
(void*)FontFamily_addFontFromAssetManager },
{ "nAddAxisValue", "(JIF)V", (void*)FontFamily_addAxisValue },
};
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/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 58e4051..a3edf8d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -486,6 +486,7 @@
<protected-broadcast android:name="android.intent.action.ACTION_RADIO_OFF" />
<protected-broadcast android:name="android.accounts.LOGIN_ACCOUNTS_CHANGED" />
+ <protected-broadcast android:name="android.accounts.action.VISIBLE_ACCOUNTS_CHANGED" />
<protected-broadcast android:name="com.android.sync.SYNC_CONN_STATUS_CHANGED" />
<protected-broadcast android:name="com.android.phone.SIP_INCOMING_CALL" />
@@ -3606,6 +3607,10 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
+ <service android:name="com.android.server.PreloadsFileCacheExpirationJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
</application>
</manifest>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 4432e3c..e1068ea 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4100,8 +4100,9 @@
</declare-styleable>
<declare-styleable name="ProgressBar">
+ <!-- Defines the minimum value. -->
<attr name="min" format="integer" />
- <!-- Defines the maximum value the progress can take. -->
+ <!-- Defines the maximum value. -->
<attr name="max" format="integer" />
<!-- Defines the default progress value, between 0 and max. -->
<attr name="progress" format="integer" />
@@ -7198,6 +7199,9 @@
<!-- Whether the preference has enabled to have its view recycled when used in the list
view. This is true by default. -->
<attr name="recycleEnabled" format="boolean" />
+ <!-- Whether to use single line for the preference title text. By default, preference title
+ will be constrained to one line, so the default value of this attribute is true. -->
+ <attr name="singleLineTitle" format="boolean" />
</declare-styleable>
<!-- Base attributes available to CheckBoxPreference. -->
@@ -8565,6 +8569,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/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index cf6bd9e..b9409f2 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1422,7 +1422,7 @@
<enum name="video" value="2" />
<!-- Apps which primarily work with images or photos, such as camera or gallery apps. -->
<enum name="image" value="3" />
- <!-- Apps which are primarily social apps, such as messaging, communication, or social network apps. -->
+ <!-- Apps which are primarily social apps, such as messaging, communication, email, or social network apps. -->
<enum name="social" value="4" />
<!-- Apps which are primarily news apps, such as newspapers, magazines, or sports apps. -->
<enum name="news" value="5" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 9b2aba3..68e766e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2508,6 +2508,9 @@
<!-- How long history of previous vibrations should be kept for the dumpsys. -->
<integer name="config_previousVibrationsDumpLimit">20</integer>
+ <!-- The default vibration strength, must be between 1 and 255 inclusive. -->
+ <integer name="config_defaultVibrationAmplitude">255</integer>
+
<!-- Number of retries Cell Data should attempt for a given error code before
restarting the modem.
Error codes not listed will not lead to modem restarts.
@@ -2688,6 +2691,10 @@
<!-- List of packages to enable in carrier demo mode (comma separated). -->
<string name="config_carrierDemoModePackages" translatable="false"></string>
+ <!-- Number of days preloaded file cache should be preserved on a device before it can be
+ deleted -->
+ <integer name="config_keepPreloadsMinDays">7</integer>
+
<!-- Flag indicating whether round icons should be parsed from the application manifest. -->
<bool name="config_useRoundIcon">false</bool>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index f965c69..baad148 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2808,6 +2808,8 @@
<public name="recycleEnabled"/>
<public name="isStatic" />
<public name="isFeatureSplit" />
+ <public name="singleLineTitle" />
+ <public name="fontProviderCerts" />
</public-group>
<public-group type="style" first-id="0x010302e0">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index a9a7116..d4db258 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -209,8 +209,7 @@
<!-- Displayed to tell the user that they should switch their network preference. -->
<string name="NetworkPreferenceSwitchTitle">Can\u2019t reach network</string>
<!-- Displayed to tell the user that they should switch their network preference. -->
- <string name="NetworkPreferenceSwitchSummary">To improve reception, try changing the type selected at Settings > Cellular networks > Preferred network type."</string>
-
+ <string name="NetworkPreferenceSwitchSummary">To improve reception, try changing the type selected at System > Network & Internet > Mobile networks > Preferred network type."</string>
<!-- Displayed to tell the user that peer changed TTY mode -->
<string name="peerTtyModeFull">Peer requested TTY Mode FULL</string>
@@ -263,7 +262,7 @@
<string-array name="wfcOperatorErrorNotificationMessages">
<item>Register with your carrier</item>
</string-array>
- <!-- Template for showing cellular network operator name while WFC is active -->
+ <!-- Template for showing mobile network operator name while WFC is active -->
<string-array name="wfcSpnFormats">
<item>%s</item>
<item>%s Wi-Fi Calling</item>
@@ -272,8 +271,8 @@
<string name="wifi_calling_off_summary">Off</string>
<!-- WFC, summary for Wi-Fi Preferred -->
<string name="wfc_mode_wifi_preferred_summary">Wi-Fi preferred</string>
- <!-- WFC, summary for Cellular Preferred -->
- <string name="wfc_mode_cellular_preferred_summary">Cellular preferred</string>
+ <!-- WFC, summary for Mobile data Preferred -->
+ <string name="wfc_mode_cellular_preferred_summary">Mobile preferred</string>
<!-- WFC, summary for Wi-Fi Only -->
<string name="wfc_mode_wifi_only_summary">Wi-Fi only</string>
@@ -2972,18 +2971,18 @@
<!-- A notification is shown when the user connects to a Wi-Fi network and the system detects that that network has no Internet access. This is the notification's message. -->
<string name="wifi_no_internet_detailed">Tap for options</string>
- <!-- A notification might be shown if the device switches to another network type (e.g., cellular data) because it detects that the network it was using (e.g., Wi-Fi) has lost Internet connectivity. This is the notification's title. %1$s is the network type that the device switched to, e.g., cellular data. It is one of the strings in the network_switch_type_name array. -->
+ <!-- A notification might be shown if the device switches to another network type (e.g., mobile data) because it detects that the network it was using (e.g., Wi-Fi) has lost Internet connectivity. This is the notification's title. %1$s is the network type that the device switched to, e.g., cellular data. It is one of the strings in the network_switch_type_name array. -->
<string name="network_switch_metered">Switched to <xliff:g id="network_type">%1$s</xliff:g></string>
- <!-- A notification might be shown if the device switches to another network type (e.g., cellular data) because it detects that the network it was using (e.g., Wi-Fi) has lost Internet connectivity. This is the notification's message. %1$s is the network that the device switched to, e.g., cellular data. %2$s is the network type the device switched from, e.g., Wi-Fi. Both are strings in the network_switch_type_name array. -->
+ <!-- A notification might be shown if the device switches to another network type (e.g., mobile data) because it detects that the network it was using (e.g., Wi-Fi) has lost Internet connectivity. This is the notification's message. %1$s is the network that the device switched to, e.g., cellular data. %2$s is the network type the device switched from, e.g., Wi-Fi. Both are strings in the network_switch_type_name array. -->
<string name="network_switch_metered_detail">Device uses <xliff:g id="new_network">%1$s</xliff:g> when <xliff:g id="previous_network">%2$s</xliff:g> has no Internet access. Charges may apply.</string>
- <!-- A toast might be shown if the device switches to another network type (e.g., cellular data) because it detects that the network it was using (e.g., Wi-Fi) has lost Internet connectivity. This is the text of the toast. %1$s is the network that the device switched from, e.g., Wi-Fi. %2$s is the network type the device switched from, e.g., cellular data. Both are strings in the network_switch_type_name array. -->
+ <!-- A toast might be shown if the device switches to another network type (e.g., mobile data) because it detects that the network it was using (e.g., Wi-Fi) has lost Internet connectivity. This is the text of the toast. %1$s is the network that the device switched from, e.g., Wi-Fi. %2$s is the network type the device switched from, e.g., cellular data. Both are strings in the network_switch_type_name array. -->
<string name="network_switch_metered_toast">Switched from <xliff:g id="previous_network">%1$s</xliff:g> to <xliff:g id="new_network">%2$s</xliff:g></string>
<!-- Network type names used in the network_switch_metered and network_switch_metered_detail strings. These must be kept in the sync with the values NetworkCapabilities.TRANSPORT_xxx values, and in the same order. -->
<string-array name="network_switch_type_name">
- <item>cellular data</item>
+ <item>mobile data</item>
<item>Wi-Fi</item>
<item>Bluetooth</item>
<item>Ethernet</item>
@@ -3067,13 +3066,13 @@
<!-- See SIM_REMOVED_DIALOG. This is the title of that dialog. -->
<string name="sim_removed_title">SIM card removed</string>
<!-- See SIM_REMOVED_DIALOG. This is the message of that dialog. -->
- <string name="sim_removed_message">The cellular network will be unavailable until you restart with a valid SIM card inserted.</string>
+ <string name="sim_removed_message">The mobile network will be unavailable until you restart with a valid SIM card inserted.</string>
<!-- See SIM_REMOVED_DIALOG. This is the button of that dialog. -->
<string name="sim_done_button">Done</string>
<!-- See SIM_ADDED_DIALOG. This is the title of that dialog. -->
<string name="sim_added_title">SIM card added</string>
<!-- See SIM_ADDED_DIALOG. This is the message of that dialog. -->
- <string name="sim_added_message">Restart your device to access the cellular network.</string>
+ <string name="sim_added_message">Restart your device to access the mobile network.</string>
<!-- See SIM_ADDED_DIALOG. This is the button of that dialog. -->
<string name="sim_restart_button">Restart</string>
<!-- See Carrier_App_Dialog. This is the message of that dialog. -->
@@ -3628,7 +3627,7 @@
<!-- Notification title when 4G data usage has exceeded limit threshold, and has been disabled. [CHAR LIMIT=32] -->
<string name="data_usage_4g_limit_title">4G data limit reached</string>
<!-- Notification title when mobile data usage has exceeded limit threshold, and has been disabled. [CHAR LIMIT=32] -->
- <string name="data_usage_mobile_limit_title">Cellular data limit reached</string>
+ <string name="data_usage_mobile_limit_title">Mobile data limit reached</string>
<!-- Notification title when Wi-Fi data usage has exceeded limit threshold, and has been disabled. [CHAR LIMIT=32] -->
<string name="data_usage_wifi_limit_title">Wi-Fi data limit reached</string>
<!-- Notification body when data usage has exceeded limit threshold, and has been disabled. -->
@@ -3639,7 +3638,7 @@
<!-- Notification title when 4G data usage has exceeded limit threshold. [CHAR LIMIT=32] -->
<string name="data_usage_4g_limit_snoozed_title">4G data limit exceeded</string>
<!-- Notification title when mobile data usage has exceeded limit threshold. [CHAR LIMIT=32] -->
- <string name="data_usage_mobile_limit_snoozed_title">Cellular data limit exceeded</string>
+ <string name="data_usage_mobile_limit_snoozed_title">Mobile data limit exceeded</string>
<!-- Notification title when Wi-Fi data usage has exceeded limit threshold. [CHAR LIMIT=32] -->
<string name="data_usage_wifi_limit_snoozed_title">Wi-Fi data limit exceeded</string>
<!-- Notification body when data usage has exceeded limit threshold. -->
@@ -4538,7 +4537,7 @@
<string name="app_category_video">Movies & Video</string>
<!-- Category title for apps which primarily work with images or photos, such as camera or gallery apps. [CHAR LIMIT=32] -->
<string name="app_category_image">Photos & Images</string>
- <!-- Category title for apps which are primarily social apps, such as messaging, communication, or social network apps. [CHAR LIMIT=32] -->
+ <!-- Category title for apps which are primarily social apps, such as messaging, communication, email, or social network apps. [CHAR LIMIT=32] -->
<string name="app_category_social">Social & Communication</string>
<!-- Category title for apps which are primarily news apps, such as newspapers, magazines, or sports apps. [CHAR LIMIT=32] -->
<string name="app_category_news">News & Magazines</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6acccd1a..4ef3922 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -421,6 +421,7 @@
<java-symbol type="integer" name="config_valid_wappush_index" />
<java-symbol type="integer" name="config_overrideHasPermanentMenuKey" />
<java-symbol type="integer" name="config_mdc_initial_max_retry" />
+ <java-symbol type="integer" name="config_keepPreloadsMinDays" />
<java-symbol type="bool" name="config_hasPermanentDpad" />
<java-symbol type="color" name="tab_indicator_text_v4" />
@@ -1816,6 +1817,7 @@
<java-symbol type="integer" name="config_notificationsBatteryMediumARGB" />
<java-symbol type="integer" name="config_notificationServiceArchiveSize" />
<java-symbol type="integer" name="config_previousVibrationsDumpLimit" />
+ <java-symbol type="integer" name="config_defaultVibrationAmplitude" />
<java-symbol type="integer" name="config_radioScanningTimeout" />
<java-symbol type="integer" name="config_screenBrightnessSettingMinimum" />
<java-symbol type="integer" name="config_screenBrightnessSettingMaximum" />
diff --git a/core/res/res/xml/time_zones_by_country.xml b/core/res/res/xml/time_zones_by_country.xml
index a685e2b..6c1ce44 100644
--- a/core/res/res/xml/time_zones_by_country.xml
+++ b/core/res/res/xml/time_zones_by_country.xml
@@ -336,6 +336,10 @@
<timezone code="ck">Pacific/Rarotonga</timezone>
+ <!-- CHILE, -3:00 -->
+
+ <timezone code="cl">America/Punta_Arenas</timezone>
+
<!-- CHILE, -4:00 -->
<timezone code="cl">America/Santiago</timezone>
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/database/PageViewCursorTest.java b/core/tests/coretests/src/android/database/PageViewCursorTest.java
index 0be89d5..62b5410 100644
--- a/core/tests/coretests/src/android/database/PageViewCursorTest.java
+++ b/core/tests/coretests/src/android/database/PageViewCursorTest.java
@@ -19,15 +19,9 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import android.annotation.Nullable;
import android.content.ContentResolver;
import android.os.Bundle;
import android.support.test.runner.AndroidJUnit4;
-import android.util.Log;
-import android.util.MathUtils;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
import org.junit.Before;
import org.junit.Test;
@@ -259,12 +253,19 @@
}
@Test
- public void testPagingMarker() {
+ public void testAutoPagedExtra() {
mCursor = new PageViewCursor(mDelegate, 5, 100);
assertTrue(mCursor.getExtras().getBoolean(PageViewCursor.EXTRA_AUTO_PAGED));
}
@Test
+ public void testGetWindow() {
+ mCursor = new PageViewCursor(mDelegate, 5, 5);
+ CursorWindow window = mCursor.getWindow();
+ assertEquals(5, window.getNumRows());
+ }
+
+ @Test
public void testWrap() {
Bundle queryArgs = new Bundle();
queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 5);
diff --git a/core/tests/coretests/src/android/graphics/VariationParserTest.java b/core/tests/coretests/src/android/graphics/VariationParserTest.java
index a2ead40..fdabb13 100644
--- a/core/tests/coretests/src/android/graphics/VariationParserTest.java
+++ b/core/tests/coretests/src/android/graphics/VariationParserTest.java
@@ -28,7 +28,7 @@
@SmallTest
public void testParseFontVariationSetting() {
- int tag = FontListParser.makeTag('w', 'd', 't', 'h');
+ int tag = FontListParser.makeTag("wdth");
List<FontConfig.Axis> axes = FontListParser.parseFontVariationSettings("'wdth' 1");
assertEquals(tag, axes.get(0).getTag());
assertEquals(1.0f, axes.get(0).getStyleValue());
@@ -45,7 +45,7 @@
assertEquals(tag, axes.get(0).getTag());
assertEquals(0.5f, axes.get(0).getStyleValue());
- tag = FontListParser.makeTag('A', 'X', ' ', ' ');
+ tag = FontListParser.makeTag("AX ");
axes = FontListParser.parseFontVariationSettings("'AX ' 1");
assertEquals(tag, axes.get(0).getTag());
assertEquals(1.0f, axes.get(0).getStyleValue());
@@ -93,8 +93,8 @@
public void testParseFontVariationStyleSettings() {
List<FontConfig.Axis> axes =
FontListParser.parseFontVariationSettings("'wdth' 10,'AX '\r1");
- int tag1 = FontListParser.makeTag('w', 'd', 't', 'h');
- int tag2 = FontListParser.makeTag('A', 'X', ' ', ' ');
+ int tag1 = FontListParser.makeTag("wdth");
+ int tag2 = FontListParser.makeTag("AX ");
assertEquals(tag1, axes.get(0).getTag());
assertEquals(10.0f, axes.get(0).getStyleValue());
assertEquals(tag2, axes.get(1).getTag());
@@ -102,7 +102,7 @@
// Test only spacers are allowed before tag
axes = FontListParser.parseFontVariationSettings(" 'wdth' 10,ab'wdth' 1");
- tag1 = FontListParser.makeTag('w', 'd', 't', 'h');
+ tag1 = FontListParser.makeTag("wdth");
assertEquals(tag1, axes.get(0).getTag());
assertEquals(10.0f, axes.get(0).getStyleValue());
assertEquals(1, axes.size());
@@ -119,8 +119,8 @@
@SmallTest
public void testMakeTag() {
- assertEquals(0x77647468, FontListParser.makeTag('w', 'd', 't', 'h'));
- assertEquals(0x41582020, FontListParser.makeTag('A', 'X', ' ', ' '));
- assertEquals(0x20202020, FontListParser.makeTag(' ', ' ', ' ', ' '));
+ assertEquals(0x77647468, FontListParser.makeTag("wdth"));
+ assertEquals(0x41582020, FontListParser.makeTag("AX "));
+ assertEquals(0x20202020, FontListParser.makeTag(" "));
}
}
diff --git a/core/tests/coretests/src/android/os/BinderThreadPriorityTest.java b/core/tests/coretests/src/android/os/BinderThreadPriorityTest.java
index 7a4980a..56e977c 100644
--- a/core/tests/coretests/src/android/os/BinderThreadPriorityTest.java
+++ b/core/tests/coretests/src/android/os/BinderThreadPriorityTest.java
@@ -109,7 +109,7 @@
}
public static String expectedSchedulerGroup(int prio) {
- return prio < Process.THREAD_PRIORITY_BACKGROUND ? "/" : "/bg_non_interactive";
+ return "/";
}
public void testPassPriorityToService() throws Exception {
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/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 77a1035..8a8d027 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -339,6 +339,14 @@
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
</privapp-permissions>
+ <privapp-permissions package="com.android.tv">
+ <permission name="android.permission.DVB_DEVICE" />
+ <permission name="android.permission.GLOBAL_SEARCH" />
+ <permission name="android.permission.MODIFY_PARENTAL_CONTROLS" />
+ <permission name="com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA" />
+ <permission name="com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS" />
+ </privapp-permissions>
+
<privapp-permissions package="com.android.vpndialogs">
<permission name="android.permission.CONNECTIVITY_INTERNAL"/>
<permission name="android.permission.CONTROL_VPN"/>
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index 2ebd2cc..1f339f7 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -48,6 +48,7 @@
*/
protected int mScreenDensity = Bitmap.DENSITY_NONE;
protected int mDensity = Bitmap.DENSITY_NONE;
+ private boolean mAllowHwBitmapsInSwMode = false;
protected void throwIfCannotDraw(Bitmap bitmap) {
if (bitmap.isRecycled()) {
@@ -511,8 +512,23 @@
indices, indexOffset, indexCount, paint.getNativeInstance());
}
+ /**
+ * @hide
+ */
+ public void setHwBitmapsInSwModeEnabled(boolean enabled) {
+ mAllowHwBitmapsInSwMode = enabled;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isHwBitmapsInSwModeEnabled() {
+ return mAllowHwBitmapsInSwMode;
+ }
+
private void throwIfHwBitmapInSwMode(Bitmap bitmap) {
- if (!isHardwareAccelerated() && bitmap.getConfig() == Bitmap.Config.HARDWARE) {
+ if (!mAllowHwBitmapsInSwMode && !isHardwareAccelerated()
+ && bitmap.getConfig() == Bitmap.Config.HARDWARE) {
throw new IllegalStateException("Software rendering doesn't support hardware bitmaps");
}
}
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index 16fc2b1..1b25a627 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -81,7 +81,8 @@
}
}
- public boolean addFont(String path, int ttcIndex) {
+ public boolean addFont(String path, int ttcIndex, FontConfig.Axis[] axes, int weight,
+ int italic) {
if (mBuilderPtr == 0) {
throw new IllegalStateException("Unable to call addFont after freezing.");
}
@@ -89,22 +90,29 @@
FileChannel fileChannel = file.getChannel();
long fontSize = fileChannel.size();
ByteBuffer fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
- return nAddFont(mBuilderPtr, fontBuffer, ttcIndex);
+ if (axes != null) {
+ for (FontConfig.Axis axis : axes) {
+ nAddAxisValue(mBuilderPtr, axis.getTag(), axis.getStyleValue());
+ }
+ }
+ return nAddFont(mBuilderPtr, fontBuffer, ttcIndex, weight, italic);
} catch (IOException e) {
Log.e(TAG, "Error mapping font file " + path);
return false;
}
}
- public boolean addFontWeightStyle(ByteBuffer font, int ttcIndex, FontConfig.Axis[] axes,
- int weight, boolean style) {
+ public boolean addFontFromBuffer(ByteBuffer font, int ttcIndex, FontConfig.Axis[] axes,
+ int weight, int italic) {
if (mBuilderPtr == 0) {
throw new IllegalStateException("Unable to call addFontWeightStyle after freezing.");
}
- for (FontConfig.Axis axis : axes) {
- nAddAxisValue(mBuilderPtr, axis.getTag(), axis.getStyleValue());
+ if (axes != null) {
+ for (FontConfig.Axis axis : axes) {
+ nAddAxisValue(mBuilderPtr, axis.getTag(), axis.getStyleValue());
+ }
}
- return nAddFontWeightStyle(mBuilderPtr, font, ttcIndex, weight, style);
+ return nAddFontWeightStyle(mBuilderPtr, font, ttcIndex, weight, italic);
}
/**
@@ -120,11 +128,18 @@
* @return
*/
public boolean addFontFromAssetManager(AssetManager mgr, String path, int cookie,
- boolean isAsset, int weight, boolean isItalic) {
+ boolean isAsset, int ttcIndex, int weight, int isItalic,
+ FontConfig.Axis[] axes) {
if (mBuilderPtr == 0) {
throw new IllegalStateException("Unable to call addFontFromAsset after freezing.");
}
- return nAddFontFromAssetManager(mBuilderPtr, mgr, path, cookie, isAsset, weight, isItalic);
+ if (axes != null) {
+ for (FontConfig.Axis axis : axes) {
+ nAddAxisValue(mBuilderPtr, axis.getTag(), axis.getStyleValue());
+ }
+ }
+ return nAddFontFromAssetManager(mBuilderPtr, mgr, path, cookie, isAsset, ttcIndex, weight,
+ isItalic);
}
private static native long nInitBuilder(String lang, int variant);
@@ -137,11 +152,14 @@
@CriticalNative
private static native void nUnrefFamily(long nativePtr);
- private static native boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex);
+ // By passing -1 to weigth argument, the weight value is resolved by OS/2 table in the font.
+ // By passing -1 to italic argument, the italic value is resolved by OS/2 table in the font.
+ private static native boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex,
+ int weight, int isItalic);
private static native boolean nAddFontWeightStyle(long builderPtr, ByteBuffer font,
- int ttcIndex, int weight, boolean isItalic);
+ int ttcIndex, int weight, int isItalic);
private static native boolean nAddFontFromAssetManager(long builderPtr, AssetManager mgr,
- String path, int cookie, boolean isAsset, int weight, boolean isItalic);
+ String path, int cookie, boolean isAsset, int ttcIndex, int weight, int isItalic);
// The added axis values are only valid for the next nAddFont* method call.
@CriticalNative
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 1b6969f..b78df34 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -98,15 +98,17 @@
} catch (NumberFormatException e) {
continue; // ignoreing invalid number format
}
- int tag = makeTag(tagString.charAt(0), tagString.charAt(1), tagString.charAt(2),
- tagString.charAt(3));
+ int tag = makeTag(tagString);
axisList.add(new FontConfig.Axis(tag, styleValue));
}
return axisList;
}
- @VisibleForTesting
- public static int makeTag(char c1, char c2, char c3, char c4) {
+ public static int makeTag(String tagString) {
+ char c1 = tagString.charAt(0);
+ char c2 = tagString.charAt(1);
+ char c3 = tagString.charAt(2);
+ char c4 = tagString.charAt(3);
return (c1 << 24) | (c2 << 16) | (c3 << 8) | c4;
}
@@ -198,6 +200,13 @@
*/
private static final Pattern TAG_PATTERN = Pattern.compile("[\\x20-\\x7E]{4}");
+ public static boolean isValidTag(String tagString) {
+ if (tagString == null || tagString.length() != 4) {
+ return false;
+ }
+ return TAG_PATTERN.matcher(tagString).matches();
+ }
+
/** The 'styleValue' attribute has an optional leading '-', followed by '<digits>',
* '<digits>.<digits>', or '.<digits>' where '<digits>' is one or more of [0-9].
*/
@@ -208,8 +217,8 @@
throws XmlPullParserException, IOException {
int tag = 0;
String tagStr = parser.getAttributeValue(null, "tag");
- if (tagStr != null && TAG_PATTERN.matcher(tagStr).matches()) {
- tag = makeTag(tagStr.charAt(0), tagStr.charAt(1), tagStr.charAt(2), tagStr.charAt(3));
+ if (isValidTag(tagStr)) {
+ tag = makeTag(tagStr);
} else {
throw new XmlPullParserException("Invalid tag attribute value.", parser, null);
}
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 8511c1f..042bac6 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -21,11 +21,15 @@
import static android.content.res.FontResourcesParser.FontFamilyFilesResourceEntry;
import static android.content.res.FontResourcesParser.FamilyResourceEntry;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.AssetManager;
+import android.graphics.FontListParser;
import android.graphics.fonts.FontRequest;
import android.graphics.fonts.FontResult;
import android.os.Bundle;
@@ -34,18 +38,21 @@
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;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -55,9 +62,12 @@
import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
/**
* The Typeface class specifies the typeface and intrinsic style of a font.
@@ -148,13 +158,16 @@
public static Typeface createFromResources(AssetManager mgr, String path, int cookie) {
if (sFallbackFonts != null) {
synchronized (sDynamicTypefaceCache) {
- final String key = createAssetUid(mgr, path);
+ final String key = Builder.createAssetUid(
+ mgr, path, 0 /* ttcIndex */, null /* axes */);
Typeface typeface = sDynamicTypefaceCache.get(key);
if (typeface != null) return typeface;
FontFamily fontFamily = new FontFamily();
+ // TODO: introduce ttc index and variation settings to resource type font.
if (fontFamily.addFontFromAssetManager(mgr, path, cookie, false /* isAsset */,
- 0 /* use OS/2 table to determine weight and italic */, false)) {
+ 0 /* ttcIndex */, Builder.RESOLVE_BY_FONT_TABLE /* weight */,
+ Builder.RESOLVE_BY_FONT_TABLE /* italic */, null /* axes */)) {
fontFamily.freeze();
FontFamily[] families = {fontFamily};
typeface = createFromFamiliesWithDefault(families);
@@ -184,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;
}
@@ -198,8 +223,10 @@
FontFamily fontFamily = new FontFamily();
for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) {
if (!fontFamily.addFontFromAssetManager(mgr, fontFile.getFileName(),
- 0 /* resourceCookie */, false /* isAsset */, fontFile.getWeight(),
- fontFile.isItalic())) {
+ 0 /* resourceCookie */, false /* isAsset */, 0 /* ttcIndex */,
+ fontFile.getWeight(),
+ fontFile.isItalic() ? Builder.ITALIC : Builder.NORMAL,
+ null /* axes */)) {
return null;
}
}
@@ -207,7 +234,8 @@
FontFamily[] familyChain = { fontFamily };
typeface = createFromFamiliesWithDefault(familyChain);
synchronized (sDynamicTypefaceCache) {
- final String key = createAssetUid(mgr, path);
+ final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */,
+ null /* axes */);
sDynamicTypefaceCache.put(key, typeface);
}
return typeface;
@@ -221,7 +249,7 @@
*/
public static Typeface findFromCache(AssetManager mgr, String path) {
synchronized (sDynamicTypefaceCache) {
- final String key = createAssetUid(mgr, path);
+ final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, null /* axes */);
Typeface typeface = sDynamicTypefaceCache.get(key);
if (typeface != null) {
return typeface;
@@ -332,8 +360,9 @@
int weight = (style & BOLD) != 0 ? 700 : 400;
// TODO: this method should be
// create(fd, ttcIndex, fontVariationSettings, style).
- if (!fontFamily.addFontWeightStyle(fontBuffer, result.getTtcIndex(),
- null, weight, (style & ITALIC) != 0)) {
+ if (!fontFamily.addFontFromBuffer(fontBuffer, result.getTtcIndex(),
+ null, weight,
+ (style & ITALIC) == 0 ? Builder.NORMAL : Builder.ITALIC)) {
Log.e(TAG, "Error creating font " + request.getQuery());
callback.onTypefaceRequestFailed(
FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
@@ -432,6 +461,346 @@
};
/**
+ * A builder class for creating new Typeface instance.
+ *
+ * Examples,
+ * 1) Create Typeface from ttf file.
+ * <pre>
+ * <code>
+ * Typeface.Builder buidler = new Typeface.Builder.obtain();
+ * builder.setSourceFromFilePath("your_font_file.ttf");
+ * Typeface typeface = builder.build();
+ * builder.recycle();
+ * </code>
+ * </pre>
+ *
+ * 2) Create Typeface from ttc file in assets directory.
+ * <pre>
+ * <code>
+ * Typeface.Builder buidler = new Typeface.Builder.obtain();
+ * builder.setSourceFromAsset(getAssets(), "your_font_file.ttc");
+ * builder.setTtcIndex(2); // set index of font collection.
+ * Typeface typeface = builder.build();
+ * builder.recycle();
+ * </code>
+ * </pre>
+ *
+ * 3) Create Typeface from existing Typeface with variation settings.
+ * <pre>
+ *
+ * <p>Note that only one source can be specified for the single Typeface.</p>
+ */
+ public static final class Builder {
+ /**
+ * Value for weight and italic.
+ *
+ * Indicates the value is resolved by font metadata.
+ */
+ // Must be same with C++ constant in core/jni/android/graphics/FontFamily.cpp
+ public static final int RESOLVE_BY_FONT_TABLE = -1;
+
+ /**
+ * Value for italic.
+ *
+ * Indicates the font style is not italic.
+ */
+ public static final int NORMAL = 0;
+
+ /**
+ * Value for italic.
+ *
+ * Indicates the font style is italic.
+ */
+ public static final int ITALIC = 1;
+
+ private int mTtcIndex;
+ private FontConfig.Axis[] mAxes;
+
+ private AssetManager mAssetManager;
+ private String mPath;
+ private FileDescriptor mFd;
+ private @IntRange(from = -1) int mWeight = RESOLVE_BY_FONT_TABLE;
+
+ /** @hide */
+ @Retention(SOURCE)
+ @IntDef({RESOLVE_BY_FONT_TABLE, NORMAL, ITALIC})
+ public @interface Italic {}
+ private @Italic int mItalic = RESOLVE_BY_FONT_TABLE;
+
+ private boolean mHasSourceSet = false;
+ private boolean mRecycled = false;
+
+ /** Use Builder.obtain() instead */
+ private void Builder() {}
+
+ private static AtomicReference<Builder> mCache = new AtomicReference<>();
+
+ /**
+ * Returns Typeface.Builder from pool.
+ */
+ public static Builder obtain() {
+ final Builder builder = mCache.getAndSet(null);
+ if (builder != null) {
+ builder.mRecycled = false;
+ return builder;
+ }
+ return new Builder();
+ }
+
+ /**
+ * Resets the internal states.
+ */
+ public void reset() {
+ checkNotRecycled();
+ mTtcIndex = 0;
+ mAxes = null;
+
+ mAssetManager = null;
+ mPath = null;
+ mFd = null;
+
+ mWeight = RESOLVE_BY_FONT_TABLE;
+ mItalic = RESOLVE_BY_FONT_TABLE;
+
+ mHasSourceSet = false;
+ }
+
+ /**
+ * Returns the instance to the pool.
+ */
+ public void recycle() {
+ reset();
+ mRecycled = true;
+
+ mCache.compareAndSet(null, this);
+ }
+
+ private void checkNotRecycled() {
+ if (mRecycled) {
+ throw new IllegalStateException("Don't use Builder after calling recycle()");
+ }
+ }
+
+ private void checkSingleFontSource() {
+ if (mHasSourceSet) {
+ throw new IllegalStateException("Typeface can only built with single font source.");
+ }
+ }
+
+ /**
+ * Sets a font file as a source of Typeface.
+ *
+ * @param path The file object refers to the font file.
+ */
+ public Builder setSourceFromFile(@NonNull File path) {
+ return setSourceFromFilePath(path.getAbsolutePath());
+ }
+
+ /**
+ * Sets a font file as a source of Typeface.
+ *
+ * @param fd The file descriptor. The passed fd must be mmap-able.
+ */
+ public Builder setSourceFromFile(@NonNull FileDescriptor fd) {
+ checkNotRecycled();
+ checkSingleFontSource();
+ mFd = fd;
+ mHasSourceSet = true;
+ return this;
+ }
+
+ /**
+ * Sets a font file as a source of Typeface.
+ *
+ * @param path The full path to the font file.
+ */
+ public Builder setSourceFromFilePath(@NonNull String path) {
+ checkNotRecycled();
+ checkSingleFontSource();
+ mPath = path;
+ mHasSourceSet = true;
+ return this;
+ }
+
+ /**
+ * Sets an asset entry as a source of Typeface.
+ *
+ * @param assetManager The application's asset manager
+ * @param path The file name of the font data in the asset directory
+ */
+ public Builder setSourceFromAsset(@NonNull AssetManager assetManager,
+ @NonNull String path) {
+ checkNotRecycled();
+ checkSingleFontSource();
+ mAssetManager = Preconditions.checkNotNull(assetManager);
+ mPath = Preconditions.checkStringNotEmpty(path);
+ mHasSourceSet = true;
+ return this;
+ }
+
+ /**
+ * Sets weight of the font.
+ *
+ * By passing {@link #RESOLVE_BY_FONT_TABLE}, weight value is resolved by OS/2 table in
+ * font file if possible.
+ * @param weight a weight value or {@link #RESOLVE_BY_FONT_TABLE}
+ */
+ public Builder setWeight(@IntRange(from = -1) int weight) {
+ checkNotRecycled();
+ mWeight = weight;
+ return this;
+ }
+
+ /**
+ * Sets italic information of the font.
+ *
+ * By passing {@link #RESOLVE_BY_FONT_TABLE}, italic or normal is determined by OS/2 table
+ * in font file if possible.
+ * @param italic One of {@link #NORMAL}, {@link #ITALIC}, {@link #RESOLVE_BY_FONT_TABLE}.
+ * will be used.
+ */
+ public Builder setItalic(@Italic int italic) {
+ checkNotRecycled();
+ mItalic = italic;
+ return this;
+ }
+
+ /**
+ * Sets an idex of the font collection.
+ *
+ * Can not be used for Typeface source. build() method will return null for invalid index.
+ * @param ttcIndex An index of the font collection. If the font source is not font
+ * collection, do not call this method or specify 0.
+ */
+ public Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) {
+ checkNotRecycled();
+ mTtcIndex = ttcIndex;
+ return this;
+ }
+
+ /**
+ * Sets a font variation settings.
+ *
+ * @param variationSettings See {@link android.widget.TextView#setFontVariationSettings}.
+ */
+ public Builder setFontVariationSettings(@Nullable String variationSettings) {
+ checkNotRecycled();
+ if (mAxes != null) {
+ throw new IllegalStateException("Font variation settings are already set.");
+ }
+ final List<FontConfig.Axis> axesList = FontListParser.parseFontVariationSettings(
+ variationSettings);
+ mAxes = axesList.toArray(new FontConfig.Axis[axesList.size()]);
+ return this;
+ }
+
+ /**
+ * Sets a font variation settings.
+ *
+ * @param axes An array of font variation axis tag-value pairs.
+ */
+ public Builder setFontVariationSettings(@Nullable FontConfig.Axis[] axes) {
+ checkNotRecycled();
+ if (mAxes != null) {
+ throw new IllegalStateException("Font variation settings are already set.");
+ }
+ mAxes = axes;
+ return this;
+ }
+
+ /**
+ * Creates a unique id for a given AssetManager and asset path.
+ *
+ * @param mgr AssetManager instance
+ * @param path The path for the asset.
+ * @param ttcIndex The TTC index for the font.
+ * @param axes The font variation settings.
+ * @return Unique id for a given AssetManager and asset path.
+ */
+ private static String createAssetUid(final AssetManager mgr, String path, int ttcIndex,
+ @Nullable FontConfig.Axis[] axes) {
+ final SparseArray<String> pkgs = mgr.getAssignedPackageIdentifiers();
+ final StringBuilder builder = new StringBuilder();
+ final int size = pkgs.size();
+ for (int i = 0; i < size; i++) {
+ builder.append(pkgs.valueAt(i));
+ builder.append("-");
+ }
+ builder.append(path);
+ builder.append("-");
+ builder.append(Integer.toString(ttcIndex));
+ builder.append("-");
+ if (axes != null) {
+ for (FontConfig.Axis axis : axes) {
+ builder.append(Integer.toHexString(axis.getTag()));
+ builder.append("-");
+ builder.append(Float.toString(axis.getStyleValue()));
+ }
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Generates new Typeface from specified configuration.
+ *
+ * @return Newly created Typeface. May return null if some parameters are invalid.
+ */
+ public Typeface build() {
+ checkNotRecycled();
+ if (!mHasSourceSet) {
+ return null;
+ }
+
+ if (mFd != null) { // set source by setSourceFromFile(FileDescriptor)
+ try (FileInputStream fis = new FileInputStream(mFd)) {
+ FileChannel channel = fis.getChannel();
+ long size = channel.size();
+ ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, size);
+
+ final FontFamily fontFamily = new FontFamily();
+ if (!fontFamily.addFontFromBuffer(buffer, mTtcIndex, mAxes, mWeight, mItalic)) {
+ fontFamily.abortCreation();
+ return null;
+ }
+ fontFamily.freeze();
+ FontFamily[] families = { fontFamily };
+ return createFromFamiliesWithDefault(families);
+ } catch (IOException e) {
+ return null;
+ }
+ } else if (mAssetManager != null) { // set source by setSourceFromAsset()
+ final String key = createAssetUid(mAssetManager, mPath, mTtcIndex, mAxes);
+ synchronized (sDynamicTypefaceCache) {
+ Typeface typeface = sDynamicTypefaceCache.get(key);
+ if (typeface != null) return typeface;
+ final FontFamily fontFamily = new FontFamily();
+ if (!fontFamily.addFontFromAssetManager(mAssetManager, mPath, mTtcIndex,
+ true /* isAsset */, mTtcIndex, mWeight, mItalic, mAxes)) {
+ fontFamily.abortCreation();
+ return null;
+ }
+ fontFamily.freeze();
+ FontFamily[] families = { fontFamily };
+ typeface = createFromFamiliesWithDefault(families);
+ sDynamicTypefaceCache.put(key, typeface);
+ return typeface;
+ }
+ } else if (mPath != null) { // set source by setSourceFromFile(File)
+ final FontFamily fontFamily = new FontFamily();
+ if (!fontFamily.addFont(mPath, mTtcIndex, mAxes, mWeight, mItalic)) {
+ fontFamily.abortCreation();
+ return null;
+ }
+ fontFamily.freeze();
+ FontFamily[] families = { fontFamily };
+ return createFromFamiliesWithDefault(families);
+ } else {
+ throw new IllegalArgumentException("No source was set.");
+ }
+ }
+ }
+
+ /**
* Create a typeface object given a family name, and option style information.
* If null is passed for the name, then the "default" font will be chosen.
* The resulting typeface object can be queried (getStyle()) to discover what
@@ -518,49 +887,26 @@
* @return The new typeface.
*/
public static Typeface createFromAsset(AssetManager mgr, String path) {
+ if (path == null) {
+ throw new NullPointerException(); // for backward compatibility
+ }
if (sFallbackFonts != null) {
- synchronized (sDynamicTypefaceCache) {
- final String key = createAssetUid(mgr, path);
- Typeface typeface = sDynamicTypefaceCache.get(key);
- if (typeface != null) return typeface;
-
- FontFamily fontFamily = new FontFamily();
- if (fontFamily.addFontFromAssetManager(mgr, path, 0, true /* isAsset */,
- 0 /* use OS/2 table to determine weight and italic */, false)) {
- fontFamily.freeze();
- FontFamily[] families = { fontFamily };
- typeface = createFromFamiliesWithDefault(families);
- sDynamicTypefaceCache.put(key, typeface);
+ final Builder builder = Builder.obtain();
+ try {
+ builder.setSourceFromAsset(mgr, path);
+ Typeface typeface = builder.build();
+ if (typeface != null) {
return typeface;
- } else {
- fontFamily.abortCreation();
}
+ } finally {
+ builder.recycle();
}
}
+ // For the compatibility reasons, throw runtime exception if failed to create Typeface.
throw new RuntimeException("Font asset not found " + path);
}
/**
- * Creates a unique id for a given AssetManager and asset path.
- *
- * @param mgr AssetManager instance
- * @param path The path for the asset.
- * @return Unique id for a given AssetManager and asset path.
- */
- private static String createAssetUid(final AssetManager mgr, String path) {
- final SparseArray<String> pkgs = mgr.getAssignedPackageIdentifiers();
- final StringBuilder builder = new StringBuilder();
- builder.append("asset:");
- final int size = pkgs.size();
- for (int i = 0; i < size; i++) {
- builder.append(pkgs.valueAt(i));
- builder.append("-");
- }
- builder.append(path);
- return builder.toString();
- }
-
- /**
* Creates a unique id for a given font provider and query.
*/
private static String createProviderUid(String authority, String query) {
@@ -578,7 +924,9 @@
* @param path The path to the font data.
* @return The new typeface.
*/
- public static Typeface createFromFile(File path) {
+ public static Typeface createFromFile(@Nullable File path) {
+ // For the compatibility reasons, leaving possible NPE here.
+ // See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull
return createFromFile(path.getAbsolutePath());
}
@@ -588,15 +936,24 @@
* @param path The full path to the font data.
* @return The new typeface.
*/
- public static Typeface createFromFile(String path) {
+ public static Typeface createFromFile(@Nullable String path) {
+ if (path == null) {
+ // For the compatibility reasons, need to throw NPE if the argument is null.
+ // See android.graphics.cts.TypefaceTest#testCreateFromFileByFileNameNull
+ throw new NullPointerException();
+ }
if (sFallbackFonts != null) {
- FontFamily fontFamily = new FontFamily();
- if (fontFamily.addFont(path, 0 /* ttcIndex */)) {
- fontFamily.freeze();
- FontFamily[] families = { fontFamily };
- return createFromFamiliesWithDefault(families);
- } else {
- fontFamily.abortCreation();
+ final Builder builder = Builder.obtain();
+ try {
+ builder.setSourceFromFilePath(path);
+ Typeface typeface = builder.build();
+ if (typeface != null) {
+ // For the compatibility reasons, throw runtime exception if failed to create
+ // Typeface.
+ return typeface;
+ }
+ } finally {
+ builder.recycle();
}
}
throw new RuntimeException("Font not found " + path);
@@ -606,9 +963,8 @@
* Create a new typeface from an array of font families.
*
* @param families array of font families
- * @hide
*/
- public static Typeface createFromFamilies(FontFamily[] families) {
+ private static Typeface createFromFamilies(FontFamily[] families) {
long[] ptrArray = new long[families.length];
for (int i = 0; i < families.length; i++) {
ptrArray[i] = families[i].mNativePtr;
@@ -621,9 +977,8 @@
* also the font families in the fallback list.
*
* @param families array of font families
- * @hide
*/
- public static Typeface createFromFamiliesWithDefault(FontFamily[] families) {
+ private static Typeface createFromFamiliesWithDefault(FontFamily[] families) {
long[] ptrArray = new long[families.length + sFallbackFonts.length];
for (int i = 0; i < families.length; i++) {
ptrArray[i] = families[i].mNativePtr;
@@ -660,8 +1015,8 @@
continue;
}
}
- if (!fontFamily.addFontWeightStyle(fontBuffer, font.getTtcIndex(), font.getAxes(),
- font.getWeight(), font.isItalic())) {
+ if (!fontFamily.addFontFromBuffer(fontBuffer, font.getTtcIndex(), font.getAxes(),
+ font.getWeight(), font.isItalic() ? Builder.ITALIC : Builder.NORMAL)) {
Log.e(TAG, "Error creating font " + font.getFontName() + "#" + font.getTtcIndex());
}
}
@@ -806,6 +1161,7 @@
}
private static native long nativeCreateFromTypeface(long native_instance, int style);
+ // TODO: clean up: change List<FontConfig.Axis> to FontConfig.Axis[]
private static native long nativeCreateFromTypefaceWithVariation(
long native_instance, List<FontConfig.Axis> axes);
private static native long nativeCreateWeightAlias(long native_instance, int weight);
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/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 244c525..09840a5 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -4430,6 +4430,7 @@
if (curOff > (dtohl(entry.type->header.size)-sizeof(ResTable_map))) {
ALOGW("ResTable_map at %d is beyond type chunk data %d",
(int)curOff, dtohl(entry.type->header.size));
+ free(set);
return BAD_TYPE;
}
map = (const ResTable_map*)(((const uint8_t*)entry.type) + curOff);
@@ -4442,6 +4443,7 @@
if (grp->dynamicRefTable.lookupResourceId(&newName) != NO_ERROR) {
ALOGE("Failed resolving ResTable_map name at %d with ident 0x%08x",
(int) curOff, (int) newName);
+ free(set);
return UNKNOWN_ERROR;
}
}
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 68d3dd5..8823a92 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -33,65 +33,10 @@
const int Tree::MAX_CACHED_BITMAP_SIZE = 2048;
-void Path::draw(SkCanvas* outCanvas, const SkMatrix& groupStackedMatrix, float scaleX, float scaleY,
- bool useStagingData) {
- float matrixScale = getMatrixScale(groupStackedMatrix);
- if (matrixScale == 0) {
- // When either x or y is scaled to 0, we don't need to draw anything.
- return;
- }
-
- SkMatrix pathMatrix(groupStackedMatrix);
- pathMatrix.postScale(scaleX, scaleY);
-
- //TODO: try apply the path matrix to the canvas instead of creating a new path.
- SkPath renderPath;
- renderPath.reset();
-
- if (useStagingData) {
- SkPath tmpPath;
- getStagingPath(&tmpPath);
- renderPath.addPath(tmpPath, pathMatrix);
- } else {
- renderPath.addPath(getUpdatedPath(), pathMatrix);
- }
-
- float minScale = fmin(scaleX, scaleY);
- float strokeScale = minScale * matrixScale;
- drawPath(outCanvas, renderPath, strokeScale, pathMatrix, useStagingData);
-}
-
void Path::dump() {
ALOGD("Path: %s has %zu points", mName.c_str(), mProperties.getData().points.size());
}
-float Path::getMatrixScale(const SkMatrix& groupStackedMatrix) {
- // Given unit vectors A = (0, 1) and B = (1, 0).
- // After matrix mapping, we got A' and B'. Let theta = the angel b/t A' and B'.
- // Therefore, the final scale we want is min(|A'| * sin(theta), |B'| * sin(theta)),
- // which is (|A'| * |B'| * sin(theta)) / max (|A'|, |B'|);
- // If max (|A'|, |B'|) = 0, that means either x or y has a scale of 0.
- //
- // For non-skew case, which is most of the cases, matrix scale is computing exactly the
- // scale on x and y axis, and take the minimal of these two.
- // For skew case, an unit square will mapped to a parallelogram. And this function will
- // return the minimal height of the 2 bases.
- SkVector skVectors[2];
- skVectors[0].set(0, 1);
- skVectors[1].set(1, 0);
- groupStackedMatrix.mapVectors(skVectors, 2);
- float scaleX = hypotf(skVectors[0].fX, skVectors[0].fY);
- float scaleY = hypotf(skVectors[1].fX, skVectors[1].fY);
- float crossProduct = skVectors[0].cross(skVectors[1]);
- float maxScale = fmax(scaleX, scaleY);
-
- float matrixScale = 0;
- if (maxScale > 0) {
- matrixScale = fabs(crossProduct) / maxScale;
- }
- return matrixScale;
-}
-
// Called from UI thread during the initial setup/theme change.
Path::Path(const char* pathStr, size_t strLength) {
PathParser::ParseResult result;
@@ -104,18 +49,19 @@
mStagingProperties.syncProperties(path.mStagingProperties);
}
-const SkPath& Path::getUpdatedPath() {
- if (mSkPathDirty) {
- mSkPath.reset();
- VectorDrawableUtils::verbsToPath(&mSkPath, mProperties.getData());
- mSkPathDirty = false;
+const SkPath& Path::getUpdatedPath(bool useStagingData, SkPath* tempStagingPath) {
+ if (useStagingData) {
+ tempStagingPath->reset();
+ VectorDrawableUtils::verbsToPath(tempStagingPath, mStagingProperties.getData());
+ return *tempStagingPath;
+ } else {
+ if (mSkPathDirty) {
+ mSkPath.reset();
+ VectorDrawableUtils::verbsToPath(&mSkPath, mProperties.getData());
+ mSkPathDirty = false;
+ }
+ return mSkPath;
}
- return mSkPath;
-}
-
-void Path::getStagingPath(SkPath* outPath) {
- outPath->reset();
- VectorDrawableUtils::verbsToPath(outPath, mStagingProperties.getData());
}
void Path::syncProperties() {
@@ -157,26 +103,35 @@
}
}
-const SkPath& FullPath::getUpdatedPath() {
- if (!mSkPathDirty && !mProperties.mTrimDirty) {
+const SkPath& FullPath::getUpdatedPath(bool useStagingData, SkPath* tempStagingPath) {
+ if (!useStagingData && !mSkPathDirty && !mProperties.mTrimDirty) {
return mTrimmedSkPath;
}
- Path::getUpdatedPath();
- if (mProperties.getTrimPathStart() != 0.0f || mProperties.getTrimPathEnd() != 1.0f) {
- mProperties.mTrimDirty = false;
- applyTrim(&mTrimmedSkPath, mSkPath, mProperties.getTrimPathStart(),
- mProperties.getTrimPathEnd(), mProperties.getTrimPathOffset());
- return mTrimmedSkPath;
+ Path::getUpdatedPath(useStagingData, tempStagingPath);
+ SkPath *outPath;
+ if (useStagingData) {
+ SkPath inPath = *tempStagingPath;
+ applyTrim(tempStagingPath, inPath, mStagingProperties.getTrimPathStart(),
+ mStagingProperties.getTrimPathEnd(), mStagingProperties.getTrimPathOffset());
+ outPath = tempStagingPath;
} else {
- return mSkPath;
+ if (mProperties.getTrimPathStart() != 0.0f || mProperties.getTrimPathEnd() != 1.0f) {
+ mProperties.mTrimDirty = false;
+ applyTrim(&mTrimmedSkPath, mSkPath, mProperties.getTrimPathStart(),
+ mProperties.getTrimPathEnd(), mProperties.getTrimPathOffset());
+ outPath = &mTrimmedSkPath;
+ } else {
+ outPath = &mSkPath;
+ }
}
-}
-
-void FullPath::getStagingPath(SkPath* outPath) {
- Path::getStagingPath(outPath);
- SkPath inPath = *outPath;
- applyTrim(outPath, inPath, mStagingProperties.getTrimPathStart(),
- mStagingProperties.getTrimPathEnd(), mStagingProperties.getTrimPathOffset());
+ const FullPathProperties& properties = useStagingData ? mStagingProperties : mProperties;
+ bool setFillPath = properties.getFillGradient() != nullptr
+ || properties.getFillColor() != SK_ColorTRANSPARENT;
+ if (setFillPath) {
+ SkPath::FillType ft = static_cast<SkPath::FillType>(properties.getFillType());
+ outPath->setFillType(ft);
+ }
+ return *outPath;
}
void FullPath::dump() {
@@ -192,16 +147,17 @@
return SkColorSetA(color, alphaBytes * alpha);
}
-void FullPath::drawPath(SkCanvas* outCanvas, SkPath& renderPath, float strokeScale,
- const SkMatrix& matrix, bool useStagingData){
+void FullPath::draw(SkCanvas* outCanvas, bool useStagingData) {
const FullPathProperties& properties = useStagingData ? mStagingProperties : mProperties;
+ SkPath tempStagingPath;
+ const SkPath& renderPath = getUpdatedPath(useStagingData, &tempStagingPath);
// Draw path's fill, if fill color or gradient is valid
bool needsFill = false;
SkPaint paint;
if (properties.getFillGradient() != nullptr) {
paint.setColor(applyAlpha(SK_ColorBLACK, properties.getFillAlpha()));
- paint.setShader(properties.getFillGradient()->makeWithLocalMatrix(matrix));
+ paint.setShader(sk_sp<SkShader>(SkSafeRef(properties.getFillGradient())));
needsFill = true;
} else if (properties.getFillColor() != SK_ColorTRANSPARENT) {
paint.setColor(applyAlpha(properties.getFillColor(), properties.getFillAlpha()));
@@ -211,8 +167,6 @@
if (needsFill) {
paint.setStyle(SkPaint::Style::kFill_Style);
paint.setAntiAlias(true);
- SkPath::FillType ft = static_cast<SkPath::FillType>(properties.getFillType());
- renderPath.setFillType(ft);
outCanvas->drawPath(renderPath, paint);
}
@@ -220,7 +174,7 @@
bool needsStroke = false;
if (properties.getStrokeGradient() != nullptr) {
paint.setColor(applyAlpha(SK_ColorBLACK, properties.getStrokeAlpha()));
- paint.setShader(properties.getStrokeGradient()->makeWithLocalMatrix(matrix));
+ paint.setShader(sk_sp<SkShader>(SkSafeRef(properties.getStrokeGradient())));
needsStroke = true;
} else if (properties.getStrokeColor() != SK_ColorTRANSPARENT) {
paint.setColor(applyAlpha(properties.getStrokeColor(), properties.getStrokeAlpha()));
@@ -232,7 +186,7 @@
paint.setStrokeJoin(SkPaint::Join(properties.getStrokeLineJoin()));
paint.setStrokeCap(SkPaint::Cap(properties.getStrokeLineCap()));
paint.setStrokeMiter(properties.getStrokeMiterLimit());
- paint.setStrokeWidth(properties.getStrokeWidth() * strokeScale);
+ paint.setStrokeWidth(properties.getStrokeWidth());
outCanvas->drawPath(renderPath, paint);
}
}
@@ -306,36 +260,28 @@
}
}
-void ClipPath::drawPath(SkCanvas* outCanvas, SkPath& renderPath,
- float strokeScale, const SkMatrix& matrix, bool useStagingData){
- outCanvas->clipPath(renderPath);
+void ClipPath::draw(SkCanvas* outCanvas, bool useStagingData) {
+ SkPath tempStagingPath;
+ outCanvas->clipPath(getUpdatedPath(useStagingData, &tempStagingPath));
}
Group::Group(const Group& group) : Node(group) {
mStagingProperties.syncProperties(group.mStagingProperties);
}
-void Group::draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix, float scaleX,
- float scaleY, bool useStagingData) {
- // TODO: Try apply the matrix to the canvas instead of passing it down the tree
-
- // Calculate current group's matrix by preConcat the parent's and
- // and the current one on the top of the stack.
- // Basically the Mfinal = Mviewport * M0 * M1 * M2;
- // Mi the local matrix at level i of the group tree.
+void Group::draw(SkCanvas* outCanvas, bool useStagingData) {
+ // Save the current clip and matrix information, which is local to this group.
+ SkAutoCanvasRestore saver(outCanvas, true);
+ // apply the current group's matrix to the canvas
SkMatrix stackedMatrix;
const GroupProperties& prop = useStagingData ? mStagingProperties : mProperties;
getLocalMatrix(&stackedMatrix, prop);
- stackedMatrix.postConcat(currentMatrix);
-
- // Save the current clip information, which is local to this group.
- outCanvas->save();
+ outCanvas->concat(stackedMatrix);
// Draw the group tree in the same order as the XML file.
for (auto& child : mChildren) {
- child->draw(outCanvas, stackedMatrix, scaleX, scaleY, useStagingData);
+ child->draw(outCanvas, useStagingData);
}
- // Restore the previous clip information.
- outCanvas->restore();
+ // Restore the previous clip and matrix information.
}
void Group::dump() {
@@ -556,7 +502,8 @@
mStagingProperties.getViewportHeight() : mProperties.getViewportHeight();
float scaleX = outCache.width() / viewportWidth;
float scaleY = outCache.height() / viewportHeight;
- mRootNode->draw(&outCanvas, SkMatrix::I(), scaleX, scaleY, useStagingData);
+ outCanvas.scale(scaleX, scaleY);
+ mRootNode->draw(&outCanvas, useStagingData);
}
bool Tree::allocateBitmapIfNeeded(Cache& cache, int width, int height) {
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index 8244a39..729a4dd 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -109,8 +109,7 @@
mName = node.mName;
}
Node() {}
- virtual void draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix,
- float scaleX, float scaleY, bool useStagingData) = 0;
+ virtual void draw(SkCanvas* outCanvas, bool useStagingData) = 0;
virtual void dump() = 0;
void setName(const char* name) {
mName = name;
@@ -169,9 +168,6 @@
Path() {}
void dump() override;
- void draw(SkCanvas* outCanvas, const SkMatrix& groupStackedMatrix,
- float scaleX, float scaleY, bool useStagingData) override;
- static float getMatrixScale(const SkMatrix& groupStackedMatrix);
virtual void syncProperties() override;
virtual void onPropertyChanged(Properties* prop) override {
if (prop == &mStagingProperties) {
@@ -193,10 +189,7 @@
PathProperties* mutateProperties() { return &mProperties; }
protected:
- virtual const SkPath& getUpdatedPath();
- virtual void getStagingPath(SkPath* outPath);
- virtual void drawPath(SkCanvas *outCanvas, SkPath& renderPath,
- float strokeScale, const SkMatrix& matrix, bool useStagingData) = 0;
+ virtual const SkPath& getUpdatedPath(bool useStagingData, SkPath* tempStagingPath);
// Internal data, render thread only.
bool mSkPathDirty = true;
@@ -364,6 +357,7 @@
FullPath(const FullPath& path); // for cloning
FullPath(const char* path, size_t strLength) : Path(path, strLength) {}
FullPath() : Path() {}
+ void draw(SkCanvas* outCanvas, bool useStagingData) override;
void dump() override;
FullPathProperties* mutateStagingProperties() { return &mStagingProperties; }
const FullPathProperties* stagingProperties() { return &mStagingProperties; }
@@ -387,10 +381,7 @@
}
protected:
- const SkPath& getUpdatedPath() override;
- void getStagingPath(SkPath* outPath) override;
- void drawPath(SkCanvas* outCanvas, SkPath& renderPath,
- float strokeScale, const SkMatrix& matrix, bool useStagingData) override;
+ const SkPath& getUpdatedPath(bool useStagingData, SkPath* tempStagingPath) override;
private:
FullPathProperties mProperties = FullPathProperties(this);
@@ -407,10 +398,7 @@
ClipPath(const ClipPath& path) : Path(path) {}
ClipPath(const char* path, size_t strLength) : Path(path, strLength) {}
ClipPath() : Path() {}
-
-protected:
- void drawPath(SkCanvas* outCanvas, SkPath& renderPath,
- float strokeScale, const SkMatrix& matrix, bool useStagingData) override;
+ void draw(SkCanvas* outCanvas, bool useStagingData) override;
};
class ANDROID_API Group: public Node {
@@ -519,8 +507,7 @@
GroupProperties* mutateProperties() { return &mProperties; }
// Methods below could be called from either UI thread or Render Thread.
- virtual void draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix,
- float scaleX, float scaleY, bool useStagingData) override;
+ virtual void draw(SkCanvas* outCanvas, bool useStagingData) override;
void getLocalMatrix(SkMatrix* outMatrix, const GroupProperties& properties);
void dump() override;
static bool isValidProperty(int propertyId);
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index 79429ec..a895cba 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -206,10 +206,10 @@
return new T();
}
sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override {
- return sk_sp<SkSurface>();
+ return nullptr;
}
- sk_sp<SkImage> onNewImageSnapshot(SkBudgeted) override {
- return sk_sp<SkImage>();
+ sk_sp<SkImage> onNewImageSnapshot() override {
+ return nullptr;
}
T* canvas() { return static_cast<T*>(getCanvas()); }
void onCopyOnWrite(ContentChangeMode) override {}
diff --git a/libs/hwui/tests/unit/VectorDrawableTests.cpp b/libs/hwui/tests/unit/VectorDrawableTests.cpp
index 8e0d3ee..6f264e1 100644
--- a/libs/hwui/tests/unit/VectorDrawableTests.cpp
+++ b/libs/hwui/tests/unit/VectorDrawableTests.cpp
@@ -347,51 +347,6 @@
}
}
-TEST(VectorDrawable, matrixScale) {
- struct MatrixAndScale {
- float buffer[9];
- float matrixScale;
- };
-
- const MatrixAndScale sMatrixAndScales[] {
- {
- {1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f},
- 1.0
- },
- {
- {1.0f, 0.0f, 240.0f, 0.0f, 1.0f, 240.0f, 0.0f, 0.0f, 1.0f},
- 1.0f,
- },
- {
- {1.5f, 0.0f, 24.0f, 0.0f, 1.5f, 24.0f, 0.0f, 0.0f, 1.0f},
- 1.5f,
- },
- {
- {0.99999994f, 0.0f, 300.0f, 0.0f, 0.99999994f, 158.57864f, 0.0f, 0.0f, 1.0f},
- 0.99999994f,
- },
- {
- {0.7071067f, 0.7071067f, 402.5305f, -0.7071067f, 0.7071067f, 169.18524f, 0.0f, 0.0f, 1.0f},
- 0.99999994f,
- },
- {
- {0.0f, 0.9999999f, 482.5305f, -0.9999999f, 0.0f, 104.18525f, 0.0f, 0.0f, 1.0f},
- 0.9999999f,
- },
- {
- {-0.35810637f, -0.93368083f, 76.55821f, 0.93368083f, -0.35810637f, 89.538506f, 0.0f, 0.0f, 1.0f},
- 1.0000001f,
- },
- };
-
- for (MatrixAndScale matrixAndScale : sMatrixAndScales) {
- SkMatrix matrix;
- matrix.set9(matrixAndScale.buffer);
- float actualMatrixScale = VectorDrawable::Path::getMatrixScale(matrix);
- EXPECT_EQ(matrixAndScale.matrixScale, actualMatrixScale);
- }
-}
-
TEST(VectorDrawable, groupProperties) {
//TODO: Also need to test property sync and dirty flag when properties change.
VectorDrawable::Group group;
@@ -458,7 +413,7 @@
// Setting the fill gradient increments the ref count of the shader by 1
path.mutateStagingProperties()->setFillGradient(shader);
- path.draw(&canvas, SkMatrix::I(), 1.0f, 1.0f, true);
+ path.draw(&canvas, true);
// Resetting the fill gradient decrements the ref count of the shader by 1
path.mutateStagingProperties()->setFillGradient(nullptr);
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index ec52742c..f6c5ade 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -777,14 +777,14 @@
<string name="daltonizer_type_overridden">Overridden by <xliff:g id="title" example="Simulate color space">%1$s</xliff:g></string>
<!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery charging/discharging -->
- <string name="power_remaining_duration_only">about <xliff:g id="time">%1$s</xliff:g> left</string>
+ <string name="power_remaining_duration_only">Approx. <xliff:g id="time">%1$s</xliff:g> left</string>
<!-- [CHAR_LIMIT=40] Short label for estimated remaining duration of battery charging/discharging -->
<string name="power_remaining_duration_only_short"><xliff:g id="time">%1$s</xliff:g> left</string>
<!-- [CHAR_LIMIT=40] Label for battery level chart when discharging with duration -->
<string name="power_discharging_duration"><xliff:g id="level">%1$s</xliff:g>
- - approx. <xliff:g id="time">%2$s</xliff:g> left</string>
+ - about <xliff:g id="time">%2$s</xliff:g> left</string>
<!-- [CHAR_LIMIT=40] Label for battery level chart when discharging with duration -->
<string name="power_discharging_duration_short"><xliff:g id="level">%1$s</xliff:g>
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java
new file mode 100644
index 0000000..d3bdeb7
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java
@@ -0,0 +1,50 @@
+/*
+ * 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.settingslib.wifi;
+
+import android.content.Context;
+import android.os.Looper;
+
+/**
+ * Factory method used to inject WifiTracker instances.
+ */
+public class WifiTrackerFactory {
+ private static boolean sTestingMode = false;
+
+ private static WifiTracker sTestingWifiTracker;
+
+ public static void enableTestingMode() {
+ sTestingMode = true;
+ }
+
+ public static void disableTestingMode() {
+ sTestingMode = false;
+ }
+
+ public static void setTestingWifiTracker(WifiTracker tracker) {
+ sTestingWifiTracker = tracker;
+ }
+
+ public static WifiTracker create(
+ Context context, WifiTracker.WifiListener wifiListener, Looper workerLooper,
+ boolean includeSaved, boolean includeScans, boolean includePasspoints) {
+ if(sTestingMode) {
+ return sTestingWifiTracker;
+ }
+ return new WifiTracker(
+ context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints);
+ }
+}
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/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 1f58d4c..f8d1bfb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1582,7 +1582,7 @@
*/
public void reportSimUnlocked(int subId) {
if (DEBUG_SIM_STATES) Log.v(TAG, "reportSimUnlocked(subId=" + subId + ")");
- int slotId = SubscriptionManager.getSlotId(subId);
+ int slotId = SubscriptionManager.getSlotIndex(subId);
handleSimStateChange(subId, slotId, State.READY);
}
@@ -1751,7 +1751,7 @@
for (int i = 0; i < list.size(); i++) {
final SubscriptionInfo info = list.get(i);
final int id = info.getSubscriptionId();
- int slotId = SubscriptionManager.getSlotId(id);
+ int slotId = SubscriptionManager.getSlotIndex(id);
if (state == getSimState(id) && bestSlotId > slotId ) {
resultId = id;
bestSlotId = slotId;
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 4dfaf45..374086d 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -25,6 +25,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.NightDisplayController;
import com.android.internal.util.Preconditions;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.plugins.ActivityStarter;
@@ -248,6 +249,9 @@
mProviders.put(PluginDependencyProvider.class, () ->
new PluginDependencyProvider(get(PluginManager.class)));
+ mProviders.put(LocalBluetoothManager.class, () ->
+ LocalBluetoothManager.getInstance(mContext, null));
+
// Put all dependencies above here so the factory can override them if it wants.
SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
}
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/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index e8ba8f3..2597ce1 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -248,7 +248,6 @@
private void showMenu(Rect stackBounds, Rect movementBounds) {
if (!mMenuVisible) {
- setVisible(true);
updateActionViews(stackBounds);
if (mMenuContainerAnimator != null) {
mMenuContainerAnimator.cancel();
@@ -295,7 +294,9 @@
if (animationFinishedRunnable != null) {
animationFinishedRunnable.run();
}
- setVisible(false);
+ if (getSystemService(AccessibilityManager.class).isEnabled()) {
+ finish();
+ }
}
});
mMenuContainerAnimator.addUpdateListener(mMenuBgUpdateListener);
@@ -405,7 +406,6 @@
}
private void updateDismissFraction(float fraction) {
- setVisible(true);
int alpha;
if (mMenuVisible) {
mMenuContainer.setAlpha(1-fraction);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
index b34a07d..b2b5b02 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
@@ -17,6 +17,7 @@
package com.android.systemui.pip.phone;
import android.graphics.PointF;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
@@ -28,6 +29,7 @@
*/
public class PipTouchState {
private static final String TAG = "PipTouchHandler";
+ private static final boolean DEBUG = true;
private ViewConfiguration mViewConfig;
@@ -72,6 +74,9 @@
initOrResetVelocityTracker();
mActivePointerId = ev.getPointerId(0);
+ if (DEBUG) {
+ Log.e(TAG, "Setting active pointer id on DOWN: " + mActivePointerId);
+ }
mLastTouch.set(ev.getX(), ev.getY());
mDownTouch.set(mLastTouch);
mAllowDraggingOffscreen = true;
@@ -87,6 +92,11 @@
// Update the velocity tracker
mVelocityTracker.addMovement(ev);
int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (pointerIndex == -1) {
+ Log.e(TAG, "Invalid active pointer id on MOVE: " + mActivePointerId);
+ break;
+ }
+
float x = ev.getX(pointerIndex);
float y = ev.getY(pointerIndex);
mLastDelta.set(x - mLastTouch.x, y - mLastTouch.y);
@@ -119,6 +129,10 @@
// Select a new active pointer id and reset the movement state
final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
mActivePointerId = ev.getPointerId(newPointerIndex);
+ if (DEBUG) {
+ Log.e(TAG, "Relinquish active pointer id on POINTER_UP: " +
+ mActivePointerId);
+ }
mLastTouch.set(ev.getX(newPointerIndex), ev.getY(newPointerIndex));
}
break;
@@ -136,6 +150,11 @@
mVelocity.set(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (pointerIndex == -1) {
+ Log.e(TAG, "Invalid active pointer id on UP: " + mActivePointerId);
+ break;
+ }
+
mLastTouch.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
// Fall through to clean up
@@ -251,6 +270,7 @@
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
pw.println(innerPrefix + "mAllowTouches=" + mAllowTouches);
+ pw.println(innerPrefix + "mActivePointerId=" + mActivePointerId);
pw.println(innerPrefix + "mDownTouch=" + mDownTouch);
pw.println(innerPrefix + "mDownDelta=" + mDownDelta);
pw.println(innerPrefix + "mLastTouch=" + mLastTouch);
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/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 2b52b48..5fb642f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -31,6 +31,7 @@
import android.app.IActivityManager;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -107,6 +108,7 @@
private int mNavigationBarMode;
private AccessibilityManager mAccessibilityManager;
private MagnificationContentObserver mMagnificationObserver;
+ private ContentResolver mContentResolver;
private int mDisabledFlags1;
private StatusBar mStatusBar;
@@ -138,9 +140,10 @@
mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class);
mAccessibilityManager.addAccessibilityServicesStateChangeListener(
this::updateAccessibilityServicesState);
+ mContentResolver = getContext().getContentResolver();
mMagnificationObserver = new MagnificationContentObserver(
getContext().getMainThreadHandler());
- getContext().getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
+ mContentResolver.registerContentObserver(Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED), false,
mMagnificationObserver);
@@ -163,7 +166,7 @@
mCommandQueue.removeCallbacks(this);
mAccessibilityManager.removeAccessibilityServicesStateChangeListener(
this::updateAccessibilityServicesState);
- getContext().getContentResolver().unregisterContentObserver(mMagnificationObserver);
+ mContentResolver.unregisterContentObserver(mMagnificationObserver);
try {
WindowManagerGlobal.getWindowManagerService()
.removeRotationWatcher(mRotationWatcher);
@@ -563,7 +566,7 @@
private void updateAccessibilityServicesState() {
int requestingServices = 0;
try {
- if (Settings.Secure.getInt(getContext().getContentResolver(),
+ if (Settings.Secure.getInt(mContentResolver,
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED) == 1) {
requestingServices++;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 15c4afe..36d24b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -29,6 +29,7 @@
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.systemui.Dependency;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -52,7 +53,7 @@
private int mState;
public BluetoothControllerImpl(Context context, Looper bgLooper) {
- mLocalBluetoothManager = LocalBluetoothManager.getInstance(context, null);
+ mLocalBluetoothManager = Dependency.get(LocalBluetoothManager.class);
if (mLocalBluetoothManager != null) {
mLocalBluetoothManager.getEventManager().setReceiverHandler(new Handler(bgLooper));
mLocalBluetoothManager.getEventManager().registerCallback(this);
@@ -174,24 +175,30 @@
private void updateConnected() {
// Make sure our connection state is up to date.
int state = mLocalBluetoothManager.getBluetoothAdapter().getConnectionState();
- if (state != mConnectionState) {
- mConnectionState = state;
- mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
+ if (mLastDevice != null && !mLastDevice.isConnected()) {
+ // Clear out last device if no longer connected.
+ mLastDevice = null;
}
- if (mLastDevice != null && mLastDevice.isConnected()) {
- // Our current device is still valid.
- return;
- }
- mLastDevice = null;
+ // If any of the devices are in a higher state than the adapter, move the adapter into
+ // that state.
for (CachedBluetoothDevice device : getDevices()) {
- if (device.isConnected()) {
+ int maxDeviceState = device.getMaxConnectionState();
+ if (maxDeviceState > state) {
+ state = maxDeviceState;
+ }
+ if (mLastDevice == null && device.isConnected()) {
+ // Set as last connected device only if we don't have one.
mLastDevice = device;
}
}
- if (mLastDevice == null && mConnectionState == BluetoothAdapter.STATE_CONNECTED) {
+
+ if (mLastDevice == null && state == BluetoothAdapter.STATE_CONNECTED) {
// If somehow we think we are connected, but have no connected devices, we aren't
// connected.
- mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
+ state = BluetoothAdapter.STATE_DISCONNECTED;
+ }
+ if (state != mConnectionState) {
+ mConnectionState = state;
mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
}
}
@@ -238,7 +245,6 @@
public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
mLastDevice = cachedDevice;
updateConnected();
- mConnectionState = state;
mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
new file mode 100644
index 0000000..8808988
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.statusbar.policy;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothProfile;
+import android.testing.TestableLooper;
+
+import com.android.settingslib.bluetooth.BluetoothEventManager;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class BluetoothControllerImplTest extends SysuiTestCase {
+
+ private LocalBluetoothManager mMockBluetoothManager;
+ private CachedBluetoothDeviceManager mMockDeviceManager;
+ private LocalBluetoothAdapter mMockAdapter;
+ private TestableLooper mTestableLooper;
+ private BluetoothControllerImpl mBluetoothControllerImpl;
+
+ private List<CachedBluetoothDevice> mDevices;
+
+ @Before
+ public void setup() throws Exception {
+ mTestableLooper = new TestableLooper();
+ mMockBluetoothManager = mDependency.injectMockDependency(LocalBluetoothManager.class);
+ mDevices = new ArrayList<>();
+ mMockDeviceManager = mock(CachedBluetoothDeviceManager.class);
+ when(mMockDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices);
+ when(mMockBluetoothManager.getCachedDeviceManager()).thenReturn(mMockDeviceManager);
+ mMockAdapter = mock(LocalBluetoothAdapter.class);
+ when(mMockBluetoothManager.getBluetoothAdapter()).thenReturn(mMockAdapter);
+ when(mMockBluetoothManager.getEventManager()).thenReturn(mock(BluetoothEventManager.class));
+
+ mBluetoothControllerImpl = new BluetoothControllerImpl(mContext,
+ mTestableLooper.getLooper());
+ }
+
+ @Test
+ public void testNoConnectionWithDevices() {
+ CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+ when(device.isConnected()).thenReturn(true);
+ when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
+ mDevices.add(device);
+ when(mMockAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_DISCONNECTED);
+
+ mBluetoothControllerImpl.onConnectionStateChanged(null,
+ BluetoothAdapter.STATE_DISCONNECTED);
+ assertTrue(mBluetoothControllerImpl.isBluetoothConnected());
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index a8c8752..85eecdf 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -36,6 +36,8 @@
import android.view.autofill.AutofillValue;
import android.widget.ArrayAdapter;
import android.widget.ListView;
+import android.widget.RemoteViews;
+
import com.android.internal.R;
import libcore.util.Objects;
@@ -110,15 +112,15 @@
final Dataset dataset = response.getDatasets().get(i);
final int index = dataset.getFieldIds().indexOf(focusedViewId);
if (index >= 0) {
- final AutofillValue value = dataset.getFieldValues().get(index);
+ final RemoteViews presentation = dataset.getFieldPresentation(index);
final View view;
try {
- view = dataset.getPresentation().apply(context, null);
+ view = presentation.apply(context, null);
} catch (RuntimeException e) {
Slog.e(TAG, "Error inflating remote views", e);
continue;
}
-
+ final AutofillValue value = dataset.getFieldValues().get(index);
String valueText = null;
if (value.isText()) {
valueText = value.getTextValue().toString().toLowerCase();
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/Android.mk b/services/core/Android.mk
index 794ece6..d312902 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -28,6 +28,7 @@
tzdata_update2 \
android.hidl.base@1.0-java-static \
android.hardware.biometrics.fingerprint@2.1-java-static \
+ android.hardware.vibrator@1.0-java-constants \
ifneq ($(INCREMENTAL_BUILDS),)
LOCAL_PROGUARD_ENABLED := disabled
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index c9dd116..98242f9 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -489,7 +489,8 @@
mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
}
});
- } else if (mSentLowBatteryBroadcast && mLastBatteryLevel >= mLowBatteryCloseWarningLevel) {
+ } else if (mSentLowBatteryBroadcast &&
+ mBatteryProps.batteryLevel >= mLowBatteryCloseWarningLevel) {
mSentLowBatteryBroadcast = false;
final Intent statusIntent = new Intent(Intent.ACTION_BATTERY_OKAY);
statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
diff --git a/services/core/java/com/android/server/GraphicsStatsService.java b/services/core/java/com/android/server/GraphicsStatsService.java
index 14a9a31..19bedfb 100644
--- a/services/core/java/com/android/server/GraphicsStatsService.java
+++ b/services/core/java/com/android/server/GraphicsStatsService.java
@@ -32,6 +32,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.Trace;
+import android.os.UserHandle;
import android.util.Log;
import android.view.IGraphicsStats;
import android.view.IGraphicsStatsCallback;
@@ -167,7 +168,10 @@
long callingIdentity = Binder.clearCallingIdentity();
try {
mAppOps.checkPackage(uid, packageName);
- PackageInfo info = mContext.getPackageManager().getPackageInfo(packageName, 0);
+ PackageInfo info = mContext.getPackageManager().getPackageInfoAsUser(
+ packageName,
+ 0,
+ UserHandle.getUserId(uid));
synchronized (mLock) {
pfd = requestBufferForProcessLocked(token, uid, pid, packageName, info.versionCode);
}
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/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index 46c9f25..78c0fe6 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -667,7 +667,7 @@
@Override
public boolean setActiveScorer(String packageName) {
// Only the system can set the active scorer
- if (!isCallerSystemProcess(getCallingUid()) || !callerCanRequestScores()) {
+ if (!isCallerSystemProcess(getCallingUid()) && !callerCanRequestScores()) {
throw new SecurityException(
"Caller is neither the system process nor a score requester.");
}
@@ -736,7 +736,7 @@
@Override
public List<NetworkScorerAppData> getAllValidScorers() {
// Only the system can access this data.
- if (!isCallerSystemProcess(getCallingUid()) || !callerCanRequestScores()) {
+ if (!isCallerSystemProcess(getCallingUid()) && !callerCanRequestScores()) {
throw new SecurityException(
"Caller is neither the system process nor a score requester.");
}
@@ -747,7 +747,7 @@
@Override
public void disableScoring() {
// Only the active scorer or the system should be allowed to disable scoring.
- if (!isCallerActiveScorer(getCallingUid()) || !callerCanRequestScores()) {
+ if (!isCallerActiveScorer(getCallingUid()) && !callerCanRequestScores()) {
throw new SecurityException(
"Caller is neither the active scorer nor the scorer manager.");
}
diff --git a/services/core/java/com/android/server/PreloadsFileCacheExpirationJobService.java b/services/core/java/com/android/server/PreloadsFileCacheExpirationJobService.java
new file mode 100644
index 0000000..6fd0256
--- /dev/null
+++ b/services/core/java/com/android/server/PreloadsFileCacheExpirationJobService.java
@@ -0,0 +1,85 @@
+/*
+ * 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.server;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Environment;
+import android.os.SystemProperties;
+import android.util.Slog;
+import android.util.TimeUtils;
+
+import com.android.internal.R;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * {@link JobService} that marks
+ * {@link Environment#getDataPreloadsFileCacheDirectory() preloaded file cache} as expired after a
+ * pre-configured timeout.
+ */
+public class PreloadsFileCacheExpirationJobService extends JobService {
+ private static final boolean DEBUG = false; // Do not submit with true
+ private static final String TAG = "PreloadsFileCacheExpirationJobService";
+
+ // TODO move all JOB_IDs into a single class to avoid collisions
+ private static final int JOB_ID = 100500;
+
+ private static final String PERSIST_SYS_PRELOADS_FILE_CACHE_EXPIRED
+ = "persist.sys.preloads.file_cache_expired";
+
+ public static void schedule(Context context) {
+ int keepPreloadsMinDays = Resources.getSystem().getInteger(
+ R.integer.config_keepPreloadsMinDays); // Default is 1 week
+ long keepPreloadsMinTimeoutMs = DEBUG ? TimeUnit.MINUTES.toMillis(2)
+ : TimeUnit.DAYS.toMillis(keepPreloadsMinDays);
+ long keepPreloadsMaxTimeoutMs = DEBUG ? TimeUnit.MINUTES.toMillis(3)
+ : TimeUnit.DAYS.toMillis(keepPreloadsMinDays + 1);
+
+ if (DEBUG) {
+ StringBuilder sb = new StringBuilder("Scheduling expiration job to run in ");
+ TimeUtils.formatDuration(keepPreloadsMinTimeoutMs, sb);
+ Slog.i(TAG, sb.toString());
+ }
+ JobInfo expirationJob = new JobInfo.Builder(JOB_ID,
+ new ComponentName(context, PreloadsFileCacheExpirationJobService.class))
+ .setPersisted(true)
+ .setMinimumLatency(keepPreloadsMinTimeoutMs)
+ .setOverrideDeadline(keepPreloadsMaxTimeoutMs)
+ .build();
+
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ jobScheduler.schedule(expirationJob);
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ SystemProperties.set(PERSIST_SYS_PRELOADS_FILE_CACHE_EXPIRED, "1");
+ Slog.i(TAG, "Set " + PERSIST_SYS_PRELOADS_FILE_CACHE_EXPIRED + "=1");
+ return false;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ return false;
+ }
+}
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 5fe6952..c4676d1 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -22,8 +22,10 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.input.InputManager;
+import android.hardware.vibrator.V1_0.Constants.EffectStrength;
import android.media.AudioManager;
import android.os.PowerSaveState;
import android.os.BatteryStats;
@@ -42,6 +44,7 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.Vibrator;
+import android.os.VibrationEffect;
import android.os.WorkSource;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
@@ -67,12 +70,14 @@
private static final boolean DEBUG = false;
private static final String SYSTEM_UI_PACKAGE = "com.android.systemui";
- private final LinkedList<Vibration> mVibrations;
private final LinkedList<VibrationInfo> mPreviousVibrations;
private final int mPreviousVibrationsLimit;
- private Vibration mCurrentVibration;
+ private final boolean mSupportsAmplitudeControl;
+ private final int mDefaultVibrationAmplitude;
+ private final VibrationEffect[] mFallbackEffects;
private final WorkSource mTmpWorkSource = new WorkSource();
private final Handler mH = new Handler();
+ private final Object mLock = new Object();
private final Context mContext;
private final PowerManager.WakeLock mWakeLock;
@@ -81,14 +86,15 @@
private PowerManagerInternal mPowerManagerInternal;
private InputManager mIm;
- volatile VibrateThread mThread;
+ private volatile VibrateThread mThread;
- // mInputDeviceVibrators lock should be acquired after mVibrations lock, if both are
+ // mInputDeviceVibrators lock should be acquired after mLock, if both are
// to be acquired
private final ArrayList<Vibrator> mInputDeviceVibrators = new ArrayList<Vibrator>();
private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators
private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators
+ private Vibration mCurrentVibration;
private int mCurVibUid = -1;
private boolean mLowPowerMode;
private SettingsObserver mSettingObserver;
@@ -97,106 +103,87 @@
native static void vibratorInit();
native static void vibratorOn(long milliseconds);
native static void vibratorOff();
+ native static boolean vibratorSupportsAmplitudeControl();
+ native static void vibratorSetAmplitude(int amplitude);
+ native static long vibratorPerformEffect(long effect, long strength);
private class Vibration implements IBinder.DeathRecipient {
private final IBinder mToken;
- private final long mTimeout;
- private final long mStartTime;
- private final long[] mPattern;
- private final int mRepeat;
- private final int mUsageHint;
- private final int mUid;
- private final String mOpPkg;
+ private final VibrationEffect mEffect;
+ private final long mStartTime;
+ private final int mUsageHint;
+ private final int mUid;
+ private final String mOpPkg;
- Vibration(IBinder token, long millis, int usageHint, int uid, String opPkg) {
- this(token, millis, null, 0, usageHint, uid, opPkg);
- }
-
- Vibration(IBinder token, long[] pattern, int repeat, int usageHint, int uid,
- String opPkg) {
- this(token, 0, pattern, repeat, usageHint, uid, opPkg);
- }
-
- private Vibration(IBinder token, long millis, long[] pattern,
- int repeat, int usageHint, int uid, String opPkg) {
+ private Vibration(IBinder token, VibrationEffect effect,
+ int usageHint, int uid, String opPkg) {
mToken = token;
- mTimeout = millis;
+ mEffect = effect;
mStartTime = SystemClock.uptimeMillis();
- mPattern = pattern;
- mRepeat = repeat;
mUsageHint = usageHint;
mUid = uid;
mOpPkg = opPkg;
}
public void binderDied() {
- synchronized (mVibrations) {
- mVibrations.remove(this);
+ synchronized (mLock) {
if (this == mCurrentVibration) {
doCancelVibrateLocked();
- startNextVibrationLocked();
}
}
}
public boolean hasLongerTimeout(long millis) {
- if (mTimeout == 0) {
- // This is a pattern, return false to play the simple
- // vibration.
- return false;
+ // If the current effect is a one shot vibration that will end after the given timeout
+ // for the new one shot vibration, then just let the current vibration finish. All
+ // other effect types will get pre-empted.
+ if (mEffect instanceof VibrationEffect.OneShot) {
+ VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) mEffect;
+ return mStartTime + oneShot.getTiming() > SystemClock.uptimeMillis() + millis;
}
- if ((mStartTime + mTimeout)
- < (SystemClock.uptimeMillis() + millis)) {
- // If this vibration will end before the time passed in, let
- // the new vibration play.
- return false;
- }
- return true;
+ return false;
}
public boolean isSystemHapticFeedback() {
+ boolean repeating = false;
+ if (mEffect instanceof VibrationEffect.Waveform) {
+ VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) mEffect;
+ repeating = (waveform.getRepeatIndex() < 0);
+ }
return (mUid == Process.SYSTEM_UID || mUid == 0 || SYSTEM_UI_PACKAGE.equals(mOpPkg))
- && mRepeat < 0;
+ && !repeating;
}
}
private static class VibrationInfo {
- long timeout;
- long startTime;
- long[] pattern;
- int repeat;
- int usageHint;
- int uid;
- String opPkg;
+ private final long mStartTime;
+ private final VibrationEffect mEffect;
+ private final int mUsageHint;
+ private final int mUid;
+ private final String mOpPkg;
- public VibrationInfo(long timeout, long startTime, long[] pattern, int repeat,
+ public VibrationInfo(long startTime, VibrationEffect effect,
int usageHint, int uid, String opPkg) {
- this.timeout = timeout;
- this.startTime = startTime;
- this.pattern = pattern;
- this.repeat = repeat;
- this.usageHint = usageHint;
- this.uid = uid;
- this.opPkg = opPkg;
+ mStartTime = startTime;
+ mEffect = effect;
+ mUsageHint = usageHint;
+ mUid = uid;
+ mOpPkg = opPkg;
}
@Override
public String toString() {
return new StringBuilder()
- .append("timeout: ")
- .append(timeout)
.append(", startTime: ")
- .append(startTime)
- .append(", pattern: ")
- .append(Arrays.toString(pattern))
- .append(", repeat: ")
- .append(repeat)
+ .append(mStartTime)
+ .append(", effect: ")
+ .append(mEffect)
.append(", usageHint: ")
- .append(usageHint)
+ .append(mUsageHint)
.append(", uid: ")
- .append(uid)
+ .append(mUid)
.append(", opPkg: ")
- .append(opPkg)
+ .append(mOpPkg)
.toString();
}
}
@@ -207,25 +194,38 @@
// restart instead of a fresh boot.
vibratorOff();
+ mSupportsAmplitudeControl = vibratorSupportsAmplitudeControl();
+
mContext = context;
- PowerManager pm = (PowerManager)context.getSystemService(
- Context.POWER_SERVICE);
+ PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
mWakeLock.setReferenceCounted(true);
- mAppOpsService = IAppOpsService.Stub.asInterface(ServiceManager.getService(Context.APP_OPS_SERVICE));
+ mAppOpsService =
+ IAppOpsService.Stub.asInterface(ServiceManager.getService(Context.APP_OPS_SERVICE));
mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
BatteryStats.SERVICE_NAME));
mPreviousVibrationsLimit = mContext.getResources().getInteger(
com.android.internal.R.integer.config_previousVibrationsDumpLimit);
- mVibrations = new LinkedList<>();
+ mDefaultVibrationAmplitude = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_defaultVibrationAmplitude);
+
mPreviousVibrations = new LinkedList<>();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
context.registerReceiver(mIntentReceiver, filter);
+
+ long[] clickEffectTimings = getLongIntArray(context.getResources(),
+ com.android.internal.R.array.config_virtualKeyVibePattern);
+ VibrationEffect clickEffect = VibrationEffect.createWaveform(clickEffectTimings, -1);
+ VibrationEffect doubleClickEffect = VibrationEffect.createWaveform(
+ new long[] {0, 30, 100, 30} /*timings*/, -1);
+
+ mFallbackEffects = new VibrationEffect[] { clickEffect, doubleClickEffect };
+
}
public void systemReady() {
@@ -242,7 +242,7 @@
@Override
public void onLowPowerModeChanged(PowerSaveState result) {
- updateInputDeviceVibrators();
+ updateVibrators();
}
});
@@ -253,11 +253,11 @@
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- updateInputDeviceVibrators();
+ updateVibrators();
}
}, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mH);
- updateInputDeviceVibrators();
+ updateVibrators();
}
private final class SettingsObserver extends ContentObserver {
@@ -267,7 +267,7 @@
@Override
public void onChange(boolean SelfChange) {
- updateInputDeviceVibrators();
+ updateVibrators();
}
}
@@ -276,6 +276,15 @@
return doVibratorExists();
}
+ @Override // Binder call
+ public boolean hasAmplitudeControl() {
+ synchronized (mInputDeviceVibrators) {
+ // Input device vibrators don't support amplitude controls yet, but are still used over
+ // the system vibrator when connected.
+ return mSupportsAmplitudeControl && mInputDeviceVibrators.isEmpty();
+ }
+ }
+
private void verifyIncomingUid(int uid) {
if (uid == Binder.getCallingUid()) {
return;
@@ -287,103 +296,96 @@
Binder.getCallingPid(), Binder.getCallingUid(), null);
}
+ /**
+ * Validate the incoming VibrationEffect.
+ *
+ * We can't throw exceptions here since we might be called from some system_server component,
+ * which would bring the whole system down.
+ *
+ * @return whether the VibrationEffect is valid
+ */
+ private static boolean verifyVibrationEffect(VibrationEffect effect) {
+ if (effect == null) {
+ // Effect must not be null.
+ Slog.wtf(TAG, "effect must not be null");
+ return false;
+ }
+ try {
+ effect.validate();
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Encountered issue when verifying VibrationEffect.", e);
+ return false;
+ }
+ return true;
+ }
+
+ private static long[] getLongIntArray(Resources r, int resid) {
+ int[] ar = r.getIntArray(resid);
+ if (ar == null) {
+ return null;
+ }
+ long[] out = new long[ar.length];
+ for (int i = 0; i < ar.length; i++) {
+ out[i] = ar[i];
+ }
+ return out;
+ }
+
@Override // Binder call
- public void vibrate(int uid, String opPkg, long milliseconds, int usageHint,
+ public void vibrate(int uid, String opPkg, VibrationEffect effect, int usageHint,
IBinder token) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires VIBRATE permission");
}
+ if (token == null) {
+ Slog.e(TAG, "token must not be null");
+ return;
+ }
verifyIncomingUid(uid);
- // We're running in the system server so we cannot crash. Check for a
- // timeout of 0 or negative. This will ensure that a vibration has
- // either a timeout of > 0 or a non-null pattern.
- if (milliseconds <= 0 || (mCurrentVibration != null
- && mCurrentVibration.hasLongerTimeout(milliseconds))) {
- // Ignore this vibration since the current vibration will play for
- // longer than milliseconds.
+ if (!verifyVibrationEffect(effect)) {
return;
}
- if (DEBUG) {
- Slog.d(TAG, "Vibrating for " + milliseconds + " ms.");
- }
-
- Vibration vib = new Vibration(token, milliseconds, usageHint, uid, opPkg);
-
- final long ident = Binder.clearCallingIdentity();
- try {
- synchronized (mVibrations) {
- removeVibrationLocked(token);
- doCancelVibrateLocked();
- addToPreviousVibrationsLocked(vib);
- startVibrationLocked(vib);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- private boolean isAll0(long[] pattern) {
- int N = pattern.length;
- for (int i = 0; i < N; i++) {
- if (pattern[i] != 0) {
- return false;
- }
- }
- return true;
- }
-
- @Override // Binder call
- public void vibratePattern(int uid, String packageName, long[] pattern, int repeat,
- int usageHint, IBinder token) {
- if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Requires VIBRATE permission");
- }
- verifyIncomingUid(uid);
- // so wakelock calls will succeed
- long identity = Binder.clearCallingIdentity();
- try {
- if (DEBUG) {
- String s = "";
- int N = pattern.length;
- for (int i=0; i<N; i++) {
- s += " " + pattern[i];
+ // If our current vibration is longer than the new vibration and is the same amplitude,
+ // then just let the current one finish.
+ if (effect instanceof VibrationEffect.OneShot
+ && mCurrentVibration != null
+ && mCurrentVibration.mEffect instanceof VibrationEffect.OneShot) {
+ VibrationEffect.OneShot newOneShot = (VibrationEffect.OneShot) effect;
+ VibrationEffect.OneShot currentOneShot =
+ (VibrationEffect.OneShot) mCurrentVibration.mEffect;
+ if (mCurrentVibration.hasLongerTimeout(newOneShot.getTiming())
+ && newOneShot.getAmplitude() == currentOneShot.getAmplitude()) {
+ if (DEBUG) {
+ Slog.e(TAG, "Ignoring incoming vibration in favor of current vibration");
}
- Slog.d(TAG, "Vibrating with pattern:" + s);
- }
-
- // we're running in the server so we can't fail
- if (pattern == null || pattern.length == 0
- || isAll0(pattern)
- || repeat >= pattern.length || token == null) {
return;
}
+ }
- Vibration vib = new Vibration(token, pattern, repeat, usageHint, uid, packageName);
+ Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg);
+
+ // Only link against waveforms since they potentially don't have a finish if
+ // they're repeating. Let other effects just play out until they're done.
+ if (effect instanceof VibrationEffect.Waveform) {
try {
token.linkToDeath(vib, 0);
} catch (RemoteException e) {
return;
}
+ }
- synchronized (mVibrations) {
- removeVibrationLocked(token);
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
doCancelVibrateLocked();
- if (repeat >= 0) {
- mVibrations.addFirst(vib);
- startNextVibrationLocked();
- } else {
- // A negative repeat means that this pattern is not meant
- // to repeat. Treat it like a simple vibration.
- startVibrationLocked(vib);
- }
+ startVibrationLocked(vib);
addToPreviousVibrationsLocked(vib);
}
- }
- finally {
- Binder.restoreCallingIdentity(identity);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -391,8 +393,8 @@
if (mPreviousVibrations.size() > mPreviousVibrationsLimit) {
mPreviousVibrations.removeFirst();
}
- mPreviousVibrations.addLast(new VibratorService.VibrationInfo(vib.mTimeout, vib.mStartTime,
- vib.mPattern, vib.mRepeat, vib.mUsageHint, vib.mUid, vib.mOpPkg));
+ mPreviousVibrations.addLast(new VibrationInfo(
+ vib.mStartTime, vib.mEffect, vib.mUsageHint, vib.mUid, vib.mOpPkg));
}
@Override // Binder call
@@ -401,97 +403,97 @@
android.Manifest.permission.VIBRATE,
"cancelVibrate");
- // so wakelock calls will succeed
- long identity = Binder.clearCallingIdentity();
- try {
- synchronized (mVibrations) {
- final Vibration vib = removeVibrationLocked(token);
- if (vib == mCurrentVibration) {
- if (DEBUG) {
- Slog.d(TAG, "Canceling vibration.");
- }
+ synchronized (mLock) {
+ if (mCurrentVibration != null && mCurrentVibration.mToken == token) {
+ if (DEBUG) {
+ Slog.d(TAG, "Canceling vibration.");
+ }
+ long ident = Binder.clearCallingIdentity();
+ try {
doCancelVibrateLocked();
- startNextVibrationLocked();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
}
- finally {
- Binder.restoreCallingIdentity(identity);
- }
}
- private final Runnable mVibrationRunnable = new Runnable() {
+ private final Runnable mVibrationEndRunnable = new Runnable() {
@Override
public void run() {
- synchronized (mVibrations) {
- doCancelVibrateLocked();
- startNextVibrationLocked();
- }
+ onVibrationFinished();
}
};
- // Lock held on mVibrations
private void doCancelVibrateLocked() {
+ mH.removeCallbacks(mVibrationEndRunnable);
if (mThread != null) {
- synchronized (mThread) {
- mThread.mDone = true;
- mThread.notify();
- }
+ mThread.cancel();
mThread = null;
}
doVibratorOff();
- mH.removeCallbacks(mVibrationRunnable);
reportFinishVibrationLocked();
}
- // Lock held on mVibrations
- private void startNextVibrationLocked() {
- if (mVibrations.size() <= 0) {
- reportFinishVibrationLocked();
- mCurrentVibration = null;
- return;
+ // Callback for whenever the current vibration has finished played out
+ public void onVibrationFinished() {
+ if (DEBUG) {
+ Slog.e(TAG, "Vibration finished, cleaning up");
}
- startVibrationLocked(mVibrations.getFirst());
+ synchronized (mLock) {
+ // Make sure the vibration is really done. This also reports that the vibration is
+ // finished.
+ doCancelVibrateLocked();
+ }
}
- // Lock held on mVibrations
private void startVibrationLocked(final Vibration vib) {
- try {
- if (mLowPowerMode
- && vib.mUsageHint != AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
- return;
+ if (mLowPowerMode && vib.mUsageHint != AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
+ if (DEBUG) {
+ Slog.e(TAG, "Vibrate ignored, low power mode");
}
-
- if (vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE &&
- !shouldVibrateForRingtone()) {
- return;
- }
-
- int mode = mAppOpsService.checkAudioOperation(AppOpsManager.OP_VIBRATE,
- vib.mUsageHint, vib.mUid, vib.mOpPkg);
- if (mode == AppOpsManager.MODE_ALLOWED) {
- mode = mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
- AppOpsManager.OP_VIBRATE, vib.mUid, vib.mOpPkg);
- }
- if (mode == AppOpsManager.MODE_ALLOWED) {
- mCurrentVibration = vib;
- } else {
- if (mode == AppOpsManager.MODE_ERRORED) {
- Slog.w(TAG, "Would be an error: vibrate from uid " + vib.mUid);
- }
- mH.post(mVibrationRunnable);
- return;
- }
- } catch (RemoteException e) {
+ return;
}
- if (vib.mTimeout != 0) {
- doVibratorOn(vib.mTimeout, vib.mUid, vib.mUsageHint);
- mH.postDelayed(mVibrationRunnable, vib.mTimeout);
- } else {
+
+ if (vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE &&
+ !shouldVibrateForRingtone()) {
+ if (DEBUG) {
+ Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
+ }
+ return;
+ }
+
+ final int mode = getAppOpMode(vib);
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ if (mode == AppOpsManager.MODE_ERRORED) {
+ // We might be getting calls from within system_server, so we don't actually want
+ // to throw a SecurityException here.
+ Slog.w(TAG, "Would be an error: vibrate from uid " + vib.mUid);
+ }
+ return;
+ }
+ startVibrationInnerLocked(vib);
+ }
+
+ private void startVibrationInnerLocked(Vibration vib) {
+ mCurrentVibration = vib;
+ if (vib.mEffect instanceof VibrationEffect.OneShot) {
+ VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) vib.mEffect;
+ doVibratorOn(oneShot.getTiming(), oneShot.getAmplitude(), vib.mUid, vib.mUsageHint);
+ mH.postDelayed(mVibrationEndRunnable, oneShot.getTiming());
+ } else if (vib.mEffect instanceof VibrationEffect.Waveform) {
// mThread better be null here. doCancelVibrate should always be
// called before startNextVibrationLocked or startVibrationLocked.
- mThread = new VibrateThread(vib);
+ VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) vib.mEffect;
+ mThread = new VibrateThread(waveform, vib.mUid, vib.mUsageHint);
mThread.start();
+ } else if (vib.mEffect instanceof VibrationEffect.Prebaked) {
+ long timeout = doVibratorPrebakedEffectLocked(vib);
+ if (timeout > 0) {
+ mH.postDelayed(mVibrationEndRunnable, timeout);
+ }
+ } else {
+ Slog.e(TAG, "Unknown vibration type, ignoring");
}
}
@@ -507,104 +509,115 @@
}
}
+ private int getAppOpMode(Vibration vib) {
+ int mode;
+ try {
+ mode = mAppOpsService.checkAudioOperation(AppOpsManager.OP_VIBRATE,
+ vib.mUsageHint, vib.mUid, vib.mOpPkg);
+ if (mode == AppOpsManager.MODE_ALLOWED) {
+ mode = mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
+ AppOpsManager.OP_VIBRATE, vib.mUid, vib.mOpPkg);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get appop mode for vibration!", e);
+ mode = AppOpsManager.MODE_IGNORED;
+ }
+ return mode;
+ }
+
private void reportFinishVibrationLocked() {
if (mCurrentVibration != null) {
try {
mAppOpsService.finishOperation(AppOpsManager.getToken(mAppOpsService),
AppOpsManager.OP_VIBRATE, mCurrentVibration.mUid,
mCurrentVibration.mOpPkg);
- } catch (RemoteException e) {
- }
+ } catch (RemoteException e) { }
mCurrentVibration = null;
}
}
- // Lock held on mVibrations
- private Vibration removeVibrationLocked(IBinder token) {
- ListIterator<Vibration> iter = mVibrations.listIterator(0);
- while (iter.hasNext()) {
- Vibration vib = iter.next();
- if (vib.mToken == token) {
- iter.remove();
- unlinkVibration(vib);
- return vib;
- }
- }
- // We might be looking for a simple vibration which is only stored in
- // mCurrentVibration.
- if (mCurrentVibration != null && mCurrentVibration.mToken == token) {
- unlinkVibration(mCurrentVibration);
- return mCurrentVibration;
- }
- return null;
- }
-
private void unlinkVibration(Vibration vib) {
- if (vib.mPattern != null) {
- // If Vibration object has a pattern,
- // the Vibration object has also been linkedToDeath.
+ if (vib.mEffect instanceof VibrationEffect.Waveform) {
vib.mToken.unlinkToDeath(vib, 0);
}
}
- private void updateInputDeviceVibrators() {
- synchronized (mVibrations) {
- doCancelVibrateLocked();
+ private void updateVibrators() {
+ synchronized (mLock) {
+ boolean devicesUpdated = updateInputDeviceVibratorsLocked();
+ boolean lowPowerModeUpdated = updateLowPowerModeLocked();
- synchronized (mInputDeviceVibrators) {
- mVibrateInputDevicesSetting = false;
- try {
- mVibrateInputDevicesSetting = Settings.System.getIntForUser(
- mContext.getContentResolver(),
- Settings.System.VIBRATE_INPUT_DEVICES, UserHandle.USER_CURRENT) > 0;
- } catch (SettingNotFoundException snfe) {
- }
+ if (devicesUpdated || lowPowerModeUpdated) {
+ // If the state changes out from under us then just reset.
+ doCancelVibrateLocked();
+ }
+ }
+ }
- mLowPowerMode = mPowerManagerInternal
- .getLowPowerState(ServiceType.VIBRATION).batterySaverEnabled;
+ private boolean updateInputDeviceVibratorsLocked() {
+ boolean changed = false;
+ boolean vibrateInputDevices = false;
+ try {
+ vibrateInputDevices = Settings.System.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.System.VIBRATE_INPUT_DEVICES, UserHandle.USER_CURRENT) > 0;
+ } catch (SettingNotFoundException snfe) {
+ }
+ if (vibrateInputDevices != mVibrateInputDevicesSetting) {
+ changed = true;
+ mVibrateInputDevicesSetting = vibrateInputDevices;
+ }
- if (mVibrateInputDevicesSetting) {
- if (!mInputDeviceListenerRegistered) {
- mInputDeviceListenerRegistered = true;
- mIm.registerInputDeviceListener(this, mH);
- }
- } else {
- if (mInputDeviceListenerRegistered) {
- mInputDeviceListenerRegistered = false;
- mIm.unregisterInputDeviceListener(this);
- }
- }
+ if (mVibrateInputDevicesSetting) {
+ if (!mInputDeviceListenerRegistered) {
+ mInputDeviceListenerRegistered = true;
+ mIm.registerInputDeviceListener(this, mH);
+ }
+ } else {
+ if (mInputDeviceListenerRegistered) {
+ mInputDeviceListenerRegistered = false;
+ mIm.unregisterInputDeviceListener(this);
+ }
+ }
- mInputDeviceVibrators.clear();
- if (mVibrateInputDevicesSetting) {
- int[] ids = mIm.getInputDeviceIds();
- for (int i = 0; i < ids.length; i++) {
- InputDevice device = mIm.getInputDevice(ids[i]);
- Vibrator vibrator = device.getVibrator();
- if (vibrator.hasVibrator()) {
- mInputDeviceVibrators.add(vibrator);
- }
- }
+ mInputDeviceVibrators.clear();
+ if (mVibrateInputDevicesSetting) {
+ int[] ids = mIm.getInputDeviceIds();
+ for (int i = 0; i < ids.length; i++) {
+ InputDevice device = mIm.getInputDevice(ids[i]);
+ Vibrator vibrator = device.getVibrator();
+ if (vibrator.hasVibrator()) {
+ mInputDeviceVibrators.add(vibrator);
}
}
-
- startNextVibrationLocked();
+ return true;
}
+ return changed;
+ }
+
+ private boolean updateLowPowerModeLocked() {
+ boolean lowPowerMode = mPowerManagerInternal
+ .getLowPowerState(ServiceType.VIBRATION).batterySaverEnabled;
+ if (lowPowerMode != mLowPowerMode) {
+ mLowPowerMode = lowPowerMode;
+ return true;
+ }
+ return false;
}
@Override
public void onInputDeviceAdded(int deviceId) {
- updateInputDeviceVibrators();
+ updateVibrators();
}
@Override
public void onInputDeviceChanged(int deviceId) {
- updateInputDeviceVibrators();
+ updateVibrators();
}
@Override
public void onInputDeviceRemoved(int deviceId) {
- updateInputDeviceVibrators();
+ updateVibrators();
}
private boolean doVibratorExists() {
@@ -619,41 +632,44 @@
return vibratorExists();
}
- private void doVibratorOn(long millis, int uid, int usageHint) {
+ private void doVibratorOn(long millis, int amplitude, int uid, int usageHint) {
synchronized (mInputDeviceVibrators) {
+ if (amplitude == VibrationEffect.DEFAULT_AMPLITUDE) {
+ amplitude = mDefaultVibrationAmplitude;
+ }
if (DEBUG) {
- Slog.d(TAG, "Turning vibrator on for " + millis + " ms.");
+ Slog.d(TAG, "Turning vibrator on for " + millis + " ms" +
+ " with amplitude " + amplitude + ".");
}
- try {
- mBatteryStatsService.noteVibratorOn(uid, millis);
- mCurVibUid = uid;
- } catch (RemoteException e) {
- }
+ noteVibratorOnLocked(uid, millis);
final int vibratorCount = mInputDeviceVibrators.size();
if (vibratorCount != 0) {
- final AudioAttributes attributes = new AudioAttributes.Builder().setUsage(usageHint)
- .build();
+ final AudioAttributes attributes =
+ new AudioAttributes.Builder().setUsage(usageHint).build();
for (int i = 0; i < vibratorCount; i++) {
mInputDeviceVibrators.get(i).vibrate(millis, attributes);
}
} else {
+ // Note: ordering is important here! Many haptic drivers will reset their amplitude
+ // when enabled, so we always have to enable frst, then set the amplitude.
vibratorOn(millis);
+ doVibratorSetAmplitude(amplitude);
}
}
}
+ private void doVibratorSetAmplitude(int amplitude) {
+ if (mSupportsAmplitudeControl) {
+ vibratorSetAmplitude(amplitude);
+ }
+ }
+
private void doVibratorOff() {
synchronized (mInputDeviceVibrators) {
if (DEBUG) {
Slog.d(TAG, "Turning vibrator off.");
}
- if (mCurVibUid >= 0) {
- try {
- mBatteryStatsService.noteVibratorOff(mCurVibUid);
- } catch (RemoteException e) {
- }
- mCurVibUid = -1;
- }
+ noteVibratorOffLocked();
final int vibratorCount = mInputDeviceVibrators.size();
if (vibratorCount != 0) {
for (int i = 0; i < vibratorCount; i++) {
@@ -665,86 +681,175 @@
}
}
- private class VibrateThread extends Thread {
- final Vibration mVibration;
- boolean mDone;
+ private long doVibratorPrebakedEffectLocked(Vibration vib) {
+ synchronized (mInputDeviceVibrators) {
+ VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.mEffect;
+ // Input devices don't support prebaked effect, so skip trying it with them.
+ final int vibratorCount = mInputDeviceVibrators.size();
+ if (vibratorCount == 0) {
+ long timeout = vibratorPerformEffect(prebaked.getId(), EffectStrength.MEDIUM);
+ if (timeout > 0) {
+ noteVibratorOnLocked(vib.mUid, timeout);
+ return timeout;
+ }
+ }
+ final int id = prebaked.getId();
+ if (id < 0 || id >= mFallbackEffects.length) {
+ Slog.w(TAG, "Failed to play prebaked effect, no fallback");
+ return 0;
+ }
+ VibrationEffect effect = mFallbackEffects[id];
+ Vibration fallbackVib =
+ new Vibration(vib.mToken, effect, vib.mUsageHint, vib.mUid, vib.mOpPkg);
+ startVibrationInnerLocked(fallbackVib);
+ }
+ return 0;
+ }
- VibrateThread(Vibration vib) {
- mVibration = vib;
- mTmpWorkSource.set(vib.mUid);
+ private void noteVibratorOnLocked(int uid, long millis) {
+ try {
+ mBatteryStatsService.noteVibratorOn(uid, millis);
+ mCurVibUid = uid;
+ } catch (RemoteException e) {
+ }
+ }
+
+ private void noteVibratorOffLocked() {
+ if (mCurVibUid >= 0) {
+ try {
+ mBatteryStatsService.noteVibratorOff(mCurVibUid);
+ } catch (RemoteException e) { }
+ mCurVibUid = -1;
+ }
+ }
+
+ private class VibrateThread extends Thread {
+ private final VibrationEffect.Waveform mWaveform;
+ private final int mUid;
+ private final int mUsageHint;
+
+ private boolean mForceStop;
+
+ VibrateThread(VibrationEffect.Waveform waveform, int uid, int usageHint) {
+ mWaveform = waveform;
+ mUid = uid;
+ mUsageHint = usageHint;
+ mTmpWorkSource.set(uid);
mWakeLock.setWorkSource(mTmpWorkSource);
- mWakeLock.acquire();
}
- private void delay(long duration) {
+ private long delayLocked(long duration) {
+ long durationRemaining = duration;
if (duration > 0) {
- long bedtime = duration + SystemClock.uptimeMillis();
+ final long bedtime = duration + SystemClock.uptimeMillis();
do {
try {
- this.wait(duration);
+ this.wait(durationRemaining);
}
- catch (InterruptedException e) {
- }
- if (mDone) {
+ catch (InterruptedException e) { }
+ if (mForceStop) {
break;
}
- duration = bedtime - SystemClock.uptimeMillis();
- } while (duration > 0);
+ durationRemaining = bedtime - SystemClock.uptimeMillis();
+ } while (durationRemaining > 0);
+ return duration - durationRemaining;
}
+ return 0;
}
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
- synchronized (this) {
- final long[] pattern = mVibration.mPattern;
- final int len = pattern.length;
- final int repeat = mVibration.mRepeat;
- final int uid = mVibration.mUid;
- final int usageHint = mVibration.mUsageHint;
- int index = 0;
- long duration = 0;
-
- while (!mDone) {
- // add off-time duration to any accumulated on-time duration
- if (index < len) {
- duration += pattern[index++];
- }
-
- // sleep until it is time to start the vibrator
- delay(duration);
- if (mDone) {
- break;
- }
-
- if (index < len) {
- // read on-time duration and start the vibrator
- // duration is saved for delay() at top of loop
- duration = pattern[index++];
- if (duration > 0) {
- VibratorService.this.doVibratorOn(duration, uid, usageHint);
- }
- } else {
- if (repeat < 0) {
- break;
- } else {
- index = repeat;
- duration = 0;
- }
- }
+ mWakeLock.acquire();
+ try {
+ boolean finished = playWaveform();
+ if (finished) {
+ onVibrationFinished();
}
+ } finally {
mWakeLock.release();
}
- synchronized (mVibrations) {
- if (mThread == this) {
- mThread = null;
+ }
+
+ /**
+ * Play the waveform.
+ *
+ * @return true if it finished naturally, false otherwise (e.g. it was canceled).
+ */
+ public boolean playWaveform() {
+ synchronized (this) {
+ final long[] timings = mWaveform.getTimings();
+ final int[] amplitudes = mWaveform.getAmplitudes();
+ final int len = timings.length;
+ final int repeat = mWaveform.getRepeatIndex();
+
+ int index = 0;
+ long onDuration = 0;
+ while (!mForceStop) {
+ if (index < len) {
+ final int amplitude = amplitudes[index];
+ final long duration = timings[index++];
+ if (duration <= 0) {
+ continue;
+ }
+ if (amplitude != 0) {
+ if (onDuration <= 0) {
+ // Telling the vibrator to start multiple times usually causes
+ // effects to feel "choppy" because the motor resets at every on
+ // command. Instead we figure out how long our next "on" period is
+ // going to be, tell the motor to stay on for the full duration,
+ // and then wake up to change the amplitude at the appropriate
+ // intervals.
+ onDuration =
+ getTotalOnDuration(timings, amplitudes, index - 1, repeat);
+ doVibratorOn(onDuration, amplitude, mUid, mUsageHint);
+ } else {
+ doVibratorSetAmplitude(amplitude);
+ }
+ }
+
+ long waitTime = delayLocked(duration);
+ if (amplitude != 0) {
+ onDuration -= waitTime;
+ }
+ } else if (repeat < 0) {
+ break;
+ } else {
+ index = repeat;
+ }
}
- if (!mDone) {
- // If this vibration finished naturally, start the next
- // vibration.
- unlinkVibration(mVibration);
- startNextVibrationLocked();
+ return !mForceStop;
+ }
+ }
+
+ public void cancel() {
+ synchronized (this) {
+ mThread.mForceStop = true;
+ mThread.notify();
+ }
+ }
+
+ /**
+ * Get the duration the vibrator will be on starting at startIndex until the next time it's
+ * off.
+ */
+ private long getTotalOnDuration(
+ long[] timings, int[] amplitudes, int startIndex, int repeatIndex) {
+ int i = startIndex;
+ long timing = 0;
+ while(amplitudes[i] != 0) {
+ timing += timings[i++];
+ if (i >= timings.length) {
+ if (repeatIndex >= 0) {
+ i = repeatIndex;
+ } else {
+ break;
+ }
+ }
+ if (i == startIndex) {
+ return 1000;
}
}
+ return timing;
}
}
@@ -752,7 +857,7 @@
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
- synchronized (mVibrations) {
+ synchronized (mLock) {
// When the system is entering a non-interactive state, we want
// to cancel vibrations in case a misbehaving app has abandoned
// them. However it may happen that the system is currently playing
@@ -762,16 +867,6 @@
&& !mCurrentVibration.isSystemHapticFeedback()) {
doCancelVibrateLocked();
}
-
- // Clear all remaining vibrations.
- Iterator<Vibration> it = mVibrations.iterator();
- while (it.hasNext()) {
- Vibration vibration = it.next();
- if (vibration != mCurrentVibration) {
- unlinkVibration(vibration);
- it.remove();
- }
- }
}
}
}
@@ -788,7 +883,7 @@
return;
}
pw.println("Previous vibrations:");
- synchronized (mVibrations) {
+ synchronized (mLock) {
for (VibrationInfo info : mPreviousVibrations) {
pw.print(" ");
pw.println(info.toString());
@@ -830,7 +925,10 @@
if (description == null) {
description = "Shell command";
}
- vibrate(Binder.getCallingUid(), description, duration, AudioAttributes.USAGE_UNKNOWN,
+
+ VibrationEffect effect =
+ VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE);
+ vibrate(Binder.getCallingUid(), description, effect, AudioAttributes.USAGE_UNKNOWN,
mToken);
return 0;
}
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index be021ea..ce4ca02 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -71,7 +71,6 @@
"/system/bin/mediaserver",
"/system/bin/sdcard",
"/system/bin/surfaceflinger",
- "media.codec", // system/bin/mediacodec
"media.extractor", // system/bin/mediaextractor
"com.android.bluetooth", // Bluetooth service
};
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index f954f75..490e63d 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -119,6 +119,7 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
+import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
@@ -210,10 +211,11 @@
/** protected by the {@link #cacheLock} */
private final TokenCache accountTokenCaches = new TokenCache();
- /** protected by the {@link #cacheLock} */
- // TODO use callback to set up the map.
- private final Map<String, LinkedHashSet<String>> mApplicationAccountRequestMappings =
- new HashMap<>();
+ /** protected by the {@link #mReceiversForType}
+ * type -> (packageName -> number of active receivers)
+ * type == null is used to get notifications about all account types
+ */
+ private final Map<String, Map<String, Integer>> mReceiversForType = new HashMap<>();
/**
* protected by the {@link #cacheLock}
@@ -512,7 +514,7 @@
@Override
public Map<String, Integer> getPackagesAndVisibilityForAccount(Account account) {
- if (account == null) throw new IllegalArgumentException("account is null");
+ Preconditions.checkNotNull(account, "account cannot be null");
int callingUid = Binder.getCallingUid();
int userId = UserHandle.getUserId(callingUid);
UserAccounts accounts = getUserAccounts(userId);
@@ -541,23 +543,23 @@
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
-
}
@Override
- public int getAccountVisibility(Account a, String packageName) {
- if (a == null) throw new IllegalArgumentException("account is null");
+ public int getAccountVisibility(Account account, String packageName) {
+ Preconditions.checkNotNull(account, "account cannot be null");
+ Preconditions.checkNotNull(packageName, "packageName cannot be null");
int callingUid = Binder.getCallingUid();
- if (!isAccountManagedByCaller(a.type, callingUid, UserHandle.getUserId(callingUid))
+ UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid));
+ if (!isAccountManagedByCaller(account.type, callingUid, accounts.userId)
&& !isSystemUid(callingUid)) {
String msg = String.format(
"uid %s cannot get secrets for accounts of type: %s",
callingUid,
- a.type);
+ account.type);
throw new SecurityException(msg);
}
- return resolveAccountVisibility(a, packageName,
- getUserAccounts(UserHandle.getUserId(callingUid)));
+ return resolveAccountVisibility(account, packageName, accounts);
}
/**
@@ -573,7 +575,8 @@
private int getAccountVisibility(Account account, String packageName, UserAccounts accounts) {
final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
try {
- Integer visibility = accounts.accountsDb.findAccountVisibility(account, packageName);
+ Integer visibility =
+ accounts.accountsDb.findAccountVisibility(account, packageName);
return visibility != null ? visibility : AccountManager.VISIBILITY_UNDEFINED;
} finally {
StrictMode.setThreadPolicy(oldPolicy);
@@ -592,6 +595,7 @@
*/
private Integer resolveAccountVisibility(Account account, @NonNull String packageName,
UserAccounts accounts) {
+
Preconditions.checkNotNull(packageName, "packageName cannot be null");
int uid = -1;
@@ -693,19 +697,21 @@
}
@Override
- public boolean setAccountVisibility(Account a, String packageName, int newVisibility) {
- if (a == null) throw new IllegalArgumentException("account is null");
+ public boolean setAccountVisibility(Account account, String packageName, int newVisibility) {
+ Preconditions.checkNotNull(account, "account cannot be null");
+ Preconditions.checkNotNull(packageName, "packageName cannot be null");
int callingUid = Binder.getCallingUid();
- if (!isAccountManagedByCaller(a.type, callingUid, UserHandle.getUserId(callingUid))
+ UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid));
+ if (!isAccountManagedByCaller(account.type, callingUid, accounts.userId)
&& !isSystemUid(callingUid)) {
String msg = String.format(
"uid %s cannot get secrets for accounts of type: %s",
callingUid,
- a.type);
+ account.type);
throw new SecurityException(msg);
}
- return setAccountVisibility(a, packageName, newVisibility, true /* notify */,
- getUserAccounts(UserHandle.getUserId(callingUid)));
+ return setAccountVisibility(account, packageName, newVisibility, true /* notify */,
+ accounts);
}
/**
@@ -722,16 +728,18 @@
private boolean setAccountVisibility(Account account, String packageName, int newVisibility,
boolean notify, UserAccounts accounts) {
synchronized (accounts.cacheLock) {
- LinkedHashSet<String> interestedPackages;
+ Map<String, Integer> packagesToVisibility;
if (notify) {
if (isSpecialPackageKey(packageName)) {
- interestedPackages = getRequestingPackageNames(account.type, accounts);
+ packagesToVisibility =
+ getRequestingPackages(account, accounts);
} else {
if (!packageExistsForUser(packageName, accounts.userId)) {
return false; // package is not installed.
}
- interestedPackages = new LinkedHashSet<>();
- interestedPackages.add(packageName);
+ packagesToVisibility = new HashMap<>();
+ packagesToVisibility.put(packageName,
+ resolveAccountVisibility(account, packageName, accounts));
}
} else {
// Notifications will not be send.
@@ -740,19 +748,13 @@
// package is not installed and not meta value.
return false;
}
- interestedPackages = new LinkedHashSet<>();
+ packagesToVisibility = new HashMap<>();
}
- Integer[] interestedPackagesVisibility = new Integer[interestedPackages.size()];
final long accountId = accounts.accountsDb.findDeAccountId(account);
if (accountId < 0) {
return false;
}
- int index = 0;
- for (String interestedPackage : interestedPackages) {
- interestedPackagesVisibility[index++] =
- resolveAccountVisibility(account, interestedPackage, accounts);
- }
final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
try {
@@ -764,45 +766,113 @@
StrictMode.setThreadPolicy(oldPolicy);
}
- index = 0;
- for (String interestedPackage : interestedPackages) {
- int visibility = resolveAccountVisibility(account, interestedPackage, accounts);
- if (visibility != interestedPackagesVisibility[index++]) {
- sendNotification(interestedPackage, account, accounts.userId);
- }
- }
if (notify) {
+ for (Entry<String, Integer> packageToVisibility : packagesToVisibility.entrySet()) {
+ if (packageToVisibility.getValue() != AccountManager.VISIBILITY_NOT_VISIBLE) {
+ notifyPackage(packageToVisibility.getKey(), accounts);
+ }
+ }
sendAccountsChangedBroadcast(accounts.userId);
}
return true;
}
}
- /**
- * Sends a direct intent to a package, notifying it of a visible account change.
- *
- * @param packageName to send Account to
- * @param account to send to package
- * @param userId User
- */
- private void sendNotification(String packageName, Account account, int userId) {
- // TODO send notification so apps subscribed in runtime.
+ @Override
+ public void registerAccountListener(String[] accountTypes, String opPackageName) {
+ int callingUid = Binder.getCallingUid();
+ mAppOpsManager.checkPackage(callingUid, opPackageName);
+ registerAccountListener(accountTypes, opPackageName,
+ getUserAccounts(UserHandle.getUserId(callingUid)));
}
- private void sendNotification(Account account, UserAccounts accounts) {
- LinkedHashSet<String> interestedPackages = getRequestingPackageNames(account.type,
- accounts);
- for (String packageName : interestedPackages) {
- int visibility = resolveAccountVisibility(account, packageName, accounts);
- if (visibility != AccountManager.VISIBILITY_NOT_VISIBLE) {
- sendNotification(packageName, account, accounts.userId);
+ private void registerAccountListener(String[] accountTypes, String opPackageName,
+ UserAccounts accounts) {
+ synchronized (accounts.mReceiversForType) {
+ if (accountTypes == null) {
+ // null for any type
+ accountTypes = new String[] {null};
+ }
+ for (String type : accountTypes) {
+ Map<String, Integer> receivers = accounts.mReceiversForType.get(type);
+ if (receivers == null) {
+ receivers = new HashMap<>();
+ accounts.mReceiversForType.put(type, receivers);
+ }
+ Integer cnt = receivers.get(opPackageName);
+ receivers.put(opPackageName, cnt != null ? cnt + 1 : 1);
}
}
}
- LinkedHashSet<String> getRequestingPackageNames(String accountType, UserAccounts accounts) {
- // TODO return packages registered to get notifications.
- return new LinkedHashSet<String>();
+ @Override
+ public void unregisterAccountListener(String[] accountTypes, String opPackageName) {
+ int callingUid = Binder.getCallingUid();
+ mAppOpsManager.checkPackage(callingUid, opPackageName);
+ UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid));
+ synchronized (accounts.mReceiversForType) {
+ if (accountTypes == null) {
+ // null for any type
+ accountTypes = new String[] {null};
+ }
+ for (String type : accountTypes) {
+ Map<String, Integer> receivers = accounts.mReceiversForType.get(type);
+ if (receivers == null || receivers.get(opPackageName) == null) {
+ throw new IllegalArgumentException("attempt to unregister wrong receiver");
+ }
+ Integer cnt = receivers.get(opPackageName);
+ if (cnt == 1) {
+ receivers.remove(opPackageName);
+ } else {
+ receivers.put(opPackageName, cnt - 1);
+ }
+ }
+ }
+ }
+
+ // Send notification to all packages which can potentially see the account
+ private void sendNotificationAccountUpdated(Account account, UserAccounts accounts) {
+ Map<String, Integer> packagesToVisibility = getRequestingPackages(account, accounts);
+ // packages with VISIBILITY_USER_MANAGED_NOT_VISIBL still get notification.
+ // Should we notify VISIBILITY_NOT_VISIBLE packages when account is added?
+ for (Entry<String, Integer> packageToVisibility : packagesToVisibility.entrySet()) {
+ if (packageToVisibility.getValue() != AccountManager.VISIBILITY_NOT_VISIBLE) {
+ notifyPackage(packageToVisibility.getKey(), accounts);
+ }
+ }
+ }
+
+ /**
+ * Sends a direct intent to a package, notifying it of account visibility change.
+ *
+ * @param packageName to send Account to
+ * @param accounts UserAccount that currently hosts the account
+ */
+ private void notifyPackage(String packageName, UserAccounts accounts) {
+ Intent intent = new Intent(AccountManager.ACTION_VISIBLE_ACCOUNTS_CHANGED);
+ intent.setPackage(packageName);
+ intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mContext.sendBroadcastAsUser(intent, new UserHandle(accounts.userId));
+ }
+
+ // Returns a map from package name to visibility, for packages subscribed
+ // to notifications about any account type, or type of provided account
+ // account type or all types.
+ private Map<String, Integer> getRequestingPackages(Account account, UserAccounts accounts) {
+ Set<String> packages = new HashSet<>();
+ synchronized (accounts.mReceiversForType) {
+ for (String type : new String[] {account.type, null}) {
+ Map<String, Integer> receivers = accounts.mReceiversForType.get(type);
+ if (receivers != null) {
+ packages.addAll(receivers.keySet());
+ }
+ }
+ }
+ Map<String, Integer> result = new HashMap<>();
+ for (String packageName : packages) {
+ result.put(packageName, resolveAccountVisibility(account, packageName, accounts));
+ }
+ return result;
}
private boolean packageExistsForUser(String packageName, int userId) {
@@ -950,6 +1020,8 @@
if (obsoleteAuthType.contains(account.type)) {
Slog.w(TAG, "deleting account " + account.name + " because type "
+ account.type + "'s registered authenticator no longer exist.");
+ Map<String, Integer> packagesToVisibility =
+ getRequestingPackages(account, accounts);
accountsDb.beginTransaction();
try {
accountsDb.deleteDeAccount(accountId);
@@ -970,12 +1042,12 @@
accounts.userDataCache.remove(account);
accounts.authTokenCache.remove(account);
accounts.accountTokenCaches.remove(account);
- LinkedHashSet<String> interestedPackages =
- getRequestingPackageNames(account.type, accounts);
- for (String packageName : interestedPackages) {
- sendNotification(packageName, null, accounts.userId);
- }
+ for (Entry<String, Integer> packageToVisibility : packagesToVisibility.entrySet()) {
+ if (packageToVisibility.getValue() != AccountManager.VISIBILITY_NOT_VISIBLE) {
+ notifyPackage(packageToVisibility.getKey(), accounts);
+ }
+ }
} else {
ArrayList<String> accountNames = accountNamesByType.get(account.type);
if (accountNames == null) {
@@ -1224,7 +1296,7 @@
+ ", caller's uid " + Binder.getCallingUid()
+ ", pid " + Binder.getCallingPid());
}
- if (account == null) throw new IllegalArgumentException("account is null");
+ Preconditions.checkNotNull(account, "account cannot be null");
int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
@@ -1260,8 +1332,8 @@
account, key, callingUid, Binder.getCallingPid());
Log.v(TAG, msg);
}
- if (account == null) throw new IllegalArgumentException("account is null");
- if (key == null) throw new IllegalArgumentException("key is null");
+ Preconditions.checkNotNull(account, "account cannot be null");
+ Preconditions.checkNotNull(key, "key cannot be null");
int userId = UserHandle.getCallingUserId();
if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
String msg = String.format(
@@ -1414,9 +1486,7 @@
callingUid);
Log.v(TAG, msg);
}
- if (account == null) {
- throw new IllegalArgumentException("account is null");
- }
+ Preconditions.checkNotNull(account, "account cannot be null");
int userId = UserHandle.getCallingUserId();
if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
String msg = String.format(
@@ -1564,7 +1634,7 @@
addAccountToLinkedRestrictedUsers(account, accounts.userId);
}
- sendNotification(account, accounts);
+ sendNotificationAccountUpdated(account, accounts);
// Only send LOGIN_ACCOUNTS_CHANGED when the database changed.
sendAccountsChangedBroadcast(accounts.userId);
@@ -1608,9 +1678,9 @@
+ ", caller's uid " + callingUid
+ ", pid " + Binder.getCallingPid());
}
- if (response == null) throw new IllegalArgumentException("response is null");
- if (account == null) throw new IllegalArgumentException("account is null");
- if (features == null) throw new IllegalArgumentException("features is null");
+ Preconditions.checkArgument(account != null, "account cannot be null");
+ Preconditions.checkArgument(response != null, "response cannot be null");
+ Preconditions.checkArgument(features != null, "features cannot be null");
int userId = UserHandle.getCallingUserId();
checkReadAccountsPermitted(callingUid, account.type, userId,
opPackageName);
@@ -1806,8 +1876,7 @@
}
}
- // Notify authenticator.
- sendNotification(resultAccount, accounts);
+ sendNotificationAccountUpdated(resultAccount, accounts);
sendAccountsChangedBroadcast(accounts.userId);
}
return resultAccount;
@@ -1839,8 +1908,9 @@
+ ", pid " + Binder.getCallingPid()
+ ", for user id " + userId);
}
- if (response == null) throw new IllegalArgumentException("response is null");
- if (account == null) throw new IllegalArgumentException("account is null");
+ Preconditions.checkArgument(account != null, "account cannot be null");
+ Preconditions.checkArgument(response != null, "response cannot be null");
+
// Only allow the system process to modify accounts of other users
if (isCrossUser(callingUid, userId)) {
throw new SecurityException(
@@ -2006,17 +2076,7 @@
+ " is still locked. CE data will be removed later");
}
synchronized (accounts.cacheLock) {
- LinkedHashSet<String> interestedPackages =
- accounts.mApplicationAccountRequestMappings.get(account.type);
- if (interestedPackages == null) {
- interestedPackages = new LinkedHashSet<>();
- }
- int[] visibilityForInterestedPackages = new int[interestedPackages.size()];
- int index = 0;
- for (String packageName : interestedPackages) {
- int visibility = resolveAccountVisibility(account, packageName, accounts);
- visibilityForInterestedPackages[index++] = visibility;
- }
+ Map<String, Integer> packagesToVisibility = getRequestingPackages(account, accounts);
accounts.accountsDb.beginTransaction();
// Set to a dummy value, this will only be used if the database
// transaction succeeds.
@@ -2040,18 +2100,13 @@
}
if (isChanged) {
removeAccountFromCacheLocked(accounts, account);
- index = 0;
- for (String packageName : interestedPackages) {
- if ((visibilityForInterestedPackages[index]
- != AccountManager.VISIBILITY_NOT_VISIBLE)
- && (visibilityForInterestedPackages[index]
- != AccountManager.VISIBILITY_UNDEFINED)) {
- sendNotification(packageName, account, accounts.userId);
+ for (Entry<String, Integer> packageToVisibility : packagesToVisibility.entrySet()) {
+ if (packageToVisibility.getValue() != AccountManager.VISIBILITY_NOT_VISIBLE) {
+ notifyPackage(packageToVisibility.getKey(), accounts);
}
- ++index;
}
- // Only broadcast LOGIN_ACCOUNTS_CHANGED if a change occured.
+ // Only broadcast LOGIN_ACCOUNTS_CHANGED if a change occurred.
sendAccountsChangedBroadcast(accounts.userId);
String action = userUnlocked ? AccountsDb.DEBUG_ACTION_ACCOUNT_REMOVE
: AccountsDb.DEBUG_ACTION_ACCOUNT_REMOVE_DE;
@@ -2094,13 +2149,13 @@
@Override
public void invalidateAuthToken(String accountType, String authToken) {
int callerUid = Binder.getCallingUid();
+ Preconditions.checkNotNull(accountType, "accountType cannot be null");
+ Preconditions.checkNotNull(authToken, "authToken cannot be null");
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "invalidateAuthToken: accountType " + accountType
+ ", caller's uid " + callerUid
+ ", pid " + Binder.getCallingPid());
}
- if (accountType == null) throw new IllegalArgumentException("accountType is null");
- if (authToken == null) throw new IllegalArgumentException("authToken is null");
int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
@@ -2210,8 +2265,8 @@
+ ", caller's uid " + callingUid
+ ", pid " + Binder.getCallingPid());
}
- if (account == null) throw new IllegalArgumentException("account is null");
- if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+ Preconditions.checkNotNull(account, "account cannot be null");
+ Preconditions.checkNotNull(authTokenType, "authTokenType cannot be null");
int userId = UserHandle.getCallingUserId();
if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
String msg = String.format(
@@ -2243,8 +2298,8 @@
+ ", caller's uid " + callingUid
+ ", pid " + Binder.getCallingPid());
}
- if (account == null) throw new IllegalArgumentException("account is null");
- if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+ Preconditions.checkNotNull(account, "account cannot be null");
+ Preconditions.checkNotNull(authTokenType, "authTokenType cannot be null");
int userId = UserHandle.getCallingUserId();
if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
String msg = String.format(
@@ -2270,7 +2325,7 @@
+ ", caller's uid " + callingUid
+ ", pid " + Binder.getCallingPid());
}
- if (account == null) throw new IllegalArgumentException("account is null");
+ Preconditions.checkNotNull(account, "account cannot be null");
int userId = UserHandle.getCallingUserId();
if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
String msg = String.format(
@@ -2317,7 +2372,7 @@
accounts.accountsDb.endTransaction();
if (isChanged) {
// Send LOGIN_ACCOUNTS_CHANGED only if the something changed.
- sendNotification(account, accounts);
+ sendNotificationAccountUpdated(account, accounts);
sendAccountsChangedBroadcast(accounts.userId);
}
}
@@ -2332,7 +2387,7 @@
+ ", caller's uid " + callingUid
+ ", pid " + Binder.getCallingPid());
}
- if (account == null) throw new IllegalArgumentException("account is null");
+ Preconditions.checkNotNull(account, "account cannot be null");
int userId = UserHandle.getCallingUserId();
if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
String msg = String.format(
@@ -2444,8 +2499,8 @@
public void getAuthTokenLabel(IAccountManagerResponse response, final String accountType,
final String authTokenType)
throws RemoteException {
- if (accountType == null) throw new IllegalArgumentException("accountType is null");
- if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+ Preconditions.checkArgument(accountType != null, "accountType cannot be null");
+ Preconditions.checkArgument(authTokenType != null, "authTokenType cannot be null");
final int callingUid = getCallingUid();
clearCallingIdentity();
@@ -2508,7 +2563,7 @@
+ ", caller's uid " + Binder.getCallingUid()
+ ", pid " + Binder.getCallingPid());
}
- if (response == null) throw new IllegalArgumentException("response is null");
+ Preconditions.checkArgument(response != null, "response cannot be null");
try {
if (account == null) {
Slog.w(TAG, "getAuthToken called with null account");
@@ -2909,8 +2964,8 @@
+ ", pid " + Binder.getCallingPid()
+ ", for user id " + userId);
}
- if (response == null) throw new IllegalArgumentException("response is null");
- if (accountType == null) throw new IllegalArgumentException("accountType is null");
+ Preconditions.checkArgument(response != null, "response cannot be null");
+ Preconditions.checkArgument(accountType != null, "accountType cannot be null");
// Only allow the system process to add accounts of other users
if (isCrossUser(callingUid, userId)) {
throw new SecurityException(
@@ -2996,12 +3051,8 @@
+ ", caller's uid " + Binder.getCallingUid()
+ ", pid " + Binder.getCallingPid());
}
- if (response == null) {
- throw new IllegalArgumentException("response is null");
- }
- if (accountType == null) {
- throw new IllegalArgumentException("accountType is null");
- }
+ Preconditions.checkArgument(response != null, "response cannot be null");
+ Preconditions.checkArgument(accountType != null, "accountType cannot be null");
final int uid = Binder.getCallingUid();
final int userId = UserHandle.getUserId(uid);
@@ -3190,10 +3241,7 @@
+ ", pid " + Binder.getCallingPid()
+ ", for user id " + userId);
}
- if (response == null) {
- throw new IllegalArgumentException("response is null");
- }
-
+ Preconditions.checkArgument(response != null, "response cannot be null");
// Session bundle is the encrypted bundle of the original bundle created by authenticator.
// Account type is added to it before encryption.
if (sessionBundle == null || sessionBundle.size() == 0) {
@@ -3690,9 +3738,9 @@
// the account to be accessed by apps for which user or authenticator granted visibility.
int visibility = resolveAccountVisibility(account, packageName,
- getUserAccounts(UserHandle.getUserId(uid)));
+ getUserAccounts(UserHandle.getUserId(uid)));
return (visibility == AccountManager.VISIBILITY_VISIBLE
- || visibility == AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
+ || visibility == AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
}
@Override
@@ -4843,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();
@@ -5630,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 2821446..2be5e77 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -295,6 +295,7 @@
import android.service.voice.IVoiceInteractionSession;
import android.service.voice.VoiceInteractionManagerInternal;
import android.service.voice.VoiceInteractionSession;
+import android.service.vr.IPersistentVrStateCallbacks;
import android.telecom.TelecomManager;
import android.text.TextUtils;
import android.text.format.DateUtils;
@@ -367,7 +368,6 @@
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.statusbar.StatusBarManagerInternal;
-import com.android.server.vr.PersistentVrStateListener;
import com.android.server.vr.VrManagerInternal;
import com.android.server.wm.WindowManagerService;
@@ -624,8 +624,8 @@
private int mVrState = NON_VR_MODE;
private int mTopAppVrThreadTid = 0;
private int mPersistentVrThreadTid = 0;
- final PersistentVrStateListener mPersistentVrModeListener =
- new PersistentVrStateListener() {
+ final IPersistentVrStateCallbacks mPersistentVrModeListener =
+ new IPersistentVrStateCallbacks.Stub() {
@Override
public void onPersistentVrStateChanged(boolean enabled) {
synchronized(ActivityManagerService.this) {
@@ -1182,13 +1182,13 @@
@Override
public String toString() {
- String result = Integer.toString(sourceUserId) + " @ " + uri.toString();
+ String result = uri.toString() + " [user " + sourceUserId + "]";
if (prefix) result += " [prefix]";
return result;
}
public String toSafeString() {
- String result = Integer.toString(sourceUserId) + " @ " + uri.toSafeString();
+ String result = uri.toSafeString() + " [user " + sourceUserId + "]";
if (prefix) result += " [prefix]";
return result;
}
@@ -5479,37 +5479,61 @@
return tracesFile;
}
+ public static class DumpStackFileObserver extends FileObserver {
+ // Keep in sync with frameworks/native/cmds/dumpstate/utils.cpp
+ private static final int TRACE_DUMP_TIMEOUT_MS = 10000; // 10 seconds
+ static final int TRACE_DUMP_TIMEOUT_SECONDS = TRACE_DUMP_TIMEOUT_MS / 1000;
+
+ private final String mTracesPath;
+ private boolean mClosed;
+
+ public DumpStackFileObserver(String tracesPath) {
+ super(tracesPath, FileObserver.CLOSE_WRITE);
+ mTracesPath = tracesPath;
+ }
+
+ @Override
+ public synchronized void onEvent(int event, String path) {
+ mClosed = true;
+ notify();
+ }
+
+ public void dumpWithTimeout(int pid) {
+ Process.sendSignal(pid, Process.SIGNAL_QUIT);
+ synchronized (this) {
+ try {
+ wait(TRACE_DUMP_TIMEOUT_MS); // Wait for traces file to be closed.
+ } catch (InterruptedException e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+ if (!mClosed) {
+ Slog.w(TAG, "Didn't see close of " + mTracesPath + " for pid " + pid +
+ ". Attempting native stack collection.");
+ Debug.dumpNativeBacktraceToFileTimeout(pid, mTracesPath, TRACE_DUMP_TIMEOUT_SECONDS);
+ }
+ mClosed = false;
+ }
+ }
+
private static void dumpStackTraces(String tracesPath, ArrayList<Integer> firstPids,
ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
// Use a FileObserver to detect when traces finish writing.
// The order of traces is considered important to maintain for legibility.
- final boolean[] closed = new boolean[1];
- FileObserver observer = new FileObserver(tracesPath, FileObserver.CLOSE_WRITE) {
- @Override
- public synchronized void onEvent(int event, String path) { closed[0] = true; notify(); }
- };
-
+ DumpStackFileObserver observer = new DumpStackFileObserver(tracesPath);
try {
observer.startWatching();
// First collect all of the stacks of the most important pids.
if (firstPids != null) {
- try {
- int num = firstPids.size();
- for (int i = 0; i < num; i++) {
- synchronized (observer) {
- if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for pid "
- + firstPids.get(i));
- final long sime = SystemClock.elapsedRealtime();
- Process.sendSignal(firstPids.get(i), Process.SIGNAL_QUIT);
- observer.wait(1000); // Wait for write-close, give up after 1 sec
- if (!closed[0]) Slog.w(TAG, "Didn't see close of " + tracesPath);
- if (DEBUG_ANR) Slog.d(TAG, "Done with pid " + firstPids.get(i)
- + " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
- }
- }
- } catch (InterruptedException e) {
- Slog.wtf(TAG, e);
+ int num = firstPids.size();
+ for (int i = 0; i < num; i++) {
+ if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for pid "
+ + firstPids.get(i));
+ final long sime = SystemClock.elapsedRealtime();
+ observer.dumpWithTimeout(firstPids.get(i));
+ if (DEBUG_ANR) Slog.d(TAG, "Done with pid " + firstPids.get(i)
+ + " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
}
}
@@ -5521,7 +5545,8 @@
if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for native pid " + pid);
final long sime = SystemClock.elapsedRealtime();
- Debug.dumpNativeBacktraceToFileTimeout(pid, tracesPath, 10);
+ Debug.dumpNativeBacktraceToFileTimeout(
+ pid, tracesPath, DumpStackFileObserver.TRACE_DUMP_TIMEOUT_SECONDS);
if (DEBUG_ANR) Slog.d(TAG, "Done with native pid " + pid
+ " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
}
@@ -5548,19 +5573,12 @@
ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
if (lastPids.indexOfKey(stats.pid) >= 0) {
numProcs++;
- try {
- synchronized (observer) {
- if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for extra pid "
- + stats.pid);
- final long stime = SystemClock.elapsedRealtime();
- Process.sendSignal(stats.pid, Process.SIGNAL_QUIT);
- observer.wait(1000); // Wait for write-close, give up after 1 sec
- if (DEBUG_ANR) Slog.d(TAG, "Done with extra pid " + stats.pid
- + " in " + (SystemClock.elapsedRealtime()-stime) + "ms");
- }
- } catch (InterruptedException e) {
- Slog.wtf(TAG, e);
- }
+ if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for extra pid "
+ + stats.pid);
+ final long stime = SystemClock.elapsedRealtime();
+ observer.dumpWithTimeout(stats.pid);
+ if (DEBUG_ANR) Slog.d(TAG, "Done with extra pid " + stats.pid
+ + " in " + (SystemClock.elapsedRealtime()-stime) + "ms");
} else if (DEBUG_ANR) {
Slog.d(TAG, "Skipping next CPU consuming process, not a java proc: "
+ stats.pid);
@@ -8650,8 +8668,15 @@
if (!checkHoldingPermissionsLocked(pm, pi, grantUri, callingUid, modeFlags)) {
// Require they hold a strong enough Uri permission
if (!checkUriPermissionLocked(grantUri, callingUid, modeFlags)) {
- throw new SecurityException("Uid " + callingUid
- + " does not have permission to uri " + grantUri);
+ if (android.Manifest.permission.MANAGE_DOCUMENTS.equals(pi.readPermission)) {
+ throw new SecurityException(
+ "UID " + callingUid + " does not have permission to " + grantUri
+ + "; you could obtain access using ACTION_OPEN_DOCUMENT "
+ + "or related APIs");
+ } else {
+ throw new SecurityException(
+ "UID " + callingUid + " does not have permission to " + grantUri);
+ }
}
}
return targetUid;
@@ -10925,7 +10950,8 @@
} catch (RemoteException ignored) {
}
if (cpi == null) {
- return "Failed to find provider " + authority + " for user " + userId;
+ return "Failed to find provider " + authority + " for user " + userId
+ + "; expected to find a valid ContentProvider for this authority";
}
ProcessRecord r = null;
@@ -11005,18 +11031,17 @@
return null;
}
- String msg;
+ final String suffix;
if (!cpi.exported) {
- msg = "Permission Denial: opening provider " + cpi.name
- + " from " + (r != null ? r : "(null)") + " (pid=" + callingPid
- + ", uid=" + callingUid + ") that is not exported from uid "
- + cpi.applicationInfo.uid;
+ suffix = " that is not exported from UID " + cpi.applicationInfo.uid;
+ } else if (android.Manifest.permission.MANAGE_DOCUMENTS.equals(cpi.readPermission)) {
+ suffix = " requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs";
} else {
- msg = "Permission Denial: opening provider " + cpi.name
- + " from " + (r != null ? r : "(null)") + " (pid=" + callingPid
- + ", uid=" + callingUid + ") requires "
- + cpi.readPermission + " or " + cpi.writePermission;
+ suffix = " requires " + cpi.readPermission + " or " + cpi.writePermission;
}
+ final String msg = "Permission Denial: opening provider " + cpi.name
+ + " from " + (r != null ? r : "(null)") + " (pid=" + callingPid
+ + ", uid=" + callingUid + ")" + suffix;
Slog.w(TAG, msg);
return msg;
}
diff --git a/services/core/java/com/android/server/am/ActivityMetricsLogger.java b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
index dd8c05e..04a09fe 100644
--- a/services/core/java/com/android/server/am/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
@@ -321,6 +321,7 @@
if (info.launchedActivity.info.launchToken != null) {
builder.addTaggedData(FIELD_INSTANT_APP_LAUNCH_TOKEN,
info.launchedActivity.info.launchToken);
+ info.launchedActivity.info.launchToken = null;
}
builder.addTaggedData(APP_TRANSITION_IS_EPHEMERAL,
info.launchedActivity.info.applicationInfo.isInstantApp() ? 1 : 0);
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index ecaa751..6cea483 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -2395,6 +2395,10 @@
return (config.uiMode & Configuration.UI_MODE_TYPE_MASK) == UI_MODE_TYPE_VR_HEADSET;
}
+ int getUid() {
+ return info.applicationInfo.uid;
+ }
+
@Override
public String toString() {
if (stringName != null) {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index e64b4b3..9a4f804 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -741,8 +741,10 @@
/** Checks if there are tasks with specific UID in the stack. */
boolean isUidPresent(int uid) {
for (TaskRecord task : mTaskHistory) {
- if (task.effectiveUid == uid) {
- return true;
+ for (ActivityRecord r : task.mActivities) {
+ if (r.getUid() == uid) {
+ return true;
+ }
}
}
return false;
@@ -751,7 +753,9 @@
/** Get all UIDs that are present in the stack. */
void getPresentUIDs(IntArray presentUIDs) {
for (TaskRecord task : mTaskHistory) {
- presentUIDs.add(task.effectiveUid);
+ for (ActivityRecord r : task.mActivities) {
+ presentUIDs.add(r.getUid());
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index c1bff36..5c49dfd 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1227,6 +1227,10 @@
mService.setProfileApp(aInfo.applicationInfo, aInfo.processName, profilerInfo);
}
}
+ final String intentLaunchToken = intent.getLaunchToken();
+ if (aInfo.launchToken == null && intentLaunchToken != null) {
+ aInfo.launchToken = intentLaunchToken;
+ }
}
return aInfo;
}
@@ -1632,7 +1636,11 @@
mDisplayAccessUIDs.clear();
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
final ActivityDisplay activityDisplay = mActivityDisplays.valueAt(displayNdx);
- mDisplayAccessUIDs.append(activityDisplay.mDisplayId, activityDisplay.getPresentUIDs());
+ // Only bother calculating the whitelist for private displays
+ if (activityDisplay.isPrivate()) {
+ mDisplayAccessUIDs.append(
+ activityDisplay.mDisplayId, activityDisplay.getPresentUIDs());
+ }
}
// Store updated lists in DisplayManager. Callers from outside of AM should get them there.
mDisplayManagerInternal.setDisplayAccessUIDs(mDisplayAccessUIDs);
@@ -2287,7 +2295,18 @@
mResizingTasksDuringAnimation.clear();
}
- void moveTasksToFullscreenStackLocked(int fromStackId, boolean onTop) {
+ private class MoveTaskToFullscreenArgs {
+ public int fromStackId;
+ public boolean onTop;
+ };
+ // Used only to closure over the arguments to moveTasksToFullscreenStack without
+ // allocation
+ private MoveTaskToFullscreenArgs mMoveToFullscreenArgs = new MoveTaskToFullscreenArgs();
+
+ private void moveTasksToFullscreenStackInnerLocked() {
+ int fromStackId = mMoveToFullscreenArgs.fromStackId;
+ boolean onTop = mMoveToFullscreenArgs.onTop;
+
final ActivityStack stack = getStack(fromStackId);
if (stack == null) {
return;
@@ -2359,6 +2378,13 @@
}
}
+ void moveTasksToFullscreenStackLocked(int fromStackId, boolean onTop) {
+ mMoveToFullscreenArgs.fromStackId = fromStackId;
+ mMoveToFullscreenArgs.onTop = onTop;
+
+ mWindowManager.inSurfaceTransaction(this::moveTasksToFullscreenStackInnerLocked);
+ }
+
void resizeDockedStackLocked(Rect dockedBounds, Rect tempDockedTaskBounds,
Rect tempDockedTaskInsetBounds, Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds,
boolean preserveWindows) {
@@ -2471,12 +2497,12 @@
return activityContainer.mStack;
}
- /**
- * Removes the stack associated with the given {@param stackId}. If the {@param stackId} is the
- * pinned stack, then its tasks are not explicitly removed when the stack is destroyed, but
- * instead moved back onto the fullscreen stack.
- */
- void removeStackLocked(int stackId) {
+
+ // Used only to closure over the argument to removeStack without allocation.
+ private int mRemoveStackStackId;
+ void removeStackInnerLocked() {
+ int stackId = mRemoveStackStackId;
+
final ActivityStack stack = getStack(stackId);
if (stack == null) {
return;
@@ -2515,6 +2541,16 @@
}
/**
+ * Removes the stack associated with the given {@param stackId}. If the {@param stackId} is the
+ * pinned stack, then its tasks are not explicitly removed when the stack is destroyed, but
+ * instead moved back onto the fullscreen stack.
+ */
+ void removeStackLocked(int stackId) {
+ mRemoveStackStackId = stackId;
+ mWindowManager.inSurfaceTransaction(this::removeStackInnerLocked);
+ }
+
+ /**
* Removes the task with the specified task id.
*
* @param taskId Identifier of the task to be removed.
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 13c8865..a668fea 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1236,6 +1236,10 @@
mWindowContainerController.positionChildAt(appController, index);
}
r.onOverrideConfigurationSent();
+
+ // Make sure the list of display UID whitelists is updated
+ // now that this record is in a new task.
+ mService.mStackSupervisor.updateUIDsPresentOnDisplay();
}
/**
diff --git a/services/core/java/com/android/server/connectivity/PacManager.java b/services/core/java/com/android/server/connectivity/PacManager.java
index 34826b6..d56fb1a 100644
--- a/services/core/java/com/android/server/connectivity/PacManager.java
+++ b/services/core/java/com/android/server/connectivity/PacManager.java
@@ -15,6 +15,7 @@
*/
package com.android.server.connectivity;
+import android.annotation.WorkerThread;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -74,7 +75,7 @@
public static final String KEY_PROXY = "keyProxy";
private String mCurrentPac;
@GuardedBy("mProxyLock")
- private Uri mPacUrl = Uri.EMPTY;
+ private volatile Uri mPacUrl = Uri.EMPTY;
private AlarmManager mAlarmManager;
@GuardedBy("mProxyLock")
@@ -87,33 +88,37 @@
private int mCurrentDelay;
private int mLastPort;
- private boolean mHasSentBroadcast;
- private boolean mHasDownloaded;
+ private volatile boolean mHasSentBroadcast;
+ private volatile boolean mHasDownloaded;
private Handler mConnectivityHandler;
private int mProxyMessage;
/**
- * Used for locking when setting mProxyService and all references to mPacUrl or mCurrentPac.
+ * Used for locking when setting mProxyService and all references to mCurrentPac.
*/
private final Object mProxyLock = new Object();
+ /**
+ * Runnable to download PAC script.
+ * The behavior relies on the assamption it always run on mNetThread to guarantee that the
+ * latest data fetched from mPacUrl is stored in mProxyService.
+ */
private Runnable mPacDownloader = new Runnable() {
@Override
+ @WorkerThread
public void run() {
String file;
- synchronized (mProxyLock) {
- if (Uri.EMPTY.equals(mPacUrl)) return;
- final int oldTag = TrafficStats
- .getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_PAC);
- try {
- file = get(mPacUrl);
- } catch (IOException ioe) {
- file = null;
- Log.w(TAG, "Failed to load PAC file: " + ioe);
- } finally {
- TrafficStats.setThreadStatsTag(oldTag);
- }
+ final Uri pacUrl = mPacUrl;
+ if (Uri.EMPTY.equals(pacUrl)) return;
+ final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_PAC);
+ try {
+ file = get(pacUrl);
+ } catch (IOException ioe) {
+ file = null;
+ Log.w(TAG, "Failed to load PAC file: " + ioe);
+ } finally {
+ TrafficStats.setThreadStatsTag(oldTag);
}
if (file != null) {
synchronized (mProxyLock) {
@@ -176,9 +181,7 @@
// Allow to send broadcast, nothing to do.
return false;
}
- synchronized (mProxyLock) {
- mPacUrl = proxy.getPacFileUrl();
- }
+ mPacUrl = proxy.getPacFileUrl();
mCurrentDelay = DELAY_1;
mHasSentBroadcast = false;
mHasDownloaded = false;
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index a947b41..f9bc12b 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -62,7 +62,18 @@
private final int mDisplayId;
private final int mLayerStack;
- private DisplayInfo mOverrideDisplayInfo; // set by the window manager
+ /**
+ * Override information set by the window manager. Will be reported instead of {@link #mInfo}
+ * if not null.
+ * @see #setDisplayInfoOverrideFromWindowManagerLocked(DisplayInfo)
+ * @see #getDisplayInfoLocked()
+ */
+ private DisplayInfo mOverrideDisplayInfo;
+ /**
+ * Current display info. Initialized with {@link #mBaseDisplayInfo}. Set to {@code null} if
+ * needs to be updated.
+ * @see #getDisplayInfoLocked()
+ */
private DisplayInfo mInfo;
// The display device that this logical display is based on and which
@@ -261,6 +272,9 @@
mPrimaryDisplayDeviceInfo = deviceInfo;
mInfo = null;
+ // Make sure that WM will be notified of new changes. It will then decide whether to
+ // apply them or not and will set the value again.
+ mOverrideDisplayInfo = null;
}
}
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 940f621..6e09ee2 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -83,6 +83,7 @@
import com.android.server.job.controllers.IdleController;
import com.android.server.job.controllers.JobStatus;
import com.android.server.job.controllers.StateController;
+import com.android.server.job.controllers.StorageController;
import com.android.server.job.controllers.TimeController;
import libcore.util.EmptyArray;
@@ -133,6 +134,8 @@
List<StateController> mControllers;
/** Need direct access to this for testing. */
BatteryController mBatteryController;
+ /** Need direct access to this for testing. */
+ StorageController mStorageController;
/**
* Queue of pending jobs. The JobServiceContext class will receive jobs from this list
* when ready to execute them.
@@ -197,6 +200,7 @@
private static final String KEY_MIN_IDLE_COUNT = "min_idle_count";
private static final String KEY_MIN_CHARGING_COUNT = "min_charging_count";
private static final String KEY_MIN_BATTERY_NOT_LOW_COUNT = "min_battery_not_low_count";
+ private static final String KEY_MIN_STORAGE_NOT_LOW_COUNT = "min_storage_not_low_count";
private static final String KEY_MIN_CONNECTIVITY_COUNT = "min_connectivity_count";
private static final String KEY_MIN_CONTENT_COUNT = "min_content_count";
private static final String KEY_MIN_READY_JOBS_COUNT = "min_ready_jobs_count";
@@ -211,6 +215,7 @@
private static final int DEFAULT_MIN_IDLE_COUNT = 1;
private static final int DEFAULT_MIN_CHARGING_COUNT = 1;
private static final int DEFAULT_MIN_BATTERY_NOT_LOW_COUNT = 1;
+ private static final int DEFAULT_MIN_STORAGE_NOT_LOW_COUNT = 1;
private static final int DEFAULT_MIN_CONNECTIVITY_COUNT = 1;
private static final int DEFAULT_MIN_CONTENT_COUNT = 1;
private static final int DEFAULT_MIN_READY_JOBS_COUNT = 1;
@@ -238,6 +243,11 @@
*/
int MIN_BATTERY_NOT_LOW_COUNT = DEFAULT_MIN_BATTERY_NOT_LOW_COUNT;
/**
+ * Minimum # of "storage not low" jobs that must be ready in order to force the JMS to
+ * schedule things early.
+ */
+ int MIN_STORAGE_NOT_LOW_COUNT = DEFAULT_MIN_STORAGE_NOT_LOW_COUNT;
+ /**
* Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule
* things early. 1 == Run connectivity jobs as soon as ready.
*/
@@ -323,6 +333,8 @@
DEFAULT_MIN_CHARGING_COUNT);
MIN_BATTERY_NOT_LOW_COUNT = mParser.getInt(KEY_MIN_BATTERY_NOT_LOW_COUNT,
DEFAULT_MIN_BATTERY_NOT_LOW_COUNT);
+ MIN_STORAGE_NOT_LOW_COUNT = mParser.getInt(KEY_MIN_STORAGE_NOT_LOW_COUNT,
+ DEFAULT_MIN_STORAGE_NOT_LOW_COUNT);
MIN_CONNECTIVITY_COUNT = mParser.getInt(KEY_MIN_CONNECTIVITY_COUNT,
DEFAULT_MIN_CONNECTIVITY_COUNT);
MIN_CONTENT_COUNT = mParser.getInt(KEY_MIN_CONTENT_COUNT,
@@ -370,6 +382,9 @@
pw.print(" "); pw.print(KEY_MIN_BATTERY_NOT_LOW_COUNT); pw.print("=");
pw.print(MIN_BATTERY_NOT_LOW_COUNT); pw.println();
+ pw.print(" "); pw.print(KEY_MIN_STORAGE_NOT_LOW_COUNT); pw.print("=");
+ pw.print(MIN_STORAGE_NOT_LOW_COUNT); pw.println();
+
pw.print(" "); pw.print(KEY_MIN_CONNECTIVITY_COUNT); pw.print("=");
pw.print(MIN_CONNECTIVITY_COUNT); pw.println();
@@ -802,6 +817,8 @@
mControllers.add(IdleController.get(this));
mBatteryController = BatteryController.get(this);
mControllers.add(mBatteryController);
+ mStorageController = StorageController.get(this);
+ mControllers.add(mStorageController);
mControllers.add(AppIdleController.get(this));
mControllers.add(ContentObserverController.get(this));
mControllers.add(DeviceIdleJobsController.get(this));
@@ -1202,6 +1219,7 @@
class MaybeReadyJobQueueFunctor implements JobStatusFunctor {
int chargingCount;
int batteryNotLowCount;
+ int storageNotLowCount;
int idleCount;
int backoffCount;
int connectivityCount;
@@ -1242,6 +1260,9 @@
if (job.hasBatteryNotLowConstraint()) {
batteryNotLowCount++;
}
+ if (job.hasStorageNotLowConstraint()) {
+ storageNotLowCount++;
+ }
if (job.hasContentTriggerConstraint()) {
contentCount++;
}
@@ -1261,6 +1282,7 @@
connectivityCount >= mConstants.MIN_CONNECTIVITY_COUNT ||
chargingCount >= mConstants.MIN_CHARGING_COUNT ||
batteryNotLowCount >= mConstants.MIN_BATTERY_NOT_LOW_COUNT ||
+ storageNotLowCount >= mConstants.MIN_STORAGE_NOT_LOW_COUNT ||
contentCount >= mConstants.MIN_CONTENT_COUNT ||
(runnableJobs != null
&& runnableJobs.size() >= mConstants.MIN_READY_JOBS_COUNT)) {
@@ -1285,6 +1307,7 @@
backoffCount = 0;
connectivityCount = 0;
batteryNotLowCount = 0;
+ storageNotLowCount = 0;
contentCount = 0;
runnableJobs = null;
}
@@ -1828,6 +1851,19 @@
}
}
+ int getStorageSeq() {
+ synchronized (mLock) {
+ return mStorageController != null ? mStorageController.getTracker().getSeq() : -1;
+ }
+ }
+
+ boolean getStorageNotLow() {
+ synchronized (mLock) {
+ return mStorageController != null
+ ? mStorageController.getTracker().isStorageNotLow() : false;
+ }
+ }
+
private String printContextIdToJobMap(JobStatus[] map, String initial) {
StringBuilder s = new StringBuilder(initial + ": ");
for (int i=0; i<map.length; i++) {
diff --git a/services/core/java/com/android/server/job/JobSchedulerShellCommand.java b/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
index ec23407..848704e 100644
--- a/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -54,6 +54,10 @@
return runGetBatteryCharging(pw);
case "get-battery-not-low":
return runGetBatteryNotLow(pw);
+ case "get-storage-seq":
+ return runGetStorageSeq(pw);
+ case "get-storage-not-low":
+ return runGetStorageNotLow(pw);
default:
return handleDefaultCommands(cmd);
}
@@ -181,6 +185,18 @@
return 0;
}
+ private int runGetStorageSeq(PrintWriter pw) {
+ int seq = mInternal.getStorageSeq();
+ pw.println(seq);
+ return 0;
+ }
+
+ private int runGetStorageNotLow(PrintWriter pw) {
+ boolean val = mInternal.getStorageNotLow();
+ pw.println(val);
+ return 0;
+ }
+
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
@@ -204,6 +220,10 @@
pw.println(" Return whether the battery is currently considered to be charging.");
pw.println(" get-battery-not-low");
pw.println(" Return whether the battery is currently considered to not be low.");
+ pw.println(" get-storage-seq");
+ pw.println(" Return the last storage update sequence number that was received.");
+ pw.println(" get-storage-not-low");
+ pw.println(" Return whether storage is currently considered to not be low.");
pw.println();
}
diff --git a/services/core/java/com/android/server/job/controllers/BatteryController.java b/services/core/java/com/android/server/job/controllers/BatteryController.java
index 05527be..91a962d 100644
--- a/services/core/java/com/android/server/job/controllers/BatteryController.java
+++ b/services/core/java/com/android/server/job/controllers/BatteryController.java
@@ -120,7 +120,7 @@
mStateChangedListener.onControllerStateChanged();
}
// Also tell the scheduler that any ready jobs should be flushed.
- if (stablePower) {
+ if (stablePower || batteryNotLow) {
mStateChangedListener.onRunJobNow(null);
}
}
diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
index b65330a..94ca24c 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -138,7 +138,7 @@
* We know the network has just come up. We want to run any jobs that are ready.
*/
@Override
- public synchronized void onNetworkActive() {
+ public void onNetworkActive() {
synchronized (mLock) {
for (int i = 0; i < mTrackedJobs.size(); i++) {
final JobStatus js = mTrackedJobs.get(i);
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 9a55fed..ebb53a1 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -49,6 +49,7 @@
static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING;
static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE;
static final int CONSTRAINT_BATTERY_NOT_LOW = JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW;
+ static final int CONSTRAINT_STORAGE_NOT_LOW = JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW;
static final int CONSTRAINT_TIMING_DELAY = 1<<31;
static final int CONSTRAINT_DEADLINE = 1<<30;
static final int CONSTRAINT_UNMETERED = 1<<29;
@@ -334,6 +335,10 @@
return (requiredConstraints&(CONSTRAINT_CHARGING|CONSTRAINT_BATTERY_NOT_LOW)) != 0;
}
+ public boolean hasStorageNotLowConstraint() {
+ return (requiredConstraints&CONSTRAINT_STORAGE_NOT_LOW) != 0;
+ }
+
public boolean hasTimingDelayConstraint() {
return (requiredConstraints&CONSTRAINT_TIMING_DELAY) != 0;
}
@@ -386,6 +391,10 @@
return setConstraintSatisfied(CONSTRAINT_BATTERY_NOT_LOW, state);
}
+ boolean setStorageNotLowConstraintSatisfied(boolean state) {
+ return setConstraintSatisfied(CONSTRAINT_STORAGE_NOT_LOW, state);
+ }
+
boolean setTimingDelayConstraintSatisfied(boolean state) {
return setConstraintSatisfied(CONSTRAINT_TIMING_DELAY, state);
}
@@ -460,13 +469,14 @@
}
static final int CONSTRAINTS_OF_INTEREST =
- CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_TIMING_DELAY |
+ CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_STORAGE_NOT_LOW |
+ CONSTRAINT_TIMING_DELAY |
CONSTRAINT_CONNECTIVITY | CONSTRAINT_UNMETERED | CONSTRAINT_NOT_ROAMING |
CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER;
// Soft override covers all non-"functional" constraints
static final int SOFT_OVERRIDE_CONSTRAINTS =
- CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW
+ CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_STORAGE_NOT_LOW
| CONSTRAINT_TIMING_DELAY | CONSTRAINT_IDLE;
/**
@@ -562,6 +572,9 @@
if ((constraints& CONSTRAINT_BATTERY_NOT_LOW) != 0) {
pw.print(" BATTERY_NOT_LOW");
}
+ if ((constraints& CONSTRAINT_STORAGE_NOT_LOW) != 0) {
+ pw.print(" STORAGE_NOT_LOW");
+ }
if ((constraints&CONSTRAINT_TIMING_DELAY) != 0) {
pw.print(" TIMING_DELAY");
}
diff --git a/services/core/java/com/android/server/job/controllers/StorageController.java b/services/core/java/com/android/server/job/controllers/StorageController.java
new file mode 100644
index 0000000..60ae5a7
--- /dev/null
+++ b/services/core/java/com/android/server/job/controllers/StorageController.java
@@ -0,0 +1,192 @@
+/*
+ * 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.server.job.controllers;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.job.JobSchedulerService;
+import com.android.server.job.StateChangedListener;
+import com.android.server.storage.DeviceStorageMonitorService;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Simple controller that tracks the status of the device's storage.
+ */
+public class StorageController extends StateController {
+ private static final String TAG = "JobScheduler.Stor";
+
+ private static final Object sCreationLock = new Object();
+ private static volatile StorageController sController;
+
+ private List<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
+ private StorageTracker mStorageTracker;
+
+ public static StorageController get(JobSchedulerService taskManagerService) {
+ synchronized (sCreationLock) {
+ if (sController == null) {
+ sController = new StorageController(taskManagerService,
+ taskManagerService.getContext(), taskManagerService.getLock());
+ }
+ }
+ return sController;
+ }
+
+ @VisibleForTesting
+ public StorageTracker getTracker() {
+ return mStorageTracker;
+ }
+
+ @VisibleForTesting
+ public static StorageController getForTesting(StateChangedListener stateChangedListener,
+ Context context) {
+ return new StorageController(stateChangedListener, context, new Object());
+ }
+
+ private StorageController(StateChangedListener stateChangedListener, Context context,
+ Object lock) {
+ super(stateChangedListener, context, lock);
+ mStorageTracker = new StorageTracker();
+ mStorageTracker.startTracking();
+ }
+
+ @Override
+ public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
+ if (taskStatus.hasStorageNotLowConstraint()) {
+ mTrackedTasks.add(taskStatus);
+ taskStatus.setStorageNotLowConstraintSatisfied(mStorageTracker.isStorageNotLow());
+ }
+ }
+
+ @Override
+ public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) {
+ if (taskStatus.hasPowerConstraint()) {
+ mTrackedTasks.remove(taskStatus);
+ }
+ }
+
+ private void maybeReportNewStorageState() {
+ final boolean storageNotLow = mStorageTracker.isStorageNotLow();
+ boolean reportChange = false;
+ synchronized (mLock) {
+ for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
+ final JobStatus ts = mTrackedTasks.get(i);
+ boolean previous = ts.setStorageNotLowConstraintSatisfied(storageNotLow);
+ if (previous != storageNotLow) {
+ reportChange = true;
+ }
+ }
+ }
+ // Let the scheduler know that state has changed. This may or may not result in an
+ // execution.
+ if (reportChange) {
+ mStateChangedListener.onControllerStateChanged();
+ }
+ // Also tell the scheduler that any ready jobs should be flushed.
+ if (storageNotLow) {
+ mStateChangedListener.onRunJobNow(null);
+ }
+ }
+
+ public class StorageTracker extends BroadcastReceiver {
+ /**
+ * Track whether storage is low.
+ */
+ private boolean mStorageLow;
+ /** Sequence number of last broadcast. */
+ private int mLastBatterySeq = -1;
+
+ public StorageTracker() {
+ }
+
+ public void startTracking() {
+ IntentFilter filter = new IntentFilter();
+
+ // Storage status. Just need to register, since STORAGE_LOW is a sticky
+ // broadcast we will receive that if it is currently active.
+ filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
+ filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
+ mContext.registerReceiver(this, filter);
+ }
+
+ public boolean isStorageNotLow() {
+ return !mStorageLow;
+ }
+
+ public int getSeq() {
+ return mLastBatterySeq;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ onReceiveInternal(intent);
+ }
+
+ @VisibleForTesting
+ public void onReceiveInternal(Intent intent) {
+ final String action = intent.getAction();
+ mLastBatterySeq = intent.getIntExtra(DeviceStorageMonitorService.EXTRA_SEQUENCE,
+ mLastBatterySeq);
+ if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Available storage too low to do work. @ "
+ + SystemClock.elapsedRealtime());
+ }
+ mStorageLow = true;
+ } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Available stoage high enough to do work. @ "
+ + SystemClock.elapsedRealtime());
+ }
+ mStorageLow = false;
+ maybeReportNewStorageState();
+ }
+ }
+ }
+
+ @Override
+ public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
+ pw.print("Storage: not low = ");
+ pw.print(mStorageTracker.isStorageNotLow());
+ pw.print(", seq=");
+ pw.println(mStorageTracker.getSeq());
+ pw.print("Tracking ");
+ pw.print(mTrackedTasks.size());
+ pw.println(":");
+ for (int i = 0; i < mTrackedTasks.size(); i++) {
+ final JobStatus js = mTrackedTasks.get(i);
+ if (!js.shouldDump(filterUid)) {
+ continue;
+ }
+ pw.print(" #");
+ js.printUniqueId(pw);
+ pw.print(" from ");
+ UserHandle.formatUid(pw, js.getSourceUid());
+ pw.println();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 3dcc5d9..7e10a09 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -105,6 +105,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.os.Vibrator;
+import android.os.VibrationEffect;
import android.provider.Settings;
import android.service.notification.Adjustment;
import android.service.notification.Condition;
@@ -1305,14 +1306,16 @@
mRankingHelper.updateNotificationChannel(pkg, uid, channel);
}
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
final int N = mNotificationList.size();
for (int i = N - 1; i >= 0; --i) {
NotificationRecord r = mNotificationList.get(i);
- if (channel.getId() != null && channel.getId().equals(r.getChannel().getId())) {
+ if (r.sbn.getPackageName().equals(pkg)
+ && r.sbn.getUid() == uid
+ && channel.getId() != null
+ && channel.getId().equals(r.getChannel().getId())) {
r.updateNotificationChannel(mRankingHelper.getNotificationChannel(
- r.sbn.getPackageName(), r.getUser().getIdentifier(),
- channel.getId(), false));
+ pkg, uid, channel.getId(), false));
}
}
}
@@ -3097,8 +3100,18 @@
if (mIsTelevision && (new Notification.TvExtender(notification)).getChannel() != null) {
channelId = (new Notification.TvExtender(notification)).getChannel();
}
- final NotificationChannel channel = mRankingHelper.getNotificationChannelWithFallback(pkg,
+ final NotificationChannel channel = mRankingHelper.getNotificationChannel(pkg,
notificationUid, channelId, false /* includeDeleted */);
+ if (channel == null) {
+ // STOPSHIP TODO: remove before release - should always throw without a valid channel.
+ if (channelId == null) {
+ Log.e(TAG, "Cannot post notification without channel ID when targeting O "
+ + " - notification=" + notification);
+ return;
+ }
+ throw new IllegalArgumentException("No Channel found for channelId=" + channelId
+ + ", notification=" + notification);
+ }
final StatusBarNotification n = new StatusBarNotification(
pkg, opPkg, id, tag, notificationUid, callingPid, notification,
user, null, System.currentTimeMillis());
@@ -3613,9 +3626,12 @@
// notifying app does not have the VIBRATE permission.
long identity = Binder.clearCallingIdentity();
try {
- mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(), vibration,
- ((record.getNotification().flags & Notification.FLAG_INSISTENT) != 0)
- ? 0: -1, record.getAudioAttributes());
+ final boolean insistent =
+ (record.getNotification().flags & Notification.FLAG_INSISTENT) != 0;
+ final VibrationEffect effect = VibrationEffect.createWaveform(
+ vibration, insistent ? 0 : -1 /*repeatIndex*/);
+ mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
+ effect, record.getAudioAttributes());
return true;
} finally{
Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index d751a22..b043230 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -294,8 +294,9 @@
stats.isNoisy = mSound != null || mVibration != null;
if (mPreChannelsNotification
- && (getChannel().getUserLockedFields()
- & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0) {
+ && (importance == IMPORTANCE_UNSPECIFIED
+ || (getChannel().getUserLockedFields()
+ & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0)) {
if (!stats.isNoisy && requestedImportance > IMPORTANCE_LOW) {
requestedImportance = IMPORTANCE_LOW;
}
@@ -358,6 +359,7 @@
}
void dump(PrintWriter pw, String prefix, Context baseContext, boolean redact) {
+ prefix = prefix + " ";
final Notification notification = sbn.getNotification();
final Icon icon = notification.getSmallIcon();
String iconStr = String.valueOf(icon);
@@ -365,21 +367,21 @@
iconStr += " / " + idDebugString(baseContext, icon.getResPackage(), icon.getResId());
}
pw.println(prefix + this);
- pw.println(prefix + " uid=" + sbn.getUid() + " userId=" + sbn.getUserId());
- pw.println(prefix + " icon=" + iconStr);
- pw.println(prefix + " pri=" + notification.priority);
- pw.println(prefix + " key=" + sbn.getKey());
- pw.println(prefix + " seen=" + mIsSeen);
- pw.println(prefix + " groupKey=" + getGroupKey());
- pw.println(prefix + " fullscreenIntent=" + notification.fullScreenIntent);
- pw.println(prefix + " contentIntent=" + notification.contentIntent);
- pw.println(prefix + " deleteIntent=" + notification.deleteIntent);
- pw.println(prefix + " tickerText=" + notification.tickerText);
- pw.println(prefix + " contentView=" + notification.contentView);
- pw.println(prefix + String.format(" color=0x%08x", notification.color));
- pw.println(prefix + " timeout=" + TimeUtils.formatForLogging(notification.getTimeout()));
+ pw.println(prefix + "uid=" + sbn.getUid() + " userId=" + sbn.getUserId());
+ pw.println(prefix + "icon=" + iconStr);
+ pw.println(prefix + "pri=" + notification.priority);
+ pw.println(prefix + "key=" + sbn.getKey());
+ pw.println(prefix + "seen=" + mIsSeen);
+ pw.println(prefix + "groupKey=" + getGroupKey());
+ pw.println(prefix + "fullscreenIntent=" + notification.fullScreenIntent);
+ pw.println(prefix + "contentIntent=" + notification.contentIntent);
+ pw.println(prefix + "deleteIntent=" + notification.deleteIntent);
+ pw.println(prefix + "tickerText=" + notification.tickerText);
+ pw.println(prefix + "contentView=" + notification.contentView);
+ pw.println(prefix + String.format("color=0x%08x", notification.color));
+ pw.println(prefix + "timeout=" + TimeUtils.formatForLogging(notification.getTimeout()));
if (notification.actions != null && notification.actions.length > 0) {
- pw.println(prefix + " actions={");
+ pw.println(prefix + "actions={");
final int N = notification.actions.length;
for (int i = 0; i < N; i++) {
final Notification.Action action = notification.actions[i];
@@ -395,7 +397,7 @@
pw.println(prefix + " }");
}
if (notification.extras != null && notification.extras.size() > 0) {
- pw.println(prefix + " extras={");
+ pw.println(prefix + "extras={");
for (String key : notification.extras.keySet()) {
pw.print(prefix + " " + key + "=");
Object val = notification.extras.get(key);
@@ -425,46 +427,46 @@
pw.println();
}
}
- pw.println(prefix + " }");
+ pw.println(prefix + "}");
}
- pw.println(prefix + " stats=" + stats.toString());
- pw.println(prefix + " mContactAffinity=" + mContactAffinity);
- pw.println(prefix + " mRecentlyIntrusive=" + mRecentlyIntrusive);
- pw.println(prefix + " mPackagePriority=" + mPackagePriority);
- pw.println(prefix + " mPackageVisibility=" + mPackageVisibility);
- pw.println(prefix + " mUserImportance="
+ pw.println(prefix + "stats=" + stats.toString());
+ pw.println(prefix + "mContactAffinity=" + mContactAffinity);
+ pw.println(prefix + "mRecentlyIntrusive=" + mRecentlyIntrusive);
+ pw.println(prefix + "mPackagePriority=" + mPackagePriority);
+ pw.println(prefix + "mPackageVisibility=" + mPackageVisibility);
+ pw.println(prefix + "mUserImportance="
+ NotificationListenerService.Ranking.importanceToString(mUserImportance));
- pw.println(prefix + " mImportance="
+ pw.println(prefix + "mImportance="
+ NotificationListenerService.Ranking.importanceToString(mImportance));
- pw.println(prefix + " mImportanceExplanation=" + mImportanceExplanation);
- pw.println(prefix + " mIntercept=" + mIntercept);
- pw.println(prefix + " mGlobalSortKey=" + mGlobalSortKey);
- pw.println(prefix + " mRankingTimeMs=" + mRankingTimeMs);
- pw.println(prefix + " mCreationTimeMs=" + mCreationTimeMs);
- pw.println(prefix + " mVisibleSinceMs=" + mVisibleSinceMs);
- pw.println(prefix + " mUpdateTimeMs=" + mUpdateTimeMs);
- pw.println(prefix + " mSuppressedVisualEffects= " + mSuppressedVisualEffects);
+ pw.println(prefix + "mImportanceExplanation=" + mImportanceExplanation);
+ pw.println(prefix + "mIntercept=" + mIntercept);
+ pw.println(prefix + "mGlobalSortKey=" + mGlobalSortKey);
+ pw.println(prefix + "mRankingTimeMs=" + mRankingTimeMs);
+ pw.println(prefix + "mCreationTimeMs=" + mCreationTimeMs);
+ pw.println(prefix + "mVisibleSinceMs=" + mVisibleSinceMs);
+ pw.println(prefix + "mUpdateTimeMs=" + mUpdateTimeMs);
+ pw.println(prefix + "mSuppressedVisualEffects= " + mSuppressedVisualEffects);
if (mPreChannelsNotification) {
- pw.println(prefix + String.format(" defaults=0x%08x flags=0x%08x",
+ pw.println(prefix + String.format("defaults=0x%08x flags=0x%08x",
notification.defaults, notification.flags));
- pw.println(prefix + " n.sound=" + notification.sound);
- pw.println(prefix + " n.audioStreamType=" + notification.audioStreamType);
- pw.println(prefix + " n.audioAttributes=" + notification.audioAttributes);
+ pw.println(prefix + "n.sound=" + notification.sound);
+ pw.println(prefix + "n.audioStreamType=" + notification.audioStreamType);
+ pw.println(prefix + "n.audioAttributes=" + notification.audioAttributes);
pw.println(prefix + String.format(" led=0x%08x onMs=%d offMs=%d",
notification.ledARGB, notification.ledOnMS, notification.ledOffMS));
- pw.println(prefix + " vibrate=" + Arrays.toString(notification.vibrate));
+ pw.println(prefix + "vibrate=" + Arrays.toString(notification.vibrate));
}
- pw.println(prefix + " mSound= " + mSound);
- pw.println(prefix + " mVibration= " + mVibration);
- pw.println(prefix + " mAttributes= " + mAttributes);
- pw.println(prefix + " mLight= " + mLight);
- pw.println(prefix + " mShowBadge=" + mShowBadge);
- pw.println(prefix + " effectiveNotificationChannel=" + getChannel());
+ pw.println(prefix + "mSound= " + mSound);
+ pw.println(prefix + "mVibration= " + mVibration);
+ pw.println(prefix + "mAttributes= " + mAttributes);
+ pw.println(prefix + "mLight= " + mLight);
+ pw.println(prefix + "mShowBadge=" + mShowBadge);
+ pw.println(prefix + "effectiveNotificationChannel=" + getChannel());
if (getPeopleOverride() != null) {
- pw.println(prefix + " overridePeople= " + TextUtils.join(",", getPeopleOverride()));
+ pw.println(prefix + "overridePeople= " + TextUtils.join(",", getPeopleOverride()));
}
if (getSnoozeCriteria() != null) {
- pw.println(prefix + " snoozeCriteria=" + TextUtils.join(",", getSnoozeCriteria()));
+ pw.println(prefix + "snoozeCriteria=" + TextUtils.join(",", getSnoozeCriteria()));
}
}
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 6a00722..e13df19 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -39,7 +39,6 @@
void updateNotificationChannel(String pkg, int uid, NotificationChannel channel);
void updateNotificationChannelFromAssistant(String pkg, int uid, NotificationChannel channel);
NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted);
- NotificationChannel getNotificationChannelWithFallback(String pkg, int uid, String channelId, boolean includeDeleted);
void deleteNotificationChannel(String pkg, int uid, String channelId);
void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId);
void permanentlyDeleteNotificationChannels(String pkg, int uid);
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 7feaf5a..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)) {
@@ -213,7 +213,11 @@
}
}
- clampDefaultChannel(r);
+ try {
+ deleteDefaultChannelIfNeeded(r);
+ } catch (NameNotFoundException e) {
+ Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e);
+ }
}
}
}
@@ -247,60 +251,94 @@
r.priority = priority;
r.visibility = visibility;
r.showBadge = showBadge;
- createDefaultChannelIfMissing(r);
+
+ try {
+ createDefaultChannelIfNeeded(r);
+ } catch (NameNotFoundException e) {
+ Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e);
+ }
+
if (r.uid == Record.UNKNOWN_UID) {
mRestoredWithoutUids.put(pkg, r);
} else {
mRecords.put(key, r);
}
- clampDefaultChannel(r);
}
return r;
}
- // Clamp the importance level of the default channel for apps targeting the new SDK version,
- // unless the user has already changed the importance.
- private void clampDefaultChannel(Record r) {
- try {
- if (r.uid != Record.UNKNOWN_UID) {
- int userId = UserHandle.getUserId(r.uid);
- final ApplicationInfo applicationInfo =
- mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
- if (applicationInfo.targetSdkVersion > Build.VERSION_CODES.N_MR1) {
- final NotificationChannel defaultChannel =
- r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID);
- if ((defaultChannel.getUserLockedFields()
- & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0) {
- defaultChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
- updateConfig();
- }
- }
- }
- } catch (NameNotFoundException e) {
- // oh well.
+ private boolean shouldHaveDefaultChannel(Record r) throws NameNotFoundException {
+ final int userId = UserHandle.getUserId(r.uid);
+ final ApplicationInfo applicationInfo = mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
+ if (applicationInfo.targetSdkVersion <= Build.VERSION_CODES.N_MR1) {
+ // Pre-O apps should have it.
+ return true;
}
+
+ // STOPSHIP TODO: remove before release - O+ apps should never have a default channel.
+ // But for now, leave the default channel until an app has created its first channel.
+ boolean hasCreatedAChannel = false;
+ final int size = r.channels.size();
+ for (int i = 0; i < size; i++) {
+ final NotificationChannel notificationChannel = r.channels.valueAt(i);
+ if (notificationChannel != null &&
+ notificationChannel.getId() != NotificationChannel.DEFAULT_CHANNEL_ID) {
+ hasCreatedAChannel = true;
+ break;
+ }
+ }
+ if (!hasCreatedAChannel) {
+ return true;
+ }
+
+ // Otherwise, should not have the default channel.
+ return false;
}
- private void createDefaultChannelIfMissing(Record r) {
+ private void deleteDefaultChannelIfNeeded(Record r) throws NameNotFoundException {
if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
- NotificationChannel channel;
- channel = new NotificationChannel(
- NotificationChannel.DEFAULT_CHANNEL_ID,
- mContext.getString(R.string.default_notification_channel_label),
- r.importance);
- channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
- channel.setLockscreenVisibility(r.visibility);
- if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
- channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
- }
- if (r.priority != DEFAULT_PRIORITY) {
- channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
- }
- if (r.visibility != DEFAULT_VISIBILITY) {
- channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
- }
- r.channels.put(channel.getId(), channel);
+ // Not present
+ return;
}
+
+ if (shouldHaveDefaultChannel(r)) {
+ // Keep the default channel until upgraded.
+ return;
+ }
+
+ // Remove Default Channel.
+ r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
+ }
+
+ private void createDefaultChannelIfNeeded(Record r) throws NameNotFoundException {
+ if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+ // Already exists
+ return;
+ }
+
+ if (!shouldHaveDefaultChannel(r)) {
+ // Keep the default channel until upgraded.
+ return;
+ }
+
+ // Create Default Channel
+ NotificationChannel channel;
+ channel = new NotificationChannel(
+ NotificationChannel.DEFAULT_CHANNEL_ID,
+ mContext.getString(R.string.default_notification_channel_label),
+ r.importance);
+ channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
+ channel.setLockscreenVisibility(r.visibility);
+ if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
+ channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
+ }
+ if (r.priority != DEFAULT_PRIORITY) {
+ channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
+ }
+ if (r.visibility != DEFAULT_VISIBILITY) {
+ channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
+ }
+ r.channels.put(channel.getId(), channel);
}
public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
@@ -508,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();
@@ -619,21 +658,6 @@
}
@Override
- public NotificationChannel getNotificationChannelWithFallback(String pkg, int uid,
- String channelId, boolean includeDeleted) {
- Record r = getOrCreateRecord(pkg, uid);
- if (channelId == null) {
- channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
- }
- NotificationChannel channel = r.channels.get(channelId);
- if (channel != null && (includeDeleted || !channel.isDeleted())) {
- return channel;
- } else {
- return r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID);
- }
- }
-
- @Override
public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
boolean includeDeleted) {
Preconditions.checkNotNull(pkg);
@@ -660,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
@@ -1057,10 +1081,9 @@
Record fullRecord = getRecord(pkg,
mPm.getPackageUidAsUser(pkg, changeUserId));
if (fullRecord != null) {
- clampDefaultChannel(fullRecord);
+ deleteDefaultChannelIfNeeded(fullRecord);
}
- } catch (NameNotFoundException e) {
- }
+ } catch (NameNotFoundException e) {}
}
}
diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java
index 86124a8..59f8a2d 100644
--- a/services/core/java/com/android/server/pm/InstantAppResolver.java
+++ b/services/core/java/com/android/server/pm/InstantAppResolver.java
@@ -200,6 +200,7 @@
// Intent that is launched if the package couldn't be installed for any reason.
final Intent failureIntent = new Intent(origIntent);
failureIntent.setFlags(failureIntent.getFlags() | Intent.FLAG_IGNORE_EPHEMERAL);
+ failureIntent.setLaunchToken(token);
try {
final IIntentSender failureIntentTarget = ActivityManager.getService()
.getIntentSender(
@@ -216,6 +217,7 @@
// Intent that is launched if the package was installed successfully.
final Intent successIntent = new Intent(origIntent);
+ successIntent.setLaunchToken(token);
try {
final IIntentSender successIntentTarget = ActivityManager.getService()
.getIntentSender(
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 837aa32..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();
}
}
}
@@ -18646,7 +18656,7 @@
public void getPackageSizeInfo(final String packageName, int userHandle,
final IPackageStatsObserver observer) {
throw new UnsupportedOperationException(
- "Shame on you for calling a hidden API. Shame!");
+ "Shame on you for calling the hidden API getPackageSizeInfo(). Shame!");
}
private boolean getPackageSizeInfoLI(String packageName, int userId, PackageStats stats) {
@@ -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/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index a904d17..c693a47 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -466,11 +466,22 @@
}
}
- // Cache miss. Return not found for the moment.
- //
- // TODO(calin): this may be because of a newly installed package, an update
- // or a new added user. We can either perform a full look up again or register
- // observers to be notified of package/user updates.
+ if (DEBUG) {
+ // TODO(calin): Consider checking for /data/data symlink.
+ // /data/data/ symlinks /data/user/0/ and there's nothing stopping apps
+ // to load dex files through it.
+ try {
+ String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath));
+ if (dexPathReal != dexPath) {
+ Slog.d(TAG, "Dex loaded with symlink. dexPath=" +
+ dexPath + " dexPathReal=" + dexPathReal);
+ }
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ // Cache miss. The cache is updated during installs and uninstalls,
+ // so if we get here we're pretty sure the dex path does not exist.
return new DexSearchResult(null, DEX_SEARCH_NOT_FOUND);
}
@@ -519,12 +530,9 @@
public int searchDex(String dexPath, int userId) {
// First check that this package is installed or active for the given user.
- // If we don't have a data dir it means this user is trying to load something
- // unavailable for them.
+ // A missing data dir means the package is not installed.
Set<String> userDataDirs = mAppDataDirs.get(userId);
if (userDataDirs == null) {
- Slog.w(TAG, "Trying to load a dex path which does not exist for the current " +
- "user. dexPath=" + dexPath + ", userId=" + userId);
return DEX_SEARCH_NOT_FOUND;
}
@@ -540,19 +548,6 @@
}
}
- // TODO(calin): What if we get a symlink? e.g. data dir may be a symlink,
- // /data/data/ -> /data/user/0/.
- if (DEBUG) {
- try {
- String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath));
- if (dexPathReal != dexPath) {
- Slog.d(TAG, "Dex loaded with symlink. dexPath=" +
- dexPath + " dexPathReal=" + dexPathReal);
- }
- } catch (IOException e) {
- // Ignore
- }
- }
return DEX_SEARCH_NOT_FOUND;
}
}
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/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 7eb4df8..31e22b9 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -177,11 +177,13 @@
import android.os.UEventObserver;
import android.os.UserHandle;
import android.os.Vibrator;
+import android.os.VibrationEffect;
import android.provider.MediaStore;
import android.provider.Settings;
import android.service.dreams.DreamManagerInternal;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
+import android.service.vr.IPersistentVrStateCallbacks;
import android.speech.RecognizerIntent;
import android.telecom.TelecomManager;
import android.util.DisplayMetrics;
@@ -236,7 +238,6 @@
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.wm.AppTransition;
import com.android.server.vr.VrManagerInternal;
-import com.android.server.vr.PersistentVrStateListener;
import java.io.File;
import java.io.FileReader;
@@ -999,8 +1000,8 @@
}
MyOrientationListener mOrientationListener;
- final PersistentVrStateListener mPersistentVrModeListener =
- new PersistentVrStateListener() {
+ final IPersistentVrStateCallbacks mPersistentVrModeListener =
+ new IPersistentVrStateCallbacks.Stub() {
@Override
public void onPersistentVrStateChanged(boolean enabled) {
mPersistentVrModeEnabled = enabled;
@@ -2401,7 +2402,9 @@
ApplicationInfo appInfo;
try {
- appInfo = mContext.getPackageManager().getApplicationInfo(attrs.packageName,
+ appInfo = mContext.getPackageManager().getApplicationInfoAsUser(
+ attrs.packageName,
+ 0 /* flags */,
UserHandle.getUserId(callingUid));
} catch (PackageManager.NameNotFoundException e) {
appInfo = null;
@@ -7527,17 +7530,35 @@
if (hapticsDisabled && !always) {
return false;
}
- long[] pattern = null;
+
+ VibrationEffect effect = getVibrationEffect(effectId);
+ if (effect == null) {
+ return false;
+ }
+
+ int owningUid;
+ String owningPackage;
+ if (win != null) {
+ owningUid = win.getOwningUid();
+ owningPackage = win.getOwningPackage();
+ } else {
+ owningUid = android.os.Process.myUid();
+ owningPackage = mContext.getOpPackageName();
+ }
+ mVibrator.vibrate(owningUid, owningPackage, effect, VIBRATION_ATTRIBUTES);
+ return true;
+ }
+
+ private VibrationEffect getVibrationEffect(int effectId) {
+ long[] pattern;
switch (effectId) {
+ case HapticFeedbackConstants.VIRTUAL_KEY:
+ return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
case HapticFeedbackConstants.LONG_PRESS:
pattern = mLongPressVibePattern;
break;
- case HapticFeedbackConstants.VIRTUAL_KEY:
- pattern = mVirtualKeyVibePattern;
- break;
case HapticFeedbackConstants.KEYBOARD_TAP:
- pattern = mKeyboardTapVibePattern;
- break;
+ return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
case HapticFeedbackConstants.CLOCK_TICK:
pattern = mClockTickVibePattern;
break;
@@ -7554,25 +7575,15 @@
pattern = mContextClickVibePattern;
break;
default:
- return false;
- }
- int owningUid;
- String owningPackage;
- if (win != null) {
- owningUid = win.getOwningUid();
- owningPackage = win.getOwningPackage();
- } else {
- owningUid = android.os.Process.myUid();
- owningPackage = mContext.getOpPackageName();
+ return null;
}
if (pattern.length == 1) {
// One-shot vibration
- mVibrator.vibrate(owningUid, owningPackage, pattern[0], VIBRATION_ATTRIBUTES);
+ return VibrationEffect.createOneShot(pattern[0], VibrationEffect.DEFAULT_AMPLITUDE);
} else {
// Pattern vibration
- mVibrator.vibrate(owningUid, owningPackage, pattern, -1, VIBRATION_ATTRIBUTES);
+ return VibrationEffect.createWaveform(pattern, -1);
}
- return true;
}
@Override
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/storage/DeviceStorageMonitorService.java b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
index 12836db..0639eee 100644
--- a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
+++ b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
@@ -34,10 +34,12 @@
import android.os.Environment;
import android.os.FileObserver;
import android.os.Handler;
-import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
import android.os.StatFs;
import android.os.SystemClock;
import android.os.SystemProperties;
@@ -52,6 +54,7 @@
import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.concurrent.atomic.AtomicInteger;
import dalvik.system.VMRuntime;
@@ -76,12 +79,19 @@
public class DeviceStorageMonitorService extends SystemService {
static final String TAG = "DeviceStorageMonitorService";
+ /**
+ * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+ * Current int sequence number of the update.
+ */
+ public static final String EXTRA_SEQUENCE = "seq";
+
// TODO: extend to watch and manage caches on all private volumes
static final boolean DEBUG = false;
static final boolean localLOGV = false;
static final int DEVICE_MEMORY_WHAT = 1;
+ static final int FORCE_MEMORY_WHAT = 2;
private static final int MONITOR_INTERVAL = 1; //in minutes
private static final int LOW_MEMORY_NOTIFICATION_ID = 1;
@@ -112,6 +122,8 @@
private static final File CACHE_PATH = Environment.getDownloadCacheDirectory();
private long mThreadStartTime = -1;
+ boolean mUpdatesStopped;
+ AtomicInteger mSeq = new AtomicInteger(1);
boolean mClearSucceeded = false;
boolean mClearingCache;
private final Intent mStorageLowIntent;
@@ -152,11 +164,17 @@
@Override
public void handleMessage(Message msg) {
//don't handle an invalid message
- if (msg.what != DEVICE_MEMORY_WHAT) {
- Slog.e(TAG, "Will not process invalid message");
- return;
+ switch (msg.what) {
+ case DEVICE_MEMORY_WHAT:
+ checkMemory(msg.arg1 == _TRUE);
+ return;
+ case FORCE_MEMORY_WHAT:
+ forceMemory(msg.arg1, msg.arg2);
+ return;
+ default:
+ Slog.w(TAG, "Will not process invalid message");
+ return;
}
- checkMemory(msg.arg1 == _TRUE);
}
};
@@ -239,12 +257,36 @@
}
}
+ void forceMemory(int opts, int seq) {
+ if ((opts&OPTION_UPDATES_STOPPED) == 0) {
+ if (mUpdatesStopped) {
+ mUpdatesStopped = false;
+ checkMemory(true);
+ }
+ } else {
+ mUpdatesStopped = true;
+ final boolean forceLow = (opts&OPTION_STORAGE_LOW) != 0;
+ if (mLowMemFlag != forceLow || (opts&OPTION_FORCE_UPDATE) != 0) {
+ mLowMemFlag = forceLow;
+ if (forceLow) {
+ sendNotification(seq);
+ } else {
+ cancelNotification(seq);
+ }
+ }
+ }
+ }
+
void checkMemory(boolean checkCache) {
+ if (mUpdatesStopped) {
+ return;
+ }
+
//if the thread that was started to clear cache is still running do nothing till its
//finished clearing cache. Ideally this flag could be modified by clearCache
// and should be accessed via a lock but even if it does this test will fail now and
//hopefully the next time this flag will be set to the correct value.
- if(mClearingCache) {
+ if (mClearingCache) {
if(localLOGV) Slog.i(TAG, "Thread already running just skip");
//make sure the thread is not hung for too long
long diffTime = System.currentTimeMillis() - mThreadStartTime;
@@ -284,7 +326,7 @@
// We tried to clear the cache, but that didn't get us
// below the low storage limit. Tell the user.
Slog.i(TAG, "Running low on memory. Sending notification");
- sendNotification();
+ sendNotification(0);
mLowMemFlag = true;
} else {
if (localLOGV) Slog.v(TAG, "Running low on memory " +
@@ -295,13 +337,13 @@
mFreeMemAfterLastCacheClear = mFreeMem;
if (mLowMemFlag) {
Slog.i(TAG, "Memory available. Cancelling notification");
- cancelNotification();
+ cancelNotification(0);
mLowMemFlag = false;
}
}
if (!mLowMemFlag && !mIsBootImageOnDisk && mFreeMem < BOOT_IMAGE_STORAGE_REQUIREMENT) {
Slog.i(TAG, "No boot image on disk due to lack of space. Sending notification");
- sendNotification();
+ sendNotification(0);
mLowMemFlag = true;
}
if (mFreeMem < mMemFullThreshold) {
@@ -419,7 +461,7 @@
}
};
- private final IBinder mRemoteService = new Binder() {
+ private final Binder mRemoteService = new Binder() {
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
@@ -431,44 +473,157 @@
return;
}
- dumpImpl(pw);
+ dumpImpl(fd, pw, args);
+ }
+
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out,
+ FileDescriptor err, String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) {
+ (new Shell()).exec(this, in, out, err, args, callback, resultReceiver);
}
};
- void dumpImpl(PrintWriter pw) {
- final Context context = getContext();
+ class Shell extends ShellCommand {
+ @Override
+ public int onCommand(String cmd) {
+ return onShellCommand(this, cmd);
+ }
- pw.println("Current DeviceStorageMonitor state:");
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ dumpHelp(pw);
+ }
+ }
- pw.print(" mFreeMem="); pw.print(Formatter.formatFileSize(context, mFreeMem));
- pw.print(" mTotalMemory=");
- pw.println(Formatter.formatFileSize(context, mTotalMemory));
+ static final int OPTION_FORCE_UPDATE = 1<<0;
+ static final int OPTION_UPDATES_STOPPED = 1<<1;
+ static final int OPTION_STORAGE_LOW = 1<<2;
- pw.print(" mFreeMemAfterLastCacheClear=");
- pw.println(Formatter.formatFileSize(context, mFreeMemAfterLastCacheClear));
+ int parseOptions(Shell shell) {
+ String opt;
+ int opts = 0;
+ while ((opt = shell.getNextOption()) != null) {
+ if ("-f".equals(opt)) {
+ opts |= OPTION_FORCE_UPDATE;
+ }
+ }
+ return opts;
+ }
- pw.print(" mLastReportedFreeMem=");
- pw.print(Formatter.formatFileSize(context, mLastReportedFreeMem));
- pw.print(" mLastReportedFreeMemTime=");
- TimeUtils.formatDuration(mLastReportedFreeMemTime, SystemClock.elapsedRealtime(), pw);
- pw.println();
+ int onShellCommand(Shell shell, String cmd) {
+ if (cmd == null) {
+ return shell.handleDefaultCommands(cmd);
+ }
+ PrintWriter pw = shell.getOutPrintWriter();
+ switch (cmd) {
+ case "force-low": {
+ int opts = parseOptions(shell);
+ getContext().enforceCallingOrSelfPermission(
+ android.Manifest.permission.DEVICE_POWER, null);
+ int seq = mSeq.incrementAndGet();
+ mHandler.sendMessage(mHandler.obtainMessage(FORCE_MEMORY_WHAT,
+ opts | OPTION_UPDATES_STOPPED | OPTION_STORAGE_LOW, seq));
+ if ((opts & OPTION_FORCE_UPDATE) != 0) {
+ pw.println(seq);
+ }
+ } break;
+ case "force-not-low": {
+ int opts = parseOptions(shell);
+ getContext().enforceCallingOrSelfPermission(
+ android.Manifest.permission.DEVICE_POWER, null);
+ int seq = mSeq.incrementAndGet();
+ mHandler.sendMessage(mHandler.obtainMessage(FORCE_MEMORY_WHAT,
+ opts | OPTION_UPDATES_STOPPED, seq));
+ if ((opts & OPTION_FORCE_UPDATE) != 0) {
+ pw.println(seq);
+ }
+ } break;
+ case "reset": {
+ int opts = parseOptions(shell);
+ getContext().enforceCallingOrSelfPermission(
+ android.Manifest.permission.DEVICE_POWER, null);
+ int seq = mSeq.incrementAndGet();
+ mHandler.sendMessage(mHandler.obtainMessage(FORCE_MEMORY_WHAT,
+ opts, seq));
+ if ((opts & OPTION_FORCE_UPDATE) != 0) {
+ pw.println(seq);
+ }
+ } break;
+ default:
+ return shell.handleDefaultCommands(cmd);
+ }
+ return 0;
+ }
- pw.print(" mLowMemFlag="); pw.print(mLowMemFlag);
- pw.print(" mMemFullFlag="); pw.println(mMemFullFlag);
- pw.print(" mIsBootImageOnDisk="); pw.print(mIsBootImageOnDisk);
+ static void dumpHelp(PrintWriter pw) {
+ pw.println("Device storage monitor service (devicestoragemonitor) commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println(" force-low [-f]");
+ pw.println(" Force storage to be low, freezing storage state.");
+ pw.println(" -f: force a storage change broadcast be sent, prints new sequence.");
+ pw.println(" force-not-low [-f]");
+ pw.println(" Force storage to not be low, freezing storage state.");
+ pw.println(" -f: force a storage change broadcast be sent, prints new sequence.");
+ pw.println(" reset [-f]");
+ pw.println(" Unfreeze storage state, returning to current real values.");
+ pw.println(" -f: force a storage change broadcast be sent, prints new sequence.");
+ }
- pw.print(" mClearSucceeded="); pw.print(mClearSucceeded);
- pw.print(" mClearingCache="); pw.println(mClearingCache);
+ void dumpImpl(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (args == null || args.length == 0 || "-a".equals(args[0])) {
+ final Context context = getContext();
- pw.print(" mMemLowThreshold=");
- pw.print(Formatter.formatFileSize(context, mMemLowThreshold));
- pw.print(" mMemFullThreshold=");
- pw.println(Formatter.formatFileSize(context, mMemFullThreshold));
+ pw.println("Current DeviceStorageMonitor state:");
- pw.print(" mMemCacheStartTrimThreshold=");
- pw.print(Formatter.formatFileSize(context, mMemCacheStartTrimThreshold));
- pw.print(" mMemCacheTrimToThreshold=");
- pw.println(Formatter.formatFileSize(context, mMemCacheTrimToThreshold));
+ pw.print(" mFreeMem=");
+ pw.print(Formatter.formatFileSize(context, mFreeMem));
+ pw.print(" mTotalMemory=");
+ pw.println(Formatter.formatFileSize(context, mTotalMemory));
+
+ pw.print(" mFreeMemAfterLastCacheClear=");
+ pw.println(Formatter.formatFileSize(context, mFreeMemAfterLastCacheClear));
+
+ pw.print(" mLastReportedFreeMem=");
+ pw.print(Formatter.formatFileSize(context, mLastReportedFreeMem));
+ pw.print(" mLastReportedFreeMemTime=");
+ TimeUtils.formatDuration(mLastReportedFreeMemTime, SystemClock.elapsedRealtime(), pw);
+ pw.println();
+
+ if (mUpdatesStopped) {
+ pw.print(" mUpdatesStopped=");
+ pw.print(mUpdatesStopped);
+ pw.print(" mSeq=");
+ pw.println(mSeq.get());
+ } else {
+ pw.print(" mClearSucceeded=");
+ pw.print(mClearSucceeded);
+ pw.print(" mClearingCache=");
+ pw.println(mClearingCache);
+ }
+
+ pw.print(" mLowMemFlag=");
+ pw.print(mLowMemFlag);
+ pw.print(" mMemFullFlag=");
+ pw.println(mMemFullFlag);
+
+ pw.print(" mMemLowThreshold=");
+ pw.print(Formatter.formatFileSize(context, mMemLowThreshold));
+ pw.print(" mMemFullThreshold=");
+ pw.println(Formatter.formatFileSize(context, mMemFullThreshold));
+
+ pw.print(" mMemCacheStartTrimThreshold=");
+ pw.print(Formatter.formatFileSize(context, mMemCacheStartTrimThreshold));
+ pw.print(" mMemCacheTrimToThreshold=");
+ pw.println(Formatter.formatFileSize(context, mMemCacheTrimToThreshold));
+
+ pw.print(" mIsBootImageOnDisk="); pw.println(mIsBootImageOnDisk);
+ } else {
+ Shell shell = new Shell();
+ shell.exec(mRemoteService, null, fd, null, args, null, new ResultReceiver(null));
+ }
}
/**
@@ -476,7 +631,7 @@
* an error dialog indicating low disk space and launch the Installer
* application
*/
- private void sendNotification() {
+ private void sendNotification(int seq) {
final Context context = getContext();
if(localLOGV) Slog.i(TAG, "Sending low memory notification");
//log the event to event log with the amount of free storage(in bytes) left on the device
@@ -514,13 +669,17 @@
notification.flags |= Notification.FLAG_NO_CLEAR;
notificationMgr.notifyAsUser(null, LOW_MEMORY_NOTIFICATION_ID, notification,
UserHandle.ALL);
- context.sendStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL);
+ Intent broadcast = new Intent(mStorageLowIntent);
+ if (seq != 0) {
+ broadcast.putExtra(EXTRA_SEQUENCE, seq);
+ }
+ context.sendStickyBroadcastAsUser(broadcast, UserHandle.ALL);
}
/**
* Cancels low storage notification and sends OK intent.
*/
- private void cancelNotification() {
+ private void cancelNotification(int seq) {
final Context context = getContext();
if(localLOGV) Slog.i(TAG, "Canceling low memory notification");
NotificationManager mNotificationMgr =
@@ -530,7 +689,11 @@
mNotificationMgr.cancelAsUser(null, LOW_MEMORY_NOTIFICATION_ID, UserHandle.ALL);
context.removeStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL);
- context.sendBroadcastAsUser(mStorageOkIntent, UserHandle.ALL);
+ Intent broadcast = new Intent(mStorageOkIntent);
+ if (seq != 0) {
+ broadcast.putExtra(EXTRA_SEQUENCE, seq);
+ }
+ context.sendBroadcastAsUser(broadcast, UserHandle.ALL);
}
/**
diff --git a/services/core/java/com/android/server/vr/PersistentVrStateListener.java b/services/core/java/com/android/server/vr/PersistentVrStateListener.java
deleted file mode 100644
index bccd5f1..0000000
--- a/services/core/java/com/android/server/vr/PersistentVrStateListener.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * 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.server.vr;
-
-/**
- * Listener for state changes to persistent VR mode.
- *
- * @hide Only for use within system server.
- */
-public abstract class PersistentVrStateListener {
-
- /**
- * Called when the Persistent VR mode state changes.
- *
- * @param enabled {@code true} if persistent VR mode is enabled.
- */
- public abstract void onPersistentVrStateChanged(boolean enabled);
-}
diff --git a/services/core/java/com/android/server/vr/VrManagerInternal.java b/services/core/java/com/android/server/vr/VrManagerInternal.java
index 210aa44..358861d 100644
--- a/services/core/java/com/android/server/vr/VrManagerInternal.java
+++ b/services/core/java/com/android/server/vr/VrManagerInternal.java
@@ -17,6 +17,7 @@
import android.annotation.NonNull;
import android.content.ComponentName;
+import android.service.vr.IPersistentVrStateCallbacks;
/**
* Service for accessing the VR mode manager.
@@ -101,5 +102,5 @@
/**
* Adds listener that reports state changes to persistent VR mode.
*/
- public abstract void addPersistentVrModeStateListener(PersistentVrStateListener listener);
+ public abstract void addPersistentVrModeStateListener(IPersistentVrStateCallbacks listener);
}
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index a00115c..5bcdd4c 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -40,6 +40,7 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
+import android.service.vr.IPersistentVrStateCallbacks;
import android.service.vr.IVrListener;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
@@ -123,10 +124,10 @@
private int mCurrentVrModeUser;
private boolean mWasDefaultGranted;
private boolean mGuard;
- private final RemoteCallbackList<IVrStateCallbacks> mRemoteCallbacks =
+ private final RemoteCallbackList<IVrStateCallbacks> mVrStateRemoteCallbacks =
new RemoteCallbackList<>();
- private final ArrayList<PersistentVrStateListener> mPersistentVrStateListeners =
- new ArrayList<>();
+ private final RemoteCallbackList<IPersistentVrStateCallbacks>
+ mPersistentVrStateRemoteCallbacks = new RemoteCallbackList<>();
private int mPreviousCoarseLocationMode = INVALID_APPOPS_MODE;
private int mPreviousManageOverlayMode = INVALID_APPOPS_MODE;
private VrState mPendingState;
@@ -202,16 +203,16 @@
switch(msg.what) {
case MSG_VR_STATE_CHANGE : {
boolean state = (msg.arg1 == 1);
- int i = mRemoteCallbacks.beginBroadcast();
+ int i = mVrStateRemoteCallbacks.beginBroadcast();
while (i > 0) {
i--;
try {
- mRemoteCallbacks.getBroadcastItem(i).onVrStateChanged(state);
+ mVrStateRemoteCallbacks.getBroadcastItem(i).onVrStateChanged(state);
} catch (RemoteException e) {
// Noop
}
}
- mRemoteCallbacks.finishBroadcast();
+ mVrStateRemoteCallbacks.finishBroadcast();
} break;
case MSG_PENDING_VR_STATE_CHANGE : {
synchronized(mLock) {
@@ -222,10 +223,17 @@
} break;
case MSG_PERSISTENT_VR_MODE_STATE_CHANGE : {
boolean state = (msg.arg1 == 1);
- for (int i = 0; i < mPersistentVrStateListeners.size(); i++) {
- mPersistentVrStateListeners.get(i).onPersistentVrStateChanged(
- state);
+ int i = mPersistentVrStateRemoteCallbacks.beginBroadcast();
+ while (i > 0) {
+ i--;
+ try {
+ mPersistentVrStateRemoteCallbacks.getBroadcastItem(i)
+ .onPersistentVrStateChanged(state);
+ } catch (RemoteException e) {
+ // Noop
+ }
}
+ mPersistentVrStateRemoteCallbacks.finishBroadcast();
} break;
default :
throw new IllegalStateException("Unknown message type: " + msg.what);
@@ -383,6 +391,26 @@
}
@Override
+ public void registerPersistentVrStateListener(IPersistentVrStateCallbacks cb) {
+ enforceCallerPermission(Manifest.permission.ACCESS_VR_MANAGER);
+ if (cb == null) {
+ throw new IllegalArgumentException("Callback binder object is null.");
+ }
+
+ VrManagerService.this.addPersistentStateCallback(cb);
+ }
+
+ @Override
+ public void unregisterPersistentVrStateListener(IPersistentVrStateCallbacks cb) {
+ enforceCallerPermission(Manifest.permission.ACCESS_VR_MANAGER);
+ if (cb == null) {
+ throw new IllegalArgumentException("Callback binder object is null.");
+ }
+
+ VrManagerService.this.removePersistentStateCallback(cb);
+ }
+
+ @Override
public boolean getVrModeState() {
return VrManagerService.this.getVrMode();
}
@@ -414,13 +442,21 @@
String tab = " ";
dumpStateTransitions(pw);
pw.println("\n\nRemote Callbacks:");
- int i=mRemoteCallbacks.beginBroadcast(); // create the broadcast item array
+ int i=mVrStateRemoteCallbacks.beginBroadcast(); // create the broadcast item array
while(i-->0) {
pw.print(tab);
- pw.print(mRemoteCallbacks.getBroadcastItem(i));
+ pw.print(mVrStateRemoteCallbacks.getBroadcastItem(i));
if (i>0) pw.println(",");
}
- mRemoteCallbacks.finishBroadcast();
+ mVrStateRemoteCallbacks.finishBroadcast();
+ pw.println("\n\nPersistent Vr State Remote Callbacks:");
+ i=mPersistentVrStateRemoteCallbacks.beginBroadcast();
+ while(i-->0) {
+ pw.print(tab);
+ pw.print(mPersistentVrStateRemoteCallbacks.getBroadcastItem(i));
+ if (i>0) pw.println(",");
+ }
+ mPersistentVrStateRemoteCallbacks.finishBroadcast();
pw.println("\n");
pw.println("Installed VrListenerService components:");
int userId = mCurrentVrModeUser;
@@ -443,16 +479,6 @@
pw.println(n.flattenToString());
}
}
- pw.println("Attached persistent mode listeners:");
- if (mPersistentVrStateListeners == null ||
- mPersistentVrStateListeners.size() == 0) {
- pw.println("None");
- } else {
- for (PersistentVrStateListener l : mPersistentVrStateListeners) {
- pw.print(tab);
- pw.println("listener: " + l);
- }
- }
pw.println("\n");
pw.println("********* End of VrManagerService Dump *********");
}
@@ -507,8 +533,8 @@
}
@Override
- public void addPersistentVrModeStateListener(PersistentVrStateListener listener) {
- VrManagerService.this.addPersistentVrModeStateListener(listener);
+ public void addPersistentVrModeStateListener(IPersistentVrStateCallbacks listener) {
+ VrManagerService.this.addPersistentStateCallback(listener);
}
}
@@ -1084,12 +1110,6 @@
(mPersistentVrModeEnabled) ? 1 : 0, 0));
}
- private void addPersistentVrModeStateListener(PersistentVrStateListener listener) {
- synchronized (mLock) {
- mPersistentVrStateListeners.add(listener);
- }
- }
-
private int hasVrPackage(@NonNull ComponentName targetPackageName, int userId) {
synchronized (mLock) {
return mComponentObserver.isValid(targetPackageName, userId);
@@ -1111,11 +1131,19 @@
*/
private void addStateCallback(IVrStateCallbacks cb) {
- mRemoteCallbacks.register(cb);
+ mVrStateRemoteCallbacks.register(cb);
}
private void removeStateCallback(IVrStateCallbacks cb) {
- mRemoteCallbacks.unregister(cb);
+ mVrStateRemoteCallbacks.unregister(cb);
+ }
+
+ private void addPersistentStateCallback(IPersistentVrStateCallbacks cb) {
+ mPersistentVrStateRemoteCallbacks.register(cb);
+ }
+
+ private void removePersistentStateCallback(IPersistentVrStateCallbacks cb) {
+ mPersistentVrStateRemoteCallbacks.unregister(cb);
}
private boolean getVrMode() {
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index e3941b9..16edd35 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -161,11 +161,6 @@
} else {
mClearProlongedAnimation = true;
}
-
- // Since we are finally starting our animation, we don't need the logic anymore to prevent
- // the app from showing again if we just moved between stacks.
- // See {@link WindowState#notifyMovedInStack}.
- mAppToken.resetJustMovedInStack();
}
public void setDummyAnimation() {
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 27e9dea..32cd7d8 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -55,6 +55,7 @@
import android.os.SystemClock;
import android.util.Slog;
import android.view.IApplicationToken;
+import android.view.SurfaceControl;
import android.view.WindowManager;
import android.view.WindowManagerPolicy.StartingSurface;
@@ -367,6 +368,13 @@
mEnteringAnimation = true;
mService.mActivityManagerAppTransitionNotifier.onAppTransitionFinishedLocked(token);
}
+ if (hidden && !delayed) {
+ SurfaceControl.openTransaction();
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ mChildren.get(i).mWinAnimator.hide("immediately hidden");
+ }
+ SurfaceControl.closeTransaction();
+ }
if (!mService.mClosingApps.contains(this) && !mService.mOpeningApps.contains(this)) {
// The token is not closing nor opening, so even if there is an animation set, that
@@ -969,19 +977,6 @@
mService.mWindowPlacerLocked.performSurfacePlacement();
}
- void resetJustMovedInStack() {
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- (mChildren.get(i)).resetJustMovedInStack();
- }
- }
-
- void notifyMovedInStack() {
- for (int winNdx = mChildren.size() - 1; winNdx >= 0; --winNdx) {
- final WindowState win = mChildren.get(winNdx);
- win.notifyMovedInStack();
- }
- }
-
void setAppLayoutChanges(int changes, String reason) {
if (!mChildren.isEmpty()) {
final DisplayContent dc = getDisplayContent();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e5b00f3..01a992f 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -179,11 +179,23 @@
// Mapping from a token IBinder to a WindowToken object on this display.
private final HashMap<IBinder, WindowToken> mTokenMap = new HashMap();
+ // Initial display metrics.
int mInitialDisplayWidth = 0;
int mInitialDisplayHeight = 0;
int mInitialDisplayDensity = 0;
+
+ /**
+ * Overridden display size. Initialized with {@link #mInitialDisplayWidth}
+ * and {@link #mInitialDisplayHeight}, but can be set via shell command "adb shell wm size".
+ * @see WindowManagerService#setForcedDisplaySize(int, int, int)
+ */
int mBaseDisplayWidth = 0;
int mBaseDisplayHeight = 0;
+ /**
+ * Overridden display density for current user. Initialized with {@link #mInitialDisplayDensity}
+ * but can be set from Settings or via shell command "adb shell wm density".
+ * @see WindowManagerService#setForcedDisplayDensityForUser(int, int, int)
+ */
int mBaseDisplayDensity = 0;
boolean mDisplayScalingDisabled;
private final DisplayInfo mDisplayInfo = new DisplayInfo();
@@ -1511,6 +1523,10 @@
void updateDisplayInfo() {
mDisplay.getDisplayInfo(mDisplayInfo);
mDisplay.getMetrics(mDisplayMetrics);
+
+ // Check if display metrics changed and update base values if needed.
+ updateBaseDisplayMetricsIfNeeded();
+
for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
mTaskStackContainers.get(i).updateDisplayInfo(null);
}
@@ -1526,10 +1542,11 @@
}
}
- mBaseDisplayWidth = mInitialDisplayWidth = mDisplayInfo.logicalWidth;
- mBaseDisplayHeight = mInitialDisplayHeight = mDisplayInfo.logicalHeight;
- mBaseDisplayDensity = mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi;
- mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight);
+ updateBaseDisplayMetrics(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight,
+ mDisplayInfo.logicalDensityDpi);
+ mInitialDisplayWidth = mDisplayInfo.logicalWidth;
+ mInitialDisplayHeight = mDisplayInfo.logicalHeight;
+ mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi;
}
void getLogicalDisplayRect(Rect out) {
@@ -1559,6 +1576,50 @@
}
}
+ /**
+ * If display metrics changed, overrides are not set and it's not just a rotation - update base
+ * values.
+ */
+ private void updateBaseDisplayMetricsIfNeeded() {
+ final int orientation = mDisplayInfo.rotation;
+ final boolean rotated = (orientation == ROTATION_90 || orientation == ROTATION_270);
+ final int newWidth = rotated ? mDisplayInfo.logicalHeight : mDisplayInfo.logicalWidth;
+ final int newHeight = rotated ? mDisplayInfo.logicalWidth : mDisplayInfo.logicalHeight;
+ int density = mDisplayInfo.logicalDensityDpi;
+
+ boolean displayMetricsChanged = false;
+
+ // Check if display size is not forced and changed in new display info.
+ boolean isDisplaySizeForced = mBaseDisplayWidth != mInitialDisplayWidth
+ || mBaseDisplayHeight != mInitialDisplayHeight;
+ if (!isDisplaySizeForced) {
+ displayMetricsChanged = mBaseDisplayWidth != newWidth
+ || mBaseDisplayHeight != newHeight;
+ }
+
+ // Check if display density is not forced and changed in new display info.
+ final int forcedDensity = mBaseDisplayDensity != mInitialDisplayDensity
+ ? mBaseDisplayDensity : 0;
+ if (forcedDensity != 0) {
+ density = forcedDensity;
+ } else {
+ displayMetricsChanged |= mBaseDisplayDensity != mDisplayInfo.logicalDensityDpi;
+ }
+
+ if (displayMetricsChanged) {
+ updateBaseDisplayMetrics(newWidth, newHeight, density);
+ mService.reconfigureDisplayLocked(this);
+ }
+ }
+
+ /** Update base (override) display metrics. */
+ void updateBaseDisplayMetrics(int baseWidth, int baseHeight, int baseDensity) {
+ mBaseDisplayWidth = baseWidth;
+ mBaseDisplayHeight = baseHeight;
+ mBaseDisplayDensity = baseDensity;
+ mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight);
+ }
+
void getContentRect(Rect out) {
out.set(mContentRect);
}
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index 0e6b1b6..012480e 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -299,6 +299,13 @@
}
/**
+ * @return the current aspect ratio.
+ */
+ float getAspectRatio() {
+ return mAspectRatio;
+ }
+
+ /**
* Sets the current set of actions.
*/
void setActions(List<RemoteAction> actions) {
diff --git a/services/core/java/com/android/server/wm/PinnedStackWindowController.java b/services/core/java/com/android/server/wm/PinnedStackWindowController.java
index 6a0e353..34ccf87 100644
--- a/services/core/java/com/android/server/wm/PinnedStackWindowController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackWindowController.java
@@ -64,6 +64,9 @@
mContainer.getBounds(originalBounds);
mContainer.setAnimatingBounds(toBounds);
UiThread.getHandler().post(() -> {
+ if (mContainer == null) {
+ return;
+ }
mService.mBoundsAnimationController.animateBounds(mContainer, originalBounds,
toBounds, animationDuration, moveToFullscreen);
});
@@ -79,19 +82,17 @@
return;
}
- final int displayId = mContainer.getDisplayContent().getDisplayId();
- final Rect toBounds = mService.getPictureInPictureBounds(displayId, aspectRatio);
- final Rect targetBounds = new Rect();
- mContainer.getAnimatingBounds(targetBounds);
- if (!toBounds.equals(targetBounds)) {
- animateResizePinnedStack(toBounds, -1 /* duration */);
- }
-
final PinnedStackController pinnedStackController =
mContainer.getDisplayContent().getPinnedStackController();
- pinnedStackController.setAspectRatio(
- pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)
- ? aspectRatio : -1f);
+
+ if (Float.compare(aspectRatio, pinnedStackController.getAspectRatio()) != 0) {
+ final int displayId = mContainer.getDisplayContent().getDisplayId();
+ final Rect toBounds = mService.getPictureInPictureBounds(displayId, aspectRatio);
+ animateResizePinnedStack(toBounds, -1 /* duration */);
+ pinnedStackController.setAspectRatio(
+ pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)
+ ? aspectRatio : -1f);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 99c085f..9e4d60a 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -208,10 +208,6 @@
void positionAt(int position, Rect bounds, Configuration overrideConfig) {
mStack.positionChildAt(position, this, false /* includingParents */);
resizeLocked(bounds, overrideConfig, false /* force */);
-
- for (int activityNdx = mChildren.size() - 1; activityNdx >= 0; --activityNdx) {
- mChildren.get(activityNdx).notifyMovedInStack();
- }
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5844b0b..dd2689b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5305,8 +5305,8 @@
if (displayContent.mBaseDisplayWidth != width
|| displayContent.mBaseDisplayHeight != height) {
Slog.i(TAG_WM, "FORCED DISPLAY SIZE: " + width + "x" + height);
- displayContent.mBaseDisplayWidth = width;
- displayContent.mBaseDisplayHeight = height;
+ displayContent.updateBaseDisplayMetrics(width, height,
+ displayContent.mBaseDisplayDensity);
}
} catch (NumberFormatException ex) {
}
@@ -5331,8 +5331,7 @@
// displayContent must not be null
private void setForcedDisplaySizeLocked(DisplayContent displayContent, int width, int height) {
Slog.i(TAG_WM, "Using new display size: " + width + "x" + height);
- displayContent.mBaseDisplayWidth = width;
- displayContent.mBaseDisplayHeight = height;
+ displayContent.updateBaseDisplayMetrics(width, height, displayContent.mBaseDisplayDensity);
reconfigureDisplayLocked(displayContent);
}
@@ -7306,4 +7305,49 @@
mAppFreezeListeners.remove(listener);
}
+ /**
+ * WARNING: This interrupts surface updates, be careful! Don't
+ * execute within the transaction for longer than you would
+ * execute on an animation thread.
+ * WARNING: This holds the WindowManager lock, so if exec will acquire
+ * the ActivityManager lock, you should hold it BEFORE calling this
+ * otherwise there is a risk of deadlock if another thread holding the AM
+ * lock waits on the WM lock.
+ * WARNING: This method contains locks known to the State of California
+ * to cause Deadlocks and other conditions.
+ *
+ *
+ * Begins a surface transaction with which the AM can batch operations.
+ * All Surface updates performed by the WindowManager following this
+ * will not appear on screen until after the call to
+ * closeSurfaceTransaction.
+ *
+ * ActivityManager can use this to ensure multiple 'commands' will all
+ * be reflected in a single frame. For example when reparenting a window
+ * which was previously hidden due to it's parent properties, we may
+ * need to ensure it is hidden in the same frame that the properties
+ * from the new parent are inherited, otherwise it could be revealed
+ * mistakenly.
+ *
+ *
+ * TODO(b/36393204): We can investigate totally replacing #deferSurfaceLayout
+ * with something like this but it seems that some existing cases of
+ * deferSurfaceLayout may be a little too broad, in particular the total
+ * enclosure of startActivityUnchecked which could run for quite some time.
+ */
+ public void inSurfaceTransaction(Runnable exec) {
+ // We hold the WindowManger lock to ensure relayoutWindow
+ // does not return while a Surface transaction is opening.
+ // The client depends on us to have resized the surface
+ // by that point (b/36462635)
+
+ synchronized (mWindowMap) {
+ SurfaceControl.openTransaction();
+ try {
+ exec.run();
+ } finally {
+ SurfaceControl.closeTransaction();
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ca5d551..d4c8b1f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -517,11 +517,6 @@
final private Rect mTmpRect = new Rect();
/**
- * See {@link #notifyMovedInStack}.
- */
- private boolean mJustMovedInStack;
-
- /**
* Whether the window was resized by us while it was gone for layout.
*/
boolean mResizedWhileGone = false;
@@ -1998,49 +1993,6 @@
}
}
- /**
- * Notifies this window that the corresponding task has just moved in the stack.
- * <p>
- * This is used to fix the following: If we moved in the stack, and if the last clip rect was
- * empty, meaning that our task was completely offscreen, we need to keep it invisible because
- * the actual app transition that updates the visibility is delayed by a few transactions.
- * Instead of messing around with the ordering and timing how transitions and transactions are
- * executed, we introduce this little hack which prevents this window of getting visible again
- * with the wrong bounds until the app transitions has started.
- * <p>
- * This method notifies the window about that we just moved in the stack so we can apply this
- * logic in {@link WindowStateAnimator#updateSurfaceWindowCrop}
- */
- void notifyMovedInStack() {
- mJustMovedInStack = true;
-
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = mChildren.get(i);
- c.notifyMovedInStack();
- }
- }
-
- /**
- * See {@link #notifyMovedInStack}.
- *
- * @return Whether we just got moved in the corresponding stack.
- */
- boolean hasJustMovedInStack() {
- return mJustMovedInStack;
- }
-
- /**
- * Resets that we just moved in the corresponding stack. See {@link #notifyMovedInStack}.
- */
- void resetJustMovedInStack() {
- mJustMovedInStack = false;
-
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState c = mChildren.get(i);
- c.resetJustMovedInStack();
- }
- }
-
private final class DeadWindowEventReceiver extends InputEventReceiver {
DeadWindowEventReceiver(InputChannel inputChannel) {
super(inputChannel, mService.mH.getLooper());
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 4b71338..48de7e4 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1177,10 +1177,6 @@
w.transformClipRectFromScreenToSurfaceSpace(clipRect);
- // See {@link WindowState#notifyMovedInStack} for why this is necessary.
- if (w.hasJustMovedInStack() && mLastClipRect.isEmpty() && !clipRect.isEmpty()) {
- clipRect.setEmpty();
- }
return true;
}
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index 50bae794..76ce890 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -27,8 +27,12 @@
#include <utils/Log.h>
#include <hardware/vibrator.h>
+#include <inttypes.h>
#include <stdio.h>
+using android::hardware::Return;
+using android::hardware::vibrator::V1_0::Effect;
+using android::hardware::vibrator::V1_0::EffectStrength;
using android::hardware::vibrator::V1_0::IVibrator;
using android::hardware::vibrator::V1_0::Status;
@@ -59,8 +63,8 @@
{
if (mHal != nullptr) {
Status retStatus = mHal->on(timeout_ms);
- if (retStatus == Status::ERR) {
- ALOGE("vibratorOn command failed.");
+ if (retStatus != Status::OK) {
+ ALOGE("vibratorOn command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));
}
} else {
ALOGW("Tried to vibrate but there is no vibrator device.");
@@ -71,19 +75,68 @@
{
if (mHal != nullptr) {
Status retStatus = mHal->off();
- if (retStatus == Status::ERR) {
- ALOGE("vibratorOff command failed.");
+ if (retStatus != Status::OK) {
+ ALOGE("vibratorOff command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));
}
} else {
ALOGW("Tried to stop vibrating but there is no vibrator device.");
}
}
+static jlong vibratorSupportsAmplitudeControl(JNIEnv*, jobject) {
+ if (mHal != nullptr) {
+ return mHal->supportsAmplitudeControl();
+ } else {
+ ALOGW("Unable to get max vibration amplitude, there is no vibrator device.");
+ }
+ return false;
+}
+
+static void vibratorSetAmplitude(JNIEnv*, jobject, jint amplitude) {
+ if (mHal != nullptr) {
+ Status status = mHal->setAmplitude(static_cast<uint32_t>(amplitude));
+ if (status != Status::OK) {
+ ALOGE("Failed to set vibrator amplitude (%" PRIu32 ").",
+ static_cast<uint32_t>(status));
+ }
+ } else {
+ ALOGW("Unable to set vibration amplitude, there is no vibrator device.");
+ }
+}
+
+static jlong vibratorPerformEffect(JNIEnv*, jobject, jlong effect, jint strength) {
+ if (mHal != nullptr) {
+ Status status;
+ uint32_t lengthMs;
+ mHal->perform(static_cast<Effect>(effect), static_cast<EffectStrength>(strength),
+ [&status, &lengthMs](Status retStatus, uint32_t retLengthMs) {
+ status = retStatus;
+ lengthMs = retLengthMs;
+ });
+ if (status == Status::OK) {
+ return lengthMs;
+ } else if (status != Status::UNSUPPORTED_OPERATION) {
+ // Don't warn on UNSUPPORTED_OPERATION, that's a normal even and just means the motor
+ // doesn't have a pre-defined waveform to perform for it, so we should just fall back
+ // to the framework waveforms.
+ ALOGE("Failed to perform haptic effect: effect=%" PRId64 ", strength=%" PRId32
+ ", error=%" PRIu32 ").", static_cast<int64_t>(effect),
+ static_cast<int32_t>(strength), static_cast<uint32_t>(status));
+ }
+ } else {
+ ALOGW("Unable to perform haptic effect, there is no vibrator device.");
+ }
+ return -1;
+}
+
static const JNINativeMethod method_table[] = {
{ "vibratorExists", "()Z", (void*)vibratorExists },
{ "vibratorInit", "()V", (void*)vibratorInit },
{ "vibratorOn", "(J)V", (void*)vibratorOn },
- { "vibratorOff", "()V", (void*)vibratorOff }
+ { "vibratorOff", "()V", (void*)vibratorOff },
+ { "vibratorSupportsAmplitudeControl", "()Z", (void*)vibratorSupportsAmplitudeControl},
+ { "vibratorSetAmplitude", "(I)V", (void*)vibratorSetAmplitude},
+ { "vibratorPerformEffect", "(JJ)J", (void*)vibratorPerformEffect}
};
int register_android_server_VibratorService(JNIEnv *env)
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/CertificateMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/CertificateMonitor.java
new file mode 100644
index 0000000..a2bc195
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/CertificateMonitor.java
@@ -0,0 +1,232 @@
+/*
+ * 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.server.devicepolicy;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.os.Build;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
+import android.provider.Settings;
+import android.security.Credentials;
+import android.security.KeyChain;
+import android.security.KeyChain.KeyChainConnection;
+import android.util.Log;
+
+import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.R;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.List;
+import java.util.Set;
+
+public class CertificateMonitor {
+ protected static final String LOG_TAG = DevicePolicyManagerService.LOG_TAG;
+ protected static final int MONITORING_CERT_NOTIFICATION_ID = R.plurals.ssl_ca_cert_warning;
+
+ private final DevicePolicyManagerService mService;
+ private final DevicePolicyManagerService.Injector mInjector;
+ private final Handler mHandler;
+
+ public CertificateMonitor(final DevicePolicyManagerService service,
+ final DevicePolicyManagerService.Injector injector, final Handler handler) {
+ mService = service;
+ mInjector = injector;
+ mHandler = handler;
+
+ // Broadcast filter for changes to the trusted certificate store. Listens on the background
+ // handler to avoid blocking time-critical tasks on the main handler thread.
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_USER_STARTED);
+ filter.addAction(Intent.ACTION_USER_UNLOCKED);
+ filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
+ filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ mInjector.mContext.registerReceiverAsUser(
+ mRootCaReceiver, UserHandle.ALL, filter, null, mHandler);
+ }
+
+ public String installCaCert(final UserHandle userHandle, byte[] certBuffer) {
+ // Convert certificate data from X509 format to PEM.
+ byte[] pemCert;
+ try {
+ X509Certificate cert = parseCert(certBuffer);
+ pemCert = Credentials.convertToPem(cert);
+ } catch (CertificateException | IOException ce) {
+ Log.e(LOG_TAG, "Problem converting cert", ce);
+ return null;
+ }
+
+ try (KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(userHandle)) {
+ return keyChainConnection.getService().installCaCertificate(pemCert);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "installCaCertsToKeyChain(): ", e);
+ } catch (InterruptedException e1) {
+ Log.w(LOG_TAG, "installCaCertsToKeyChain(): ", e1);
+ Thread.currentThread().interrupt();
+ }
+ return null;
+ }
+
+ public void uninstallCaCerts(final UserHandle userHandle, final String[] aliases) {
+ try (KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(userHandle)) {
+ for (int i = 0 ; i < aliases.length; i++) {
+ keyChainConnection.getService().deleteCaCertificate(aliases[i]);
+ }
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "from CaCertUninstaller: ", e);
+ } catch (InterruptedException ie) {
+ Log.w(LOG_TAG, "CaCertUninstaller: ", ie);
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ public List<String> getInstalledCaCertificates(UserHandle userHandle)
+ throws RemoteException, RuntimeException {
+ try (KeyChainConnection conn = mInjector.keyChainBindAsUser(userHandle)) {
+ return conn.getService().getUserCaAliases().getList();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return null;
+ } catch (AssertionError e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void onCertificateApprovalsChanged(int userId) {
+ mHandler.post(() -> updateInstalledCertificates(UserHandle.of(userId)));
+ }
+
+ /**
+ * Broadcast receiver for changes to the trusted certificate store.
+ */
+ private final BroadcastReceiver mRootCaReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (StorageManager.inCryptKeeperBounce()) {
+ return;
+ }
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
+ updateInstalledCertificates(UserHandle.of(userId));
+ }
+ };
+
+ private void updateInstalledCertificates(final UserHandle userHandle) {
+ if (!mInjector.getUserManager().isUserUnlocked(userHandle.getIdentifier())) {
+ return;
+ }
+
+ final List<String> installedCerts;
+ try {
+ installedCerts = getInstalledCaCertificates(userHandle);
+ } catch (RemoteException | RuntimeException e) {
+ Log.e(LOG_TAG, "Could not retrieve certificates from KeyChain service", e);
+ return;
+ }
+ mService.onInstalledCertificatesChanged(userHandle, installedCerts);
+
+ final int pendingCertificateCount =
+ installedCerts.size() - mService.getAcceptedCaCertificates(userHandle).size();
+ if (pendingCertificateCount != 0) {
+ final Notification noti = buildNotification(userHandle, pendingCertificateCount);
+ mInjector.getNotificationManager().notifyAsUser(
+ LOG_TAG, MONITORING_CERT_NOTIFICATION_ID, noti, userHandle);
+ } else {
+ mInjector.getNotificationManager().cancelAsUser(
+ LOG_TAG, MONITORING_CERT_NOTIFICATION_ID, userHandle);
+ }
+ }
+
+ private Notification buildNotification(UserHandle userHandle, int pendingCertificateCount) {
+ final Context userContext;
+ try {
+ userContext = mInjector.createContextAsUser(userHandle);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(LOG_TAG, "Create context as " + userHandle + " failed", e);
+ return null;
+ }
+
+ final Resources resources = mInjector.getResources();
+ final int smallIconId;
+ final String contentText;
+
+ int parentUserId = userHandle.getIdentifier();
+
+ if (mService.getProfileOwner(userHandle.getIdentifier()) != null) {
+ contentText = resources.getString(R.string.ssl_ca_cert_noti_managed,
+ mService.getProfileOwnerName(userHandle.getIdentifier()));
+ smallIconId = R.drawable.stat_sys_certificate_info;
+ parentUserId = mService.getProfileParentId(userHandle.getIdentifier());
+ } else if (mService.getDeviceOwnerUserId() == userHandle.getIdentifier()) {
+ final String ownerName = mService.getDeviceOwnerName();
+ contentText = resources.getString(R.string.ssl_ca_cert_noti_managed,
+ mService.getDeviceOwnerName());
+ smallIconId = R.drawable.stat_sys_certificate_info;
+ } else {
+ contentText = resources.getString(R.string.ssl_ca_cert_noti_by_unknown);
+ smallIconId = android.R.drawable.stat_sys_warning;
+ }
+
+ // Create an intent to launch an activity showing information about the certificate.
+ Intent dialogIntent = new Intent(Settings.ACTION_MONITORING_CERT_INFO);
+ dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ dialogIntent.putExtra(Settings.EXTRA_NUMBER_OF_CERTIFICATES, pendingCertificateCount);
+ dialogIntent.putExtra(Intent.EXTRA_USER_ID, userHandle.getIdentifier());
+
+ // The intent should only be allowed to resolve to a system app.
+ ActivityInfo targetInfo = dialogIntent.resolveActivityInfo(
+ mInjector.getPackageManager(), PackageManager.MATCH_SYSTEM_ONLY);
+ if (targetInfo != null) {
+ dialogIntent.setComponent(targetInfo.getComponentName());
+ }
+
+ PendingIntent notifyIntent = mInjector.pendingIntentGetActivityAsUser(userContext, 0,
+ dialogIntent, PendingIntent.FLAG_UPDATE_CURRENT, null,
+ UserHandle.of(parentUserId));
+
+ return new Notification.Builder(userContext, SystemNotificationChannels.SECURITY)
+ .setSmallIcon(smallIconId)
+ .setContentTitle(resources.getQuantityText(R.plurals.ssl_ca_cert_warning,
+ pendingCertificateCount))
+ .setContentText(contentText)
+ .setContentIntent(notifyIntent)
+ .setShowWhen(false)
+ .setColor(R.color.system_notification_accent_color)
+ .build();
+ }
+
+ private static X509Certificate parseCert(byte[] certBuffer) throws CertificateException {
+ CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ return (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(
+ certBuffer));
+ }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index a916672..ecbd312 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -114,7 +114,6 @@
import android.net.metrics.IpConnectivityLog;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
-import android.os.AsyncTask;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -184,7 +183,6 @@
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
-import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -193,9 +191,6 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
@@ -398,6 +393,7 @@
*/
boolean mIsWatch;
+ private final CertificateMonitor mCertificateMonitor;
private final SecurityLogMonitor mSecurityLogMonitor;
private NetworkLogger mNetworkLogger;
@@ -530,19 +526,6 @@
final Handler mHandler;
final Handler mBackgroundHandler;
- /** Listens on any device, even when mHasFeature == false. */
- final BroadcastReceiver mRootCaReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (StorageManager.inCryptKeeperBounce()) {
- return;
- }
- final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
- new MonitoringCertNotificationTask(DevicePolicyManagerService.this, mInjector)
- .execute(userHandle);
- }
- };
-
/** Listens only if mHasFeature == true. */
final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
@@ -630,25 +613,6 @@
handlePackagesChanged(intent.getData().getSchemeSpecificPart(), userHandle);
} else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)) {
clearWipeProfileNotification();
- } else if (KeyChain.ACTION_TRUST_STORE_CHANGED.equals(intent.getAction())) {
- mBackgroundHandler.post(() -> {
- try (final KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(
- UserHandle.of(userHandle))) {
- final List<String> caCerts =
- keyChainConnection.getService().getUserCaAliases().getList();
- synchronized (DevicePolicyManagerService.this) {
- if (getUserData(userHandle).mOwnerInstalledCaCerts
- .retainAll(caCerts)) {
- saveSettingsLocked(userHandle);
- }
- }
- } catch (InterruptedException e) {
- Slog.w(LOG_TAG, "error talking to IKeyChainService", e);
- Thread.currentThread().interrupt();
- } catch (RemoteException e) {
- Slog.w(LOG_TAG, "error talking to IKeyChainService", e);
- }
- });
}
}
@@ -1527,7 +1491,7 @@
@VisibleForTesting
static class Injector {
- private final Context mContext;
+ public final Context mContext;
Injector(Context context) {
mContext = context;
@@ -1720,6 +1684,12 @@
return "/data/system/";
}
+ PendingIntent pendingIntentGetActivityAsUser(Context context, int requestCode,
+ @NonNull Intent intent, int flags, Bundle options, UserHandle user) {
+ return PendingIntent.getActivityAsUser(
+ context, requestCode, intent, flags, options, user);
+ }
+
void registerContentObserver(Uri uri, boolean notifyForDescendents,
ContentObserver observer, int userHandle) {
mContext.getContentResolver().registerContentObserver(uri, notifyForDescendents,
@@ -1810,6 +1780,7 @@
mLocalService = new LocalService();
mLockPatternUtils = injector.newLockPatternUtils();
+ // TODO: why does SecurityLogMonitor need to be created even when mHasFeature == false?
mSecurityLogMonitor = new SecurityLogMonitor(this);
mHasFeature = mInjector.getPackageManager()
@@ -1818,27 +1789,20 @@
.hasSystemFeature(PackageManager.FEATURE_WATCH);
mBackgroundHandler = BackgroundThread.getHandler();
- // Broadcast filter for changes to the trusted certificate store. These changes get a
- // separate intent filter so we can listen to them even when device_admin is off.
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_USER_STARTED);
- filter.addAction(Intent.ACTION_USER_UNLOCKED);
- filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
- filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
- mContext.registerReceiverAsUser(mRootCaReceiver, UserHandle.ALL, filter, null, mHandler);
+ // Needed when mHasFeature == false, because it controls the certificate warning text.
+ mCertificateMonitor = new CertificateMonitor(this, mInjector, mBackgroundHandler);
if (!mHasFeature) {
// Skip the rest of the initialization
return;
}
- filter = new IntentFilter();
+ IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BOOT_COMPLETED);
filter.addAction(ACTION_EXPIRED_PASSWORD_NOTIFICATION);
filter.addAction(Intent.ACTION_USER_ADDED);
filter.addAction(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_STARTED);
- filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler);
filter = new IntentFilter();
@@ -3083,33 +3047,43 @@
}
/**
- * Remove deleted CA certificates from the "approved" list for a particular user, counting
- * the number still remaining to approve.
+ * Clean up internal state when the set of installed trusted CA certificates changes.
*
* @param userHandle user to check for. This must be a real user and not, for example,
* {@link UserHandle#ALL}.
* @param installedCertificates the full set of certificate authorities currently installed for
* {@param userHandle}. After calling this function, {@code mAcceptedCaCertificates} will
* correspond to some subset of this.
- *
- * @return number of certificates yet to be approved by {@param userHandle}.
*/
- protected synchronized int retainAcceptedCertificates(final UserHandle userHandle,
+ protected void onInstalledCertificatesChanged(final UserHandle userHandle,
final @NonNull Collection<String> installedCertificates) {
+ if (!mHasFeature) {
+ return;
+ }
enforceManageUsers();
- if (!mHasFeature) {
- return installedCertificates.size();
- } else {
+ synchronized (this) {
final DevicePolicyData policy = getUserData(userHandle.getIdentifier());
- // Remove deleted certificates. Flush xml if necessary.
- if (policy.mAcceptedCaCertificates.retainAll(installedCertificates)) {
+ boolean changed = false;
+ changed |= policy.mAcceptedCaCertificates.retainAll(installedCertificates);
+ changed |= policy.mOwnerInstalledCaCerts.retainAll(installedCertificates);
+ if (changed) {
saveSettingsLocked(userHandle.getIdentifier());
}
+ }
+ }
- // Trim approved certificates from the count.
- return installedCertificates.size() - policy.mAcceptedCaCertificates.size();
+ /**
+ * Internal method used by {@link CertificateMonitor}.
+ */
+ protected Set<String> getAcceptedCaCertificates(final UserHandle userHandle) {
+ if (!mHasFeature) {
+ return Collections.<String> emptySet();
+ }
+ synchronized (this) {
+ final DevicePolicyData policy = getUserData(userHandle.getIdentifier());
+ return policy.mAcceptedCaCertificates;
}
}
@@ -4690,7 +4664,7 @@
}
saveSettingsLocked(userId);
}
- new MonitoringCertNotificationTask(this, mInjector).execute(userId);
+ mCertificateMonitor.onCertificateApprovalsChanged(userId);
return true;
}
@@ -4713,8 +4687,7 @@
getUserData(userInfo.id).mAcceptedCaCertificates.clear();
saveSettingsLocked(userInfo.id);
}
-
- new MonitoringCertNotificationTask(this, mInjector).execute(userInfo.id);
+ mCertificateMonitor.onCertificateApprovalsChanged(userId);
}
}
}
@@ -4722,79 +4695,47 @@
@Override
public boolean installCaCert(ComponentName admin, String callerPackage, byte[] certBuffer)
throws RemoteException {
- enforceCanManageCaCerts(admin, callerPackage);
-
- byte[] pemCert;
- try {
- X509Certificate cert = parseCert(certBuffer);
- pemCert = Credentials.convertToPem(cert);
- } catch (CertificateException ce) {
- Log.e(LOG_TAG, "Problem converting cert", ce);
- return false;
- } catch (IOException ioe) {
- Log.e(LOG_TAG, "Problem reading cert", ioe);
+ if (!mHasFeature) {
return false;
}
+ enforceCanManageCaCerts(admin, callerPackage);
- final UserHandle userHandle = UserHandle.of(mInjector.userHandleGetCallingUserId());
+ final String alias;
+
+ final UserHandle userHandle = mInjector.binderGetCallingUserHandle();
final long id = mInjector.binderClearCallingIdentity();
- String alias = null;
try {
- try (final KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(
- userHandle)) {
- alias = keyChainConnection.getService().installCaCertificate(pemCert);
- } catch (RemoteException e) {
- Log.e(LOG_TAG, "installCaCertsToKeyChain(): ", e);
+ alias = mCertificateMonitor.installCaCert(userHandle, certBuffer);
+ if (alias == null) {
+ Log.w(LOG_TAG, "Problem installing cert");
+ return false;
}
- } catch (InterruptedException e1) {
- Log.w(LOG_TAG, "installCaCertsToKeyChain(): ", e1);
- Thread.currentThread().interrupt();
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
- if (alias == null) {
- Log.w(LOG_TAG, "Problem installing cert");
- } else {
- synchronized (this) {
- final int userId = userHandle.getIdentifier();
- getUserData(userId).mOwnerInstalledCaCerts.add(alias);
- saveSettingsLocked(userId);
- }
- return true;
- }
- return false;
- }
- private static X509Certificate parseCert(byte[] certBuffer) throws CertificateException {
- CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
- return (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(
- certBuffer));
+ synchronized (this) {
+ getUserData(userHandle.getIdentifier()).mOwnerInstalledCaCerts.add(alias);
+ saveSettingsLocked(userHandle.getIdentifier());
+ }
+ return true;
}
@Override
public void uninstallCaCerts(ComponentName admin, String callerPackage, String[] aliases) {
+ if (!mHasFeature) {
+ return;
+ }
enforceCanManageCaCerts(admin, callerPackage);
final int userId = mInjector.userHandleGetCallingUserId();
- final UserHandle userHandle = UserHandle.of(userId);
final long id = mInjector.binderClearCallingIdentity();
try {
- try (final KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(
- userHandle)) {
- for (int i = 0 ; i < aliases.length; i++) {
- keyChainConnection.getService().deleteCaCertificate(aliases[i]);
- }
- } catch (RemoteException e) {
- Log.e(LOG_TAG, "from CaCertUninstaller: ", e);
- return;
- }
- } catch (InterruptedException ie) {
- Log.w(LOG_TAG, "CaCertUninstaller: ", ie);
- Thread.currentThread().interrupt();
- return;
+ mCertificateMonitor.uninstallCaCerts(UserHandle.of(userId), aliases);
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
+
synchronized (this) {
if (getUserData(userId).mOwnerInstalledCaCerts.removeAll(Arrays.asList(aliases))) {
saveSettingsLocked(userId);
@@ -8192,7 +8133,6 @@
// Ensure the caller is a DO/PO or a package access delegate.
enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
DELEGATION_PACKAGE_ACCESS);
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
long id = mInjector.binderClearCallingIdentity();
try {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/MonitoringCertNotificationTask.java b/services/devicepolicy/java/com/android/server/devicepolicy/MonitoringCertNotificationTask.java
deleted file mode 100644
index 1933fe7..0000000
--- a/services/devicepolicy/java/com/android/server/devicepolicy/MonitoringCertNotificationTask.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * 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.server.devicepolicy;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.security.KeyChain.KeyChainConnection;
-import android.util.Log;
-
-import com.android.internal.notification.SystemNotificationChannels;
-import com.android.internal.R;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-public class MonitoringCertNotificationTask extends AsyncTask<Integer, Void, Void> {
- protected static final String LOG_TAG = DevicePolicyManagerService.LOG_TAG;
- protected static final int MONITORING_CERT_NOTIFICATION_ID = R.plurals.ssl_ca_cert_warning;
-
- private final DevicePolicyManagerService mService;
- private final DevicePolicyManagerService.Injector mInjector;
-
- public MonitoringCertNotificationTask(final DevicePolicyManagerService service,
- final DevicePolicyManagerService.Injector injector) {
- super();
- mService = service;
- mInjector = injector;
- }
-
- @Override
- protected Void doInBackground(Integer... params) {
- int userHandle = params[0];
-
- if (userHandle == UserHandle.USER_ALL) {
- for (UserInfo userInfo : mInjector.getUserManager().getUsers(true)) {
- repostOrClearNotification(userInfo.getUserHandle());
- }
- } else {
- repostOrClearNotification(UserHandle.of(userHandle));
- }
- return null;
- }
-
- private void repostOrClearNotification(UserHandle userHandle) {
- if (!mInjector.getUserManager().isUserUnlocked(userHandle.getIdentifier())) {
- return;
- }
-
- // Call out to KeyChain to check for CAs which are waiting for approval.
- final int pendingCertificateCount;
- try {
- pendingCertificateCount = mService.retainAcceptedCertificates(
- userHandle, getInstalledCaCertificates(userHandle));
- } catch (RemoteException | RuntimeException e) {
- Log.e(LOG_TAG, "Could not retrieve certificates from KeyChain service", e);
- return;
- }
-
- if (pendingCertificateCount != 0) {
- showNotification(userHandle, pendingCertificateCount);
- } else {
- mInjector.getNotificationManager().cancelAsUser(
- LOG_TAG, MONITORING_CERT_NOTIFICATION_ID, userHandle);
- }
- }
-
- private void showNotification(UserHandle userHandle, int pendingCertificateCount) {
- // Create a context for the target user.
- final Context userContext;
- try {
- userContext = mInjector.createContextAsUser(userHandle);
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(LOG_TAG, "Create context as " + userHandle + " failed", e);
- return;
- }
-
- // Build and show a warning notification
- int smallIconId;
- String contentText;
- int parentUserId = userHandle.getIdentifier();
- Resources resources = mInjector.getResources();
- if (mService.getProfileOwner(userHandle.getIdentifier()) != null) {
- contentText = resources.getString(R.string.ssl_ca_cert_noti_managed,
- mService.getProfileOwnerName(userHandle.getIdentifier()));
- smallIconId = R.drawable.stat_sys_certificate_info;
- parentUserId = mService.getProfileParentId(userHandle.getIdentifier());
- } else if (mService.getDeviceOwnerUserId() == userHandle.getIdentifier()) {
- contentText = resources.getString(R.string.ssl_ca_cert_noti_managed,
- mService.getDeviceOwnerName());
- smallIconId = R.drawable.stat_sys_certificate_info;
- } else {
- contentText = resources.getString(R.string.ssl_ca_cert_noti_by_unknown);
- smallIconId = android.R.drawable.stat_sys_warning;
- }
-
- Intent dialogIntent = new Intent(Settings.ACTION_MONITORING_CERT_INFO);
- dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- // TODO this next line is taken from original notification code in
- // {@link DevicePolicyManagerService} but not a very good way of doing it. Do it better.
- dialogIntent.setPackage("com.android.settings");
- dialogIntent.putExtra(Settings.EXTRA_NUMBER_OF_CERTIFICATES, pendingCertificateCount);
- dialogIntent.putExtra(Intent.EXTRA_USER_ID, userHandle.getIdentifier());
- PendingIntent notifyIntent = PendingIntent.getActivityAsUser(userContext, 0,
- dialogIntent, PendingIntent.FLAG_UPDATE_CURRENT, null,
- UserHandle.of(parentUserId));
-
- final Notification noti =
- new Notification.Builder(userContext, SystemNotificationChannels.SECURITY)
- .setSmallIcon(smallIconId)
- .setContentTitle(resources.getQuantityText(R.plurals.ssl_ca_cert_warning,
- pendingCertificateCount))
- .setContentText(contentText)
- .setContentIntent(notifyIntent)
- .setShowWhen(false)
- .setColor(R.color.system_notification_accent_color)
- .build();
-
- mInjector.getNotificationManager().notifyAsUser(
- LOG_TAG, MONITORING_CERT_NOTIFICATION_ID, noti, userHandle);
- }
-
- private List<String> getInstalledCaCertificates(UserHandle userHandle)
- throws RemoteException, RuntimeException {
- try (KeyChainConnection conn = mInjector.keyChainBindAsUser(userHandle)) {
- return conn.getService().getUserCaAliases().getList();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- return null;
- } catch (AssertionError e) {
- throw new RuntimeException(e);
- }
- }
-}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
index 2413561..5c3a37a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
@@ -76,15 +76,28 @@
* Internally how often should the monitor poll the security logs from logd.
*/
private static final long POLLING_INTERVAL_MILLISECONDS = TimeUnit.MINUTES.toMillis(1);
+ /**
+ * Overlap between two subsequent log requests, required to avoid losing out of order events.
+ */
+ private static final long OVERLAP_NANOS = TimeUnit.SECONDS.toNanos(3);
+
@GuardedBy("mLock")
private Thread mMonitorThread = null;
@GuardedBy("mLock")
- private ArrayList<SecurityEvent> mPendingLogs = new ArrayList<SecurityEvent>();
+ private ArrayList<SecurityEvent> mPendingLogs = new ArrayList<>();
@GuardedBy("mLock")
private boolean mAllowedToRetrieve = false;
/**
+ * Last events fetched from log to check for overlap between batches. We can leave it empty if
+ * we are sure there will be no overlap anymore, e.g. when we get empty batch.
+ */
+ private final ArrayList<SecurityEvent> mLastEvents = new ArrayList<>();
+ /** Timestamp of the very last event, -1 means request from the beginning of time. */
+ private long mLastEventNanos = -1;
+
+ /**
* When DO will be allowed to retrieve the log, in milliseconds since boot (as per
* {@link SystemClock#elapsedRealtime()}). After that it will mark the time to retry broadcast.
*/
@@ -98,7 +111,7 @@
mLock.lock();
try {
if (mMonitorThread == null) {
- mPendingLogs = new ArrayList<SecurityEvent>();
+ mPendingLogs = new ArrayList<>();
mAllowedToRetrieve = false;
mNextAllowedRetrievalTimeMillis = -1;
mPaused = false;
@@ -123,7 +136,7 @@
Log.e(TAG, "Interrupted while waiting for thread to stop", e);
}
// Reset state and clear buffer
- mPendingLogs = new ArrayList<SecurityEvent>();
+ mPendingLogs = new ArrayList<>();
mAllowedToRetrieve = false;
mNextAllowedRetrievalTimeMillis = -1;
mPaused = false;
@@ -181,7 +194,7 @@
void discardLogs() {
mLock.lock();
mAllowedToRetrieve = false;
- mPendingLogs = new ArrayList<SecurityEvent>();
+ mPendingLogs = new ArrayList<>();
mLock.unlock();
Slog.i(TAG, "Discarded all logs.");
}
@@ -198,7 +211,7 @@
mNextAllowedRetrievalTimeMillis = SystemClock.elapsedRealtime()
+ RATE_LIMIT_INTERVAL_MILLISECONDS;
List<SecurityEvent> result = mPendingLogs;
- mPendingLogs = new ArrayList<SecurityEvent>();
+ mPendingLogs = new ArrayList<>();
return result;
} else {
return null;
@@ -208,45 +221,141 @@
}
}
+ /**
+ * Requests the next (or the first) batch of events from the log with appropriate timestamp.
+ */
+ private void getNextBatch(ArrayList<SecurityEvent> newLogs)
+ throws IOException, InterruptedException {
+ if (mLastEventNanos < 0) {
+ // Non-blocking read that returns all logs immediately.
+ if (DEBUG) Slog.d(TAG, "SecurityLog.readEvents");
+ SecurityLog.readEvents(newLogs);
+ } else {
+ // If we have last events from the previous batch, request log events with time overlap
+ // with previously retrieved messages to avoid losing events due to reordering in logd.
+ final long startNanos = mLastEvents.isEmpty()
+ ? mLastEventNanos : Math.max(0, mLastEventNanos - OVERLAP_NANOS);
+ if (DEBUG) Slog.d(TAG, "SecurityLog.readEventsSince: " + startNanos);
+ // Non-blocking read that returns all logs with timestamps >= startNanos immediately.
+ SecurityLog.readEventsSince(startNanos, newLogs);
+ }
+
+ // Sometimes events may be reordered in logd due to simultaneous readers and writers. In
+ // this case, we have to sort it to make overlap checking work. This is very unlikely.
+ for (int i = 0; i < newLogs.size() - 1; i++) {
+ if (newLogs.get(i).getTimeNanos() > newLogs.get(i+1).getTimeNanos()) {
+ if (DEBUG) Slog.d(TAG, "Got out of order events, sorting.");
+ // Sort using comparator that compares timestamps.
+ newLogs.sort((e1, e2) -> Long.signum(e1.getTimeNanos() - e2.getTimeNanos()));
+ break;
+ }
+ }
+
+ if (DEBUG) Slog.d(TAG, "Got " + newLogs.size() + " new events.");
+ }
+
+ /**
+ * Save the last events for overlap checking with the next batch.
+ */
+ private void saveLastEvents(ArrayList<SecurityEvent> newLogs) {
+ mLastEvents.clear();
+ if (newLogs.isEmpty()) {
+ // This can happen if no events were logged yet or the buffer got cleared. In this case
+ // we aren't going to have any overlap next time, leave mLastEvents events empty.
+ return;
+ }
+
+ // Save the last timestamp.
+ mLastEventNanos = newLogs.get(newLogs.size() - 1).getTimeNanos();
+ // Position of the earliest event that has to be saved. Start from the penultimate event,
+ // going backward.
+ int pos = newLogs.size() - 2;
+ while (pos >= 0 && mLastEventNanos - newLogs.get(pos).getTimeNanos() < OVERLAP_NANOS) {
+ pos--;
+ }
+ // We either run past the start of the list or encountered an event that is too old to keep.
+ pos++;
+ mLastEvents.addAll(newLogs.subList(pos, newLogs.size()));
+ if (DEBUG) Slog.d(TAG, mLastEvents.size() + " events saved for overlap check");
+ }
+
+ /**
+ * Merges a new batch into already fetched logs and deals with overlapping and out of order
+ * events.
+ */
+ @GuardedBy("mLock")
+ private void mergeBatchLocked(final ArrayList<SecurityEvent> newLogs) {
+ // Reserve capacity so that copying doesn't occur.
+ mPendingLogs.ensureCapacity(mPendingLogs.size() + newLogs.size());
+ // Run through the first events of the batch to check if there is an overlap with previous
+ // batch and if so, skip overlapping events. Events are sorted by timestamp, so we can
+ // compare it in linear time by advancing two pointers, one for each batch.
+ int curPos = 0;
+ int lastPos = 0;
+ // For the first batch mLastEvents will be empty, so no iterations will happen.
+ while (lastPos < mLastEvents.size() && curPos < newLogs.size()) {
+ final SecurityEvent curEvent = newLogs.get(curPos);
+ final long currentNanos = curEvent.getTimeNanos();
+ if (currentNanos > mLastEventNanos) {
+ // We got past the last event of the last batch, no overlap possible anymore.
+ break;
+ }
+ final SecurityEvent lastEvent = mLastEvents.get(lastPos);
+ final long lastNanos = lastEvent.getTimeNanos();
+ if (lastNanos > currentNanos) {
+ // New event older than the last we've seen so far, must be due to reordering.
+ if (DEBUG) Slog.d(TAG, "New event in the overlap: " + currentNanos);
+ mPendingLogs.add(curEvent);
+ curPos++;
+ } else if (lastNanos < currentNanos) {
+ if (DEBUG) Slog.d(TAG, "Event disappeared from the overlap: " + lastNanos);
+ lastPos++;
+ } else {
+ // Two events have the same timestamp, check if they are the same.
+ if (lastEvent.equals(curEvent)) {
+ // Actual overlap, just skip the event.
+ if (DEBUG) Slog.d(TAG, "Skipped dup event with timestamp: " + lastNanos);
+ } else {
+ // Wow, what a coincidence, or probably the clock is too coarse.
+ mPendingLogs.add(curEvent);
+ if (DEBUG) Slog.d(TAG, "Event timestamp collision: " + lastNanos);
+ }
+ lastPos++;
+ curPos++;
+ }
+ }
+ // Save the rest of the new batch.
+ mPendingLogs.addAll(newLogs.subList(curPos, newLogs.size()));
+
+ if (mPendingLogs.size() > BUFFER_ENTRIES_MAXIMUM_LEVEL) {
+ // Truncate buffer down to half of BUFFER_ENTRIES_MAXIMUM_LEVEL.
+ mPendingLogs = new ArrayList<>(mPendingLogs.subList(
+ mPendingLogs.size() - (BUFFER_ENTRIES_MAXIMUM_LEVEL / 2),
+ mPendingLogs.size()));
+ Slog.i(TAG, "Pending logs buffer full. Discarding old logs.");
+ }
+ if (DEBUG) Slog.d(TAG, mPendingLogs.size() + " pending events in the buffer after merging");
+ }
+
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- ArrayList<SecurityEvent> logs = new ArrayList<SecurityEvent>();
- // The timestamp of the latest log entry that has been read, in nanoseconds
- long lastLogTimestampNanos = -1;
+ ArrayList<SecurityEvent> newLogs = new ArrayList<>();
while (!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(POLLING_INTERVAL_MILLISECONDS);
+ getNextBatch(newLogs);
- if (lastLogTimestampNanos < 0) {
- // Non-blocking read that returns all logs immediately.
- if (DEBUG) Slog.d(TAG, "SecurityLog.readEvents");
- SecurityLog.readEvents(logs);
- } else {
- if (DEBUG) Slog.d(TAG,
- "SecurityLog.readEventsSince: " + lastLogTimestampNanos);
- // Non-blocking read that returns all logs >= the timestamp immediately.
- SecurityLog.readEventsSince(lastLogTimestampNanos + 1, logs);
+ mLock.lockInterruptibly();
+ try {
+ mergeBatchLocked(newLogs);
+ } finally {
+ mLock.unlock();
}
- if (!logs.isEmpty()) {
- if (DEBUG) Slog.d(TAG, "processing new logs. Events: " + logs.size());
- mLock.lockInterruptibly();
- try {
- mPendingLogs.addAll(logs);
- if (mPendingLogs.size() > BUFFER_ENTRIES_MAXIMUM_LEVEL) {
- // Truncate buffer down to half of BUFFER_ENTRIES_MAXIMUM_LEVEL
- mPendingLogs = new ArrayList<SecurityEvent>(mPendingLogs.subList(
- mPendingLogs.size() - (BUFFER_ENTRIES_MAXIMUM_LEVEL / 2),
- mPendingLogs.size()));
- Slog.i(TAG, "Pending logs buffer full. Discarding old logs.");
- }
- } finally {
- mLock.unlock();
- }
- lastLogTimestampNanos = logs.get(logs.size() - 1).getTimeNanos();
- logs.clear();
- }
+
+ saveLastEvents(newLogs);
+ newLogs.clear();
notifyDeviceOwnerIfNeeded();
} catch (IOException e) {
Log.e(TAG, "Failed to read security log", e);
@@ -256,6 +365,15 @@
break;
}
}
+
+ // Discard previous batch info.
+ mLastEvents.clear();
+ if (mLastEventNanos != -1) {
+ // Make sure we don't read old events if logging is re-enabled. Since mLastEvents is
+ // empty, the next request will be done without overlap, so it is enough to add 1 ns.
+ mLastEventNanos += 1;
+ }
+
Slog.i(TAG, "MonitorThread exit.");
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 2727465..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;
@@ -183,8 +183,6 @@
"com.android.server.search.SearchManagerService$Lifecycle";
private static final String THERMAL_OBSERVER_CLASS =
"com.google.android.clockwork.ThermalObserver";
- private static final String WEAR_BLUETOOTH_SERVICE_CLASS =
- "com.google.android.clockwork.bluetooth.WearBluetoothService";
private static final String WEAR_CONNECTIVITY_SERVICE_CLASS =
"com.google.android.clockwork.connectivity.WearConnectivityService";
private static final String WEAR_TIME_SERVICE_CLASS =
@@ -1394,9 +1392,11 @@
traceEnd();
}
- traceBeginAndSlog("StartCompanionDeviceManager");
- mSystemServiceManager.startService(COMPANION_DEVICE_MANAGER_SERVICE_CLASS);
- traceEnd();
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP)) {
+ traceBeginAndSlog("StartCompanionDeviceManager");
+ mSystemServiceManager.startService(COMPANION_DEVICE_MANAGER_SERVICE_CLASS);
+ traceEnd();
+ }
traceBeginAndSlog("StartRestrictionManager");
mSystemServiceManager.startService(RestrictionsManagerService.class);
@@ -1479,10 +1479,6 @@
}
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
- traceBeginAndSlog("StartWearBluetooth");
- mSystemServiceManager.startService(WEAR_BLUETOOTH_SERVICE_CLASS);
- traceEnd();
-
traceBeginAndSlog("StartWearConnectivityService");
mSystemServiceManager.startService(WEAR_CONNECTIVITY_SERVICE_CLASS);
traceEnd();
diff --git a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
index 472f984..43c38a6 100644
--- a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
+++ b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
@@ -69,6 +69,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.LocalServices;
+import com.android.server.PreloadsFileCacheExpirationJobService;
import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.am.ActivityManagerService;
@@ -259,6 +260,7 @@
if (!deletePreloadsFolderContents()) {
Slog.w(TAG, "Failed to delete preloads folder contents");
}
+ PreloadsFileCacheExpirationJobService.schedule(mInjector.getContext());
});
stopDemoMode();
@@ -443,6 +445,11 @@
mInjector.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0;
}
+ /**
+ * Deletes contents of {@link Environment#getDataPreloadsDirectory()},
+ * but leave {@link Environment#getDataPreloadsFileCacheDirectory()}
+ * @return true if contents was sucessfully deleted
+ */
private boolean deletePreloadsFolderContents() {
final File dir = mInjector.getDataPreloadsDirectory();
final File[] files = FileUtils.listFilesOrEmpty(dir);
diff --git a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 15f7557..e285669 100644
--- a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -39,12 +39,14 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.Vibrator;
+import android.os.VibrationEffect;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -53,6 +55,7 @@
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -78,6 +81,9 @@
private int mPid = 2000;
private android.os.UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
+ private VibrateRepeatMatcher mVibrateOnceMatcher = new VibrateRepeatMatcher(-1);
+ private VibrateRepeatMatcher mVibrateLoopMatcher = new VibrateRepeatMatcher(0);
+
private static final long[] CUSTOM_VIBRATION = new long[] {
300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
@@ -90,7 +96,9 @@
private static final int CUSTOM_LIGHT_COLOR = Color.BLACK;
private static final int CUSTOM_LIGHT_ON = 10000;
private static final int CUSTOM_LIGHT_OFF = 10000;
- private static final long[] FALLBACK_VIBRATION = new long[] {100, 100, 100};
+ private static final long[] FALLBACK_VIBRATION_PATTERN = new long[] {100, 100, 100};
+ private static final VibrationEffect FALLBACK_VIBRATION =
+ VibrationEffect.createWaveform(FALLBACK_VIBRATION_PATTERN, -1);
@Before
public void setUp() {
@@ -108,7 +116,7 @@
mService.setHandler(mHandler);
mService.setLights(mLight);
mService.setScreenOn(false);
- mService.setFallbackVibrationPattern(FALLBACK_VIBRATION);
+ mService.setFallbackVibrationPattern(FALLBACK_VIBRATION_PATTERN);
}
//
@@ -272,18 +280,18 @@
}
private void verifyNeverVibrate() {
- verify(mVibrator, never()).vibrate(anyInt(), anyString(), (long[]) anyObject(),
- anyInt(), (AudioAttributes) anyObject());
+ verify(mVibrator, never()).vibrate(anyInt(), anyString(), (VibrationEffect) anyObject(),
+ (AudioAttributes) anyObject());
}
private void verifyVibrate() {
- verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), (long[]) anyObject(),
- eq(-1), (AudioAttributes) anyObject());
+ verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateOnceMatcher),
+ (AudioAttributes) anyObject());
}
private void verifyVibrateLooped() {
- verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), (long[]) anyObject(),
- eq(0), (AudioAttributes) anyObject());
+ verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateLoopMatcher),
+ (AudioAttributes) anyObject());
}
private void verifyStopVibrate() {
@@ -485,8 +493,10 @@
mService.buzzBeepBlinkLocked(r);
- verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), eq(r.getVibration()),
- eq(-1), (AudioAttributes) anyObject());
+ VibrationEffect effect = VibrationEffect.createWaveform(r.getVibration(), -1);
+
+ verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), eq(effect),
+ (AudioAttributes) anyObject());
}
@Test
@@ -501,7 +511,7 @@
mService.buzzBeepBlinkLocked(r);
verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), eq(FALLBACK_VIBRATION),
- eq(-1), (AudioAttributes) anyObject());
+ (AudioAttributes) anyObject());
verify(mRingtonePlayer, never()).playAsync
(anyObject(), anyObject(), anyBoolean(), anyObject());
}
@@ -667,4 +677,27 @@
mService.buzzBeepBlinkLocked(s);
verifyStopVibrate();
}
+
+ static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> {
+ private final int mRepeatIndex;
+
+ VibrateRepeatMatcher(int repeatIndex) {
+ mRepeatIndex = repeatIndex;
+ }
+
+ @Override
+ public boolean matches(VibrationEffect actual) {
+ if (actual instanceof VibrationEffect.Waveform &&
+ ((VibrationEffect.Waveform) actual).getRepeatIndex() == mRepeatIndex) {
+ return true;
+ }
+ // All non-waveform effects are essentially one shots.
+ return mRepeatIndex == -1;
+ }
+
+ @Override
+ public String toString() {
+ return "repeatIndex=" + mRepeatIndex;
+ }
+ }
}
diff --git a/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java b/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java
index f8a32bb..3dbd803 100644
--- a/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java
+++ b/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java
@@ -94,10 +94,12 @@
new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_UNSPECIFIED);
NotificationRecord r = getNotificationRecord(channel);
+ int notificationImportance = r.getImportance();
extractor.process(r);
- assertEquals(r.getUserImportance(), NotificationManager.IMPORTANCE_UNSPECIFIED);
+ assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, r.getUserImportance());
+ assertEquals(notificationImportance, r.getImportance());
}
@Test
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
index b7b3617..ab83b9d 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -59,21 +59,23 @@
public class NotificationManagerServiceTest {
private static final long WAIT_FOR_IDLE_TIMEOUT = 2;
- private final String pkg = "com.android.server.notification";
+ private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
private final int uid = Binder.getCallingUid();
private NotificationManagerService mNotificationManagerService;
private INotificationManager mBinderService;
private IPackageManager mPackageManager = mock(IPackageManager.class);
- final PackageManager mPackageManagerClient = mock(PackageManager.class);
- private Context mContext;
+ private final PackageManager mPackageManagerClient = mock(PackageManager.class);
+ private Context mContext = InstrumentationRegistry.getTargetContext();
+ private final String PKG = mContext.getPackageName();
private HandlerThread mThread;
- final RankingHelper mRankingHelper = mock(RankingHelper.class);
+ private final RankingHelper mRankingHelper = mock(RankingHelper.class);
+ private NotificationChannel mTestNotificationChannel = new NotificationChannel(
+ TEST_CHANNEL_ID, TEST_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT);
@Before
@Test
@UiThreadTest
public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
mNotificationManagerService = new NotificationManagerService(mContext);
// MockPackageManager - default returns ApplicationInfo with matching calling UID
@@ -93,13 +95,16 @@
mock(NotificationManagerService.NotificationListeners.class);
when(mockNotificationListeners.checkServiceTokenLocked(any())).thenReturn(
mockNotificationListeners.new ManagedServiceInfo(null,
- new ComponentName(pkg, "test_class"), uid, true, null, 0));
+ new ComponentName(PKG, "test_class"), uid, true, null, 0));
mNotificationManagerService.init(mThread.getLooper(), mPackageManager,
mPackageManagerClient, mockLightsManager, mockNotificationListeners);
// Tests call directly into the Binder.
mBinderService = mNotificationManagerService.getBinderService();
+
+ mBinderService.createNotificationChannels(
+ PKG, new ParceledListSlice(Arrays.asList(mTestNotificationChannel)));
}
public void waitForIdle() throws Exception {
@@ -127,7 +132,7 @@
private NotificationRecord generateNotificationRecord(NotificationChannel channel,
Notification.TvExtender extender) {
if (channel == null) {
- channel = new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT);
+ channel = mTestNotificationChannel;
}
Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
.setContentTitle("foo")
@@ -135,8 +140,7 @@
if (extender != null) {
nb.extend(extender);
}
- StatusBarNotification sbn = new StatusBarNotification(mContext.getPackageName(),
- mContext.getPackageName(), 1, "tag", uid, 0,
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", uid, 0,
nb.build(), new UserHandle(uid), null, 0);
return new NotificationRecord(mContext, sbn, channel);
}
@@ -256,38 +260,38 @@
@Test
@UiThreadTest
public void testEnqueueNotificationWithTag_PopulatesGetActiveNotifications() throws Exception {
- mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
generateNotificationRecord(null).getNotification(), new int[1], 0);
waitForIdle();
StatusBarNotification[] notifs =
- mBinderService.getActiveNotifications(mContext.getPackageName());
+ mBinderService.getActiveNotifications(PKG);
assertEquals(1, notifs.length);
}
@Test
@UiThreadTest
public void testCancelNotificationImmediatelyAfterEnqueue() throws Exception {
- mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
generateNotificationRecord(null).getNotification(), new int[1], 0);
- mBinderService.cancelNotificationWithTag(mContext.getPackageName(), "tag", 0, 0);
+ mBinderService.cancelNotificationWithTag(PKG, "tag", 0, 0);
waitForIdle();
StatusBarNotification[] notifs =
- mBinderService.getActiveNotifications(mContext.getPackageName());
+ mBinderService.getActiveNotifications(PKG);
assertEquals(0, notifs.length);
}
@Test
@UiThreadTest
public void testCancelNotificationWhilePostedAndEnqueued() throws Exception {
- mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
generateNotificationRecord(null).getNotification(), new int[1], 0);
waitForIdle();
- mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
generateNotificationRecord(null).getNotification(), new int[1], 0);
- mBinderService.cancelNotificationWithTag(mContext.getPackageName(), "tag", 0, 0);
+ mBinderService.cancelNotificationWithTag(PKG, "tag", 0, 0);
waitForIdle();
StatusBarNotification[] notifs =
- mBinderService.getActiveNotifications(mContext.getPackageName());
+ mBinderService.getActiveNotifications(PKG);
assertEquals(0, notifs.length);
}
@@ -295,7 +299,7 @@
@UiThreadTest
public void testCancelNotificationsFromListenerImmediatelyAfterEnqueue() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
- mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
mBinderService.cancelNotificationsFromListener(null, null);
waitForIdle();
@@ -308,9 +312,9 @@
@UiThreadTest
public void testCancelAllNotificationsImmediatelyAfterEnqueue() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
- mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
- mBinderService.cancelAllNotifications(sbn.getPackageName(), sbn.getUserId());
+ mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
waitForIdle();
StatusBarNotification[] notifs =
mBinderService.getActiveNotifications(sbn.getPackageName());
@@ -322,9 +326,9 @@
public void testCancelAllNotifications_IgnoreForegroundService() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
sbn.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
- mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
- mBinderService.cancelAllNotifications(sbn.getPackageName(), sbn.getUserId());
+ mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
waitForIdle();
StatusBarNotification[] notifs =
mBinderService.getActiveNotifications(sbn.getPackageName());
@@ -336,7 +340,7 @@
public void testCancelAllNotifications_IgnoreOtherPackages() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
sbn.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
- mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
mBinderService.cancelAllNotifications("other_pkg_name", sbn.getUserId());
waitForIdle();
@@ -349,7 +353,7 @@
@UiThreadTest
public void testCancelAllNotifications_NullPkgRemovesAll() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
- mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
mBinderService.cancelAllNotifications(null, sbn.getUserId());
waitForIdle();
@@ -362,7 +366,7 @@
@UiThreadTest
public void testCancelAllNotifications_NullPkgIgnoresUserAllNotifications() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
- mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
sbn.getId(), sbn.getNotification(), new int[1], UserHandle.USER_ALL);
// Null pkg is how we signal a user switch.
mBinderService.cancelAllNotifications(null, sbn.getUserId());
@@ -377,14 +381,14 @@
public void testTvExtenderChannelOverride_onTv() throws Exception {
mNotificationManagerService.setIsTelevision(true);
mNotificationManagerService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannelWithFallback(
+ when(mRankingHelper.getNotificationChannel(
anyString(), anyInt(), eq("foo"), anyBoolean())).thenReturn(
new NotificationChannel("foo", "foo", NotificationManager.IMPORTANCE_HIGH));
Notification.TvExtender tv = new Notification.TvExtender().setChannel("foo");
- mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
generateNotificationRecord(null, tv).getNotification(), new int[1], 0);
- verify(mRankingHelper, times(1)).getNotificationChannelWithFallback(
+ verify(mRankingHelper, times(1)).getNotificationChannel(
anyString(), anyInt(), eq("foo"), anyBoolean());
}
@@ -393,14 +397,14 @@
public void testTvExtenderChannelOverride_notOnTv() throws Exception {
mNotificationManagerService.setIsTelevision(false);
mNotificationManagerService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannelWithFallback(
+ when(mRankingHelper.getNotificationChannel(
anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
- new NotificationChannel("id", "id", NotificationManager.IMPORTANCE_HIGH));
+ mTestNotificationChannel);
Notification.TvExtender tv = new Notification.TvExtender().setChannel("foo");
- mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
generateNotificationRecord(null, tv).getNotification(), new int[1], 0);
- verify(mRankingHelper, times(1)).getNotificationChannelWithFallback(
- anyString(), anyInt(), eq("id"), anyBoolean());
+ verify(mRankingHelper, times(1)).getNotificationChannel(
+ anyString(), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean());
}
}
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
index 13d6c5d..946044d 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
@@ -289,6 +289,18 @@
}
@Test
+ public void testImportance_locked_unspecified_preUpgrade() throws Exception {
+ defaultChannel.setImportance(NotificationManager.IMPORTANCE_UNSPECIFIED);
+ defaultChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
+ StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
+ true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+ false /* lights */, false /*defaultLights */);
+
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
+ assertEquals(NotificationManager.IMPORTANCE_HIGH, record.getImportance());
+ }
+
+ @Test
public void testImportance_upgrade() throws Exception {
StatusBarNotification sbn = getNotification(false /*preO */, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
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 fab8434..40af2f8 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -78,12 +78,15 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class RankingHelperTest {
- @Mock
- NotificationUsageStats mUsageStats;
- @Mock
- RankingHandler handler;
- @Mock
- PackageManager mPm;
+ private static final String PKG = "com.android.server.notification";
+ private static final int UID = 0;
+ private static final String UPDATED_PKG = "updatedPkg";
+ private static final int UID2 = 1111111;
+ private static final String TEST_CHANNEL_ID = "test_channel_id";
+
+ @Mock NotificationUsageStats mUsageStats;
+ @Mock RankingHandler mHandler;
+ @Mock PackageManager mPm;
private Notification mNotiGroupGSortA;
private Notification mNotiGroupGSortB;
@@ -96,11 +99,6 @@
private NotificationRecord mRecordNoGroup2;
private NotificationRecord mRecordNoGroupSortA;
private RankingHelper mHelper;
- private final String pkg = "com.android.server.notification";
- private final int uid = 0;
- private final String pkg2 = "pkg2";
- private final int uid2 = 1111111;
- private static final String TEST_CHANNEL_ID = "test_channel_id";
private AudioAttributes mAudioAttributes;
private Context getContext() {
@@ -108,12 +106,12 @@
}
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
UserHandle user = UserHandle.ALL;
- mHelper = new RankingHelper(getContext(), mPm, handler, mUsageStats,
- new String[]{ImportanceExtractor.class.getName()});
+ mHelper = new RankingHelper(getContext(), mPm, mHandler, mUsageStats,
+ new String[] {ImportanceExtractor.class.getName()});
mNotiGroupGSortA = new Notification.Builder(getContext(), TEST_CHANNEL_ID)
.setContentTitle("A")
@@ -170,12 +168,9 @@
legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
final ApplicationInfo upgrade = new ApplicationInfo();
upgrade.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
- try {
- when(mPm.getApplicationInfoAsUser(eq(pkg), anyInt(), anyInt())).thenReturn(legacy);
- when(mPm.getApplicationInfoAsUser(eq(pkg2), anyInt(), anyInt())).thenReturn(upgrade);
- when(mPm.getPackageUidAsUser(eq(pkg), anyInt())).thenReturn(uid);
- } catch (PackageManager.NameNotFoundException e) {
- }
+ when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(legacy);
+ when(mPm.getApplicationInfoAsUser(eq(UPDATED_PKG), anyInt(), anyInt())).thenReturn(upgrade);
+ when(mPm.getPackageUidAsUser(eq(PKG), anyInt())).thenReturn(UID);
}
private NotificationChannel getDefaultChannel() {
@@ -202,9 +197,18 @@
return baos;
}
+ private void loadStreamXml(ByteArrayOutputStream stream) throws Exception {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(stream.toByteArray())),
+ null);
+ parser.nextTag();
+ mHelper.readXml(parser, false);
+ }
+
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());
@@ -280,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);
@@ -289,34 +294,28 @@
channel2.setVibrationPattern(new long[]{100, 67, 145, 156});
channel2.setLightColor(Color.BLUE);
- mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
- mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
- mHelper.createNotificationChannel(pkg, uid, channel2, false);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel2, false);
- mHelper.setShowBadge(pkg, uid, true);
- mHelper.setShowBadge(pkg2, uid2, false);
+ mHelper.setShowBadge(PKG, UID, true);
- ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, false, channel1.getId(),
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false, channel1.getId(),
channel2.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
- mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{pkg}, new int[]{uid});
+ mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG}, new int[]{UID});
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
- null);
- parser.nextTag();
- mHelper.readXml(parser, false);
+ loadStreamXml(baos);
- assertFalse(mHelper.canShowBadge(pkg2, uid2));
- assertTrue(mHelper.canShowBadge(pkg, uid));
- assertEquals(channel1, mHelper.getNotificationChannel(pkg, uid, channel1.getId(), false));
+ assertTrue(mHelper.canShowBadge(PKG, UID));
+ assertEquals(channel1, mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
compareChannels(channel2,
- mHelper.getNotificationChannel(pkg, uid, channel2.getId(), false));
+ mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
assertNotNull(mHelper.getNotificationChannel(
- pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false));
+ PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false));
List<NotificationChannelGroup> actualGroups =
- mHelper.getNotificationChannelGroups(pkg, uid, false).getList();
+ mHelper.getNotificationChannelGroups(PKG, UID, false).getList();
boolean foundNcg = false;
for (NotificationChannelGroup actual : actualGroups) {
if (ncg.getId().equals(actual.getId())) {
@@ -350,19 +349,19 @@
new NotificationChannel("id3", "name3", IMPORTANCE_LOW);
channel3.setGroup(ncg.getId());
- mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
- mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
- mHelper.createNotificationChannel(pkg, uid, channel2, false);
- mHelper.createNotificationChannel(pkg, uid, channel3, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel2, false);
+ mHelper.createNotificationChannel(PKG, UID, channel3, true);
- mHelper.deleteNotificationChannel(pkg, uid, channel1.getId());
- mHelper.deleteNotificationChannelGroup(pkg, uid, ncg.getId());
- assertEquals(channel2, mHelper.getNotificationChannel(pkg, uid, channel2.getId(), false));
+ mHelper.deleteNotificationChannel(PKG, UID, channel1.getId());
+ mHelper.deleteNotificationChannelGroup(PKG, UID, ncg.getId());
+ assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
- ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, true, channel1.getId(),
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel1.getId(),
channel2.getId(), channel3.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
- mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{pkg}, new int[]{uid});
+ mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG}, new int[]{UID});
XmlPullParser parser = Xml.newPullParser();
parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
@@ -370,11 +369,11 @@
parser.nextTag();
mHelper.readXml(parser, true);
- assertNull(mHelper.getNotificationChannel(pkg, uid, channel1.getId(), false));
- assertNull(mHelper.getNotificationChannel(pkg, uid, channel3.getId(), false));
- assertNull(mHelper.getNotificationChannelGroup(ncg.getId(), pkg, uid));
- //assertEquals(ncg2, mHelper.getNotificationChannelGroup(ncg2.getId(), pkg, uid));
- assertEquals(channel2, mHelper.getNotificationChannel(pkg, uid, channel2.getId(), false));
+ assertNull(mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
+ assertNull(mHelper.getNotificationChannel(PKG, UID, channel3.getId(), false));
+ assertNull(mHelper.getNotificationChannelGroup(ncg.getId(), PKG, UID));
+ //assertEquals(ncg2, mHelper.getNotificationChannelGroup(ncg2.getId(), PKG, UID));
+ assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
}
@Test
@@ -382,19 +381,15 @@
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_DEFAULT);
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
- ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid,false, channel1.getId(),
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false, channel1.getId(),
NotificationChannel.DEFAULT_CHANNEL_ID);
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
- null);
- parser.nextTag();
- mHelper.readXml(parser, false);
+ loadStreamXml(baos);
- final NotificationChannel updated = mHelper.getNotificationChannel(
- pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false);
+ final NotificationChannel updated = mHelper.getNotificationChannel(PKG, UID,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false);
assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, updated.getImportance());
assertFalse(updated.canBypassDnd());
assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE, updated.getLockscreenVisibility());
@@ -405,34 +400,30 @@
public void testChannelXml_defaultChannelUpdatedApp_userSettings() throws Exception {
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_MIN);
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
- final NotificationChannel defaultChannel = mHelper.getNotificationChannel(
- pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false);
- defaultChannel.setImportance(IMPORTANCE_LOW);
- mHelper.updateNotificationChannel(pkg, uid, defaultChannel);
+ final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG, UID,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false);
+ defaultChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
+ mHelper.updateNotificationChannel(PKG, UID, defaultChannel);
- ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, false, channel1.getId(),
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false, channel1.getId(),
NotificationChannel.DEFAULT_CHANNEL_ID);
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
- null);
- parser.nextTag();
- mHelper.readXml(parser, false);
+ loadStreamXml(baos);
- assertEquals(IMPORTANCE_LOW, mHelper.getNotificationChannel(
- pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false).getImportance());
+ assertEquals(NotificationManager.IMPORTANCE_LOW, mHelper.getNotificationChannel(
+ PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false).getImportance());
}
@Test
public void testChannelXml_upgradeCreateDefaultChannel() throws Exception {
final String preupgradeXml = "<ranking version=\"1\">\n"
- + "<package name=\"" + pkg + "\" importance=\""
- + NotificationManager.IMPORTANCE_HIGH
+ + "<package name=\"" + PKG
+ + "\" importance=\"" + NotificationManager.IMPORTANCE_HIGH
+ "\" priority=\"" + Notification.PRIORITY_MAX + "\" visibility=\""
- + Notification.VISIBILITY_SECRET + "\"" + " uid=\"" + uid + "\" />\n"
- + "<package name=\"" + pkg2 + "\" uid=\"" + uid2 + "\" visibility=\""
+ + Notification.VISIBILITY_SECRET + "\"" +" uid=\"" + UID + "\" />\n"
+ + "<package name=\"" + UPDATED_PKG + "\" uid=\"" + UID2 + "\" visibility=\""
+ Notification.VISIBILITY_PRIVATE + "\" />\n"
+ "</ranking>";
XmlPullParser parser = Xml.newPullParser();
@@ -441,30 +432,69 @@
parser.nextTag();
mHelper.readXml(parser, false);
- final NotificationChannel updated1 = mHelper.getNotificationChannel(
- pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false);
+ final NotificationChannel updated1 =
+ mHelper.getNotificationChannel(PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false);
assertEquals(NotificationManager.IMPORTANCE_HIGH, updated1.getImportance());
assertTrue(updated1.canBypassDnd());
assertEquals(Notification.VISIBILITY_SECRET, updated1.getLockscreenVisibility());
assertEquals(NotificationChannel.USER_LOCKED_IMPORTANCE
| NotificationChannel.USER_LOCKED_PRIORITY
- | NotificationChannel.USER_LOCKED_VISIBILITY, updated1.getUserLockedFields());
+ | NotificationChannel.USER_LOCKED_VISIBILITY,
+ updated1.getUserLockedFields());
- final NotificationChannel updated2 = mHelper.getNotificationChannel(
- pkg2, uid2, NotificationChannel.DEFAULT_CHANNEL_ID, false);
- // clamped
- assertEquals(IMPORTANCE_LOW, updated2.getImportance());
- assertFalse(updated2.canBypassDnd());
- assertEquals(Notification.VISIBILITY_PRIVATE, updated2.getLockscreenVisibility());
- assertEquals(NotificationChannel.USER_LOCKED_VISIBILITY, updated2.getUserLockedFields());
+ // STOPSHIP - this should be reversed after the STOPSHIP is removed in the tested code.
+ // No Default Channel created for updated packages
+ // assertEquals(null, mHelper.getNotificationChannel(UPDATED_PKG, UID2,
+ // NotificationChannel.DEFAULT_CHANNEL_ID, false));
+ assertTrue(mHelper.getNotificationChannel(UPDATED_PKG, UID2,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false) != null);
+ }
+
+ @Test
+ public void testChannelXml_upgradeDeletesDefaultChannel() throws Exception {
+ final NotificationChannel defaultChannel = mHelper.getNotificationChannel(
+ PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false);
+ assertTrue(defaultChannel != null);
+ ByteArrayOutputStream baos =
+ writeXmlAndPurge(PKG, UID, false, NotificationChannel.DEFAULT_CHANNEL_ID);
+ // Load package at higher sdk.
+ final ApplicationInfo upgraded = new ApplicationInfo();
+ upgraded.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
+ when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(upgraded);
+ loadStreamXml(baos);
+
+ // STOPSHIP - this should be reversed after the STOPSHIP is removed in the tested code.
+ // Default Channel should be gone.
+ // assertEquals(null, mHelper.getNotificationChannel(PKG, UID,
+ // NotificationChannel.DEFAULT_CHANNEL_ID, false));
+ assertTrue(mHelper.getNotificationChannel(UPDATED_PKG, UID2,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false) != null);
+ }
+
+ @Test
+ public void testDeletesDefaultChannelAfterChannelIsCreated() throws Exception {
+ mHelper.createNotificationChannel(PKG, UID,
+ new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true);
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
+ NotificationChannel.DEFAULT_CHANNEL_ID, "bananas");
+
+ // Load package at higher sdk.
+ final ApplicationInfo upgraded = new ApplicationInfo();
+ upgraded.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
+ when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(upgraded);
+ loadStreamXml(baos);
+
+ // Default Channel should be gone.
+ assertEquals(null, mHelper.getNotificationChannel(PKG, UID,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false));
}
@Test
public void testCreateChannel_blocked() throws Exception {
- mHelper.setImportance(pkg, uid, NotificationManager.IMPORTANCE_NONE);
+ mHelper.setImportance(PKG, UID, NotificationManager.IMPORTANCE_NONE);
- mHelper.createNotificationChannel(pkg, uid,
- new NotificationChannel(pkg, "bananas", IMPORTANCE_LOW), true);
+ mHelper.createNotificationChannel(PKG, UID,
+ new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true);
}
@Test
@@ -474,16 +504,16 @@
new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
- mHelper.createNotificationChannel(pkg, uid, channel, false);
+ mHelper.createNotificationChannel(PKG, UID, channel, false);
// same id, try to update
final NotificationChannel channel2 =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
- mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+ mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+ assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
}
@Test
@@ -494,17 +524,17 @@
channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
- mHelper.createNotificationChannel(pkg, uid, channel, false);
+ mHelper.createNotificationChannel(PKG, UID, channel, false);
// same id, try to update
final NotificationChannel channel2 =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
- mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+ mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+ assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
}
@Test
@@ -515,7 +545,7 @@
channel.enableLights(false);
channel.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
- mHelper.createNotificationChannel(pkg, uid, channel, false);
+ mHelper.createNotificationChannel(PKG, UID, channel, false);
// same id, try to update
final NotificationChannel channel2 =
@@ -523,10 +553,10 @@
channel2.enableVibration(true);
channel2.setVibrationPattern(new long[]{100});
- mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+ mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+ assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
}
@Test
@@ -537,17 +567,17 @@
channel.enableLights(false);
channel.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
- mHelper.createNotificationChannel(pkg, uid, channel, false);
+ mHelper.createNotificationChannel(PKG, UID, channel, false);
// same id, try to update
final NotificationChannel channel2 =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
channel2.enableLights(true);
- mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+ mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+ assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
}
@Test
@@ -558,17 +588,17 @@
channel.setBypassDnd(true);
channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
- mHelper.createNotificationChannel(pkg, uid, channel, false);
+ mHelper.createNotificationChannel(PKG, UID, channel, false);
// same id, try to update all fields
final NotificationChannel channel2 =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
channel2.setBypassDnd(false);
- mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+ mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+ assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
}
@Test
@@ -579,17 +609,17 @@
channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
channel.lockFields(NotificationChannel.USER_LOCKED_SOUND);
- mHelper.createNotificationChannel(pkg, uid, channel, false);
+ mHelper.createNotificationChannel(PKG, UID, channel, false);
// same id, try to update all fields
final NotificationChannel channel2 =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
channel2.setSound(new Uri.Builder().scheme("test2").build(), mAudioAttributes);
- mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+ mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+ assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
}
@Test
@@ -599,16 +629,16 @@
channel.setShowBadge(true);
channel.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
- mHelper.createNotificationChannel(pkg, uid, channel, false);
+ mHelper.createNotificationChannel(PKG, UID, channel, false);
final NotificationChannel channel2 =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
channel2.setShowBadge(false);
- mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+ mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+ assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
}
@Test
@@ -621,7 +651,7 @@
channel.setBypassDnd(true);
channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
- mHelper.createNotificationChannel(pkg, uid, channel, false);
+ mHelper.createNotificationChannel(PKG, UID, channel, false);
// same id, try to update all fields
final NotificationChannel channel2 =
@@ -631,17 +661,15 @@
channel2.setBypassDnd(false);
channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
- mHelper.updateNotificationChannel(pkg, uid, channel2);
+ mHelper.updateNotificationChannel(PKG, UID, channel2);
// all fields should be changed
- assertEquals(channel2, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+ assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
}
@Test
- public void testGetChannelWithFallback() throws Exception {
- NotificationChannel channel =
- mHelper.getNotificationChannelWithFallback(pkg, uid, "garbage", false);
- assertEquals(NotificationChannel.DEFAULT_CHANNEL_ID, channel.getId());
+ public void testGetNotificationChannel_ReturnsNullForUnknownChannel() throws Exception {
+ assertEquals(null, mHelper.getNotificationChannel(PKG, UID, "garbage", false));
}
@Test
@@ -659,10 +687,10 @@
}
channel.lockFields(lockMask);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
NotificationChannel savedChannel =
- mHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
+ mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
assertEquals(channel.getName(), savedChannel.getName());
assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
@@ -686,10 +714,10 @@
}
channel.lockFields(lockMask);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
NotificationChannel savedChannel =
- mHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
+ mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
assertEquals(channel.getName(), savedChannel.getName());
assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
@@ -699,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);
@@ -709,16 +742,16 @@
channel.enableVibration(true);
channel.setVibrationPattern(new long[]{100, 67, 145, 156});
- mHelper.createNotificationChannel(pkg, uid, channel, true);
- mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
+ mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
// Does not return deleted channel
NotificationChannel response =
- mHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
+ mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
assertNull(response);
// Returns deleted channel
- response = mHelper.getNotificationChannel(pkg, uid, channel.getId(), true);
+ response = mHelper.getNotificationChannel(PKG, UID, channel.getId(), true);
compareChannels(channel, response);
assertTrue(response.isDeleted());
}
@@ -738,14 +771,14 @@
NotificationChannel channel2 =
new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
channelMap.put(channel2.getId(), channel2);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
- mHelper.createNotificationChannel(pkg, uid, channel2, true);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
+ mHelper.createNotificationChannel(PKG, UID, channel2, true);
- mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
+ mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
// Returns only non-deleted channels
List<NotificationChannel> channels =
- mHelper.getNotificationChannels(pkg, uid, false).getList();
+ mHelper.getNotificationChannels(PKG, UID, false).getList();
assertEquals(2, channels.size()); // Default channel + non-deleted channel
for (NotificationChannel nc : channels) {
if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
@@ -754,7 +787,7 @@
}
// Returns deleted channels too
- channels = mHelper.getNotificationChannels(pkg, uid, true).getList();
+ channels = mHelper.getNotificationChannels(PKG, UID, true).getList();
assertEquals(3, channels.size()); // Includes default channel
for (NotificationChannel nc : channels) {
if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
@@ -771,35 +804,35 @@
new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
NotificationChannel channel3 =
new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
- mHelper.createNotificationChannel(pkg, uid, channel2, true);
- mHelper.createNotificationChannel(pkg, uid, channel3, true);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
+ mHelper.createNotificationChannel(PKG, UID, channel2, true);
+ mHelper.createNotificationChannel(PKG, UID, channel3, true);
- mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
- mHelper.deleteNotificationChannel(pkg, uid, channel3.getId());
+ mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
+ mHelper.deleteNotificationChannel(PKG, UID, channel3.getId());
- assertEquals(2, mHelper.getDeletedChannelCount(pkg, uid));
- assertEquals(0, mHelper.getDeletedChannelCount(pkg2, uid2));
+ assertEquals(2, mHelper.getDeletedChannelCount(PKG, UID));
+ assertEquals(0, mHelper.getDeletedChannelCount("pkg2", UID2));
}
@Test
public void testUpdateDeletedChannels() throws Exception {
NotificationChannel channel =
new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
- mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
+ mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
try {
- mHelper.updateNotificationChannel(pkg, uid, channel);
+ mHelper.updateNotificationChannel(PKG, UID, channel);
fail("Updated deleted channel");
} catch (IllegalArgumentException e) {
// :)
}
try {
- mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel);
+ mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel);
fail("Updated deleted channel");
} catch (IllegalArgumentException e) {
// :)
@@ -813,24 +846,24 @@
new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
channel.setVibrationPattern(vibration);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
- mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
+ mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
NotificationChannel newChannel = new NotificationChannel(
channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
newChannel.setVibrationPattern(new long[]{100});
- mHelper.createNotificationChannel(pkg, uid, newChannel, true);
+ mHelper.createNotificationChannel(PKG, UID, newChannel, true);
// No long deleted, using old settings
compareChannels(channel,
- mHelper.getNotificationChannel(pkg, uid, newChannel.getId(), false));
+ mHelper.getNotificationChannel(PKG, UID, newChannel.getId(), false));
}
@Test
public void testCreateChannel_defaultChannelId() throws Exception {
try {
- mHelper.createNotificationChannel(pkg2, uid2, new NotificationChannel(
+ mHelper.createNotificationChannel(PKG, UID, new NotificationChannel(
NotificationChannel.DEFAULT_CHANNEL_ID, "ha", IMPORTANCE_HIGH), true);
fail("Allowed to create default channel");
} catch (IllegalArgumentException e) {
@@ -845,26 +878,26 @@
new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
channel.setVibrationPattern(vibration);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
NotificationChannel newChannel = new NotificationChannel(
channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
newChannel.setVibrationPattern(new long[]{100});
- mHelper.createNotificationChannel(pkg, uid, newChannel, true);
+ mHelper.createNotificationChannel(PKG, UID, newChannel, true);
// Old settings not overridden
compareChannels(channel,
- mHelper.getNotificationChannel(pkg, uid, newChannel.getId(), false));
+ mHelper.getNotificationChannel(PKG, UID, newChannel.getId(), false));
}
@Test
public void testCreateChannel_addMissingSound() throws Exception {
final NotificationChannel channel =
new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
assertNotNull(mHelper.getNotificationChannel(
- pkg, uid, channel.getId(), false).getSound());
+ PKG, UID, channel.getId(), false).getSound());
}
@Test
@@ -873,9 +906,9 @@
final NotificationChannel channel = new NotificationChannel("id2", "name2",
NotificationManager.IMPORTANCE_DEFAULT);
channel.setSound(sound, mAudioAttributes);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
assertEquals(sound, mHelper.getNotificationChannel(
- pkg, uid, channel.getId(), false).getSound());
+ PKG, UID, channel.getId(), false).getSound());
}
@Test
@@ -885,13 +918,13 @@
NotificationChannel channel2 =
new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
- mHelper.createNotificationChannel(pkg, uid, channel2, false);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel2, false);
- mHelper.permanentlyDeleteNotificationChannels(pkg, uid);
+ mHelper.permanentlyDeleteNotificationChannels(PKG, UID);
// Only default channel remains
- assertEquals(1, mHelper.getNotificationChannels(pkg, uid, true).getList().size());
+ assertEquals(1, mHelper.getNotificationChannels(PKG, UID, true).getList().size());
}
@Test
@@ -907,28 +940,28 @@
new NotificationChannel("deleted", "belongs to deleted", IMPORTANCE_DEFAULT);
groupedAndDeleted.setGroup("totally");
- mHelper.createNotificationChannelGroup(pkg, uid, notDeleted, true);
- mHelper.createNotificationChannelGroup(pkg, uid, deleted, true);
- mHelper.createNotificationChannel(pkg, uid, nonGroupedNonDeletedChannel, true);
- mHelper.createNotificationChannel(pkg, uid, groupedAndDeleted, true);
- mHelper.createNotificationChannel(pkg, uid, groupedButNotDeleted, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, notDeleted, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, deleted, true);
+ mHelper.createNotificationChannel(PKG, UID, nonGroupedNonDeletedChannel, true);
+ mHelper.createNotificationChannel(PKG, UID, groupedAndDeleted, true);
+ mHelper.createNotificationChannel(PKG, UID, groupedButNotDeleted, true);
- mHelper.deleteNotificationChannelGroup(pkg, uid, deleted.getId());
+ mHelper.deleteNotificationChannelGroup(PKG, UID, deleted.getId());
- assertNull(mHelper.getNotificationChannelGroup(deleted.getId(), pkg, uid));
- assertNotNull(mHelper.getNotificationChannelGroup(notDeleted.getId(), pkg, uid));
+ assertNull(mHelper.getNotificationChannelGroup(deleted.getId(), PKG, UID));
+ assertNotNull(mHelper.getNotificationChannelGroup(notDeleted.getId(), PKG, UID));
- assertNull(mHelper.getNotificationChannel(pkg, uid, groupedAndDeleted.getId(), false));
+ assertNull(mHelper.getNotificationChannel(PKG, UID, groupedAndDeleted.getId(), false));
compareChannels(groupedAndDeleted,
- mHelper.getNotificationChannel(pkg, uid, groupedAndDeleted.getId(), true));
+ mHelper.getNotificationChannel(PKG, UID, groupedAndDeleted.getId(), true));
compareChannels(groupedButNotDeleted,
- mHelper.getNotificationChannel(pkg, uid, groupedButNotDeleted.getId(), false));
+ mHelper.getNotificationChannel(PKG, UID, groupedButNotDeleted.getId(), false));
compareChannels(nonGroupedNonDeletedChannel, mHelper.getNotificationChannel(
- pkg, uid, nonGroupedNonDeletedChannel.getId(), false));
+ PKG, UID, nonGroupedNonDeletedChannel.getId(), false));
// notDeleted
- assertEquals(1, mHelper.getNotificationChannelGroups(pkg, uid).size());
+ assertEquals(1, mHelper.getNotificationChannelGroups(PKG, UID).size());
}
@Test
@@ -936,52 +969,52 @@
// Deleted
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
- mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
+ mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
- assertEquals(0, mHelper.getNotificationChannels(pkg, uid, true).getList().size());
+ assertEquals(0, mHelper.getNotificationChannels(PKG, UID, true).getList().size());
// Not deleted
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
- mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
- assertEquals(2, mHelper.getNotificationChannels(pkg, uid, false).getList().size());
+ mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
+ assertEquals(2, mHelper.getNotificationChannels(PKG, UID, false).getList().size());
}
@Test
public void testOnPackageChanged_packageRemoval_importance() throws Exception {
- mHelper.setImportance(pkg, uid, NotificationManager.IMPORTANCE_HIGH);
+ mHelper.setImportance(PKG, UID, NotificationManager.IMPORTANCE_HIGH);
- mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
+ mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
- assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(pkg, uid));
+ assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG, UID));
}
@Test
public void testOnPackageChanged_packageRemoval_groups() throws Exception {
NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
- mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
- mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
- mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
+ mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
- assertEquals(0, mHelper.getNotificationChannelGroups(pkg, uid).size());
+ assertEquals(0, mHelper.getNotificationChannelGroups(PKG, UID, true).getList().size());
}
@Test
public void testRecordDefaults() throws Exception {
- assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(pkg, uid));
- assertEquals(true, mHelper.canShowBadge(pkg, uid));
- assertEquals(1, mHelper.getNotificationChannels(pkg, uid, false).getList().size());
+ assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG, UID));
+ assertEquals(true, mHelper.canShowBadge(PKG, UID));
+ assertEquals(1, mHelper.getNotificationChannels(PKG, UID, false).getList().size());
}
@Test
public void testCreateGroup() throws Exception {
NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
- mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
- assertEquals(ncg, mHelper.getNotificationChannelGroups(pkg, uid).iterator().next());
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+ assertEquals(ncg, mHelper.getNotificationChannelGroups(PKG, UID).iterator().next());
}
@Test
@@ -990,7 +1023,7 @@
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
channel1.setGroup("garbage");
try {
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
fail("Created a channel with a bad group");
} catch (IllegalArgumentException e) {
}
@@ -999,45 +1032,45 @@
@Test
public void testCannotCreateChannel_goodGroup() throws Exception {
NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
- mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
channel1.setGroup(ncg.getId());
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
assertEquals(ncg.getId(),
- mHelper.getNotificationChannel(pkg, uid, channel1.getId(), false).getGroup());
+ mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false).getGroup());
}
@Test
public void testGetChannelGroups() throws Exception {
NotificationChannelGroup unused = new NotificationChannelGroup("unused", "s");
- mHelper.createNotificationChannelGroup(pkg, uid, unused, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, unused, true);
NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
- mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
- mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
channel1.setGroup(ncg.getId());
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
NotificationChannel channel1a =
new NotificationChannel("id1a", "name1", NotificationManager.IMPORTANCE_HIGH);
channel1a.setGroup(ncg.getId());
- mHelper.createNotificationChannel(pkg, uid, channel1a, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1a, true);
NotificationChannel channel2 =
new NotificationChannel("id2", "name1", NotificationManager.IMPORTANCE_HIGH);
channel2.setGroup(ncg2.getId());
- mHelper.createNotificationChannel(pkg, uid, channel2, true);
+ mHelper.createNotificationChannel(PKG, UID, channel2, true);
NotificationChannel channel3 =
new NotificationChannel("id3", "name1", NotificationManager.IMPORTANCE_HIGH);
- mHelper.createNotificationChannel(pkg, uid, channel3, true);
+ mHelper.createNotificationChannel(PKG, UID, channel3, true);
List<NotificationChannelGroup> actual =
- mHelper.getNotificationChannelGroups(pkg, uid, true).getList();
+ mHelper.getNotificationChannelGroups(PKG, UID, true).getList();
assertEquals(3, actual.size());
for (NotificationChannelGroup group : actual) {
if (group.getId() == null) {
@@ -1063,19 +1096,19 @@
@Test
public void testGetChannelGroups_noSideEffects() throws Exception {
NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
- mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
channel1.setGroup(ncg.getId());
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
- mHelper.getNotificationChannelGroups(pkg, uid, true).getList();
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
+ mHelper.getNotificationChannelGroups(PKG, UID, true).getList();
channel1.setImportance(IMPORTANCE_LOW);
- mHelper.updateNotificationChannel(pkg, uid, channel1);
+ mHelper.updateNotificationChannel(PKG, UID, channel1);
List<NotificationChannelGroup> actual =
- mHelper.getNotificationChannelGroups(pkg, uid, true).getList();
+ mHelper.getNotificationChannelGroups(PKG, UID, true).getList();
assertEquals(2, actual.size());
for (NotificationChannelGroup group : actual) {
@@ -1088,14 +1121,14 @@
@Test
public void testCreateChannel_updateName() throws Exception {
NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
- mHelper.createNotificationChannel(pkg, uid, nc, true);
- NotificationChannel actual = mHelper.getNotificationChannel(pkg, uid, "id", false);
+ mHelper.createNotificationChannel(PKG, UID, nc, true);
+ NotificationChannel actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
assertEquals("hello", actual.getName());
nc = new NotificationChannel("id", "goodbye", IMPORTANCE_HIGH);
- mHelper.createNotificationChannel(pkg, uid, nc, true);
+ mHelper.createNotificationChannel(PKG, UID, nc, true);
- actual = mHelper.getNotificationChannel(pkg, uid, "id", false);
+ actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
assertEquals("goodbye", actual.getName());
assertEquals(IMPORTANCE_DEFAULT, actual.getImportance());
}
@@ -1115,7 +1148,7 @@
String pkgName = "pkg" + i;
int numChannels = ThreadLocalRandom.current().nextInt(1, 10);
for (int j = 0; j < numChannels; j++) {
- mHelper.createNotificationChannel(pkgName, uid,
+ mHelper.createNotificationChannel(pkgName, UID,
new NotificationChannel("" + j, "a", IMPORTANCE_HIGH), true);
}
expectedChannels.put(pkgName, numChannels);
@@ -1123,7 +1156,7 @@
// delete the first channel of the first package
String pkg = expectedChannels.keyAt(0);
- mHelper.deleteNotificationChannel("pkg" + 0, uid, "0");
+ mHelper.deleteNotificationChannel("pkg" + 0, UID, "0");
// dump should not include deleted channels
int count = expectedChannels.get(pkg);
expectedChannels.put(pkg, count - 1);
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/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index 6cca771..a9c69f6 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -563,6 +563,14 @@
}
@Test
+ public void testSetActiveScorer_requestNetworkScoresPermission() {
+ when(mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+ mNetworkScoreService.setActiveScorer(null);
+ }
+
+ @Test
public void testDisableScoring_notActiveScorer_noRequestNetworkScoresPermission() {
bindToScorer(false /*callerIsScorer*/);
when(mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES))
@@ -577,6 +585,36 @@
}
@Test
+ public void testDisableScoring_activeScorer_noRequestNetworkScoresPermission() {
+ bindToScorer(true /*callerIsScorer*/);
+ when(mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES))
+ .thenReturn(PackageManager.PERMISSION_DENIED);
+
+ mNetworkScoreService.disableScoring();
+ }
+
+ @Test
+ public void testGetAllValidScorer_noRequestNetworkScoresPermission() {
+ when(mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES))
+ .thenReturn(PackageManager.PERMISSION_DENIED);
+
+ try {
+ mNetworkScoreService.getAllValidScorers();
+ fail("SecurityException expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testGetAllValidScorer_requestNetworkScoresPermission() {
+ when(mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+ mNetworkScoreService.getAllValidScorers();
+ }
+
+ @Test
public void testRegisterNetworkScoreCache_noRequestNetworkScoresPermission() {
doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
eq(permission.REQUEST_NETWORK_SCORES), anyString());
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index e433b60..308632f 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -24,6 +24,8 @@
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.nullable;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -87,6 +89,17 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link AccountManagerService}.
+ * <p>Run with:<pre>
+ * mmma -j40 frameworks/base/services/tests/servicestests
+ * adb install -r ${OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk
+ * adb shell am instrument -w -e class package com.android.server.accounts \
+ * com.android.frameworks.servicestests\
+ * /android.support.test.runner.AndroidJUnitRunner
+ * </pre>
+ */
public class AccountManagerServiceTest extends AndroidTestCase {
private static final String TAG = AccountManagerServiceTest.class.getSimpleName();
private static final long ONE_DAY_IN_MILLISECOND = 86400000;
@@ -103,6 +116,8 @@
@Captor private ArgumentCaptor<Intent> mIntentCaptor;
@Captor private ArgumentCaptor<Bundle> mBundleCaptor;
+ private int mVisibleAccountsChangedBroadcasts;
+ private int mLoginAccountsChangedBroadcasts;
private static final int LATCH_TIMEOUT_MS = 500;
private static final String PREN_DB = "pren.db";
@@ -1042,7 +1057,7 @@
waitForLatch(latch);
// Verify notification is cancelled
verify(mMockNotificationManager).cancelNotificationWithTag(
- anyString(), anyString(), anyInt(), anyInt());
+ anyString(), nullable(String.class), anyInt(), anyInt());
verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
Bundle result = mBundleCaptor.getValue();
@@ -1889,7 +1904,7 @@
waitForLatch(latch);
// Verify notification is cancelled
verify(mMockNotificationManager).cancelNotificationWithTag(
- anyString(), anyString(), anyInt(), anyInt());
+ anyString(), nullable(String.class), anyInt(), anyInt());
verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
Bundle result = mBundleCaptor.getValue();
@@ -2446,6 +2461,161 @@
verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
}
+ @SmallTest
+ public void testRegisterAccountListener() throws Exception {
+ unlockSystemUser();
+ mAms.registerAccountListener(
+ new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1},
+ "testpackage"); // opPackageName
+
+ mAms.registerAccountListener(
+ null, //accountTypes
+ "testpackage"); // opPackageName
+
+ // Check that two previously registered receivers can be unregistered successfully.
+ mAms.unregisterAccountListener(
+ new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1},
+ "testpackage"); // opPackageName
+
+ mAms.unregisterAccountListener(
+ null, //accountTypes
+ "testpackage"); // opPackageName
+ }
+
+ @SmallTest
+ public void testRegisterAccountListenerAndAddAccount() throws Exception {
+ unlockSystemUser();
+ mAms.registerAccountListener(
+ new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1},
+ "testpackage"); // opPackageName
+
+ mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, "p11", null);
+ // Notification about new account
+ updateBroadcastCounters(2);
+ assertEquals(mVisibleAccountsChangedBroadcasts, 1);
+ assertEquals(mLoginAccountsChangedBroadcasts, 1);
+ }
+
+ @SmallTest
+ public void testRegisterAccountListenerAndAddAccountOfDifferentType() throws Exception {
+ unlockSystemUser();
+ mAms.registerAccountListener(
+ new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_2},
+ "testpackage"); // opPackageName
+
+ mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, "p11", null);
+ mAms.addAccountExplicitly(
+ AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE, "p11", null);
+ // Notification about new account
+
+ updateBroadcastCounters(2);
+ assertEquals(mVisibleAccountsChangedBroadcasts, 0); // broadcast was not sent
+ assertEquals(mLoginAccountsChangedBroadcasts, 2);
+ }
+
+ @SmallTest
+ public void testRegisterAccountListenerWithAddingTwoAccounts() throws Exception {
+ unlockSystemUser();
+ mAms.registerAccountListener(
+ new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1},
+ "testpackage"); // opPackageName
+ mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, "p11", null);
+ mAms.unregisterAccountListener(
+ new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1},
+ "testpackage"); // opPackageName
+ mAms.addAccountExplicitly(
+ AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE, "p11", null);
+
+ updateBroadcastCounters(3);
+ assertEquals(mVisibleAccountsChangedBroadcasts, 1);
+ assertEquals(mLoginAccountsChangedBroadcasts, 2);
+
+ mAms.removeAccountInternal(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS);
+ mAms.registerAccountListener( null /* accountTypes */, "testpackage");
+ mAms.removeAccountInternal(AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE);
+
+ updateBroadcastCounters(6);
+ assertEquals(mVisibleAccountsChangedBroadcasts, 2);
+ assertEquals(mLoginAccountsChangedBroadcasts, 4);
+ }
+
+ @SmallTest
+ public void testRegisterAccountListenerForThreePackages() throws Exception {
+ unlockSystemUser();
+ mAms.registerAccountListener(
+ new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1},
+ "testpackage1"); // opPackageName
+ mAms.registerAccountListener(
+ new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1},
+ "testpackage2"); // opPackageName
+ mAms.registerAccountListener(
+ new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1},
+ "testpackage3"); // opPackageName
+ mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, "p11", null);
+ updateBroadcastCounters(4);
+ assertEquals(mVisibleAccountsChangedBroadcasts, 3);
+ assertEquals(mLoginAccountsChangedBroadcasts, 1);
+
+ mAms.unregisterAccountListener(
+ new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1},
+ "testpackage3"); // opPackageName
+ // Remove account with 2 active listeners.
+ mAms.removeAccountInternal(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS);
+ updateBroadcastCounters(7);
+ assertEquals(mVisibleAccountsChangedBroadcasts, 5);
+ assertEquals(mLoginAccountsChangedBroadcasts, 2); // 3 add, 2 remove
+
+ // Add account of another type.
+ mAms.addAccountExplicitly(
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS_TYPE_2, "p11", null);
+
+ updateBroadcastCounters(8);
+ assertEquals(mVisibleAccountsChangedBroadcasts, 5);
+ assertEquals(mLoginAccountsChangedBroadcasts, 3);
+ }
+
+ @SmallTest
+ public void testRegisterAccountListenerCredentialsUpdate() throws Exception {
+ unlockSystemUser();
+ mAms.registerAccountListener(
+ new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1},
+ "testpackage"); // opPackageName
+ mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, "p11", null);
+ mAms.setPassword(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, "pwd");
+ updateBroadcastCounters(4);
+ assertEquals(mVisibleAccountsChangedBroadcasts, 2);
+ assertEquals(mLoginAccountsChangedBroadcasts, 2);
+ }
+
+ @SmallTest
+ public void testUnregisterAccountListenerNotRegistered() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.unregisterAccountListener(
+ new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1},
+ "testpackage"); // opPackageName
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ // IllegalArgumentException is expected.
+ }
+ }
+
+ private void updateBroadcastCounters (int expectedBroadcasts){
+ mVisibleAccountsChangedBroadcasts = 0;
+ mLoginAccountsChangedBroadcasts = 0;
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockContext, times(expectedBroadcasts)).sendBroadcastAsUser(captor.capture(),
+ any(UserHandle.class));
+ for (Intent intent : captor.getAllValues()) {
+ if (AccountManager.ACTION_VISIBLE_ACCOUNTS_CHANGED. equals(intent.getAction())) {
+ mVisibleAccountsChangedBroadcasts++;
+ }
+ if (AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION. equals(intent.getAction())) {
+ mLoginAccountsChangedBroadcasts++;
+ }
+ }
+ }
+
private void waitForLatch(CountDownLatch latch) {
try {
latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java
index 614680e..d176a0d 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java
@@ -73,6 +73,8 @@
new Account(ACCOUNT_NAME_INTERVENE, ACCOUNT_TYPE_1);
public static final Account ACCOUNT_ERROR =
new Account(ACCOUNT_NAME_ERROR, ACCOUNT_TYPE_1);
+ public static final Account ACCOUNT_SUCCESS_TYPE_2 =
+ new Account(ACCOUNT_NAME_SUCCESS, ACCOUNT_TYPE_2);
public static final String SESSION_DATA_NAME_1 = "session.data.name.1";
public static final String SESSION_DATA_VALUE_1 = "session.data.value.1";
@@ -81,4 +83,4 @@
"com.android.server.accounts.account_manager_service_test.error.message";
private AccountManagerServiceTestFixtures() {}
-}
\ No newline at end of file
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index ca9285b..a6ce1d5 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -17,13 +17,18 @@
import android.app.IActivityManager;
import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.app.backup.IBackupManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManagerInternal;
import android.database.ContentObserver;
import android.media.IAudioService;
import android.net.IIpConnectivityMetrics;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Looper;
import android.os.PowerManagerInternal;
import android.os.UserHandle;
@@ -302,6 +307,12 @@
}
@Override
+ PendingIntent pendingIntentGetActivityAsUser(Context context, int requestCode,
+ Intent intent, int flags, Bundle options, UserHandle user) {
+ return null;
+ }
+
+ @Override
void registerContentObserver(Uri uri, boolean notifyForDescendents,
ContentObserver observer, int userHandle) {
mContentObservers.put(new Pair<Uri, Integer>(uri, userHandle), observer);
diff --git a/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java b/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java
index d18457b..ce5b8cb 100644
--- a/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java
@@ -33,6 +33,8 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.app.RetailDemoModeServiceInternal;
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -97,6 +99,7 @@
private @Mock AudioManager mAudioManager;
private @Mock WifiManager mWifiManager;
private @Mock LockPatternUtils mLockPatternUtils;
+ private @Mock JobScheduler mJobScheduler;
private MockPreloadAppsInstaller mPreloadAppsInstaller;
private MockContentResolver mContentResolver;
private MockContactsProvider mContactsProvider;
@@ -110,6 +113,12 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ Context originalContext = InstrumentationRegistry.getContext();
+ when(mContext.getApplicationInfo()).thenReturn(originalContext.getApplicationInfo());
+ when(mContext.getResources()).thenReturn(originalContext.getResources());
+ when(mContext.getSystemServiceName(eq(JobScheduler.class))).thenReturn(
+ Context.JOB_SCHEDULER_SERVICE);
+ when(mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE)).thenReturn(mJobScheduler);
mContentResolver = new MockContentResolver(mContext);
mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
mContactsProvider = new MockContactsProvider(mContext);
@@ -218,6 +227,8 @@
// verify that the preloaded directory is emptied.
assertEquals("Preloads directory is not emptied",
0, mTestPreloadsDir.list().length);
+ // Verify that the expiration job was scheduled
+ verify(mJobScheduler).schedule(any(JobInfo.class));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
index 921e0e3..b5826f0 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -132,9 +132,10 @@
sWm.mDisplayEnabled = true;
// Create an app window with token on a display.
- final TaskStack stack = createTaskStackOnDisplay(sDisplayContent);
+ final DisplayContent defaultDisplayContent = sWm.getDefaultDisplayContentLocked();
+ final TaskStack stack = createTaskStackOnDisplay(defaultDisplayContent);
final Task task = createTaskInStack(stack, 0 /* userId */);
- final TestAppWindowToken appWindowToken = new TestAppWindowToken(sDisplayContent);
+ final TestAppWindowToken appWindowToken = new TestAppWindowToken(defaultDisplayContent);
task.addChild(appWindowToken, 0);
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
TYPE_BASE_APPLICATION);
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 96481dd..6f78245 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -74,8 +74,8 @@
private final static Session sMockSession = mock(Session.class);
// The default display is removed in {@link #setUp} and then we iterate over all displays to
// make sure we don't collide with any existing display. If we run into no other display, the
- // added display should be treated as default.
- private static int sNextDisplayId = Display.DEFAULT_DISPLAY;
+ // added display should be treated as default. This cannot be the default display
+ private static int sNextDisplayId = Display.DEFAULT_DISPLAY + 1;
static int sNextStackId = FIRST_DYNAMIC_STACK_ID;
private static int sNextTaskId = 0;
@@ -106,17 +106,23 @@
sWm = TestWindowManagerPolicy.getWindowManagerService(context);
sPolicy = (TestWindowManagerPolicy) sWm.mPolicy;
sLayersController = new WindowLayersController(sWm);
- sDisplayContent = sWm.mRoot.getDisplayContent(context.getDisplay().getDisplayId());
- if (sDisplayContent != null) {
- sDisplayContent.removeImmediately();
- }
+
// Make sure that display ids don't overlap, so there won't be several displays with same
// ids among RootWindowContainer children.
for (DisplayContent dc : sWm.mRoot.mChildren) {
if (dc.getDisplayId() >= sNextDisplayId) {
sNextDisplayId = dc.getDisplayId() + 1;
}
+
+ // The default display must be preserved as some tests require it to function
+ // (such as policy rotation).
+ if (dc.getDisplayId() != Display.DEFAULT_DISPLAY) {
+ // It is safe to remove these displays as new displays will always be created with
+ // new ids.
+ dc.removeImmediately();
+ }
}
+
context.getDisplay().getDisplayInfo(sDisplayInfo);
sDisplayContent = createNewDisplay();
sWm.mDisplayEnabled = true;
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index 199a12a..bc5e4d5 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -1897,7 +1897,7 @@
number = extractNetworkPortionAlt(number);
String emergencyNumbers = "";
- int slotId = SubscriptionManager.getSlotId(subId);
+ int slotId = SubscriptionManager.getSlotIndex(subId);
// retrieve the list of emergency numbers
// check read-write ecclist property first
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index dd6f9cb..201f3ad 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -521,14 +521,14 @@
}
/**
- * Get the active SubscriptionInfo associated with the slotIdx
- * @param slotIdx the slot which the subscription is inserted
+ * Get the active SubscriptionInfo associated with the slotIndex
+ * @param slotIndex the slot which the subscription is inserted
* @return SubscriptionInfo, maybe null if its not active
*/
- public SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIdx) {
- if (VDBG) logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIdx=" + slotIdx);
- if (!isValidSlotId(slotIdx)) {
- logd("[getActiveSubscriptionInfoForSimSlotIndex]- invalid slotIdx");
+ public SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex) {
+ if (VDBG) logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIndex=" + slotIndex);
+ if (!isValidSlotIndex(slotIndex)) {
+ logd("[getActiveSubscriptionInfoForSimSlotIndex]- invalid slotIndex");
return null;
}
@@ -537,7 +537,7 @@
try {
ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
if (iSub != null) {
- result = iSub.getActiveSubscriptionInfoForSimSlotIndex(slotIdx,
+ result = iSub.getActiveSubscriptionInfoForSimSlotIndex(slotIndex,
mContext.getOpPackageName());
}
} catch (RemoteException ex) {
@@ -671,24 +671,24 @@
/**
* Add a new SubscriptionInfo to SubscriptionInfo database if needed
* @param iccId the IccId of the SIM card
- * @param slotId the slot which the SIM is inserted
+ * @param slotIndex the slot which the SIM is inserted
* @return the URL of the newly created row or the updated row
* @hide
*/
- public Uri addSubscriptionInfoRecord(String iccId, int slotId) {
- if (VDBG) logd("[addSubscriptionInfoRecord]+ iccId:" + iccId + " slotId:" + slotId);
+ public Uri addSubscriptionInfoRecord(String iccId, int slotIndex) {
+ if (VDBG) logd("[addSubscriptionInfoRecord]+ iccId:" + iccId + " slotIndex:" + slotIndex);
if (iccId == null) {
logd("[addSubscriptionInfoRecord]- null iccId");
}
- if (!isValidSlotId(slotId)) {
- logd("[addSubscriptionInfoRecord]- invalid slotId");
+ if (!isValidSlotIndex(slotIndex)) {
+ logd("[addSubscriptionInfoRecord]- invalid slotIndex");
}
try {
ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
if (iSub != null) {
// FIXME: This returns 1 on success, 0 on error should should we return it?
- iSub.addSubInfoRecord(iccId, slotId);
+ iSub.addSubInfoRecord(iccId, slotIndex);
}
} catch (RemoteException ex) {
// ignore it
@@ -830,15 +830,15 @@
}
/**
- * Get slotId associated with the subscription.
- * @return slotId as a positive integer or a negative value if an error either
+ * Get slotIndex associated with the subscription.
+ * @return slotIndex as a positive integer or a negative value if an error either
* SIM_NOT_INSERTED or < 0 if an invalid slot index
* @hide
*/
- public static int getSlotId(int subId) {
+ public static int getSlotIndex(int subId) {
if (!isValidSubscriptionId(subId)) {
if (DBG) {
- logd("[getSlotId]- fail");
+ logd("[getSlotIndex]- fail");
}
}
@@ -847,7 +847,7 @@
try {
ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
if (iSub != null) {
- result = iSub.getSlotId(subId);
+ result = iSub.getSlotIndex(subId);
}
} catch (RemoteException ex) {
// ignore it
@@ -858,8 +858,8 @@
}
/** @hide */
- public static int[] getSubId(int slotId) {
- if (!isValidSlotId(slotId)) {
+ public static int[] getSubId(int slotIndex) {
+ if (!isValidSlotIndex(slotIndex)) {
logd("[getSubId]- fail");
return null;
}
@@ -869,7 +869,7 @@
try {
ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
if (iSub != null) {
- subId = iSub.getSubId(slotId);
+ subId = iSub.getSubId(slotIndex);
}
} catch (RemoteException ex) {
// ignore it
@@ -1155,8 +1155,8 @@
}
/** @hide */
- public static boolean isValidSlotId(int slotId) {
- return slotId >= 0 && slotId < TelephonyManager.getDefault().getSimCount();
+ public static boolean isValidSlotIndex(int slotIndex) {
+ return slotIndex >= 0 && slotIndex < TelephonyManager.getDefault().getSimCount();
}
/** @hide */
@@ -1179,7 +1179,7 @@
if (VDBG) logd("putPhoneIdAndSubIdExtra: phoneId=" + phoneId + " subId=" + subId);
intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
intent.putExtra(PhoneConstants.PHONE_KEY, phoneId);
- //FIXME this is using phoneId and slotId interchangeably
+ //FIXME this is using phoneId and slotIndex interchangeably
//Eventually, this should be removed as it is not the slot id
intent.putExtra(PhoneConstants.SLOT_KEY, phoneId);
}
@@ -1228,9 +1228,9 @@
}
/**
- * Returns a constant indicating the state of sim for the slot idx.
+ * Returns a constant indicating the state of sim for the slot index.
*
- * @param slotIdx
+ * @param slotIndex
*
* {@See TelephonyManager#SIM_STATE_UNKNOWN}
* {@See TelephonyManager#SIM_STATE_ABSENT}
@@ -1244,13 +1244,13 @@
*
* {@hide}
*/
- public static int getSimStateForSlotIdx(int slotIdx) {
+ public static int getSimStateForSlotIndex(int slotIndex) {
int simState = TelephonyManager.SIM_STATE_UNKNOWN;
try {
ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
if (iSub != null) {
- simState = iSub.getSimStateForSlotIdx(slotIdx);
+ simState = iSub.getSimStateForSlotIndex(slotIndex);
}
} catch (RemoteException ex) {
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 4dda766..0eaa359 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -918,15 +918,15 @@
* <p>Requires Permission:
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
*
- * @param slotId of which deviceID is returned
+ * @param slotIndex of which deviceID is returned
*/
/** {@hide} */
- public String getDeviceSoftwareVersion(int slotId) {
+ public String getDeviceSoftwareVersion(int slotIndex) {
ITelephony telephony = getITelephony();
if (telephony == null) return null;
try {
- return telephony.getDeviceSoftwareVersionForSlot(slotId, getOpPackageName());
+ return telephony.getDeviceSoftwareVersionForSlot(slotIndex, getOpPackageName());
} catch (RemoteException ex) {
return null;
} catch (NullPointerException ex) {
@@ -961,15 +961,15 @@
* <p>Requires Permission:
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
*
- * @param slotId of which deviceID is returned
+ * @param slotIndex of which deviceID is returned
*/
- public String getDeviceId(int slotId) {
- // FIXME this assumes phoneId == slotId
+ public String getDeviceId(int slotIndex) {
+ // FIXME this assumes phoneId == slotIndex
try {
IPhoneSubInfo info = getSubscriberInfo();
if (info == null)
return null;
- return info.getDeviceIdForPhone(slotId, mContext.getOpPackageName());
+ return info.getDeviceIdForPhone(slotIndex, mContext.getOpPackageName());
} catch (RemoteException ex) {
return null;
} catch (NullPointerException ex) {
@@ -996,17 +996,17 @@
* <p>Requires Permission:
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
*
- * @param slotId of which deviceID is returned
+ * @param slotIndex of which deviceID is returned
*
* @hide
*/
@SystemApi
- public String getImei(int slotId) {
+ public String getImei(int slotIndex) {
ITelephony telephony = getITelephony();
if (telephony == null) return null;
try {
- return telephony.getImeiForSlot(slotId, getOpPackageName());
+ return telephony.getImeiForSlot(slotIndex, getOpPackageName());
} catch (RemoteException ex) {
return null;
} catch (NullPointerException ex) {
@@ -1026,11 +1026,11 @@
/**
* Returns the NAI. Return null if NAI is not available.
*
- * @param slotId of which Nai is returned
+ * @param slotIndex of which Nai is returned
*/
/** {@hide}*/
- public String getNai(int slotId) {
- int[] subId = SubscriptionManager.getSubId(slotId);
+ public String getNai(int slotIndex) {
+ int[] subId = SubscriptionManager.getSubId(slotIndex);
try {
IPhoneSubInfo info = getSubscriberInfo();
if (info == null)
@@ -1226,23 +1226,23 @@
*
* @hide
*/
- public int getCurrentPhoneTypeForSlot(int slotId) {
+ public int getCurrentPhoneTypeForSlot(int slotIndex) {
try{
ITelephony telephony = getITelephony();
if (telephony != null) {
- return telephony.getActivePhoneTypeForSlot(slotId);
+ return telephony.getActivePhoneTypeForSlot(slotIndex);
} else {
// This can happen when the ITelephony interface is not up yet.
- return getPhoneTypeFromProperty(slotId);
+ return getPhoneTypeFromProperty(slotIndex);
}
} catch (RemoteException ex) {
// This shouldn't happen in the normal case, as a backup we
// read from the system property.
- return getPhoneTypeFromProperty(slotId);
+ return getPhoneTypeFromProperty(slotIndex);
} catch (NullPointerException ex) {
// This shouldn't happen in the normal case, as a backup we
// read from the system property.
- return getPhoneTypeFromProperty(slotId);
+ return getPhoneTypeFromProperty(slotIndex);
}
}
@@ -1962,17 +1962,17 @@
/**
* @return true if a ICC card is present for a subscription
*
- * @param slotId for which icc card presence is checked
+ * @param slotIndex for which icc card presence is checked
*/
/** {@hide} */
- // FIXME Input argument slotId should be of type int
- public boolean hasIccCard(int slotId) {
+ // FIXME Input argument slotIndex should be of type int
+ public boolean hasIccCard(int slotIndex) {
try {
ITelephony telephony = getITelephony();
if (telephony == null)
return false;
- return telephony.hasIccCardUsingSlotId(slotId);
+ return telephony.hasIccCardUsingSlotIndex(slotIndex);
} catch (RemoteException ex) {
// Assume no ICC card if remote exception which shouldn't happen
return false;
@@ -1997,31 +1997,31 @@
* @see #SIM_STATE_CARD_RESTRICTED
*/
public int getSimState() {
- int slotIdx = getDefaultSim();
- // slotIdx may be invalid due to sim being absent. In that case query all slots to get
+ int slotIndex = getDefaultSim();
+ // slotIndex may be invalid due to sim being absent. In that case query all slots to get
// sim state
- if (slotIdx < 0) {
+ if (slotIndex < 0) {
// query for all slots and return absent if all sim states are absent, otherwise
// return unknown
for (int i = 0; i < getPhoneCount(); i++) {
int simState = getSimState(i);
if (simState != SIM_STATE_ABSENT) {
- Rlog.d(TAG, "getSimState: default sim:" + slotIdx + ", sim state for " +
- "slotIdx=" + i + " is " + simState + ", return state as unknown");
+ Rlog.d(TAG, "getSimState: default sim:" + slotIndex + ", sim state for " +
+ "slotIndex=" + i + " is " + simState + ", return state as unknown");
return SIM_STATE_UNKNOWN;
}
}
- Rlog.d(TAG, "getSimState: default sim:" + slotIdx + ", all SIMs absent, return " +
+ Rlog.d(TAG, "getSimState: default sim:" + slotIndex + ", all SIMs absent, return " +
"state as absent");
return SIM_STATE_ABSENT;
}
- return getSimState(slotIdx);
+ return getSimState(slotIndex);
}
/**
* Returns a constant indicating the state of the device SIM card in a slot.
*
- * @param slotIdx
+ * @param slotIndex
*
* @see #SIM_STATE_UNKNOWN
* @see #SIM_STATE_ABSENT
@@ -2034,8 +2034,8 @@
* @see #SIM_STATE_CARD_IO_ERROR
* @see #SIM_STATE_CARD_RESTRICTED
*/
- public int getSimState(int slotIdx) {
- int simState = SubscriptionManager.getSimStateForSlotIdx(slotIdx);
+ public int getSimState(int slotIndex) {
+ int simState = SubscriptionManager.getSimStateForSlotIndex(slotIndex);
return simState;
}
@@ -3210,12 +3210,12 @@
*
* @hide
*/
- public int getCallStateForSlot(int slotId) {
+ public int getCallStateForSlot(int slotIndex) {
try {
ITelephony telephony = getITelephony();
if (telephony == null)
return CALL_STATE_IDLE;
- return telephony.getCallStateForSlot(slotId);
+ return telephony.getCallStateForSlot(slotIndex);
} catch (RemoteException ex) {
// the phone process is restarting.
return CALL_STATE_IDLE;
@@ -4033,7 +4033,7 @@
/** {@hide} */
public int getDefaultSim() {
- return SubscriptionManager.getSlotId(SubscriptionManager.getDefaultSubscriptionId());
+ return SubscriptionManager.getSlotIndex(SubscriptionManager.getDefaultSubscriptionId());
}
/**
@@ -4398,7 +4398,7 @@
* feature or {@link null} if the service is not available. If an ImsServiceController is
* available, the {@link IImsServiceFeatureListener} callback is registered as a listener for
* feature updates.
- * @param slotId The SIM slot that we are requesting the {@link IImsServiceController} for.
+ * @param slotIndex The SIM slot that we are requesting the {@link IImsServiceController} for.
* @param feature The IMS Feature we are requesting, corresponding to {@link ImsFeature}.
* @param callback Listener that will send updates to ImsManager when there are updates to
* ImsServiceController.
@@ -4406,12 +4406,12 @@
* it is unavailable.
* @hide
*/
- public IImsServiceController getImsServiceControllerAndListen(int slotId, @Feature int feature,
+ public IImsServiceController getImsServiceControllerAndListen(int slotIndex, @Feature int feature,
IImsServiceFeatureListener callback) {
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- return telephony.getImsServiceControllerAndListen(slotId, feature, callback);
+ return telephony.getImsServiceControllerAndListen(slotIndex, feature, callback);
}
} catch (RemoteException e) {
Rlog.e(TAG, "getImsServiceControllerAndListen, RemoteException: " + e.getMessage());
@@ -5641,7 +5641,7 @@
/**
* Set SIM card power state. Request is equivalent to inserting or removing the card.
*
- * @param slotId SIM slot id
+ * @param slotIndex SIM slot id
* @param powerUp True if powering up the SIM, otherwise powering down
*
* <p>Requires Permission:
@@ -5649,11 +5649,11 @@
*
* @hide
**/
- public void setSimPowerStateForSlot(int slotId, boolean powerUp) {
+ public void setSimPowerStateForSlot(int slotIndex, boolean powerUp) {
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- telephony.setSimPowerStateForSlot(slotId, powerUp);
+ telephony.setSimPowerStateForSlot(slotIndex, powerUp);
}
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelephony#setSimPowerStateForSlot", e);
@@ -6237,7 +6237,7 @@
}
/**
- * Set the allowed carrier list for slotId
+ * Set the allowed carrier list for slotIndex
* Require system privileges. In the future we may add this to carrier APIs.
*
* <p>Requires Permission:
@@ -6251,11 +6251,11 @@
* @hide
*/
@SystemApi
- public int setAllowedCarriers(int slotId, List<CarrierIdentifier> carriers) {
+ public int setAllowedCarriers(int slotIndex, List<CarrierIdentifier> carriers) {
try {
ITelephony service = getITelephony();
if (service != null) {
- return service.setAllowedCarriers(slotId, carriers);
+ return service.setAllowedCarriers(slotIndex, carriers);
}
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelephony#setAllowedCarriers", e);
@@ -6266,7 +6266,7 @@
}
/**
- * Get the allowed carrier list for slotId.
+ * Get the allowed carrier list for slotIndex.
* Require system privileges. In the future we may add this to carrier APIs.
*
* <p>Requires Permission:
@@ -6280,11 +6280,11 @@
* @hide
*/
@SystemApi
- public List<CarrierIdentifier> getAllowedCarriers(int slotId) {
+ public List<CarrierIdentifier> getAllowedCarriers(int slotIndex) {
try {
ITelephony service = getITelephony();
if (service != null) {
- return service.getAllowedCarriers(slotId);
+ return service.getAllowedCarriers(slotIndex);
}
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelephony#getAllowedCarriers", e);
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index f6aef08..71f2c6b 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -52,12 +52,12 @@
SubscriptionInfo getActiveSubscriptionInfoForIccId(String iccId, String callingPackage);
/**
- * Get the active SubscriptionInfo associated with the slotIdx
- * @param slotIdx the slot which the subscription is inserted
+ * Get the active SubscriptionInfo associated with the slotIndex
+ * @param slotIndex the slot which the subscription is inserted
* @param callingPackage The package maing the call.
* @return SubscriptionInfo, maybe null if its not active
*/
- SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIdx, String callingPackage);
+ SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex, String callingPackage);
/**
* Get the SubscriptionInfo(s) of the active subscriptions. The records will be sorted
@@ -96,10 +96,10 @@
/**
* Add a new SubscriptionInfo to subinfo database if needed
* @param iccId the IccId of the SIM card
- * @param slotId the slot which the SIM is inserted
+ * @param slotIndex the slot which the SIM is inserted
* @return the URL of the newly created row or the updated row
*/
- int addSubInfoRecord(String iccId, int slotId);
+ int addSubInfoRecord(String iccId, int slotIndex);
/**
* Set SIM icon tint color by simInfo index
@@ -142,9 +142,9 @@
*/
int setDataRoaming(int roaming, int subId);
- int getSlotId(int subId);
+ int getSlotIndex(int subId);
- int[] getSubId(int slotId);
+ int[] getSubId(int slotIndex);
int getDefaultSubId();
@@ -177,10 +177,10 @@
String getSubscriptionProperty(int subId, String propKey, String callingPackage);
/**
- * Get the SIM state for the slot idx
+ * Get the SIM state for the slot index
* @return SIM state as the ordinal of IccCardConstants.State
*/
- int getSimStateForSlotIdx(int slotIdx);
+ int getSimStateForSlotIndex(int slotIndex);
boolean isActiveSubId(int subId);
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 40d1dbb..9d12c24 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -381,7 +381,7 @@
/**
* Returns the call state for a slot.
*/
- int getCallStateForSlot(int slotId);
+ int getCallStateForSlot(int slotIndex);
int getDataActivity();
int getDataState();
@@ -397,9 +397,9 @@
* Returns the current active phone type as integer for particular slot.
* Returns TelephonyManager.PHONE_TYPE_CDMA if RILConstants.CDMA_PHONE
* and TelephonyManager.PHONE_TYPE_GSM if RILConstants.GSM_PHONE
- * @param slotId - slot to query.
+ * @param slotIndex - slot to query.
*/
- int getActivePhoneTypeForSlot(int slotId);
+ int getActivePhoneTypeForSlot(int slotIndex);
/**
* Returns the CDMA ERI icon index to display
@@ -573,10 +573,10 @@
/**
* Return true if an ICC card is present for a subId.
- * @param slotId user preferred slotId.
+ * @param slotIndex user preferred slotIndex.
* Return true if an ICC card is present
*/
- boolean hasIccCardUsingSlotId(int slotId);
+ boolean hasIccCardUsingSlotIndex(int slotIndex);
/**
* Return if the current radio is LTE on CDMA. This
@@ -777,7 +777,7 @@
* requested as well as registering the ImsServiceController for callbacks using the
* IImsServiceFeatureListener interface.
*/
- IImsServiceController getImsServiceControllerAndListen(int slotId, int feature,
+ IImsServiceController getImsServiceControllerAndListen(int slotIndex, int feature,
IImsServiceFeatureListener callback);
/**
@@ -1085,22 +1085,22 @@
/**
* Returns the IMEI for the given slot.
*
- * @param slotId - device slot.
+ * @param slotIndex - device slot.
* @param callingPackage The package making the call.
* <p>Requires Permission:
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
*/
- String getImeiForSlot(int slotId, String callingPackage);
+ String getImeiForSlot(int slotIndex, String callingPackage);
/**
* Returns the device software version.
*
- * @param slotId - device slot.
+ * @param slotIndex - device slot.
* @param callingPackage The package making the call.
* <p>Requires Permission:
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
*/
- String getDeviceSoftwareVersionForSlot(int slotId, String callingPackage);
+ String getDeviceSoftwareVersionForSlot(int slotIndex, String callingPackage);
/**
* Returns the subscription ID associated with the specified PhoneAccount.
@@ -1226,22 +1226,22 @@
List<TelephonyHistogram> getTelephonyHistograms();
/**
- * Set the allowed carrier list for slotId
+ * Set the allowed carrier list for slotIndex
* Require system privileges. In the future we may add this to carrier APIs.
*
* @return The number of carriers set successfully. Should match length of
* carriers on success.
*/
- int setAllowedCarriers(int slotId, in List<CarrierIdentifier> carriers);
+ int setAllowedCarriers(int slotIndex, in List<CarrierIdentifier> carriers);
/**
- * Get the allowed carrier list for slotId.
+ * Get the allowed carrier list for slotIndex.
* Require system privileges. In the future we may add this to carrier APIs.
*
* @return List of {@link android.service.carrier.CarrierIdentifier}; empty list
* means all carriers are allowed.
*/
- List<CarrierIdentifier> getAllowedCarriers(int slotId);
+ List<CarrierIdentifier> getAllowedCarriers(int slotIndex);
/**
* Action set from carrier signalling broadcast receivers to enable/disable metered apns
@@ -1288,11 +1288,11 @@
/**
* Set SIM card power state. Request is equivalent to inserting or removing the card.
- * @param slotId SIM slot id
+ * @param slotIndex SIM slot id
* @param powerUp True if powering up the SIM, otherwise powering down
* @hide
* */
- void setSimPowerStateForSlot(int slotId, boolean powerUp);
+ void setSimPowerStateForSlot(int slotIndex, boolean powerUp);
/**
* Returns a list of Forbidden PLMNs from the specified SIM App
diff --git a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
index b12ed94..2757296 100644
--- a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
+++ b/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
@@ -24,6 +24,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.VibrationEffect;
import android.test.suitebuilder.annotation.SmallTest;
/**
@@ -48,7 +49,9 @@
*/
public void testVibrate() throws RemoteException {
try {
- mVibratorService.vibrate(Process.myUid(), null, 2000, AudioManager.STREAM_ALARM,
+ final VibrationEffect effect =
+ VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE);
+ mVibratorService.vibrate(Process.myUid(), null, effect, AudioManager.STREAM_ALARM,
new Binder());
fail("vibrate did not throw SecurityException as expected");
} catch (SecurityException e) {
@@ -57,23 +60,6 @@
}
/**
- * Test that calling {@link android.os.IVibratorService#vibratePattern(long[],
- * int, android.os.IBinder)} requires permissions.
- * <p>Tests permission:
- * {@link android.Manifest.permission#VIBRATE}
- * @throws RemoteException
- */
- public void testVibratePattern() throws RemoteException {
- try {
- mVibratorService.vibratePattern(Process.myUid(), null, new long[] {0}, 0,
- AudioManager.STREAM_ALARM, new Binder());
- fail("vibratePattern did not throw SecurityException as expected");
- } catch (SecurityException e) {
- // expected
- }
- }
-
- /**
* Test that calling {@link android.os.IVibratorService#cancelVibrate()} requires permissions.
* <p>Tests permission:
* {@link android.Manifest.permission#VIBRATE}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
index 11328dc..e118889 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
@@ -174,6 +174,12 @@
}
@LayoutlibDelegate
+ /*package*/ static synchronized int[] nativeGetSupportedAxes(long native_instance) {
+ // nativeCreateFromTypefaceWithVariation is not supported so we do not keep the axes
+ return null;
+ }
+
+ @LayoutlibDelegate
/*package*/ static long nativeCreateWeightAlias(long native_instance, int weight) {
Typeface_Delegate delegate = sManager.getDelegate(native_instance);
if (delegate == null) {