Merge "Mark some system intents as SystemApi"
diff --git a/Android.mk b/Android.mk
index e766e60..b98d3bc 100644
--- a/Android.mk
+++ b/Android.mk
@@ -109,6 +109,7 @@
core/java/android/app/backup/IRestoreObserver.aidl \
core/java/android/app/backup/IRestoreSession.aidl \
core/java/android/app/backup/ISelectBackupTransportCallback.aidl \
+ core/java/android/app/usage/ICacheQuotaService.aidl \
core/java/android/app/usage/IStorageStatsManager.aidl \
core/java/android/app/usage/IUsageStatsManager.aidl \
core/java/android/bluetooth/IBluetooth.aidl \
@@ -322,6 +323,7 @@
core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl \
core/java/android/view/IDockedStackListener.aidl \
core/java/android/view/IGraphicsStats.aidl \
+ core/java/android/view/IGraphicsStatsCallback.aidl \
core/java/android/view/IInputFilter.aidl \
core/java/android/view/IInputFilterHost.aidl \
core/java/android/view/IOnKeyguardExitResult.aidl \
@@ -561,6 +563,7 @@
LOCAL_MODULE := framework
+LOCAL_DX_FLAGS := --core-library --multi-dex
LOCAL_JACK_FLAGS := --multi-dex native
LOCAL_RMTYPEDEFS := true
@@ -707,6 +710,7 @@
frameworks/base/core/java/android/service/notification/StatusBarNotification.aidl \
frameworks/base/core/java/android/service/chooser/ChooserTarget.aidl \
frameworks/base/core/java/android/speech/tts/Voice.aidl \
+ frameworks/base/core/java/android/app/usage/CacheQuotaHint.aidl \
frameworks/base/core/java/android/app/usage/ExternalStorageStats.aidl \
frameworks/base/core/java/android/app/usage/StorageStats.aidl \
frameworks/base/core/java/android/app/usage/UsageEvents.aidl \
@@ -1411,6 +1415,8 @@
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := ext
+LOCAL_DX_FLAGS := --core-library
+
ifneq ($(INCREMENTAL_BUILDS),)
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_JACK_ENABLED := incremental
@@ -1418,6 +1424,24 @@
include $(BUILD_JAVA_LIBRARY)
+# ==== c++ proto device library ==============================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libplatformprotos
+# b/34740546, work around clang-tidy segmentation fault.
+LOCAL_TIDY_CHECKS := -modernize*
+LOCAL_PROTOC_OPTIMIZE_TYPE := lite
+LOCAL_PROTOC_FLAGS := \
+ --include_source_info \
+ -Iexternal/protobuf/src
+LOCAL_SRC_FILES := \
+ $(call all-proto-files-under, core/proto) \
+ $(call all-proto-files-under, libs/incident/proto)
+LOCAL_C_INCLUDES := \
+ $(call generated-sources-dir-for,STATIC_LIBRARIES,libplatformprotos,)/proto
+LOCAL_EXPORT_C_INCLUDES := \
+ $(call generated-sources-dir-for,STATIC_LIBRARIES,libplatformprotos,)/proto
+include $(BUILD_STATIC_LIBRARY)
+
# ==== c++ proto host library ==============================
include $(CLEAR_VARS)
LOCAL_MODULE := libplatformprotos
diff --git a/api/current.txt b/api/current.txt
index 4528712..475aa54 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -30,7 +30,6 @@
field public static final java.lang.String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD";
field public static final java.lang.String BIND_MIDI_DEVICE_SERVICE = "android.permission.BIND_MIDI_DEVICE_SERVICE";
field public static final java.lang.String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE";
- field public static final java.lang.String BIND_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE";
field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
field public static final java.lang.String BIND_QUICK_SETTINGS_TILE = "android.permission.BIND_QUICK_SETTINGS_TILE";
@@ -298,6 +297,7 @@
field public static final int authorities = 16842776; // 0x1010018
field public static final int autoAdvanceViewId = 16843535; // 0x101030f
field public static final int autoCompleteTextViewStyle = 16842859; // 0x101006b
+ field public static final int autoFillMode = 16844116; // 0x1010554
field public static final int autoLink = 16842928; // 0x10100b0
field public static final int autoMirrored = 16843754; // 0x10103ea
field public static final int autoRemoveFromRecents = 16843847; // 0x1010447
@@ -3655,6 +3655,7 @@
method public void onLowMemory();
method public boolean onMenuItemSelected(int, android.view.MenuItem);
method public boolean onMenuOpened(int, android.view.Menu);
+ method public void onMovedToDisplay(int);
method public void onMultiWindowModeChanged(boolean);
method public boolean onNavigateUp();
method public boolean onNavigateUpFromChild(android.app.Activity);
@@ -6199,9 +6200,9 @@
method public void addUserRestriction(android.content.ComponentName, java.lang.String);
method public boolean bindDeviceAdminServiceAsUser(android.content.ComponentName, android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle);
method public void clearCrossProfileIntentFilters(android.content.ComponentName);
- method public void clearDeviceOwnerApp(java.lang.String);
+ method public deprecated void clearDeviceOwnerApp(java.lang.String);
method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
- method public void clearProfileOwner(android.content.ComponentName);
+ method public deprecated void clearProfileOwner(android.content.ComponentName);
method public boolean clearResetPasswordToken(android.content.ComponentName);
method public void clearUserRestriction(android.content.ComponentName, java.lang.String);
method public android.content.Intent createAdminSupportIntent(java.lang.String);
@@ -13164,6 +13165,7 @@
field public static final deprecated int LA_88 = 10; // 0xa
field public static final deprecated int L_8 = 9; // 0x9
field public static final int OPAQUE = -1; // 0xffffffff
+ field public static final int RGBA_1010102 = 43; // 0x2b
field public static final deprecated int RGBA_4444 = 7; // 0x7
field public static final deprecated int RGBA_5551 = 6; // 0x6
field public static final int RGBA_8888 = 1; // 0x1
@@ -13489,6 +13491,21 @@
package android.graphics.drawable {
+ public class AdaptiveIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
+ method public void draw(android.graphics.Canvas);
+ method public android.graphics.drawable.Drawable getBackground();
+ method public static float getExtraInsetPercentage();
+ method public android.graphics.drawable.Drawable getForeground();
+ method public android.graphics.Path getIconMask();
+ method public int getOpacity();
+ method public void invalidateDrawable(android.graphics.drawable.Drawable);
+ method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
+ method public void setAlpha(int);
+ method public void setColorFilter(android.graphics.ColorFilter);
+ method public void setOpacity(int);
+ method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
+ }
+
public abstract interface Animatable {
method public abstract boolean isRunning();
method public abstract void start();
@@ -13878,21 +13895,6 @@
method public void addLevel(int, int, android.graphics.drawable.Drawable);
}
- public class MaskableIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
- method public void draw(android.graphics.Canvas);
- method public android.graphics.drawable.Drawable getBackground();
- method public static float getExtraInsetPercentage();
- method public android.graphics.drawable.Drawable getForeground();
- method public android.graphics.Path getIconMask();
- method public int getOpacity();
- method public void invalidateDrawable(android.graphics.drawable.Drawable);
- method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
- method public void setAlpha(int);
- method public void setColorFilter(android.graphics.ColorFilter);
- method public void setOpacity(int);
- method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
- }
-
public class NinePatchDrawable extends android.graphics.drawable.Drawable {
ctor public deprecated NinePatchDrawable(android.graphics.Bitmap, byte[], android.graphics.Rect, java.lang.String);
ctor public NinePatchDrawable(android.content.res.Resources, android.graphics.Bitmap, byte[], android.graphics.Rect, java.lang.String);
@@ -14394,6 +14396,7 @@
method public void writeToParcel(android.os.Parcel, int);
field public static final int BLOB = 33; // 0x21
field public static final android.os.Parcelable.Creator<android.hardware.HardwareBuffer> CREATOR;
+ field public static final int RGBA_1010102 = 43; // 0x2b
field public static final int RGBA_8888 = 1; // 0x1
field public static final int RGBA_FP16 = 22; // 0x16
field public static final int RGBX_8888 = 2; // 0x2
@@ -22478,6 +22481,7 @@
ctor public MediaRecorder();
method public static final int getAudioSourceMax();
method public int getMaxAmplitude() throws java.lang.IllegalStateException;
+ method public android.os.Bundle getMetrics();
method public android.view.Surface getSurface();
method public void pause() throws java.lang.IllegalStateException;
method public void prepare() throws java.io.IOException, java.lang.IllegalStateException;
@@ -31251,10 +31255,8 @@
method public void allocateBytes(java.io.File, long, int) throws java.io.IOException;
method public void allocateBytes(java.io.FileDescriptor, long, int) throws java.io.IOException;
method public long getAllocatableBytes(java.io.File, int) throws java.io.IOException;
- method public long getCacheQuotaBytes();
- method public long getCacheSizeBytes();
- method public long getExternalCacheQuotaBytes();
- method public long getExternalCacheSizeBytes();
+ method public long getCacheQuotaBytes(java.io.File);
+ method public long getCacheSizeBytes(java.io.File);
method public java.lang.String getMountedObbPath(java.lang.String);
method public android.os.storage.StorageVolume getPrimaryStorageVolume();
method public android.os.storage.StorageVolume getStorageVolume(java.io.File);
@@ -34262,7 +34264,7 @@
method public static final deprecated void setLocationProviderEnabled(android.content.ContentResolver, java.lang.String, boolean);
field public static final java.lang.String ACCESSIBILITY_DISPLAY_INVERSION_ENABLED = "accessibility_display_inversion_enabled";
field public static final java.lang.String ACCESSIBILITY_ENABLED = "accessibility_enabled";
- field public static final java.lang.String ACCESSIBILITY_SPEAK_PASSWORD = "speak_password";
+ field public static final deprecated java.lang.String ACCESSIBILITY_SPEAK_PASSWORD = "speak_password";
field public static final deprecated java.lang.String ADB_ENABLED = "adb_enabled";
field public static final java.lang.String ALLOWED_GEOLOCATION_ORIGINS = "allowed_geolocation_origins";
field public static final deprecated java.lang.String ALLOW_MOCK_LOCATION = "mock_location";
@@ -36242,9 +36244,10 @@
}
public static final class Dataset.Builder {
- ctor public Dataset.Builder(java.lang.CharSequence);
+ 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 setPresentation(android.widget.RemoteViews);
method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue);
}
@@ -36266,6 +36269,7 @@
method public android.service.autofill.FillResponse build();
method public android.service.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender);
method public android.service.autofill.FillResponse.Builder setExtras(android.os.Bundle);
+ method public android.service.autofill.FillResponse.Builder setPresentation(android.widget.RemoteViews);
}
public final class SaveCallback {
@@ -36477,22 +36481,6 @@
package android.service.notification {
- public final class Adjustment implements android.os.Parcelable {
- ctor public Adjustment(java.lang.String, java.lang.String, android.os.Bundle, java.lang.CharSequence, int);
- ctor protected Adjustment(android.os.Parcel);
- method public int describeContents();
- method public java.lang.CharSequence getExplanation();
- method public java.lang.String getKey();
- method public java.lang.String getPackage();
- method public android.os.Bundle getSignals();
- method public int getUser();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
- field public static final java.lang.String KEY_CHANNEL_ID = "key_channel_id";
- field public static final java.lang.String KEY_PEOPLE = "key_people";
- field public static final java.lang.String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
- }
-
public final class Condition implements android.os.Parcelable {
ctor public Condition(android.net.Uri, java.lang.String, int);
ctor public Condition(android.net.Uri, java.lang.String, java.lang.String, java.lang.String, int, int, int);
@@ -36539,21 +36527,6 @@
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.ConditionProviderService";
}
- public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService {
- ctor public NotificationAssistantService();
- method public final void adjustNotification(android.service.notification.Adjustment);
- method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
- method public void createNotificationChannel(java.lang.String, android.app.NotificationChannel);
- method public void deleteNotificationChannel(java.lang.String, java.lang.String);
- method public java.util.List<android.app.NotificationChannel> getNotificationChannels(java.lang.String);
- method public final android.os.IBinder onBind(android.content.Intent);
- method public abstract android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification);
- method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, java.lang.String);
- method public final void unsnoozeNotification(java.lang.String);
- method public void updateNotificationChannel(java.lang.String, android.app.NotificationChannel);
- field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
- }
-
public abstract class NotificationListenerService extends android.app.Service {
ctor public NotificationListenerService();
method public final void cancelAllNotifications();
@@ -36582,7 +36555,6 @@
method public static void requestRebind(android.content.ComponentName);
method public final void requestUnbind();
method public final void setNotificationsShown(java.lang.String[]);
- method public final void snoozeNotification(java.lang.String, java.lang.String);
method public final void snoozeNotification(java.lang.String, long);
field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4
field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
@@ -36619,14 +36591,12 @@
public static class NotificationListenerService.Ranking {
ctor public NotificationListenerService.Ranking();
method public boolean canShowBadge();
- method public java.util.List<java.lang.String> getAdditionalPeople();
method public android.app.NotificationChannel getChannel();
method public int getImportance();
method public java.lang.CharSequence getImportanceExplanation();
method public java.lang.String getKey();
method public java.lang.String getOverrideGroupKey();
method public int getRank();
- method public java.util.List<android.service.notification.SnoozeCriterion> getSnoozeCriteria();
method public int getSuppressedVisualEffects();
method public boolean isAmbient();
method public boolean matchesInterruptionFilter();
@@ -36640,17 +36610,6 @@
field public static final android.os.Parcelable.Creator<android.service.notification.NotificationListenerService.RankingMap> CREATOR;
}
- public final class SnoozeCriterion implements android.os.Parcelable {
- ctor public SnoozeCriterion(java.lang.String, java.lang.CharSequence, java.lang.CharSequence);
- ctor protected SnoozeCriterion(android.os.Parcel);
- method public int describeContents();
- method public java.lang.CharSequence getConfirmation();
- method public java.lang.CharSequence getExplanation();
- method public java.lang.String getId();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.service.notification.SnoozeCriterion> CREATOR;
- }
-
public class StatusBarNotification implements android.os.Parcelable {
ctor public deprecated StatusBarNotification(java.lang.String, java.lang.String, int, java.lang.String, int, int, int, android.app.Notification, android.os.UserHandle, long);
ctor public StatusBarNotification(android.os.Parcel);
@@ -43330,6 +43289,7 @@
field public static final int SOURCE_KEYBOARD = 257; // 0x101
field public static final int SOURCE_MOUSE = 8194; // 0x2002
field public static final int SOURCE_MOUSE_RELATIVE = 131076; // 0x20004
+ field public static final int SOURCE_ROTARY_ENCODER = 4194304; // 0x400000
field public static final int SOURCE_STYLUS = 16386; // 0x4002
field public static final int SOURCE_TOUCHPAD = 1048584; // 0x100008
field public static final int SOURCE_TOUCHSCREEN = 4098; // 0x1002
@@ -44550,6 +44510,7 @@
method public float getAlpha();
method public android.view.animation.Animation getAnimation();
method public android.os.IBinder getApplicationWindowToken();
+ method public int getAutoFillMode();
method public android.view.autofill.AutoFillType getAutoFillType();
method public android.view.autofill.AutoFillValue getAutoFillValue();
method public android.graphics.drawable.Drawable getBackground();
@@ -44789,6 +44750,7 @@
method public boolean onKeyUp(int, android.view.KeyEvent);
method protected void onLayout(boolean, int, int, int, int);
method protected void onMeasure(int, int);
+ method public void onMovedToDisplay(int);
method protected void onOverScrolled(int, int, boolean, boolean);
method public void onPointerCaptureChange(boolean);
method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
@@ -44865,6 +44827,7 @@
method public void setActivated(boolean);
method public void setAlpha(float);
method public void setAnimation(android.view.animation.Animation);
+ method public void setAutoFillMode(int);
method public void setBackground(android.graphics.drawable.Drawable);
method public void setBackgroundColor(int);
method public deprecated void setBackgroundDrawable(android.graphics.drawable.Drawable);
@@ -45005,6 +44968,9 @@
field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
+ field public static final int AUTO_FILL_MODE_AUTO = 1; // 0x1
+ field public static final int AUTO_FILL_MODE_INHERIT = 0; // 0x0
+ field public static final int AUTO_FILL_MODE_MANUAL = 2; // 0x2
field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
@@ -45671,6 +45637,7 @@
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 setLongClickable(boolean);
+ method public abstract void setSanitized(boolean);
method public abstract void setSelected(boolean);
method public abstract void setText(java.lang.CharSequence);
method public abstract void setText(java.lang.CharSequence, int, int);
@@ -45679,7 +45646,6 @@
method public abstract void setTransformation(android.graphics.Matrix);
method public abstract void setUrl(java.lang.String);
method public abstract void setVisibility(int);
- field public static final int AUTO_FILL_FLAG_SANITIZED = 1; // 0x1
}
public final class ViewStub extends android.view.View {
@@ -50551,11 +50517,9 @@
method public void removeTextChangedListener(android.text.TextWatcher);
method public void setAllCaps(boolean);
method public final void setAutoLinkMask(int);
- method public void setAutoSizeMaxTextSize(int, float);
- method public void setAutoSizeMinTextSize(int, float);
- method public void setAutoSizeStepGranularity(int, float);
- method public void setAutoSizeTextPresetSizes(int[]);
- method public void setAutoSizeTextType(int);
+ method public void setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) throws java.lang.IllegalArgumentException;
+ method public void setAutoSizeTextTypeUniformWithPresetSizes(int[], int) throws java.lang.IllegalArgumentException;
+ method public void setAutoSizeTextTypeWithDefaults(int);
method public void setBreakStrategy(int);
method public void setCompoundDrawablePadding(int);
method public void setCompoundDrawableTintList(android.content.res.ColorStateList);
@@ -51080,6 +51044,8 @@
field public static final int OP_INT_TO_FLOAT = 130; // 0x82
field public static final int OP_INT_TO_LONG = 129; // 0x81
field public static final int OP_INT_TO_SHORT = 143; // 0x8f
+ field public static final int OP_INVOKE_CUSTOM = 252; // 0xfc
+ field public static final int OP_INVOKE_CUSTOM_RANGE = 253; // 0xfd
field public static final int OP_INVOKE_DIRECT = 112; // 0x70
field public static final deprecated int OP_INVOKE_DIRECT_EMPTY = 240; // 0xf0
field public static final int OP_INVOKE_DIRECT_JUMBO = 9471; // 0x24ff
@@ -51270,7 +51236,7 @@
method public static dalvik.system.DexFile loadDex(java.lang.String, java.lang.String, int) throws java.io.IOException;
}
- public final class InMemoryDexClassLoader extends java.lang.ClassLoader {
+ public final class InMemoryDexClassLoader extends dalvik.system.BaseDexClassLoader {
ctor public InMemoryDexClassLoader(java.nio.ByteBuffer, java.lang.ClassLoader);
}
diff --git a/api/removed.txt b/api/removed.txt
index ab22b6e..e467811 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -180,6 +180,10 @@
package android.os.storage {
public class StorageManager {
+ method public deprecated long getCacheQuotaBytes();
+ method public deprecated long getCacheSizeBytes();
+ method public deprecated long getExternalCacheQuotaBytes();
+ method public deprecated long getExternalCacheSizeBytes();
method public android.os.storage.StorageVolume getPrimaryVolume();
method public android.os.storage.StorageVolume[] getVolumeList();
}
diff --git a/api/system-current.txt b/api/system-current.txt
index 0dadb4e..529781e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -410,6 +410,7 @@
field public static final int authorities = 16842776; // 0x1010018
field public static final int autoAdvanceViewId = 16843535; // 0x101030f
field public static final int autoCompleteTextViewStyle = 16842859; // 0x101006b
+ field public static final int autoFillMode = 16844116; // 0x1010554
field public static final int autoLink = 16842928; // 0x10100b0
field public static final int autoMirrored = 16843754; // 0x10103ea
field public static final int autoRemoveFromRecents = 16843847; // 0x1010447
@@ -3779,6 +3780,7 @@
method public void onLowMemory();
method public boolean onMenuItemSelected(int, android.view.MenuItem);
method public boolean onMenuOpened(int, android.view.Menu);
+ method public void onMovedToDisplay(int);
method public void onMultiWindowModeChanged(boolean);
method public boolean onNavigateUp();
method public boolean onNavigateUpFromChild(android.app.Activity);
@@ -6404,9 +6406,9 @@
method public void addUserRestriction(android.content.ComponentName, java.lang.String);
method public boolean bindDeviceAdminServiceAsUser(android.content.ComponentName, android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle);
method public void clearCrossProfileIntentFilters(android.content.ComponentName);
- method public void clearDeviceOwnerApp(java.lang.String);
+ method public deprecated void clearDeviceOwnerApp(java.lang.String);
method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
- method public void clearProfileOwner(android.content.ComponentName);
+ method public deprecated void clearProfileOwner(android.content.ComponentName);
method public boolean clearResetPasswordToken(android.content.ComponentName);
method public void clearUserRestriction(android.content.ComponentName, java.lang.String);
method public android.content.Intent createAdminSupportIntent(java.lang.String);
@@ -6940,14 +6942,22 @@
field public static final java.lang.String EXTRA_LOG_EVENT_ID = "android.app.backup.extra.LOG_EVENT_ID";
field public static final java.lang.String EXTRA_LOG_EVENT_PACKAGE_NAME = "android.app.backup.extra.LOG_EVENT_PACKAGE_NAME";
field public static final java.lang.String EXTRA_LOG_EVENT_PACKAGE_VERSION = "android.app.backup.extra.LOG_EVENT_PACKAGE_VERSION";
+ field public static final java.lang.String EXTRA_LOG_OLD_VERSION = "android.app.backup.extra.LOG_OLD_VERSION";
field public static final int LOG_EVENT_CATEGORY_AGENT = 2; // 0x2
field public static final int LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY = 3; // 0x3
field public static final int LOG_EVENT_CATEGORY_TRANSPORT = 1; // 0x1
+ field public static final int LOG_EVENT_ID_APP_HAS_NO_AGENT = 28; // 0x1c
+ field public static final int LOG_EVENT_ID_CANT_FIND_AGENT = 30; // 0x1e
field public static final int LOG_EVENT_ID_FULL_BACKUP_TIMEOUT = 4; // 0x4
field public static final int LOG_EVENT_ID_FULL_RESTORE_TIMEOUT = 45; // 0x2d
field public static final int LOG_EVENT_ID_KEY_VALUE_BACKUP_TIMEOUT = 21; // 0x15
field public static final int LOG_EVENT_ID_KEY_VALUE_RESTORE_TIMEOUT = 31; // 0x1f
field public static final int LOG_EVENT_ID_NO_PACKAGES = 49; // 0x31
+ field public static final int LOG_EVENT_ID_NO_RESTORE_METADATA_AVAILABLE = 22; // 0x16
+ field public static final int LOG_EVENT_ID_PACKAGE_NOT_FOUND = 12; // 0xc
+ field public static final int LOG_EVENT_ID_PACKAGE_NOT_PRESENT = 26; // 0x1a
+ field public static final int LOG_EVENT_ID_PACKAGE_TRANSPORT_NOT_PRESENT = 15; // 0xf
+ field public static final int LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER = 36; // 0x24
}
public abstract class BackupObserver {
@@ -7178,6 +7188,35 @@
package android.app.usage {
+ public final class CacheQuotaHint implements android.os.Parcelable {
+ ctor public CacheQuotaHint(android.app.usage.CacheQuotaHint.Builder);
+ method public int describeContents();
+ method public long getQuota();
+ method public int getUid();
+ method public android.app.usage.UsageStats getUsageStats();
+ method public java.lang.String getVolumeUuid();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.usage.CacheQuotaHint> CREATOR;
+ field public static final long QUOTA_NOT_SET = -1L; // 0xffffffffffffffffL
+ }
+
+ public static final class CacheQuotaHint.Builder {
+ ctor public CacheQuotaHint.Builder();
+ ctor public CacheQuotaHint.Builder(android.app.usage.CacheQuotaHint);
+ method public android.app.usage.CacheQuotaHint build();
+ method public android.app.usage.CacheQuotaHint.Builder setQuota(long);
+ method public android.app.usage.CacheQuotaHint.Builder setUid(int);
+ method public android.app.usage.CacheQuotaHint.Builder setUsageStats(android.app.usage.UsageStats);
+ method public android.app.usage.CacheQuotaHint.Builder setVolumeUuid(java.lang.String);
+ }
+
+ public abstract class CacheQuotaService extends android.app.Service {
+ ctor public CacheQuotaService();
+ method public android.os.IBinder onBind(android.content.Intent);
+ method public abstract java.util.List<android.app.usage.CacheQuotaHint> onComputeCacheQuotaHints(java.util.List<android.app.usage.CacheQuotaHint>);
+ field public static final java.lang.String SERVICE_INTERFACE = "android.app.usage.CacheQuotaService";
+ }
+
public final class ConfigurationStats implements android.os.Parcelable {
ctor public ConfigurationStats(android.app.usage.ConfigurationStats);
method public int describeContents();
@@ -8990,6 +9029,7 @@
method public abstract android.content.pm.PackageManager getPackageManager();
method public abstract java.lang.String getPackageName();
method public abstract java.lang.String getPackageResourcePath();
+ method public abstract java.io.File getPreloadsFileCache();
method public abstract android.content.res.Resources getResources();
method public abstract android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
method public final java.lang.String getString(int);
@@ -9201,6 +9241,7 @@
method public android.content.pm.PackageManager getPackageManager();
method public java.lang.String getPackageName();
method public java.lang.String getPackageResourcePath();
+ method public java.io.File getPreloadsFileCache();
method public android.content.res.Resources getResources();
method public android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
method public java.lang.Object getSystemService(java.lang.String);
@@ -13760,6 +13801,7 @@
field public static final deprecated int LA_88 = 10; // 0xa
field public static final deprecated int L_8 = 9; // 0x9
field public static final int OPAQUE = -1; // 0xffffffff
+ field public static final int RGBA_1010102 = 43; // 0x2b
field public static final deprecated int RGBA_4444 = 7; // 0x7
field public static final deprecated int RGBA_5551 = 6; // 0x6
field public static final int RGBA_8888 = 1; // 0x1
@@ -14085,6 +14127,21 @@
package android.graphics.drawable {
+ public class AdaptiveIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
+ method public void draw(android.graphics.Canvas);
+ method public android.graphics.drawable.Drawable getBackground();
+ method public static float getExtraInsetPercentage();
+ method public android.graphics.drawable.Drawable getForeground();
+ method public android.graphics.Path getIconMask();
+ method public int getOpacity();
+ method public void invalidateDrawable(android.graphics.drawable.Drawable);
+ method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
+ method public void setAlpha(int);
+ method public void setColorFilter(android.graphics.ColorFilter);
+ method public void setOpacity(int);
+ method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
+ }
+
public abstract interface Animatable {
method public abstract boolean isRunning();
method public abstract void start();
@@ -14474,21 +14531,6 @@
method public void addLevel(int, int, android.graphics.drawable.Drawable);
}
- public class MaskableIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
- method public void draw(android.graphics.Canvas);
- method public android.graphics.drawable.Drawable getBackground();
- method public static float getExtraInsetPercentage();
- method public android.graphics.drawable.Drawable getForeground();
- method public android.graphics.Path getIconMask();
- method public int getOpacity();
- method public void invalidateDrawable(android.graphics.drawable.Drawable);
- method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
- method public void setAlpha(int);
- method public void setColorFilter(android.graphics.ColorFilter);
- method public void setOpacity(int);
- method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
- }
-
public class NinePatchDrawable extends android.graphics.drawable.Drawable {
ctor public deprecated NinePatchDrawable(android.graphics.Bitmap, byte[], android.graphics.Rect, java.lang.String);
ctor public NinePatchDrawable(android.content.res.Resources, android.graphics.Bitmap, byte[], android.graphics.Rect, java.lang.String);
@@ -14990,6 +15032,7 @@
method public void writeToParcel(android.os.Parcel, int);
field public static final int BLOB = 33; // 0x21
field public static final android.os.Parcelable.Creator<android.hardware.HardwareBuffer> CREATOR;
+ field public static final int RGBA_1010102 = 43; // 0x2b
field public static final int RGBA_8888 = 1; // 0x1
field public static final int RGBA_FP16 = 22; // 0x16
field public static final int RGBX_8888 = 2; // 0x2
@@ -24126,6 +24169,7 @@
ctor public MediaRecorder();
method public static final int getAudioSourceMax();
method public int getMaxAmplitude() throws java.lang.IllegalStateException;
+ method public android.os.Bundle getMetrics();
method public android.view.Surface getSurface();
method public void pause() throws java.lang.IllegalStateException;
method public void prepare() throws java.io.IOException, java.lang.IllegalStateException;
@@ -26959,6 +27003,13 @@
public class NetworkBadging {
method public static android.graphics.drawable.Drawable getWifiIcon(int, int, android.content.res.Resources.Theme);
+ field public static final int BADGING_4K = 30; // 0x1e
+ field public static final int BADGING_HD = 20; // 0x14
+ field public static final int BADGING_NONE = 0; // 0x0
+ field public static final int BADGING_SD = 10; // 0xa
+ }
+
+ public static abstract class NetworkBadging.Badging implements java.lang.annotation.Annotation {
}
public final class NetworkCapabilities implements android.os.Parcelable {
@@ -27222,10 +27273,10 @@
field public static final java.lang.String ATTRIBUTES_KEY_BADGING_CURVE = "android.net.attributes.key.BADGING_CURVE";
field public static final java.lang.String ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL = "android.net.attributes.key.HAS_CAPTIVE_PORTAL";
field public static final java.lang.String ATTRIBUTES_KEY_RANKING_SCORE_OFFSET = "android.net.attributes.key.RANKING_SCORE_OFFSET";
- field public static final int BADGING_4K = 30; // 0x1e
- field public static final int BADGING_HD = 20; // 0x14
- field public static final int BADGING_NONE = 0; // 0x0
- field public static final int BADGING_SD = 10; // 0xa
+ field public static final deprecated int BADGING_4K = 30; // 0x1e
+ field public static final deprecated int BADGING_HD = 20; // 0x14
+ field public static final deprecated int BADGING_NONE = 0; // 0x0
+ field public static final deprecated int BADGING_SD = 10; // 0xa
field public static final android.os.Parcelable.Creator<android.net.ScoredNetwork> CREATOR;
field public final android.os.Bundle attributes;
field public final boolean meteredHint;
@@ -27233,7 +27284,7 @@
field public final android.net.RssiCurve rssiCurve;
}
- public static abstract class ScoredNetwork.Badging implements java.lang.annotation.Annotation {
+ public static abstract deprecated class ScoredNetwork.Badging implements java.lang.annotation.Annotation {
}
public class TrafficStats {
@@ -34128,10 +34179,8 @@
method public void allocateBytes(java.io.File, long, int) throws java.io.IOException;
method public void allocateBytes(java.io.FileDescriptor, long, int) throws java.io.IOException;
method public long getAllocatableBytes(java.io.File, int) throws java.io.IOException;
- method public long getCacheQuotaBytes();
- method public long getCacheSizeBytes();
- method public long getExternalCacheQuotaBytes();
- method public long getExternalCacheSizeBytes();
+ method public long getCacheQuotaBytes(java.io.File);
+ method public long getCacheSizeBytes(java.io.File);
method public java.lang.String getMountedObbPath(java.lang.String);
method public android.os.storage.StorageVolume getPrimaryStorageVolume();
method public android.os.storage.StorageVolume getStorageVolume(java.io.File);
@@ -37323,7 +37372,7 @@
method public static final deprecated void setLocationProviderEnabled(android.content.ContentResolver, java.lang.String, boolean);
field public static final java.lang.String ACCESSIBILITY_DISPLAY_INVERSION_ENABLED = "accessibility_display_inversion_enabled";
field public static final java.lang.String ACCESSIBILITY_ENABLED = "accessibility_enabled";
- field public static final java.lang.String ACCESSIBILITY_SPEAK_PASSWORD = "speak_password";
+ field public static final deprecated java.lang.String ACCESSIBILITY_SPEAK_PASSWORD = "speak_password";
field public static final deprecated java.lang.String ADB_ENABLED = "adb_enabled";
field public static final java.lang.String ALLOWED_GEOLOCATION_ORIGINS = "allowed_geolocation_origins";
field public static final deprecated java.lang.String ALLOW_MOCK_LOCATION = "mock_location";
@@ -39315,9 +39364,10 @@
}
public static final class Dataset.Builder {
- ctor public Dataset.Builder(java.lang.CharSequence);
+ 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 setPresentation(android.widget.RemoteViews);
method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue);
}
@@ -39339,6 +39389,7 @@
method public android.service.autofill.FillResponse build();
method public android.service.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender);
method public android.service.autofill.FillResponse.Builder setExtras(android.os.Bundle);
+ method public android.service.autofill.FillResponse.Builder setPresentation(android.widget.RemoteViews);
}
public final class SaveCallback {
@@ -39857,20 +39908,29 @@
public class TrustAgentService extends android.app.Service {
ctor public TrustAgentService();
+ method public final void addEscrowToken(byte[], android.os.UserHandle);
method public final deprecated void grantTrust(java.lang.CharSequence, long, boolean);
method public final void grantTrust(java.lang.CharSequence, long, int);
+ method public final void isEscrowTokenActive(long, android.os.UserHandle);
method public final android.os.IBinder onBind(android.content.Intent);
method public boolean onConfigure(java.util.List<android.os.PersistableBundle>);
method public void onDeviceLocked();
method public void onDeviceUnlockLockout(long);
method public void onDeviceUnlocked();
+ method public void onEscrowTokenAdded(byte[], long, android.os.UserHandle);
+ method public void onEscrowTokenRemoved(long, boolean);
+ method public void onEscrowTokenStateReceived(long, int);
method public void onTrustTimeout();
method public void onUnlockAttempt(boolean);
+ method public final void removeEscrowToken(long, android.os.UserHandle);
method public final void revokeTrust();
method public final void setManagingTrust(boolean);
+ method public final void unlockUserWithToken(long, byte[], android.os.UserHandle);
field public static final int FLAG_GRANT_TRUST_DISMISS_KEYGUARD = 2; // 0x2
field public static final int FLAG_GRANT_TRUST_INITIATED_BY_USER = 1; // 0x1
field public static final java.lang.String SERVICE_INTERFACE = "android.service.trust.TrustAgentService";
+ field public static final int TOKEN_STATE_ACTIVE = 1; // 0x1
+ field public static final int TOKEN_STATE_INACTIVE = 0; // 0x0
field public static final java.lang.String TRUST_AGENT_META_DATA = "android.service.trust.trustagent";
}
@@ -43390,6 +43450,7 @@
method public android.content.pm.PackageManager getPackageManager();
method public java.lang.String getPackageName();
method public java.lang.String getPackageResourcePath();
+ method public java.io.File getPreloadsFileCache();
method public android.content.res.Resources getResources();
method public android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
method public java.lang.Object getSystemService(java.lang.String);
@@ -46773,6 +46834,7 @@
field public static final int SOURCE_KEYBOARD = 257; // 0x101
field public static final int SOURCE_MOUSE = 8194; // 0x2002
field public static final int SOURCE_MOUSE_RELATIVE = 131076; // 0x20004
+ field public static final int SOURCE_ROTARY_ENCODER = 4194304; // 0x400000
field public static final int SOURCE_STYLUS = 16386; // 0x4002
field public static final int SOURCE_TOUCHPAD = 1048584; // 0x100008
field public static final int SOURCE_TOUCHSCREEN = 4098; // 0x1002
@@ -47993,6 +48055,7 @@
method public float getAlpha();
method public android.view.animation.Animation getAnimation();
method public android.os.IBinder getApplicationWindowToken();
+ method public int getAutoFillMode();
method public android.view.autofill.AutoFillType getAutoFillType();
method public android.view.autofill.AutoFillValue getAutoFillValue();
method public android.graphics.drawable.Drawable getBackground();
@@ -48232,6 +48295,7 @@
method public boolean onKeyUp(int, android.view.KeyEvent);
method protected void onLayout(boolean, int, int, int, int);
method protected void onMeasure(int, int);
+ method public void onMovedToDisplay(int);
method protected void onOverScrolled(int, int, boolean, boolean);
method public void onPointerCaptureChange(boolean);
method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
@@ -48308,6 +48372,7 @@
method public void setActivated(boolean);
method public void setAlpha(float);
method public void setAnimation(android.view.animation.Animation);
+ method public void setAutoFillMode(int);
method public void setBackground(android.graphics.drawable.Drawable);
method public void setBackgroundColor(int);
method public deprecated void setBackgroundDrawable(android.graphics.drawable.Drawable);
@@ -48448,6 +48513,9 @@
field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
+ field public static final int AUTO_FILL_MODE_AUTO = 1; // 0x1
+ field public static final int AUTO_FILL_MODE_INHERIT = 0; // 0x0
+ field public static final int AUTO_FILL_MODE_MANUAL = 2; // 0x2
field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
@@ -49114,6 +49182,7 @@
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 setLongClickable(boolean);
+ method public abstract void setSanitized(boolean);
method public abstract void setSelected(boolean);
method public abstract void setText(java.lang.CharSequence);
method public abstract void setText(java.lang.CharSequence, int, int);
@@ -49122,7 +49191,6 @@
method public abstract void setTransformation(android.graphics.Matrix);
method public abstract void setUrl(java.lang.String);
method public abstract void setVisibility(int);
- field public static final int AUTO_FILL_FLAG_SANITIZED = 1; // 0x1
}
public final class ViewStub extends android.view.View {
@@ -54358,11 +54426,9 @@
method public void removeTextChangedListener(android.text.TextWatcher);
method public void setAllCaps(boolean);
method public final void setAutoLinkMask(int);
- method public void setAutoSizeMaxTextSize(int, float);
- method public void setAutoSizeMinTextSize(int, float);
- method public void setAutoSizeStepGranularity(int, float);
- method public void setAutoSizeTextPresetSizes(int[]);
- method public void setAutoSizeTextType(int);
+ method public void setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) throws java.lang.IllegalArgumentException;
+ method public void setAutoSizeTextTypeUniformWithPresetSizes(int[], int) throws java.lang.IllegalArgumentException;
+ method public void setAutoSizeTextTypeWithDefaults(int);
method public void setBreakStrategy(int);
method public void setCompoundDrawablePadding(int);
method public void setCompoundDrawableTintList(android.content.res.ColorStateList);
@@ -54887,6 +54953,8 @@
field public static final int OP_INT_TO_FLOAT = 130; // 0x82
field public static final int OP_INT_TO_LONG = 129; // 0x81
field public static final int OP_INT_TO_SHORT = 143; // 0x8f
+ field public static final int OP_INVOKE_CUSTOM = 252; // 0xfc
+ field public static final int OP_INVOKE_CUSTOM_RANGE = 253; // 0xfd
field public static final int OP_INVOKE_DIRECT = 112; // 0x70
field public static final deprecated int OP_INVOKE_DIRECT_EMPTY = 240; // 0xf0
field public static final int OP_INVOKE_DIRECT_JUMBO = 9471; // 0x24ff
@@ -55077,7 +55145,7 @@
method public static dalvik.system.DexFile loadDex(java.lang.String, java.lang.String, int) throws java.io.IOException;
}
- public final class InMemoryDexClassLoader extends java.lang.ClassLoader {
+ public final class InMemoryDexClassLoader extends dalvik.system.BaseDexClassLoader {
ctor public InMemoryDexClassLoader(java.nio.ByteBuffer, java.lang.ClassLoader);
}
diff --git a/api/system-removed.txt b/api/system-removed.txt
index 1ba26f5..6773112 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -174,6 +174,10 @@
package android.os.storage {
public class StorageManager {
+ method public deprecated long getCacheQuotaBytes();
+ method public deprecated long getCacheSizeBytes();
+ method public deprecated long getExternalCacheQuotaBytes();
+ method public deprecated long getExternalCacheSizeBytes();
method public android.os.storage.StorageVolume getPrimaryVolume();
method public android.os.storage.StorageVolume[] getVolumeList();
}
diff --git a/api/test-current.txt b/api/test-current.txt
index fadcec3..fec5edc 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -30,7 +30,6 @@
field public static final java.lang.String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD";
field public static final java.lang.String BIND_MIDI_DEVICE_SERVICE = "android.permission.BIND_MIDI_DEVICE_SERVICE";
field public static final java.lang.String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE";
- field public static final java.lang.String BIND_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE";
field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
field public static final java.lang.String BIND_QUICK_SETTINGS_TILE = "android.permission.BIND_QUICK_SETTINGS_TILE";
@@ -298,6 +297,7 @@
field public static final int authorities = 16842776; // 0x1010018
field public static final int autoAdvanceViewId = 16843535; // 0x101030f
field public static final int autoCompleteTextViewStyle = 16842859; // 0x101006b
+ field public static final int autoFillMode = 16844116; // 0x1010554
field public static final int autoLink = 16842928; // 0x10100b0
field public static final int autoMirrored = 16843754; // 0x10103ea
field public static final int autoRemoveFromRecents = 16843847; // 0x1010447
@@ -3657,6 +3657,7 @@
method public void onLowMemory();
method public boolean onMenuItemSelected(int, android.view.MenuItem);
method public boolean onMenuOpened(int, android.view.Menu);
+ method public void onMovedToDisplay(int);
method public void onMultiWindowModeChanged(boolean);
method public boolean onNavigateUp();
method public boolean onNavigateUpFromChild(android.app.Activity);
@@ -6216,9 +6217,9 @@
method public void addUserRestriction(android.content.ComponentName, java.lang.String);
method public boolean bindDeviceAdminServiceAsUser(android.content.ComponentName, android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle);
method public void clearCrossProfileIntentFilters(android.content.ComponentName);
- method public void clearDeviceOwnerApp(java.lang.String);
+ method public deprecated void clearDeviceOwnerApp(java.lang.String);
method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
- method public void clearProfileOwner(android.content.ComponentName);
+ method public deprecated void clearProfileOwner(android.content.ComponentName);
method public boolean clearResetPasswordToken(android.content.ComponentName);
method public void clearUserRestriction(android.content.ComponentName, java.lang.String);
method public android.content.Intent createAdminSupportIntent(java.lang.String);
@@ -6292,6 +6293,7 @@
method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
method public boolean isBackupServiceEnabled(android.content.ComponentName);
method public deprecated boolean isCallerApplicationRestrictionsManagingPackage();
+ method public boolean isDefaultInputMethodSetByOwner(android.os.UserHandle);
method public boolean isDeviceManaged();
method public boolean isDeviceOwnerApp(java.lang.String);
method public boolean isLockTaskPermitted(java.lang.String);
@@ -13200,6 +13202,7 @@
field public static final deprecated int LA_88 = 10; // 0xa
field public static final deprecated int L_8 = 9; // 0x9
field public static final int OPAQUE = -1; // 0xffffffff
+ field public static final int RGBA_1010102 = 43; // 0x2b
field public static final deprecated int RGBA_4444 = 7; // 0x7
field public static final deprecated int RGBA_5551 = 6; // 0x6
field public static final int RGBA_8888 = 1; // 0x1
@@ -13525,6 +13528,22 @@
package android.graphics.drawable {
+ public class AdaptiveIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
+ method public void draw(android.graphics.Canvas);
+ method public android.graphics.drawable.Drawable getBackground();
+ method public static float getExtraInsetPercentage();
+ method public android.graphics.drawable.Drawable getForeground();
+ method public android.graphics.Path getIconMask();
+ method public int getOpacity();
+ method public android.graphics.Region getSafeZone();
+ method public void invalidateDrawable(android.graphics.drawable.Drawable);
+ method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
+ method public void setAlpha(int);
+ method public void setColorFilter(android.graphics.ColorFilter);
+ method public void setOpacity(int);
+ method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
+ }
+
public abstract interface Animatable {
method public abstract boolean isRunning();
method public abstract void start();
@@ -13914,22 +13933,6 @@
method public void addLevel(int, int, android.graphics.drawable.Drawable);
}
- public class MaskableIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
- method public void draw(android.graphics.Canvas);
- method public android.graphics.drawable.Drawable getBackground();
- method public static float getExtraInsetPercentage();
- method public android.graphics.drawable.Drawable getForeground();
- method public android.graphics.Path getIconMask();
- method public int getOpacity();
- method public android.graphics.Region getSafeZone();
- method public void invalidateDrawable(android.graphics.drawable.Drawable);
- method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
- method public void setAlpha(int);
- method public void setColorFilter(android.graphics.ColorFilter);
- method public void setOpacity(int);
- method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
- }
-
public class NinePatchDrawable extends android.graphics.drawable.Drawable {
ctor public deprecated NinePatchDrawable(android.graphics.Bitmap, byte[], android.graphics.Rect, java.lang.String);
ctor public NinePatchDrawable(android.content.res.Resources, android.graphics.Bitmap, byte[], android.graphics.Rect, java.lang.String);
@@ -14431,6 +14434,7 @@
method public void writeToParcel(android.os.Parcel, int);
field public static final int BLOB = 33; // 0x21
field public static final android.os.Parcelable.Creator<android.hardware.HardwareBuffer> CREATOR;
+ field public static final int RGBA_1010102 = 43; // 0x2b
field public static final int RGBA_8888 = 1; // 0x1
field public static final int RGBA_FP16 = 22; // 0x16
field public static final int RGBX_8888 = 2; // 0x2
@@ -22573,6 +22577,7 @@
ctor public MediaRecorder();
method public static final int getAudioSourceMax();
method public int getMaxAmplitude() throws java.lang.IllegalStateException;
+ method public android.os.Bundle getMetrics();
method public android.view.Surface getSurface();
method public void pause() throws java.lang.IllegalStateException;
method public void prepare() throws java.io.IOException, java.lang.IllegalStateException;
@@ -31369,10 +31374,8 @@
method public void allocateBytes(java.io.File, long, int) throws java.io.IOException;
method public void allocateBytes(java.io.FileDescriptor, long, int) throws java.io.IOException;
method public long getAllocatableBytes(java.io.File, int) throws java.io.IOException;
- method public long getCacheQuotaBytes();
- method public long getCacheSizeBytes();
- method public long getExternalCacheQuotaBytes();
- method public long getExternalCacheSizeBytes();
+ method public long getCacheQuotaBytes(java.io.File);
+ method public long getCacheSizeBytes(java.io.File);
method public java.lang.String getMountedObbPath(java.lang.String);
method public android.os.storage.StorageVolume getPrimaryStorageVolume();
method public android.os.storage.StorageVolume getStorageVolume(java.io.File);
@@ -34384,7 +34387,7 @@
method public static final deprecated void setLocationProviderEnabled(android.content.ContentResolver, java.lang.String, boolean);
field public static final java.lang.String ACCESSIBILITY_DISPLAY_INVERSION_ENABLED = "accessibility_display_inversion_enabled";
field public static final java.lang.String ACCESSIBILITY_ENABLED = "accessibility_enabled";
- field public static final java.lang.String ACCESSIBILITY_SPEAK_PASSWORD = "speak_password";
+ field public static final deprecated java.lang.String ACCESSIBILITY_SPEAK_PASSWORD = "speak_password";
field public static final deprecated java.lang.String ADB_ENABLED = "adb_enabled";
field public static final java.lang.String ALLOWED_GEOLOCATION_ORIGINS = "allowed_geolocation_origins";
field public static final deprecated java.lang.String ALLOW_MOCK_LOCATION = "mock_location";
@@ -36380,9 +36383,10 @@
}
public static final class Dataset.Builder {
- ctor public Dataset.Builder(java.lang.CharSequence);
+ 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 setPresentation(android.widget.RemoteViews);
method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue);
}
@@ -36404,6 +36408,7 @@
method public android.service.autofill.FillResponse build();
method public android.service.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender);
method public android.service.autofill.FillResponse.Builder setExtras(android.os.Bundle);
+ method public android.service.autofill.FillResponse.Builder setPresentation(android.widget.RemoteViews);
}
public final class SaveCallback {
@@ -43637,6 +43642,7 @@
field public static final int SOURCE_KEYBOARD = 257; // 0x101
field public static final int SOURCE_MOUSE = 8194; // 0x2002
field public static final int SOURCE_MOUSE_RELATIVE = 131076; // 0x20004
+ field public static final int SOURCE_ROTARY_ENCODER = 4194304; // 0x400000
field public static final int SOURCE_STYLUS = 16386; // 0x4002
field public static final int SOURCE_TOUCHPAD = 1048584; // 0x100008
field public static final int SOURCE_TOUCHSCREEN = 4098; // 0x1002
@@ -44859,6 +44865,7 @@
method public float getAlpha();
method public android.view.animation.Animation getAnimation();
method public android.os.IBinder getApplicationWindowToken();
+ method public int getAutoFillMode();
method public android.view.autofill.AutoFillType getAutoFillType();
method public android.view.autofill.AutoFillValue getAutoFillValue();
method public android.graphics.drawable.Drawable getBackground();
@@ -45099,6 +45106,7 @@
method public boolean onKeyUp(int, android.view.KeyEvent);
method protected void onLayout(boolean, int, int, int, int);
method protected void onMeasure(int, int);
+ method public void onMovedToDisplay(int);
method protected void onOverScrolled(int, int, boolean, boolean);
method public void onPointerCaptureChange(boolean);
method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
@@ -45161,6 +45169,8 @@
method public static int resolveSize(int, int);
method public static int resolveSizeAndState(int, int, int);
method public boolean restoreDefaultFocus();
+ method public boolean restoreFocusInCluster(int);
+ method public boolean restoreFocusNotInCluster();
method public void restoreHierarchyState(android.util.SparseArray<android.os.Parcelable>);
method public void saveHierarchyState(android.util.SparseArray<android.os.Parcelable>);
method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
@@ -45175,6 +45185,7 @@
method public void setActivated(boolean);
method public void setAlpha(float);
method public void setAnimation(android.view.animation.Animation);
+ method public void setAutoFillMode(int);
method public void setBackground(android.graphics.drawable.Drawable);
method public void setBackgroundColor(int);
method public deprecated void setBackgroundDrawable(android.graphics.drawable.Drawable);
@@ -45315,6 +45326,9 @@
field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
+ field public static final int AUTO_FILL_MODE_AUTO = 1; // 0x1
+ field public static final int AUTO_FILL_MODE_INHERIT = 0; // 0x0
+ field public static final int AUTO_FILL_MODE_MANUAL = 2; // 0x2
field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
@@ -45985,6 +45999,7 @@
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 setLongClickable(boolean);
+ method public abstract void setSanitized(boolean);
method public abstract void setSelected(boolean);
method public abstract void setText(java.lang.CharSequence);
method public abstract void setText(java.lang.CharSequence, int, int);
@@ -45993,7 +46008,6 @@
method public abstract void setTransformation(android.graphics.Matrix);
method public abstract void setUrl(java.lang.String);
method public abstract void setVisibility(int);
- field public static final int AUTO_FILL_FLAG_SANITIZED = 1; // 0x1
}
public final class ViewStub extends android.view.View {
@@ -50874,11 +50888,9 @@
method public void removeTextChangedListener(android.text.TextWatcher);
method public void setAllCaps(boolean);
method public final void setAutoLinkMask(int);
- method public void setAutoSizeMaxTextSize(int, float);
- method public void setAutoSizeMinTextSize(int, float);
- method public void setAutoSizeStepGranularity(int, float);
- method public void setAutoSizeTextPresetSizes(int[]);
- method public void setAutoSizeTextType(int);
+ method public void setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) throws java.lang.IllegalArgumentException;
+ method public void setAutoSizeTextTypeUniformWithPresetSizes(int[], int) throws java.lang.IllegalArgumentException;
+ method public void setAutoSizeTextTypeWithDefaults(int);
method public void setBreakStrategy(int);
method public void setCompoundDrawablePadding(int);
method public void setCompoundDrawableTintList(android.content.res.ColorStateList);
@@ -51411,6 +51423,8 @@
field public static final int OP_INT_TO_FLOAT = 130; // 0x82
field public static final int OP_INT_TO_LONG = 129; // 0x81
field public static final int OP_INT_TO_SHORT = 143; // 0x8f
+ field public static final int OP_INVOKE_CUSTOM = 252; // 0xfc
+ field public static final int OP_INVOKE_CUSTOM_RANGE = 253; // 0xfd
field public static final int OP_INVOKE_DIRECT = 112; // 0x70
field public static final deprecated int OP_INVOKE_DIRECT_EMPTY = 240; // 0xf0
field public static final int OP_INVOKE_DIRECT_JUMBO = 9471; // 0x24ff
@@ -51601,7 +51615,7 @@
method public static dalvik.system.DexFile loadDex(java.lang.String, java.lang.String, int) throws java.io.IOException;
}
- public final class InMemoryDexClassLoader extends java.lang.ClassLoader {
+ public final class InMemoryDexClassLoader extends dalvik.system.BaseDexClassLoader {
ctor public InMemoryDexClassLoader(java.nio.ByteBuffer, java.lang.ClassLoader);
}
diff --git a/api/test-removed.txt b/api/test-removed.txt
index ab22b6e..e467811 100644
--- a/api/test-removed.txt
+++ b/api/test-removed.txt
@@ -180,6 +180,10 @@
package android.os.storage {
public class StorageManager {
+ method public deprecated long getCacheQuotaBytes();
+ method public deprecated long getCacheSizeBytes();
+ method public deprecated long getExternalCacheQuotaBytes();
+ method public deprecated long getExternalCacheSizeBytes();
method public android.os.storage.StorageVolume getPrimaryVolume();
method public android.os.storage.StorageVolume[] getVolumeList();
}
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index b336472..1aef363 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -65,6 +65,7 @@
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
+import android.util.Pair;
import com.android.internal.content.PackageHelper;
import com.android.internal.util.ArrayUtils;
@@ -402,6 +403,7 @@
* The use of "adb install" or "cmd package install" over "pm install" is highly encouraged.
*/
private int runInstall() throws RemoteException {
+ long startedTime = SystemClock.elapsedRealtime();
final InstallParams params = makeInstallParams();
final String inPath = nextArg();
if (params.sessionParams.sizeBytes == -1 && !STDIN_PATH.equals(inPath)) {
@@ -435,10 +437,12 @@
false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
return 1;
}
- if (doCommitSession(sessionId, false /*logSuccess*/)
- != PackageInstaller.STATUS_SUCCESS) {
+ Pair<String, Integer> status = doCommitSession(sessionId, false /*logSuccess*/);
+ if (status.second != PackageInstaller.STATUS_SUCCESS) {
return 1;
}
+ Log.i(TAG, "Package " + status.first + " installed in " + (SystemClock.elapsedRealtime()
+ - startedTime) + " ms");
System.out.println("Success");
return 0;
} finally {
@@ -456,7 +460,7 @@
private int runInstallCommit() throws RemoteException {
final int sessionId = Integer.parseInt(nextArg());
- return doCommitSession(sessionId, true /*logSuccess*/);
+ return doCommitSession(sessionId, true /*logSuccess*/).second;
}
private int runInstallCreate() throws RemoteException {
@@ -554,8 +558,12 @@
sessionParams.abiOverride = checkAbiArgument(nextOptionData());
break;
case "--ephemeral":
+ case "--instant":
sessionParams.setInstallAsInstantApp(true /*isInstantApp*/);
break;
+ case "--full":
+ sessionParams.setInstallAsInstantApp(false /*isInstantApp*/);
+ break;
case "--user":
params.userId = UserHandle.parseUserArg(nextOptionData());
break;
@@ -646,7 +654,8 @@
}
}
- private int doCommitSession(int sessionId, boolean logSuccess) throws RemoteException {
+ private Pair<String, Integer> doCommitSession(int sessionId, boolean logSuccess)
+ throws RemoteException {
PackageInstaller.Session session = null;
try {
session = new PackageInstaller.Session(
@@ -666,7 +675,7 @@
System.err.println("Failure ["
+ result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
}
- return status;
+ return new Pair<>(result.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME), status);
} finally {
IoUtils.closeQuietly(session);
}
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 9e486d5..b4e119e 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -52,6 +52,8 @@
import java.lang.annotation.RetentionPolicy;
import java.util.List;
+import static android.content.pm.PackageManager.FEATURE_FINGERPRINT;
+
/**
* Accessibility services should only be used to assist users with disabilities in using
* Android devices and apps. They run in the background and receive callbacks by the system
@@ -618,7 +620,8 @@
*/
@RequiresPermission(android.Manifest.permission.USE_FINGERPRINT)
public final @Nullable FingerprintGestureController getFingerprintGestureController() {
- if (mFingerprintGestureController == null) {
+ if ((mFingerprintGestureController == null)
+ && getPackageManager().hasSystemFeature(FEATURE_FINGERPRINT)) {
FingerprintManager fingerprintManager = getSystemService(FingerprintManager.class);
if ((fingerprintManager != null) && fingerprintManager.isHardwareDetected()) {
AccessibilityServiceInfo info = getServiceInfo();
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index e135ffd..19d1a7d 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -47,6 +47,8 @@
import java.util.Collections;
import java.util.List;
+import static android.content.pm.PackageManager.FEATURE_FINGERPRINT;
+
/**
* This class describes an {@link AccessibilityService}. The system notifies an
* {@link AccessibilityService} for {@link android.view.accessibility.AccessibilityEvent}s
@@ -1042,8 +1044,7 @@
new CapabilityInfo(CAPABILITY_CAN_PERFORM_GESTURES,
R.string.capability_title_canPerformGestures,
R.string.capability_desc_canPerformGestures));
- if ((context == null)
- || context.getSystemService(FingerprintManager.class).isHardwareDetected()) {
+ if ((context == null) || fingerprintAvailable(context)) {
sAvailableCapabilityInfos.put(CAPABILITY_CAN_CAPTURE_FINGERPRINT_GESTURES,
new CapabilityInfo(CAPABILITY_CAN_CAPTURE_FINGERPRINT_GESTURES,
R.string.capability_title_canCaptureFingerprintGestures,
@@ -1053,6 +1054,10 @@
return sAvailableCapabilityInfos;
}
+ private static boolean fingerprintAvailable(Context context) {
+ return context.getPackageManager().hasSystemFeature(FEATURE_FINGERPRINT)
+ && context.getSystemService(FingerprintManager.class).isHardwareDetected();
+ }
/**
* @hide
*/
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 7d039ef..e5df278 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -288,7 +288,8 @@
}
/**
- * Account visibility was not set.
+ * Account visibility was not set. Default visibility value will be used.
+ * See {@link #PACKAGE_NAME_KEY_LEGACY_VISIBLE}, {@link #PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE}
*/
public static final int VISIBILITY_UNDEFINED = 0;
@@ -919,7 +920,14 @@
* Package name must match installed application, or be equal to
* {@link #PACKAGE_NAME_KEY_LEGACY_VISIBLE} or {@link #PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE}.
* <p>
- * See {@link #getAccountVisibility} for possible values.
+ * Possible visibility values:
+ * <ul>
+ * <li>{@link #VISIBILITY_UNDEFINED}</li>
+ * <li>{@link #VISIBILITY_VISIBLE}</li>
+ * <li>{@link #VISIBILITY_USER_MANAGED_VISIBLE}</li>
+ * <li>{@link #VISIBILITY_NOT_VISIBLE}
+ * <li>{@link #VISIBILITY_USER_MANAGED_NOT_VISIBLE}</li>
+ * </ul>
* <p>
* This method requires the caller to have a signature match with the authenticator that owns
* the specified account.
@@ -944,7 +952,6 @@
/**
* Get visibility of certain account for given application. Possible returned values are:
* <ul>
- * <li>{@link #VISIBILITY_UNDEFINED}</li>
* <li>{@link #VISIBILITY_VISIBLE}</li>
* <li>{@link #VISIBILITY_USER_MANAGED_VISIBLE}</li>
* <li>{@link #VISIBILITY_NOT_VISIBLE}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 4449454..6fc60e9 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1980,6 +1980,29 @@
}
}
+ void dispatchMovedToDisplay(int displayId) {
+ updateDisplay(displayId);
+ onMovedToDisplay(displayId);
+ }
+
+ /**
+ * Called by the system when the activity is moved from one display to another without
+ * recreation. This means that this activity is declared to handle all changes to configuration
+ * that happened when it was switched to another display, so it wasn't destroyed and created
+ * again. This call will be followed by {@link #onConfigurationChanged(Configuration)} if the
+ * applied configuration actually changed.
+ *
+ * <p>Use this callback to track changes to the displays if some activity functionality relies
+ * on an association with some display properties.
+ *
+ * @param displayId The id of the display to which activity was moved.
+ *
+ * @see #onConfigurationChanged(Configuration)
+ * @see View#onMovedToDisplay(int)
+ */
+ public void onMovedToDisplay(int displayId) {
+ }
+
/**
* Called by the system when the device configuration changes while your
* activity is running. Note that this will <em>only</em> be called if
@@ -7016,7 +7039,8 @@
}
}
} else if (who.startsWith(AUTO_FILL_AUTH_WHO_PREFIX)) {
- getSystemService(AutoFillManager.class).onAuthenticationResult(data);
+ Intent resultData = (resultCode == Activity.RESULT_OK) ? data : null;
+ getSystemService(AutoFillManager.class).onAuthenticationResult(resultData);
} else {
Fragment frag = mFragments.findFragmentByWho(who);
if (frag != null) {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 1f8e6db..be70dcd 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -16,6 +16,8 @@
package android.app;
+import static android.view.Display.INVALID_DISPLAY;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.assist.AssistContent;
@@ -1017,9 +1019,16 @@
@Override
public void scheduleActivityConfigurationChanged(
- IBinder token, Configuration overrideConfig, boolean reportToActivity) {
+ IBinder token, Configuration overrideConfig) {
sendMessage(H.ACTIVITY_CONFIGURATION_CHANGED,
- new ActivityConfigChangeData(token, overrideConfig), reportToActivity ? 1 : 0);
+ new ActivityConfigChangeData(token, overrideConfig));
+ }
+
+ @Override
+ public void scheduleActivityMovedToDisplay(IBinder token, int displayId,
+ Configuration overrideConfig) {
+ sendMessage(H.ACTIVITY_MOVED_TO_DISPLAY,
+ new ActivityConfigChangeData(token, overrideConfig), displayId);
}
@Override
@@ -1521,6 +1530,7 @@
public static final int LOCAL_VOICE_INTERACTION_STARTED = 154;
public static final int ATTACH_AGENT = 155;
public static final int APPLICATION_INFO_CHANGED = 156;
+ public static final int ACTIVITY_MOVED_TO_DISPLAY = 157;
String codeToString(int code) {
if (DEBUG_MESSAGES) {
@@ -1550,6 +1560,7 @@
case DUMP_SERVICE: return "DUMP_SERVICE";
case LOW_MEMORY: return "LOW_MEMORY";
case ACTIVITY_CONFIGURATION_CHANGED: return "ACTIVITY_CONFIGURATION_CHANGED";
+ case ACTIVITY_MOVED_TO_DISPLAY: return "ACTIVITY_MOVED_TO_DISPLAY";
case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY";
case PROFILER_CONTROL: return "PROFILER_CONTROL";
case CREATE_BACKUP_AGENT: return "CREATE_BACKUP_AGENT";
@@ -1735,7 +1746,13 @@
case ACTIVITY_CONFIGURATION_CHANGED:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged");
handleActivityConfigurationChanged((ActivityConfigChangeData) msg.obj,
- msg.arg1 == 1 ? REPORT_TO_ACTIVITY : !REPORT_TO_ACTIVITY);
+ INVALID_DISPLAY);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ break;
+ case ACTIVITY_MOVED_TO_DISPLAY:
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityMovedToDisplay");
+ handleActivityConfigurationChanged((ActivityConfigChangeData) msg.obj,
+ msg.arg1 /* displayId */);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case PROFILER_CONTROL:
@@ -3775,7 +3792,7 @@
if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
if (r.newConfig != null) {
- performConfigurationChangedForActivity(r, r.newConfig, REPORT_TO_ACTIVITY);
+ performConfigurationChangedForActivity(r, r.newConfig);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity "
+ r.activityInfo.name + " with newConfig " + r.activity.mCurrentConfig);
r.newConfig = null;
@@ -4129,7 +4146,7 @@
}
}
if (r.newConfig != null) {
- performConfigurationChangedForActivity(r, r.newConfig, REPORT_TO_ACTIVITY);
+ performConfigurationChangedForActivity(r, r.newConfig);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Updating activity vis "
+ r.activityInfo.name + " with new config "
+ r.activity.mCurrentConfig);
@@ -4842,17 +4859,32 @@
* @param r ActivityClientRecord representing the Activity.
* @param newBaseConfig The new configuration to use. This may be augmented with
* {@link ActivityClientRecord#overrideConfig}.
- * @param reportToActivity true if the change should be reported to the Activity's callback.
*/
private void performConfigurationChangedForActivity(ActivityClientRecord r,
- Configuration newBaseConfig,
- boolean reportToActivity) {
+ Configuration newBaseConfig) {
+ performConfigurationChangedForActivity(r, newBaseConfig,
+ r.activity.getDisplay().getDisplayId(), false /* movedToDifferentDisplay */);
+ }
+
+ /**
+ * Updates the configuration for an Activity. The ActivityClientRecord's
+ * {@link ActivityClientRecord#overrideConfig} is used to compute the final Configuration for
+ * that Activity. {@link ActivityClientRecord#tmpConfig} is used as a temporary for delivering
+ * the updated Configuration.
+ * @param r ActivityClientRecord representing the Activity.
+ * @param newBaseConfig The new configuration to use. This may be augmented with
+ * {@link ActivityClientRecord#overrideConfig}.
+ * @param displayId The id of the display where the Activity currently resides.
+ * @param movedToDifferentDisplay Indicates if the activity was moved to different display.
+ */
+ private void performConfigurationChangedForActivity(ActivityClientRecord r,
+ Configuration newBaseConfig, int displayId, boolean movedToDifferentDisplay) {
r.tmpConfig.setTo(newBaseConfig);
if (r.overrideConfig != null) {
r.tmpConfig.updateFrom(r.overrideConfig);
}
- performConfigurationChanged(r.activity, r.token, r.tmpConfig, r.overrideConfig,
- reportToActivity);
+ performActivityConfigurationChanged(r.activity, r.tmpConfig, r.overrideConfig, displayId,
+ movedToDifferentDisplay);
freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
}
@@ -4873,37 +4905,58 @@
}
/**
- * Decides whether to update an Activity's configuration and whether to tell the
- * Activity/Component about it.
+ * Decides whether to update a component's configuration and whether to inform it.
* @param cb The component callback to notify of configuration change.
- * @param activityToken The Activity binder token for which this configuration change happened.
- * If the change is global, this is null.
+ * @param newConfig The new configuration.
+ */
+ private void performConfigurationChanged(ComponentCallbacks2 cb, Configuration newConfig) {
+ if (!REPORT_TO_ACTIVITY) {
+ return;
+ }
+
+ // ContextThemeWrappers may override the configuration for that context. We must check and
+ // apply any overrides defined.
+ Configuration contextThemeWrapperOverrideConfig = null;
+ if (cb instanceof ContextThemeWrapper) {
+ final ContextThemeWrapper contextThemeWrapper = (ContextThemeWrapper) cb;
+ contextThemeWrapperOverrideConfig = contextThemeWrapper.getOverrideConfiguration();
+ }
+
+ // Apply the ContextThemeWrapper override if necessary.
+ // NOTE: Make sure the configurations are not modified, as they are treated as immutable
+ // in many places.
+ final Configuration configToReport = createNewConfigAndUpdateIfNotNull(
+ newConfig, contextThemeWrapperOverrideConfig);
+ cb.onConfigurationChanged(configToReport);
+ }
+
+ /**
+ * Decides whether to update an Activity's configuration and whether to inform it.
+ * @param activity The activity to notify of configuration change.
* @param newConfig The new configuration.
* @param amOverrideConfig The override config that differentiates the Activity's configuration
- * from the base global configuration.
- * This is supplied by ActivityManager.
- * @param reportToActivity Notify the Activity of the change.
+ * from the base global configuration. This is supplied by
+ * ActivityManager.
+ * @param displayId Id of the display where activity currently resides.
+ * @param movedToDifferentDisplay Indicates if the activity was moved to different display.
*/
- private void performConfigurationChanged(ComponentCallbacks2 cb,
- IBinder activityToken,
- Configuration newConfig,
- Configuration amOverrideConfig,
- boolean reportToActivity) {
- // Only for Activity objects, check that they actually call up to their
- // superclass implementation. ComponentCallbacks2 is an interface, so
- // we check the runtime type and act accordingly.
- Activity activity = (cb instanceof Activity) ? (Activity) cb : null;
- if (activity != null) {
- activity.mCalled = false;
+ private void performActivityConfigurationChanged(Activity activity, Configuration newConfig,
+ Configuration amOverrideConfig, int displayId, boolean movedToDifferentDisplay) {
+ if (activity == null) {
+ throw new IllegalArgumentException("No activity provided.");
+ }
+ final IBinder activityToken = activity.getActivityToken();
+ if (activityToken == null) {
+ throw new IllegalArgumentException("Activity token not set. Is the activity attached?");
}
boolean shouldChangeConfig = false;
- if ((activity == null) || (activity.mCurrentConfig == null)) {
+ if (activity.mCurrentConfig == null) {
shouldChangeConfig = true;
} else {
- // If the new config is the same as the config this Activity is already
- // running with and the override config also didn't change, then don't
- // bother calling onConfigurationChanged.
+ // If the new config is the same as the config this Activity is already running with and
+ // the override config also didn't change, then don't bother calling
+ // onConfigurationChanged.
int diff = activity.mCurrentConfig.diff(newConfig);
if (diff != 0 || !mResourcesManager.isSameResourcesOverrideConfig(activityToken,
amOverrideConfig)) {
@@ -4912,53 +4965,57 @@
// calling onConfigurationChanged as we're going to destroy it.
if (!mUpdatingSystemConfig
|| (~activity.mActivityInfo.getRealConfigChanged() & diff) == 0
- || !reportToActivity) {
+ || !REPORT_TO_ACTIVITY) {
shouldChangeConfig = true;
}
}
}
+ if (!shouldChangeConfig && !movedToDifferentDisplay) {
+ // Nothing significant, don't proceed with updating and reporting.
+ return;
+ }
+
+ // Propagate the configuration change to ResourcesManager and Activity.
+
+ // ContextThemeWrappers may override the configuration for that context. We must check and
+ // apply any overrides defined.
+ Configuration contextThemeWrapperOverrideConfig = activity.getOverrideConfiguration();
+
+ // We only update an Activity's configuration if this is not a global configuration change.
+ // This must also be done before the callback, or else we violate the contract that the new
+ // resources are available in ComponentCallbacks2#onConfigurationChanged(Configuration).
+ // Also apply the ContextThemeWrapper override if necessary.
+ // NOTE: Make sure the configurations are not modified, as they are treated as immutable in
+ // many places.
+ final Configuration finalOverrideConfig = createNewConfigAndUpdateIfNotNull(
+ amOverrideConfig, contextThemeWrapperOverrideConfig);
+ mResourcesManager.updateResourcesForActivity(activityToken, finalOverrideConfig,
+ displayId, movedToDifferentDisplay);
+
+ activity.mConfigChangeFlags = 0;
+ activity.mCurrentConfig = new Configuration(newConfig);
+
+ if (!REPORT_TO_ACTIVITY) {
+ // Not configured to report to activity.
+ return;
+ }
+
+ if (movedToDifferentDisplay) {
+ activity.dispatchMovedToDisplay(displayId);
+ }
if (shouldChangeConfig) {
- // Propagate the configuration change to the Activity and ResourcesManager.
+ // Apply the ContextThemeWrapper override if necessary.
+ // NOTE: Make sure the configurations are not modified, as they are treated as immutable
+ // in many places.
+ final Configuration configToReport = createNewConfigAndUpdateIfNotNull(
+ newConfig, contextThemeWrapperOverrideConfig);
- // ContextThemeWrappers may override the configuration for that context.
- // We must check and apply any overrides defined.
- Configuration contextThemeWrapperOverrideConfig = null;
- if (cb instanceof ContextThemeWrapper) {
- final ContextThemeWrapper contextThemeWrapper = (ContextThemeWrapper) cb;
- contextThemeWrapperOverrideConfig = contextThemeWrapper.getOverrideConfiguration();
- }
-
- // We only update an Activity's configuration if this is not a global
- // configuration change. This must also be done before the callback,
- // or else we violate the contract that the new resources are available
- // in {@link ComponentCallbacks2#onConfigurationChanged(Configuration)}.
- if (activityToken != null) {
- // Apply the ContextThemeWrapper override if necessary.
- // NOTE: Make sure the configurations are not modified, as they are treated
- // as immutable in many places.
- final Configuration finalOverrideConfig = createNewConfigAndUpdateIfNotNull(
- amOverrideConfig, contextThemeWrapperOverrideConfig);
- mResourcesManager.updateResourcesForActivity(activityToken, finalOverrideConfig);
- }
-
- if (reportToActivity) {
- // Apply the ContextThemeWrapper override if necessary.
- // NOTE: Make sure the configurations are not modified, as they are treated
- // as immutable in many places.
- final Configuration configToReport = createNewConfigAndUpdateIfNotNull(
- newConfig, contextThemeWrapperOverrideConfig);
- cb.onConfigurationChanged(configToReport);
- }
-
- if (activity != null) {
- if (reportToActivity && !activity.mCalled) {
- throw new SuperNotCalledException(
- "Activity " + activity.getLocalClassName() +
- " did not call through to super.onConfigurationChanged()");
- }
- activity.mConfigChangeFlags = 0;
- activity.mCurrentConfig = new Configuration(newConfig);
+ activity.mCalled = false;
+ activity.onConfigurationChanged(configToReport);
+ if (!activity.mCalled) {
+ throw new SuperNotCalledException("Activity " + activity.getLocalClassName() +
+ " did not call through to super.onConfigurationChanged()");
}
}
}
@@ -5036,9 +5093,9 @@
// config and avoid onConfigurationChanged if it hasn't changed.
Activity a = (Activity) cb;
performConfigurationChangedForActivity(mActivities.get(a.getActivityToken()),
- config, REPORT_TO_ACTIVITY);
+ config);
} else {
- performConfigurationChanged(cb, null, config, null, REPORT_TO_ACTIVITY);
+ performConfigurationChanged(cb, config);
}
}
}
@@ -5102,18 +5159,43 @@
}
}
- final void handleActivityConfigurationChanged(ActivityConfigChangeData data,
- boolean reportToActivity) {
+ /**
+ * Handle new activity configuration and/or move to a different display.
+ * @param data Configuration update data.
+ * @param displayId Id of the display where activity was moved to, -1 if there was no move and
+ * value didn't change.
+ */
+ private void handleActivityConfigurationChanged(ActivityConfigChangeData data, int displayId) {
ActivityClientRecord r = mActivities.get(data.activityToken);
+ // Check input params.
if (r == null || r.activity == null) {
+ if (DEBUG_CONFIGURATION) Slog.w(TAG, "Not found target activity to report to: " + r);
return;
}
+ final boolean movedToDifferentDisplay = displayId != INVALID_DISPLAY;
+ if (movedToDifferentDisplay) {
+ if (r.activity.getDisplay().getDisplayId() == displayId) {
+ throw new IllegalArgumentException("Activity is already on the target display: "
+ + displayId);
+ }
+ }
- if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity config changed: "
- + r.activityInfo.name + ", with callback=" + reportToActivity);
-
+ // Perform updates.
r.overrideConfig = data.overrideConfig;
- performConfigurationChangedForActivity(r, mCompatConfiguration, reportToActivity);
+ if (movedToDifferentDisplay) {
+ if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity moved to display, activity:"
+ + r.activityInfo.name + ", displayId=" + displayId
+ + ", config=" + data.overrideConfig);
+
+ performConfigurationChangedForActivity(r, mCompatConfiguration, displayId,
+ true /* movedToDifferentDisplay */);
+ final ViewRootImpl viewRoot = r.activity.mDecor.getViewRootImpl();
+ viewRoot.onMovedToDisplay(displayId);
+ } else {
+ if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity config changed: "
+ + r.activityInfo.name + ", config=" + data.overrideConfig);
+ performConfigurationChangedForActivity(r, mCompatConfiguration);
+ }
mSomeActivitiesChanged = true;
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 333e412..1652299 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1687,7 +1687,7 @@
public int installExistingPackageAsUser(String packageName, int userId)
throws NameNotFoundException {
try {
- int res = mPM.installExistingPackageAsUser(packageName, userId,
+ int res = mPM.installExistingPackageAsUser(packageName, userId, 0 /*installFlags*/,
PackageManager.INSTALL_REASON_UNKNOWN);
if (res == INSTALL_FAILED_INVALID_URI) {
throw new NameNotFoundException("Package " + packageName + " doesn't exist");
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 16989c0..045bd0a 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -638,6 +638,15 @@
}
}
+ /**
+ * @hide
+ */
+ @Nullable
+ @Override
+ public File getPreloadsFileCache() {
+ return Environment.getDataPreloadsFileCacheDirectory(getPackageName());
+ }
+
@Override
public File getFileStreamPath(String name) {
return makeFilename(getFilesDir(), name);
@@ -2072,6 +2081,13 @@
}
@Override
+ public void updateDisplay(int displayId) {
+ final DisplayAdjustments displayAdjustments = mResources.getDisplayAdjustments();
+ mDisplay = mResourcesManager.getAdjustedDisplay(displayId,
+ displayAdjustments);
+ }
+
+ @Override
public DisplayAdjustments getDisplayAdjustments(int displayId) {
return mResources.getDisplayAdjustments();
}
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index d32cf3c..8ad7810 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -1911,7 +1911,12 @@
mTmpRecords = new ArrayList<>();
mTmpIsPop = new ArrayList<>();
}
- executePostponedTransaction(null, null);
+ mExecutingActions = true;
+ try {
+ executePostponedTransaction(null, null);
+ } finally {
+ mExecutingActions = false;
+ }
}
public void execSingleAction(OpGenerator action, boolean allowStateLoss) {
@@ -2802,40 +2807,56 @@
public void dispatchCreate() {
mStateSaved = false;
+ mExecutingActions = true;
moveToState(Fragment.CREATED, false);
+ mExecutingActions = false;
}
public void dispatchActivityCreated() {
mStateSaved = false;
+ mExecutingActions = true;
moveToState(Fragment.ACTIVITY_CREATED, false);
+ mExecutingActions = false;
}
public void dispatchStart() {
mStateSaved = false;
+ mExecutingActions = true;
moveToState(Fragment.STARTED, false);
+ mExecutingActions = false;
}
public void dispatchResume() {
mStateSaved = false;
+ mExecutingActions = true;
moveToState(Fragment.RESUMED, false);
+ mExecutingActions = false;
}
public void dispatchPause() {
+ mExecutingActions = true;
moveToState(Fragment.STARTED, false);
+ mExecutingActions = false;
}
public void dispatchStop() {
+ mExecutingActions = true;
moveToState(Fragment.STOPPED, false);
+ mExecutingActions = false;
}
public void dispatchDestroyView() {
+ mExecutingActions = true;
moveToState(Fragment.CREATED, false);
+ mExecutingActions = false;
}
public void dispatchDestroy() {
mDestroyed = true;
execPendingActions();
+ mExecutingActions = true;
moveToState(Fragment.INITIALIZING, false);
+ mExecutingActions = false;
mHost = null;
mContainer = null;
mParent = null;
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 7378e2b..1735572 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -100,8 +100,9 @@
int resultCode, in String data, in Bundle extras, boolean ordered,
boolean sticky, int sendingUser, int processState);
void scheduleLowMemory();
- void scheduleActivityConfigurationChanged(IBinder token, in Configuration overrideConfig,
- boolean reportToActivity);
+ void scheduleActivityConfigurationChanged(IBinder token, in Configuration overrideConfig);
+ void scheduleActivityMovedToDisplay(IBinder token, int displayId,
+ in Configuration overrideConfig);
void scheduleRelaunchActivity(IBinder token, in List<ResultInfo> pendingResults,
in List<ReferrerIntent> pendingNewIntents, int configChanges, boolean notResumed,
in Configuration config, in Configuration overrideConfig, boolean preserveWindow);
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 5e420c0..9834350 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -32,9 +32,10 @@
* running in the pinned stack and the activity is not actually started, but the task is either
* brought to the front or a new Intent is delivered to it.
*
- * @param sourceComponent the component name of the activity that initiated the restart attempt
+ * @param launchedFromPackage the package name of the activity that initiated the restart
+ * attempt
*/
- void onPinnedActivityRestartAttempt(in ComponentName sourceComponent);
+ void onPinnedActivityRestartAttempt(String launchedFromPackage);
/**
* Called whenever the pinned stack is done animating a resize.
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 94a8990..55f7df3 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -521,7 +521,8 @@
}
// Update any existing Activity Resources references.
- updateResourcesForActivity(activityToken, overrideConfig);
+ updateResourcesForActivity(activityToken, overrideConfig, displayId,
+ false /* movedToDifferentDisplay */);
// Now request an actual Resources object.
return getOrCreateResources(activityToken, key, classLoader);
@@ -687,9 +688,12 @@
* still valid and will have the updated configuration.
* @param activityToken The Activity token.
* @param overrideConfig The configuration override to update.
+ * @param displayId Id of the display where activity currently resides.
+ * @param movedToDifferentDisplay Indicates if the activity was moved to different display.
*/
public void updateResourcesForActivity(@NonNull IBinder activityToken,
- @Nullable Configuration overrideConfig) {
+ @Nullable Configuration overrideConfig, int displayId,
+ boolean movedToDifferentDisplay) {
try {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
"ResourcesManager#updateResourcesForActivity");
@@ -697,8 +701,9 @@
final ActivityResources activityResources =
getOrCreateActivityResourcesStructLocked(activityToken);
- if (Objects.equals(activityResources.overrideConfig, overrideConfig)) {
- // They are the same, no work to do.
+ if (Objects.equals(activityResources.overrideConfig, overrideConfig)
+ && !movedToDifferentDisplay) {
+ // They are the same and no change of display id, no work to do.
return;
}
@@ -721,7 +726,7 @@
+ Configuration.resourceQualifierString(oldConfig)
+ " to newConfig="
+ Configuration.resourceQualifierString(
- activityResources.overrideConfig),
+ activityResources.overrideConfig) + " displayId=" + displayId,
here);
}
@@ -765,12 +770,12 @@
// Create the new ResourcesKey with the rebased override config.
final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
oldKey.mSplitResDirs,
- oldKey.mOverlayDirs, oldKey.mLibDirs, oldKey.mDisplayId,
+ oldKey.mOverlayDirs, oldKey.mLibDirs, displayId,
rebasedOverrideConfig, oldKey.mCompatInfo);
if (DEBUG) {
Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey
- + " to newKey=" + newKey);
+ + " to newKey=" + newKey + ", displayId=" + displayId);
}
ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(newKey);
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index 35c67d3..5a0845f 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -35,7 +35,7 @@
}
@Override
- public void onPinnedActivityRestartAttempt(ComponentName sourceComponent) throws RemoteException {
+ public void onPinnedActivityRestartAttempt(String launchedFromPackage) throws RemoteException {
}
@Override
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 3cb4654..2ace0a2 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -4545,12 +4545,22 @@
/**
* Clears the current device owner. The caller must be the device owner. This function should be
* used cautiously as once it is called it cannot be undone. The device owner can only be set as
- * a part of device setup before setup completes.
+ * a part of device setup, before it completes.
+ * <p>
+ * While some policies previously set by the device owner will be cleared by this method, it is
+ * a best-effort process and some other policies will still remain in place after the device
+ * owner is cleared.
*
* @param packageName The package name of the device owner.
* @throws SecurityException if the caller is not in {@code packageName} or {@code packageName}
* does not own the current device owner component.
+ *
+ * @deprecated This method is expected to be used for testing purposes only. The device owner
+ * will lose control of the device and its data after calling it. In order to protect any
+ * sensitive data that remains on the device, it is advised that the device owner factory resets
+ * the device instead of calling this method. See {@link #wipeData(int)}.
*/
+ @Deprecated
public void clearDeviceOwnerApp(String packageName) {
throwIfParentInstance("clearDeviceOwnerApp");
if (mService != null) {
@@ -4672,15 +4682,23 @@
}
/**
- * Clears the active profile owner and removes all user restrictions. The caller must be from
- * the same package as the active profile owner for this user, otherwise a SecurityException
- * will be thrown.
+ * Clears the active profile owner. The caller must be the profile owner of this user, otherwise
+ * a SecurityException will be thrown. This method is not available to managed profile owners.
* <p>
- * This doesn't work for managed profile owners.
+ * While some policies previously set by the profile owner will be cleared by this method, it is
+ * a best-effort process and some other policies will still remain in place after the profile
+ * owner is cleared.
*
* @param admin The component to remove as the profile owner.
- * @throws SecurityException if {@code admin} is not an active profile owner.
+ * @throws SecurityException if {@code admin} is not an active profile owner, or the method is
+ * being called from a managed profile.
+ *
+ * @deprecated This method is expected to be used for testing purposes only. The profile owner
+ * will lose control of the user and its data after calling it. In order to protect any
+ * sensitive data that remains on this user, it is advised that the profile owner deletes it
+ * instead of calling this method. See {@link #wipeData(int)}.
*/
+ @Deprecated
public void clearProfileOwner(@NonNull ComponentName admin) {
throwIfParentInstance("clearProfileOwner");
if (mService != null) {
@@ -7871,4 +7889,27 @@
throw re.rethrowFromSystemServer();
}
}
+
+ /**
+ * Called by the system to find out whether the user's IME was set by the device/profile owner
+ * or the user.
+ *
+ * @param user The user for whom to retrieve information.
+ * @return {@code true} if the user's IME was set by the device or profile owner, {@code false}
+ * otherwise.
+ * @throws SecurityException if the caller does not have permission to retrieve information
+ * about the given user's default IME. Device Owner and Profile Owner can retrieve
+ * information about the user they run on; the System can retrieve information about any
+ * user.
+ *
+ * @hide
+ */
+ @TestApi
+ public boolean isDefaultInputMethodSetByOwner(@NonNull UserHandle user) {
+ try {
+ return mService.isDefaultInputMethodSetByOwner(user);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index c2f75c8..ec97c2c 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -347,4 +347,6 @@
boolean clearResetPasswordToken(in ComponentName admin);
boolean isResetPasswordTokenActive(in ComponentName admin);
boolean resetPasswordWithToken(in ComponentName admin, String password, in byte[] token, int flags);
+
+ boolean isDefaultInputMethodSetByOwner(in UserHandle user);
}
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 8d385db..13d0f03 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -419,7 +419,7 @@
mDisplayId = root.getDisplayId();
mRoot = new ViewNode();
- ViewNodeBuilder builder = new ViewNodeBuilder(assist, mRoot, false, 0);
+ ViewNodeBuilder builder = new ViewNodeBuilder(assist, mRoot, false);
if ((root.getWindowFlags() & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
if (forAutoFill) {
// NOTE: flags are currently not supported, hence 0
@@ -1187,11 +1187,10 @@
final ViewNode mNode;
final boolean mAsync;
- ViewNodeBuilder(AssistStructure assist, ViewNode node, boolean async, int flags) {
+ ViewNodeBuilder(AssistStructure assist, ViewNode node, boolean async) {
mAssist = assist;
mNode = node;
mAsync = async;
- mNode.mSanitized = (flags & AUTO_FILL_FLAG_SANITIZED) != 0;
}
@Override
@@ -1429,16 +1428,15 @@
ViewNode node = new ViewNode();
setAutoFillId(node, forAutoFill, virtualId);
mNode.mChildren[index] = node;
- return new ViewNodeBuilder(mAssist, node, false, flags);
+ return new ViewNodeBuilder(mAssist, node, false);
}
- private ViewStructure asyncNewChild(int index, boolean forAutoFill, int virtualId,
- int flags) {
+ private ViewStructure asyncNewChild(int index, boolean forAutoFill, int virtualId) {
synchronized (mAssist) {
ViewNode node = new ViewNode();
setAutoFillId(node, forAutoFill, virtualId);
mNode.mChildren[index] = node;
- ViewNodeBuilder builder = new ViewNodeBuilder(mAssist, node, true, flags);
+ ViewNodeBuilder builder = new ViewNodeBuilder(mAssist, node, true);
mAssist.mPendingAsyncChildren.add(builder);
return builder;
}
@@ -1457,12 +1455,12 @@
@Override
public ViewStructure asyncNewChild(int index) {
- return asyncNewChild(index, false, 0, 0);
+ return asyncNewChild(index, false, 0);
}
@Override
public ViewStructure asyncNewChild(int index, int virtualId, int flags) {
- return asyncNewChild(index, true, virtualId, flags);
+ return asyncNewChild(index, true, virtualId);
}
@Override
diff --git a/core/java/android/app/backup/BackupManagerMonitor.java b/core/java/android/app/backup/BackupManagerMonitor.java
index 099878b..d2a623e 100644
--- a/core/java/android/app/backup/BackupManagerMonitor.java
+++ b/core/java/android/app/backup/BackupManagerMonitor.java
@@ -54,15 +54,31 @@
public static final String EXTRA_LOG_EVENT_CATEGORY =
"android.app.backup.extra.LOG_EVENT_CATEGORY";
+ /**
+ * string: when we have event of id LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER we send the version
+ * of the backup.
+ */
+ public static final String EXTRA_LOG_OLD_VERSION =
+ "android.app.backup.extra.LOG_OLD_VERSION";
+
// TODO complete this list with all log messages. And document properly.
public static final int LOG_EVENT_ID_FULL_BACKUP_TIMEOUT = 4;
+ public static final int LOG_EVENT_ID_PACKAGE_NOT_FOUND = 12;
+ public static final int LOG_EVENT_ID_PACKAGE_TRANSPORT_NOT_PRESENT = 15;
public static final int LOG_EVENT_ID_KEY_VALUE_BACKUP_TIMEOUT = 21;
+ public static final int LOG_EVENT_ID_NO_RESTORE_METADATA_AVAILABLE = 22;
+ public static final int LOG_EVENT_ID_PACKAGE_NOT_PRESENT = 26;
+ public static final int LOG_EVENT_ID_APP_HAS_NO_AGENT = 28;
+ public static final int LOG_EVENT_ID_CANT_FIND_AGENT = 30;
public static final int LOG_EVENT_ID_KEY_VALUE_RESTORE_TIMEOUT = 31;
+ public static final int LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER = 36;
public static final int LOG_EVENT_ID_FULL_RESTORE_TIMEOUT = 45;
public static final int LOG_EVENT_ID_NO_PACKAGES = 49;
+
+
/**
* This method will be called each time something important happens on BackupManager.
*
diff --git a/core/java/android/app/usage/CacheQuotaHint.aidl b/core/java/android/app/usage/CacheQuotaHint.aidl
new file mode 100644
index 0000000..0470ea7
--- /dev/null
+++ b/core/java/android/app/usage/CacheQuotaHint.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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.app.usage;
+
+/** {@hide} */
+parcelable CacheQuotaHint;
\ No newline at end of file
diff --git a/core/java/android/app/usage/CacheQuotaHint.java b/core/java/android/app/usage/CacheQuotaHint.java
new file mode 100644
index 0000000..4b6f99b
--- /dev/null
+++ b/core/java/android/app/usage/CacheQuotaHint.java
@@ -0,0 +1,142 @@
+/*
+ * 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.app.usage;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * CacheQuotaRequest represents a triplet of a uid, the volume UUID it is stored upon, and
+ * its usage stats. When processed, it obtains a cache quota as defined by the system which
+ * allows apps to understand how much cache to use.
+ * {@hide}
+ */
+@SystemApi
+public final class CacheQuotaHint implements Parcelable {
+ public static final long QUOTA_NOT_SET = -1;
+ private final String mUuid;
+ private final int mUid;
+ private final UsageStats mUsageStats;
+ private final long mQuota;
+
+ /**
+ * Create a new request.
+ * @param builder A builder for this object.
+ */
+ public CacheQuotaHint(Builder builder) {
+ this.mUuid = builder.mUuid;
+ this.mUid = builder.mUid;
+ this.mUsageStats = builder.mUsageStats;
+ this.mQuota = builder.mQuota;
+ }
+
+ public String getVolumeUuid() {
+ return mUuid;
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public long getQuota() {
+ return mQuota;
+ }
+
+ public UsageStats getUsageStats() {
+ return mUsageStats;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mUuid);
+ dest.writeInt(mUid);
+ dest.writeLong(mQuota);
+ dest.writeParcelable(mUsageStats, 0);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final class Builder {
+ private String mUuid;
+ private int mUid;
+ private UsageStats mUsageStats;
+ private long mQuota;
+
+ public Builder() {
+ }
+
+ public Builder(CacheQuotaHint hint) {
+ setVolumeUuid(hint.getVolumeUuid());
+ setUid(hint.getUid());
+ setUsageStats(hint.getUsageStats());
+ setQuota(hint.getQuota());
+ }
+
+ public @NonNull Builder setVolumeUuid(@Nullable String uuid) {
+ mUuid = uuid;
+ return this;
+ }
+
+ public @NonNull Builder setUid(int uid) {
+ Preconditions.checkArgumentPositive(uid, "Proposed uid was not positive.");
+ mUid = uid;
+ return this;
+ }
+
+ public @NonNull Builder setUsageStats(@Nullable UsageStats stats) {
+ mUsageStats = stats;
+ return this;
+ }
+
+ public @NonNull Builder setQuota(long quota) {
+ Preconditions.checkArgument((quota >= QUOTA_NOT_SET));
+ mQuota = quota;
+ return this;
+ }
+
+ public @NonNull CacheQuotaHint build() {
+ Preconditions.checkNotNull(mUsageStats);
+ return new CacheQuotaHint(this);
+ }
+ }
+
+ public static final Parcelable.Creator<CacheQuotaHint> CREATOR =
+ new Creator<CacheQuotaHint>() {
+ @Override
+ public CacheQuotaHint createFromParcel(Parcel in) {
+ final Builder builder = new Builder();
+ return builder.setVolumeUuid(in.readString())
+ .setUid(in.readInt())
+ .setQuota(in.readLong())
+ .setUsageStats(in.readParcelable(UsageStats.class.getClassLoader()))
+ .build();
+ }
+
+ @Override
+ public CacheQuotaHint[] newArray(int size) {
+ return new CacheQuotaHint[size];
+ }
+ };
+}
diff --git a/core/java/android/app/usage/CacheQuotaService.java b/core/java/android/app/usage/CacheQuotaService.java
new file mode 100644
index 0000000..b9430ab
--- /dev/null
+++ b/core/java/android/app/usage/CacheQuotaService.java
@@ -0,0 +1,112 @@
+/*
+ * 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.app.usage;
+
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.app.usage.ICacheQuotaService;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteCallback;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.List;
+
+/**
+ * CacheQuoteService defines a service which accepts cache quota requests and processes them,
+ * thereby filling out how much quota each request deserves.
+ * {@hide}
+ */
+@SystemApi
+public abstract class CacheQuotaService extends Service {
+ private static final String TAG = "CacheQuotaService";
+
+ /**
+ * The {@link Intent} action that must be declared as handled by a service
+ * in its manifest for the system to recognize it as a quota providing service.
+ */
+ public static final String SERVICE_INTERFACE = "android.app.usage.CacheQuotaService";
+
+ /** {@hide} **/
+ public static final String REQUEST_LIST_KEY = "requests";
+
+ private CacheQuotaServiceWrapper mWrapper;
+ private Handler mHandler;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mWrapper = new CacheQuotaServiceWrapper();
+ mHandler = new ServiceHandler(getMainLooper());
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mWrapper;
+ }
+
+ /**
+ * Processes the cache quota list upon receiving a list of requests.
+ * @param requests A list of cache quotas to fulfill.
+ * @return A completed list of cache quota requests.
+ */
+ public abstract List<CacheQuotaHint> onComputeCacheQuotaHints(
+ List<CacheQuotaHint> requests);
+
+ private final class CacheQuotaServiceWrapper extends ICacheQuotaService.Stub {
+ @Override
+ public void computeCacheQuotaHints(
+ RemoteCallback callback, List<CacheQuotaHint> requests) {
+ final Pair<RemoteCallback, List<CacheQuotaHint>> pair =
+ Pair.create(callback, requests);
+ Message msg = mHandler.obtainMessage(ServiceHandler.MSG_SEND_LIST, pair);
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ private final class ServiceHandler extends Handler {
+ public static final int MSG_SEND_LIST = 1;
+
+ public ServiceHandler(Looper looper) {
+ super(looper, null, true);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ final int action = msg.what;
+ switch (action) {
+ case MSG_SEND_LIST:
+ final Pair<RemoteCallback, List<CacheQuotaHint>> pair =
+ (Pair<RemoteCallback, List<CacheQuotaHint>>) msg.obj;
+ List<CacheQuotaHint> processed = onComputeCacheQuotaHints(pair.second);
+ final Bundle data = new Bundle();
+ data.putParcelableList(REQUEST_LIST_KEY, processed);
+
+ final RemoteCallback callback = pair.first;
+ callback.sendResult(data);
+ break;
+ default:
+ Log.w(TAG, "Handling unknown message: " + action);
+ }
+ }
+ }
+}
diff --git a/core/java/android/app/usage/ICacheQuotaService.aidl b/core/java/android/app/usage/ICacheQuotaService.aidl
new file mode 100644
index 0000000..8d984e0
--- /dev/null
+++ b/core/java/android/app/usage/ICacheQuotaService.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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.app.usage;
+
+import android.os.RemoteCallback;
+import android.app.usage.CacheQuotaHint;
+
+/** {@hide} */
+oneway interface ICacheQuotaService {
+ void computeCacheQuotaHints(in RemoteCallback callback, in List<CacheQuotaHint> requests);
+}
diff --git a/core/java/android/app/usage/IStorageStatsManager.aidl b/core/java/android/app/usage/IStorageStatsManager.aidl
index 62ebf60..f4c18dd 100644
--- a/core/java/android/app/usage/IStorageStatsManager.aidl
+++ b/core/java/android/app/usage/IStorageStatsManager.aidl
@@ -21,6 +21,7 @@
/** {@hide} */
interface IStorageStatsManager {
+ boolean isQuotaSupported(String volumeUuid, String callingPackage);
long getTotalBytes(String volumeUuid, String callingPackage);
long getFreeBytes(String volumeUuid, String callingPackage);
StorageStats queryStatsForUid(String volumeUuid, int uid, String callingPackage);
diff --git a/core/java/android/app/usage/StorageStatsManager.java b/core/java/android/app/usage/StorageStatsManager.java
index 9d30771..7d4efb9 100644
--- a/core/java/android/app/usage/StorageStatsManager.java
+++ b/core/java/android/app/usage/StorageStatsManager.java
@@ -46,6 +46,15 @@
mService = Preconditions.checkNotNull(service);
}
+ /** {@hide} */
+ public boolean isQuotaSupported(String volumeUuid) {
+ try {
+ return mService.isQuotaSupported(volumeUuid, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/**
* Return the total space on the requested storage volume.
* <p>
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index a6f91fe..08595dd 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -19,7 +19,7 @@
import android.content.ComponentName;
import android.content.res.Configuration;
-import java.io.IOException;
+import java.util.List;
/**
* UsageStatsManager local system service interface.
@@ -127,4 +127,7 @@
public abstract void applyRestoredPayload(int user, String key, byte[] payload);
+ /* Cache Quota Service API */
+ public abstract List<UsageStats> queryUsageStatsForUser(
+ int userId, int interval, long beginTime, long endTime);
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 06f7303..44f6c43 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1230,6 +1230,16 @@
public abstract File getExternalCacheDir();
/**
+ * Returns absolute path to application-specific directory in the preloaded cache.
+ * <p>Files stored in the cache directory can be deleted when the device runs low on storage.
+ * There is no guarantee when these files will be deleted.
+ * @hide
+ */
+ @Nullable
+ @SystemApi
+ public abstract File getPreloadsFileCache();
+
+ /**
* Returns absolute paths to application-specific directories on all
* shared/external storage devices where the application can place cache
* files it owns. These files are internal to the application, and not
@@ -4429,6 +4439,11 @@
public abstract Display getDisplay();
/**
+ * @hide
+ */
+ public abstract void updateDisplay(int displayId);
+
+ /**
* Indicates whether this Context is restricted.
*
* @return {@code true} if this Context is restricted, {@code false} otherwise.
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 546bfc4..c932b23 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -281,6 +281,13 @@
return mBase.getDir(name, mode);
}
+
+ /** @hide **/
+ @Override
+ public File getPreloadsFileCache() {
+ return mBase.getPreloadsFileCache();
+ }
+
@Override
public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory) {
return mBase.openOrCreateDatabase(name, mode, factory);
@@ -865,6 +872,14 @@
return mBase.getDisplay();
}
+ /**
+ * @hide
+ */
+ @Override
+ public void updateDisplay(int displayId) {
+ mBase.updateDisplay(displayId);
+ }
+
@Override
public Context createDeviceProtectedStorageContext() {
return mBase.createDeviceProtectedStorageContext();
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index e6cae69..56609eb 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -284,8 +284,6 @@
/** Whether or not the intent filter is visible to ephemeral apps. */
private boolean mVisibleToEphemeral;
- /** Whether or not the intent filter is part of an ephemeral app. */
- private boolean mEphemeral;
// These functions are the start of more optimized code for managing
// the string sets... not yet implemented.
@@ -656,19 +654,10 @@
mVisibleToEphemeral = visibleToEmphemeral;
}
/** @hide */
- public boolean isVisibleToEphemeral() {
+ public boolean isVisibleToInstantApp() {
return mVisibleToEphemeral;
}
- /** @hide */
- public void setEphemeral(boolean ephemeral) {
- mEphemeral = ephemeral;
- }
- /** @hide */
- public boolean isEphemeral() {
- return mEphemeral;
- }
-
/**
* Add a new Intent action to match against. If any actions are included
* in the filter, then an Intent's action must be one of those values for
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 1fa4181..9737b11 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -498,11 +498,12 @@
public static final int PRIVATE_FLAG_DIRECT_BOOT_AWARE = 1 << 6;
/**
- * Value for {@link #flags}: {@code true} if the application is blocked via restrictions
- * and for most purposes is considered as not installed.
- * {@hide}
+ * Value for {@link #privateFlags}: {@code true} if the application is installed
+ * as instant app.
+ *
+ * @hide
*/
- public static final int PRIVATE_FLAG_EPHEMERAL = 1 << 7;
+ public static final int PRIVATE_FLAG_INSTANT = 1 << 7;
/**
* When set, at least one component inside this application is direct boot
@@ -681,7 +682,21 @@
*
* {@hide}
*/
- public String seinfo = "default";
+ public String seInfo = "default";
+
+ /**
+ * The seinfo tag generated per-user. This value may change based upon the
+ * user's configuration. For example, when an instant app is installed for
+ * a user. It is an error if this field is ever {@code null} when trying to
+ * start a new process.
+ * <p>NOTE: We need to separate this out because we modify per-user values
+ * multiple times. This needs to be refactored since we're performing more
+ * work than necessary and these values should only be set once. When that
+ * happens, we can merge the per-user value with the seInfo state above.
+ *
+ * {@hide}
+ */
+ public String seInfoUser;
/**
* Paths to all shared libraries this application is linked against. This
@@ -1009,8 +1024,9 @@
if (resourceDirs != null) {
pw.println(prefix + "resourceDirs=" + Arrays.toString(resourceDirs));
}
- if ((flags&DUMP_FLAG_DETAILS) != 0 && seinfo != null) {
- pw.println(prefix + "seinfo=" + seinfo);
+ if ((flags&DUMP_FLAG_DETAILS) != 0 && seInfo != null) {
+ pw.println(prefix + "seinfo=" + seInfo);
+ pw.println(prefix + "seinfoUser=" + seInfoUser);
}
pw.println(prefix + "dataDir=" + dataDir);
if ((flags&DUMP_FLAG_DETAILS) != 0) {
@@ -1120,7 +1136,8 @@
primaryCpuAbi = orig.primaryCpuAbi;
secondaryCpuAbi = orig.secondaryCpuAbi;
resourceDirs = orig.resourceDirs;
- seinfo = orig.seinfo;
+ seInfo = orig.seInfo;
+ seInfoUser = orig.seInfoUser;
sharedLibraryFiles = orig.sharedLibraryFiles;
dataDir = orig.dataDir;
deviceEncryptedDataDir = deviceProtectedDataDir = orig.deviceProtectedDataDir;
@@ -1181,7 +1198,8 @@
dest.writeString(primaryCpuAbi);
dest.writeString(secondaryCpuAbi);
dest.writeStringArray(resourceDirs);
- dest.writeString(seinfo);
+ dest.writeString(seInfo);
+ dest.writeString(seInfoUser);
dest.writeStringArray(sharedLibraryFiles);
dest.writeString(dataDir);
dest.writeString(deviceProtectedDataDir);
@@ -1242,7 +1260,8 @@
primaryCpuAbi = source.readString();
secondaryCpuAbi = source.readString();
resourceDirs = source.readStringArray();
- seinfo = source.readString();
+ seInfo = source.readString();
+ seInfoUser = source.readString();
sharedLibraryFiles = source.readStringArray();
dataDir = source.readString();
deviceEncryptedDataDir = deviceProtectedDataDir = source.readString();
@@ -1330,7 +1349,6 @@
} else {
dataDir = credentialProtectedDataDir;
}
- // TODO: modify per-user ephemerality
}
/**
@@ -1415,7 +1433,7 @@
* @hide
*/
public boolean isInstantApp() {
- return (privateFlags & ApplicationInfo.PRIVATE_FLAG_EPHEMERAL) != 0;
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
}
/**
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 9d36a73..ffb777d 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -553,7 +553,8 @@
boolean setInstallLocation(int loc);
int getInstallLocation();
- int installExistingPackageAsUser(String packageName, int userId, int installReason);
+ int installExistingPackageAsUser(String packageName, int userId, int installFlags,
+ int installReason);
void verifyPendingInstall(int id, int verificationCode);
void extendVerificationTimeout(int id, int verificationCodeAtTimeout, long millisecondsToDelay);
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 40deeae..0866af2 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -37,7 +37,7 @@
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
-import android.graphics.drawable.MaskableIconDrawable;
+import android.graphics.drawable.AdaptiveIconDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -807,7 +807,7 @@
if (bmp != null) {
BitmapDrawable dr = new BitmapDrawable(mContext.getResources(), bmp);
if (shortcut.hasMaskableBitmap()) {
- return new MaskableIconDrawable(null, dr);
+ return new AdaptiveIconDrawable(null, dr);
} else {
return dr;
}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 4de967c..278a6d0 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1096,9 +1096,11 @@
@SystemApi
public void setInstallAsInstantApp(boolean isInstantApp) {
if (isInstantApp) {
- installFlags |= PackageManager.INSTALL_EPHEMERAL;
+ installFlags |= PackageManager.INSTALL_INSTANT_APP;
+ installFlags &= ~PackageManager.INSTALL_FULL_APP;
} else {
- installFlags &= ~PackageManager.INSTALL_EPHEMERAL;
+ installFlags &= ~PackageManager.INSTALL_INSTANT_APP;
+ installFlags |= PackageManager.INSTALL_FULL_APP;
}
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 308153d..5733982 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -452,17 +452,17 @@
/**
* Internal {@link PackageInfo} flag: include components that are part of an
- * ephemeral app. By default, ephemeral components are not matched.
+ * instant app. By default, instant app components are not matched.
* @hide
*/
- public static final int MATCH_EPHEMERAL = 0x00800000;
+ public static final int MATCH_INSTANT = 0x00800000;
/**
* Internal {@link PackageInfo} flag: include only components that are exposed to
* ephemeral apps.
* @hide
*/
- public static final int MATCH_VISIBLE_TO_EPHEMERAL_ONLY = 0x01000000;
+ public static final int MATCH_VISIBLE_TO_INSTANT_APP_ONLY = 0x01000000;
/**
* Internal flag used to indicate that a system component has done their
@@ -613,7 +613,7 @@
INSTALL_GRANT_RUNTIME_PERMISSIONS,
INSTALL_FORCE_VOLUME_UUID,
INSTALL_FORCE_PERMISSION_PROMPT,
- INSTALL_EPHEMERAL,
+ INSTALL_INSTANT_APP,
INSTALL_DONT_KILL_APP,
})
@Retention(RetentionPolicy.SOURCE)
@@ -714,7 +714,16 @@
*
* @hide
*/
- public static final int INSTALL_EPHEMERAL = 0x00000800;
+ public static final int INSTALL_INSTANT_APP = 0x00000800;
+
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that this package is
+ * to be installed as a heavy weight app. This is fundamentally the opposite of
+ * {@link #INSTALL_INSTANT_APP}.
+ *
+ * @hide
+ */
+ public static final int INSTALL_FULL_APP = 0x00004000;
/**
* Flag parameter for {@link #installPackage} to indicate that this package contains
@@ -1185,12 +1194,12 @@
public static final int INSTALL_FAILED_ABORTED = -115;
/**
- * Installation failed return code: ephemeral app installs are incompatible with some
+ * Installation failed return code: instant app installs are incompatible with some
* other installation flags supplied for the operation; or other circumstances such
- * as trying to upgrade a system app via an ephemeral install.
+ * as trying to upgrade a system app via an instant app install.
* @hide
*/
- public static final int INSTALL_FAILED_EPHEMERAL_INVALID = -116;
+ public static final int INSTALL_FAILED_INSTANT_APP_INVALID = -116;
/** @hide */
@IntDef(flag = true, value = {
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 7a9aaaf..56f1e0c 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -720,6 +720,8 @@
public final static int PARSE_COLLECT_CERTIFICATES = 1<<8;
public final static int PARSE_TRUSTED_OVERLAY = 1<<9;
public final static int PARSE_ENFORCE_CODE = 1<<10;
+ /** @deprecated remove when fixing b/34761192 */
+ @Deprecated
public final static int PARSE_IS_EPHEMERAL = 1<<11;
public final static int PARSE_FORCE_SDK = 1<<12;
@@ -2012,10 +2014,6 @@
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_EXTERNAL_STORAGE;
}
- if ((flags & PARSE_IS_EPHEMERAL) != 0) {
- pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_EPHEMERAL;
- }
-
if (sa.getBoolean(com.android.internal.R.styleable.AndroidManifest_isolatedSplits, false)) {
pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING;
}
@@ -2691,9 +2689,9 @@
certSha256 = certSha256.replace(":", "").toLowerCase();
pkg.usesStaticLibraries = ArrayUtils.add(pkg.usesStaticLibraries, lname);
pkg.usesStaticLibrariesVersions = ArrayUtils.appendInt(
- pkg.usesStaticLibrariesVersions, version);
+ pkg.usesStaticLibrariesVersions, version, true);
pkg.usesStaticLibrariesCertDigests = ArrayUtils.appendElement(String.class,
- pkg.usesStaticLibrariesCertDigests, certSha256);
+ pkg.usesStaticLibrariesCertDigests, certSha256, true);
XmlUtils.skipCurrentTag(parser);
@@ -2747,15 +2745,15 @@
String cls = clsSeq.toString();
char c = cls.charAt(0);
if (c == '.') {
- return (pkg + cls).intern();
+ return pkg + cls;
}
if (cls.indexOf('.') < 0) {
StringBuilder b = new StringBuilder(pkg);
b.append('.');
b.append(cls);
- return b.toString().intern();
+ return b.toString();
}
- return cls.intern();
+ return cls;
}
private static String buildCompoundName(String pkg,
@@ -2775,7 +2773,7 @@
+ pkg + ": " + nameError;
return null;
}
- return (pkg + proc).intern();
+ return pkg + proc;
}
String nameError = validateName(proc, true, false);
if (nameError != null && !"system".equals(proc)) {
@@ -2783,7 +2781,7 @@
+ pkg + ": " + nameError;
return null;
}
- return proc.intern();
+ return proc;
}
private static String buildProcessName(String pkg, String defProc,
@@ -4149,11 +4147,8 @@
ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE;
}
- final boolean hasVisibleToEphemeral =
- sa.hasValue(R.styleable.AndroidManifestActivity_visibleToInstantApps);
- final boolean isEphemeral = ((flags & PARSE_IS_EPHEMERAL) != 0);
- final boolean visibleToEphemeral = isEphemeral
- || sa.getBoolean(R.styleable.AndroidManifestActivity_visibleToInstantApps, false);
+ final boolean visibleToEphemeral =
+ sa.getBoolean(R.styleable.AndroidManifestActivity_visibleToInstantApps, false);
if (visibleToEphemeral) {
a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL;
}
@@ -4188,8 +4183,6 @@
intent, outError)) {
return null;
}
- intent.setEphemeral(isEphemeral);
- intent.setVisibleToEphemeral(visibleToEphemeral || isWebBrowsableIntent(intent));
if (intent.countActions() == 0) {
Slog.w(TAG, "No actions in intent filter at "
+ mArchiveSourcePath + " "
@@ -4198,7 +4191,8 @@
a.intents.add(intent);
}
// adjust activity flags when we implicitly expose it via a browsable filter
- if (!hasVisibleToEphemeral && intent.isVisibleToEphemeral()) {
+ intent.setVisibleToEphemeral(visibleToEphemeral || isWebBrowsableIntent(intent));
+ if (intent.isVisibleToInstantApp()) {
a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL;
}
} else if (!receiver && parser.getName().equals("preferred")) {
@@ -4207,8 +4201,6 @@
intent, outError)) {
return null;
}
- intent.setEphemeral(isEphemeral);
- intent.setVisibleToEphemeral(visibleToEphemeral || isWebBrowsableIntent(intent));
if (intent.countActions() == 0) {
Slog.w(TAG, "No actions in preferred at "
+ mArchiveSourcePath + " "
@@ -4220,7 +4212,8 @@
owner.preferredActivityFilters.add(intent);
}
// adjust activity flags when we implicitly expose it via a browsable filter
- if (!hasVisibleToEphemeral && intent.isVisibleToEphemeral()) {
+ intent.setVisibleToEphemeral(visibleToEphemeral || isWebBrowsableIntent(intent));
+ if (intent.isVisibleToInstantApp()) {
a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL;
}
} else if (parser.getName().equals("meta-data")) {
@@ -4472,9 +4465,8 @@
}
// TODO add visibleToInstantApps attribute to activity alias
- final boolean isEphemeral = ((flags & PARSE_IS_EPHEMERAL) != 0);
- final boolean visibleToEphemeral = isEphemeral
- || ((a.info.flags & ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL) != 0);
+ final boolean visibleToEphemeral =
+ ((a.info.flags & ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL) != 0);
sa.recycle();
@@ -4502,13 +4494,12 @@
+ mArchiveSourcePath + " "
+ parser.getPositionDescription());
} else {
- intent.setEphemeral(isEphemeral);
- intent.setVisibleToEphemeral(visibleToEphemeral
- || isWebBrowsableIntent(intent));
+ intent.setVisibleToEphemeral(
+ visibleToEphemeral || isWebBrowsableIntent(intent));
a.intents.add(intent);
}
// adjust activity flags when we implicitly expose it via a browsable filter
- if (intent.isVisibleToEphemeral()) {
+ if (intent.isVisibleToInstantApp()) {
a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL;
}
} else if (parser.getName().equals("meta-data")) {
@@ -4649,11 +4640,8 @@
ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE;
}
- final boolean hasVisibleToEphemeral =
- sa.hasValue(R.styleable.AndroidManifestProvider_visibleToInstantApps);
- final boolean isEphemeral = ((flags & PARSE_IS_EPHEMERAL) != 0);
- final boolean visibleToEphemeral = isEphemeral
- || sa.getBoolean(R.styleable.AndroidManifestProvider_visibleToInstantApps, false);
+ final boolean visibleToEphemeral =
+ sa.getBoolean(R.styleable.AndroidManifestProvider_visibleToInstantApps, false);
if (visibleToEphemeral) {
p.info.flags |= ProviderInfo.FLAG_VISIBLE_TO_EPHEMERAL;
}
@@ -4681,7 +4669,7 @@
p.info.authority = cpname.intern();
if (!parseProviderTags(
- res, parser, isEphemeral, hasVisibleToEphemeral, visibleToEphemeral, p, outError)) {
+ res, parser, visibleToEphemeral, p, outError)) {
return null;
}
@@ -4689,8 +4677,7 @@
}
private boolean parseProviderTags(Resources res, XmlResourceParser parser,
- boolean isEphemeral, boolean hasVisibleToEphemeral, boolean visibleToEphemeral,
- Provider outInfo, String[] outError)
+ boolean visibleToEphemeral, Provider outInfo, String[] outError)
throws XmlPullParserException, IOException {
int outerDepth = parser.getDepth();
int type;
@@ -4707,11 +4694,10 @@
intent, outError)) {
return false;
}
- intent.setEphemeral(isEphemeral);
- intent.setVisibleToEphemeral(visibleToEphemeral || isWebBrowsableIntent(intent));
outInfo.intents.add(intent);
// adjust provider flags when we implicitly expose it via a browsable filter
- if (!hasVisibleToEphemeral && intent.isVisibleToEphemeral()) {
+ intent.setVisibleToEphemeral(visibleToEphemeral || isWebBrowsableIntent(intent));
+ if (intent.isVisibleToInstantApp()) {
outInfo.info.flags |= ProviderInfo.FLAG_VISIBLE_TO_EPHEMERAL;
}
@@ -4963,11 +4949,8 @@
ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE;
}
- final boolean hasVisibleToEphemeral =
- sa.hasValue(R.styleable.AndroidManifestService_visibleToInstantApps);
- final boolean isEphemeral = ((flags & PARSE_IS_EPHEMERAL) != 0);
- final boolean visibleToEphemeral = isEphemeral
- || sa.getBoolean(R.styleable.AndroidManifestService_visibleToInstantApps, false);
+ final boolean visibleToEphemeral =
+ sa.getBoolean(R.styleable.AndroidManifestService_visibleToInstantApps, false);
if (visibleToEphemeral) {
s.info.flags |= ServiceInfo.FLAG_VISIBLE_TO_EPHEMERAL;
}
@@ -4999,10 +4982,9 @@
intent, outError)) {
return null;
}
- intent.setEphemeral(isEphemeral);
- intent.setVisibleToEphemeral(visibleToEphemeral || isWebBrowsableIntent(intent));
// adjust activity flags when we implicitly expose it via a browsable filter
- if (!hasVisibleToEphemeral && intent.isVisibleToEphemeral()) {
+ intent.setVisibleToEphemeral(visibleToEphemeral || isWebBrowsableIntent(intent));
+ if (intent.isVisibleToInstantApp()) {
s.info.flags |= ServiceInfo.FLAG_VISIBLE_TO_EPHEMERAL;
}
s.intents.add(intent);
@@ -5101,7 +5083,7 @@
if (v != null) {
if (v.type == TypedValue.TYPE_STRING) {
CharSequence cs = v.coerceToString();
- data.putString(name, cs != null ? cs.toString().intern() : null);
+ data.putString(name, cs != null ? cs.toString() : null);
} else if (v.type == TypedValue.TYPE_INT_BOOLEAN) {
data.putBoolean(name, v.data != 0);
} else if (v.type >= TypedValue.TYPE_FIRST_INT
@@ -5884,7 +5866,7 @@
// We use the boot classloader for all classes that we load.
final ClassLoader boot = Object.class.getClassLoader();
- packageName = dest.readString();
+ packageName = dest.readString().intern();
manifestPackageName = dest.readString();
splitNames = dest.readStringArray();
volumeUuid = dest.readString();
@@ -5897,6 +5879,9 @@
splitPrivateFlags = dest.createIntArray();
baseHardwareAccelerated = (dest.readInt() == 1);
applicationInfo = dest.readParcelable(boot);
+ if (applicationInfo.permission != null) {
+ applicationInfo.permission = applicationInfo.permission.intern();
+ }
// We don't serialize the "owner" package and the application info object for each of
// these components, in order to save space and to avoid circular dependencies while
@@ -5917,7 +5902,10 @@
fixupOwner(instrumentation);
dest.readStringList(requestedPermissions);
+ internStringArrayList(requestedPermissions);
protectedBroadcasts = dest.createStringArrayList();
+ internStringArrayList(protectedBroadcasts);
+
parentPackage = dest.readParcelable(boot);
childPackages = new ArrayList<>();
@@ -5927,16 +5915,23 @@
}
staticSharedLibName = dest.readString();
+ if (staticSharedLibName != null) {
+ staticSharedLibName = staticSharedLibName.intern();
+ }
staticSharedLibVersion = dest.readInt();
libraryNames = dest.createStringArrayList();
+ internStringArrayList(libraryNames);
usesLibraries = dest.createStringArrayList();
+ internStringArrayList(usesLibraries);
usesOptionalLibraries = dest.createStringArrayList();
+ internStringArrayList(usesOptionalLibraries);
usesLibraryFiles = dest.readStringArray();
final int libCount = dest.readInt();
if (libCount > 0) {
usesStaticLibraries = new ArrayList<>(libCount);
dest.readStringList(usesStaticLibraries);
+ internStringArrayList(usesStaticLibraries);
usesStaticLibrariesVersions = new int[libCount];
dest.readIntArray(usesStaticLibrariesVersions);
usesStaticLibrariesCertDigests = new String[libCount];
@@ -5955,7 +5950,13 @@
mAppMetaData = dest.readBundle();
mVersionCode = dest.readInt();
mVersionName = dest.readString();
+ if (mVersionName != null) {
+ mVersionName = mVersionName.intern();
+ }
mSharedUserId = dest.readString();
+ if (mSharedUserId != null) {
+ mSharedUserId = mSharedUserId.intern();
+ }
mSharedUserLabel = dest.readInt();
mSignatures = (Signature[]) dest.readParcelableArray(boot, Signature.class);
@@ -6006,6 +6007,15 @@
restrictUpdateHash = dest.createByteArray();
}
+ private static void internStringArrayList(List<String> list) {
+ if (list != null) {
+ final int N = list.size();
+ for (int i = 0; i < N; ++i) {
+ list.set(i, list.get(i).intern());
+ }
+ }
+ }
+
/**
* Sets the package owner and the the {@code applicationInfo} for every component
* owner by this package.
@@ -6393,6 +6403,10 @@
super(in);
final ClassLoader boot = Object.class.getClassLoader();
info = in.readParcelable(boot);
+ if (info.group != null) {
+ info.group = info.group.intern();
+ }
+
tree = (in.readInt() == 1);
group = in.readParcelable(boot);
}
@@ -6482,6 +6496,9 @@
if (state.stopped) {
return true;
}
+ if (state.instantApp != p.applicationInfo.isInstantApp()) {
+ return true;
+ }
if ((flags & PackageManager.GET_META_DATA) != 0
&& (metaData != null || p.mAppMetaData != null)) {
return true;
@@ -6517,6 +6534,11 @@
} else {
ai.flags &= ~ApplicationInfo.FLAG_SUSPENDED;
}
+ if (state.instantApp) {
+ ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_INSTANT;
+ } else {
+ ai.privateFlags &= ~ApplicationInfo.PRIVATE_FLAG_INSTANT;
+ }
if (state.hidden) {
ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_HIDDEN;
} else {
@@ -6537,6 +6559,7 @@
if (ai.category == ApplicationInfo.CATEGORY_UNDEFINED) {
ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName);
}
+ ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state);
}
public static ApplicationInfo generateApplicationInfo(Package p, int flags,
@@ -6660,6 +6683,10 @@
for (ActivityIntentInfo aii : intents) {
aii.activity = this;
}
+
+ if (info.permission != null) {
+ info.permission = info.permission.intern();
+ }
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator<Activity>() {
@@ -6744,6 +6771,10 @@
for (ServiceIntentInfo aii : intents) {
aii.service = this;
}
+
+ if (info.permission != null) {
+ info.permission = info.permission.intern();
+ }
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator<Service>() {
@@ -6825,6 +6856,18 @@
for (ProviderIntentInfo aii : intents) {
aii.provider = this;
}
+
+ if (info.readPermission != null) {
+ info.readPermission = info.readPermission.intern();
+ }
+
+ if (info.writePermission != null) {
+ info.writePermission = info.writePermission.intern();
+ }
+
+ if (info.authority != null) {
+ info.authority = info.authority.intern();
+ }
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator<Provider>() {
@@ -6897,6 +6940,14 @@
private Instrumentation(Parcel in) {
super(in);
info = in.readParcelable(Object.class.getClassLoader());
+
+ if (info.targetPackage != null) {
+ info.targetPackage = info.targetPackage.intern();
+ }
+
+ if (info.targetProcess != null) {
+ info.targetProcess = info.targetProcess.intern();
+ }
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator<Instrumentation>() {
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index e19aa99..24f1164 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -43,6 +43,7 @@
public boolean hidden; // Is the app restricted by owner / admin
public boolean suspended;
public boolean blockUninstall;
+ public boolean instantApp;
public int enabled;
public String lastDisableAppCaller;
public int domainVerificationStatus;
@@ -71,6 +72,7 @@
hidden = o.hidden;
suspended = o.suspended;
blockUninstall = o.blockUninstall;
+ instantApp = o.instantApp;
enabled = o.enabled;
lastDisableAppCaller = o.lastDisableAppCaller;
domainVerificationStatus = o.domainVerificationStatus;
@@ -188,6 +190,9 @@
if (blockUninstall != oldState.blockUninstall) {
return false;
}
+ if (instantApp != oldState.instantApp) {
+ return false;
+ }
if (enabled != oldState.enabled) {
return false;
}
diff --git a/core/java/android/content/pm/SELinuxUtil.java b/core/java/android/content/pm/SELinuxUtil.java
new file mode 100644
index 0000000..55b5e29
--- /dev/null
+++ b/core/java/android/content/pm/SELinuxUtil.java
@@ -0,0 +1,42 @@
+/*
+ * 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.content.pm;
+
+import com.android.internal.util.ArrayUtils;
+
+/**
+ * Utility methods that need to be used in application space.
+ * @hide
+ */
+public final class SELinuxUtil {
+
+ /** Append to existing seinfo label for instant apps @hide */
+ private static final String INSTANT_APP_STR = ":ephemeralapp";
+
+ /** Append to existing seinfo when modifications are complete @hide */
+ public static final String COMPLETE_STR = ":complete";
+
+ /** @hide */
+ public static String assignSeinfoUser(PackageUserState userState) {
+ String seInfo = "";
+ if (userState.instantApp)
+ seInfo += INSTANT_APP_STR;
+ seInfo += COMPLETE_STR;
+ return seInfo;
+ }
+
+}
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index e97bb2f..7b09e26 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -40,21 +40,24 @@
public final class HardwareBuffer implements Parcelable {
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef({RGBA_8888, RGBA_FP16, RGBX_8888, RGB_888, RGB_565, BLOB})
- public @interface Format {};
+ @IntDef({RGBA_8888, RGBA_FP16, RGBA_1010102, RGBX_8888, RGB_888, RGB_565, BLOB})
+ public @interface Format {
+ }
/** Format: 8 bits each red, green, blue, alpha */
- public static final int RGBA_8888 = 1;
+ public static final int RGBA_8888 = 1;
/** Format: 8 bits each red, green, blue, alpha, alpha is always 0xFF */
- public static final int RGBX_8888 = 2;
+ public static final int RGBX_8888 = 2;
/** Format: 8 bits each red, green, blue, no alpha */
- public static final int RGB_888 = 3;
+ public static final int RGB_888 = 3;
/** Format: 5 bits each red and blue, 6 bits green, no alpha */
- public static final int RGB_565 = 4;
+ public static final int RGB_565 = 4;
/** Format: 16 bits each red, green, blue, alpha */
- public static final int RGBA_FP16 = 0x16;
+ public static final int RGBA_FP16 = 0x16;
+ /** Format: 10 bits each red, green, blue, 2 bits alpha */
+ public static final int RGBA_1010102 = 0x2b;
/** Format: opaque format used for raw data transfer; must have a height of 1 */
- public static final int BLOB = 0x21;
+ public static final int BLOB = 0x21;
// Note: do not rename, this field is used by native code
private long mNativeObject;
@@ -107,7 +110,7 @@
* @param width The width in pixels of the buffer
* @param height The height in pixels of the buffer
* @param format The format of each pixel, one of {@link #RGBA_8888}, {@link #RGBA_FP16},
- * {@link #RGBX_8888}, {@link #RGB_565}, {@link #RGB_888}
+ * {@link #RGBX_8888}, {@link #RGB_565}, {@link #RGB_888}, {@link #RGBA_1010102}, {@link #BLOB}
* @param layers The number of layers in the buffer
* @param usage Flags describing how the buffer will be used, one of
* {@link #USAGE0_CPU_READ}, {@link #USAGE0_CPU_READ_OFTEN}, {@link #USAGE0_CPU_WRITE},
@@ -150,15 +153,15 @@
}
/**
- * Private use only. See {@link #create(int, int, int, int, int, long, long)}. May also be
+ * Private use only. See {@link #create(int, int, int, int, long)}. May also be
* called from JNI using an already allocated native <code>HardwareBuffer</code>.
*/
private HardwareBuffer(long nativeObject) {
mNativeObject = nativeObject;
- long nativeSize = NATIVE_HARDWARE_BUFFER_SIZE;
+ ClassLoader loader = HardwareBuffer.class.getClassLoader();
NativeAllocationRegistry registry = new NativeAllocationRegistry(
- HardwareBuffer.class.getClassLoader(), nGetNativeFinalizer(), nativeSize);
+ loader, nGetNativeFinalizer(), NATIVE_HARDWARE_BUFFER_SIZE);
mCleaner = registry.registerNativeAllocation(this, mNativeObject);
}
@@ -186,8 +189,9 @@
/**
* Returns the format of this buffer, one of {@link #RGBA_8888}, {@link #RGBA_FP16},
- * {@link #RGBX_8888}, {@link #RGB_565}, or {@link #RGB_888}.
+ * {@link #RGBX_8888}, {@link #RGB_565}, {@link #RGB_888}, {@link #RGBA_1010102}, {@link #BLOB}.
*/
+ @Format
public int getFormat() {
if (mNativeObject == 0) {
throw new IllegalStateException("This HardwareBuffer has been destroyed and its format "
@@ -291,12 +295,13 @@
* @param format The format to validate.
*
* @return True if <code>format</code> is a supported format. false otherwise.
- * See {@link #create(int, int, int, int, int, long, long)}.a
+ * See {@link #create(int, int, int, int, long)}.
*/
private static boolean isSupportedFormat(@Format int format) {
switch(format) {
case RGBA_8888:
case RGBA_FP16:
+ case RGBA_1010102:
case RGBX_8888:
case RGB_565:
case RGB_888:
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 0e450d7..4d92ab1 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -1097,7 +1097,6 @@
throws IllegalArgumentException;
private static native int nativeGetTypeFromTag(int tag)
throws IllegalArgumentException;
- private static native void nativeClassInit();
/**
* <p>Perform a 0-copy swap of the internal metadata with another object.</p>
@@ -1289,10 +1288,6 @@
}
static {
- /*
- * We use a class initializer to allow the native code to cache some field offsets
- */
- nativeClassInit();
registerAllMarshalers();
}
}
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableString.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableString.java
index d2c3908..c81e18d 100644
--- a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableString.java
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableString.java
@@ -33,7 +33,9 @@
private static final String TAG = MarshalQueryableString.class.getSimpleName();
private static final boolean DEBUG = false;
- private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
+ private static class PreloadHolder {
+ public static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
+ }
private static final byte NUL = (byte)'\0'; // used as string terminator
private class MarshalerString extends Marshaler<String> {
@@ -44,7 +46,7 @@
@Override
public void marshal(String value, ByteBuffer buffer) {
- byte[] arr = value.getBytes(UTF8_CHARSET);
+ byte[] arr = value.getBytes(PreloadHolder.UTF8_CHARSET);
buffer.put(arr);
buffer.put(NUL); // metadata strings are NUL-terminated
@@ -52,7 +54,7 @@
@Override
public int calculateMarshalSize(String value) {
- byte[] arr = value.getBytes(UTF8_CHARSET);
+ byte[] arr = value.getBytes(PreloadHolder.UTF8_CHARSET);
return arr.length + 1; // metadata strings are NUL-terminated
}
@@ -88,7 +90,7 @@
buffer.get(strBytes, /*dstOffset*/0, stringLength + 1); // including null character
// not including null character
- return new String(strBytes, /*offset*/0, stringLength, UTF8_CHARSET);
+ return new String(strBytes, /*offset*/0, stringLength, PreloadHolder.UTF8_CHARSET);
}
@Override
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index d1000ad..13b9206 100644
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -988,49 +988,31 @@
if (mAccessibilityManager.isEnabled()) {
AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
onInitializeAccessibilityEvent(event);
- String text = null;
- // This is very efficient since the properties are cached.
- final boolean speakPassword = Settings.Secure.getIntForUser(
- mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0,
- UserHandle.USER_CURRENT_OR_SELF) != 0;
- // Add text only if password announcement is enabled or if headset is
- // used to avoid leaking passwords.
- if (speakPassword || mAudioManager.isBluetoothA2dpOn()
- || mAudioManager.isWiredHeadsetOn()) {
- switch (code) {
- case Keyboard.KEYCODE_ALT:
- text = mContext.getString(R.string.keyboardview_keycode_alt);
- break;
- case Keyboard.KEYCODE_CANCEL:
- text = mContext.getString(R.string.keyboardview_keycode_cancel);
- break;
- case Keyboard.KEYCODE_DELETE:
- text = mContext.getString(R.string.keyboardview_keycode_delete);
- break;
- case Keyboard.KEYCODE_DONE:
- text = mContext.getString(R.string.keyboardview_keycode_done);
- break;
- case Keyboard.KEYCODE_MODE_CHANGE:
- text = mContext.getString(R.string.keyboardview_keycode_mode_change);
- break;
- case Keyboard.KEYCODE_SHIFT:
- text = mContext.getString(R.string.keyboardview_keycode_shift);
- break;
- case '\n':
- text = mContext.getString(R.string.keyboardview_keycode_enter);
- break;
- default:
- text = String.valueOf((char) code);
- }
- } else if (!mHeadsetRequiredToHearPasswordsAnnounced) {
- // We want the waring for required head set to be send with both the
- // hover enter and hover exit event, so set the flag after the exit.
- if (eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
- mHeadsetRequiredToHearPasswordsAnnounced = true;
- }
- text = mContext.getString(R.string.keyboard_headset_required_to_hear_password);
- } else {
- text = mContext.getString(R.string.keyboard_password_character_no_headset);
+ final String text;
+ switch (code) {
+ case Keyboard.KEYCODE_ALT:
+ text = mContext.getString(R.string.keyboardview_keycode_alt);
+ break;
+ case Keyboard.KEYCODE_CANCEL:
+ text = mContext.getString(R.string.keyboardview_keycode_cancel);
+ break;
+ case Keyboard.KEYCODE_DELETE:
+ text = mContext.getString(R.string.keyboardview_keycode_delete);
+ break;
+ case Keyboard.KEYCODE_DONE:
+ text = mContext.getString(R.string.keyboardview_keycode_done);
+ break;
+ case Keyboard.KEYCODE_MODE_CHANGE:
+ text = mContext.getString(R.string.keyboardview_keycode_mode_change);
+ break;
+ case Keyboard.KEYCODE_SHIFT:
+ text = mContext.getString(R.string.keyboardview_keycode_shift);
+ break;
+ case '\n':
+ text = mContext.getString(R.string.keyboardview_keycode_enter);
+ break;
+ default:
+ text = String.valueOf((char) code);
}
event.getText().add(text);
mAccessibilityManager.sendAccessibilityEvent(event);
diff --git a/core/java/android/net/NetworkBadging.java b/core/java/android/net/NetworkBadging.java
index 5cf2f96..4409d0a 100644
--- a/core/java/android/net/NetworkBadging.java
+++ b/core/java/android/net/NetworkBadging.java
@@ -17,6 +17,7 @@
package android.net;
import android.annotation.DrawableRes;
+import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -25,19 +26,29 @@
import android.content.res.Resources.Theme;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
-import android.net.ScoredNetwork.Badging;
import android.net.wifi.WifiManager;
import android.view.View;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Utility methods for working with network badging.
*
- * TODO: move ScoredNetwork.Badging and related constants to this class.
- *
* @hide
*/
@SystemApi
public class NetworkBadging {
+
+ @IntDef({BADGING_NONE, BADGING_SD, BADGING_HD, BADGING_4K})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Badging {}
+
+ public static final int BADGING_NONE = 0;
+ public static final int BADGING_SD = 10;
+ public static final int BADGING_HD = 20;
+ public static final int BADGING_4K = 30;
+
private NetworkBadging() {}
/**
@@ -55,7 +66,7 @@
@NonNull public static Drawable getWifiIcon(
@IntRange(from=0, to=4) int signalLevel, @Badging int badging, @Nullable Theme theme) {
Resources resources = Resources.getSystem();
- if (badging == ScoredNetwork.BADGING_NONE) {
+ if (badging == BADGING_NONE) {
return resources.getDrawable(getWifiSignalResource(signalLevel), theme);
}
Drawable[] layers = new Drawable[] {
@@ -131,19 +142,19 @@
*
* @param badging {@see ScoredNetwork#Badging} from {@link ScoredNetwork#calculateBadge(int)}.
* @return the @DrawableRes for the icon or {@link View#NO_ID} for
- * {@link ScoredNetwork#BADGING_NONE}
+ * {@link NetworkBadging#BADGING_NONE}
* @throws IllegalArgumentException for an invalid badging value.
* @hide
*/
@DrawableRes private static int getWifiBadgeResource(@Badging int badging) {
switch (badging) {
- case ScoredNetwork.BADGING_NONE:
+ case BADGING_NONE:
return View.NO_ID;
- case ScoredNetwork.BADGING_SD:
+ case BADGING_SD:
return com.android.internal.R.drawable.ic_signal_wifi_badged_sd;
- case ScoredNetwork.BADGING_HD:
+ case BADGING_HD:
return com.android.internal.R.drawable.ic_signal_wifi_badged_hd;
- case ScoredNetwork.BADGING_4K:
+ case BADGING_4K:
return com.android.internal.R.drawable.ic_signal_wifi_badged_4k;
default:
throw new IllegalArgumentException("No resource found for badge: " + badging);
diff --git a/core/java/android/net/ScoredNetwork.java b/core/java/android/net/ScoredNetwork.java
index ba0a8b5..a664a8b 100644
--- a/core/java/android/net/ScoredNetwork.java
+++ b/core/java/android/net/ScoredNetwork.java
@@ -41,7 +41,7 @@
* Key used with the {@link #attributes} bundle to define the badging curve.
*
* <p>The badging curve is a {@link RssiCurve} used to map different RSSI values to {@link
- * Badging} enums.
+ * NetworkBadging.Badging} enums.
*/
public static final String ATTRIBUTES_KEY_BADGING_CURVE =
"android.net.attributes.key.BADGING_CURVE";
@@ -70,17 +70,31 @@
public static final String ATTRIBUTES_KEY_RANKING_SCORE_OFFSET =
"android.net.attributes.key.RANKING_SCORE_OFFSET";
+ /** A {@link NetworkKey} uniquely identifying this network. */
+ public final NetworkKey networkKey;
+
+ // TODO(b/35323372): Delete these once external references are switched.
+ /** @deprecated Use {@link NetworkBadging#Badging} instead. */
+ @Deprecated
@IntDef({BADGING_NONE, BADGING_SD, BADGING_HD, BADGING_4K})
@Retention(RetentionPolicy.SOURCE)
public @interface Badging {}
+ /** @deprecated Use {@link NetworkBadging#BADGING_NONE} instead. */
+ @Deprecated
public static final int BADGING_NONE = 0;
- public static final int BADGING_SD = 10;
- public static final int BADGING_HD = 20;
- public static final int BADGING_4K = 30;
- /** A {@link NetworkKey} uniquely identifying this network. */
- public final NetworkKey networkKey;
+ /** @deprecated Use {@link NetworkBadging#BADGING_SD} instead. */
+ @Deprecated
+ public static final int BADGING_SD = 10;
+
+ /** @deprecated Use {@link NetworkBadging#BADGING_HD} instead. */
+ @Deprecated
+ public static final int BADGING_HD = 20;
+
+ /** @deprecated Use {@link NetworkBadging#BADGING_4K} instead. */
+ @Deprecated
+ public static final int BADGING_4K = 30;
/**
* The {@link RssiCurve} representing the scores for this network based on the RSSI.
@@ -276,14 +290,14 @@
}
/**
- * Return the {@link Badging} enum for this network for the given RSSI, derived from the
+ * Return the {@link NetworkBadging.Badging} enum for this network for the given RSSI, derived from the
* badging curve.
*
* <p>If no badging curve is present, {@link #BADGE_NONE} will be returned.
*
* @param rssi The rssi level for which the badge should be calculated
*/
- @Badging
+ @NetworkBadging.Badging
public int calculateBadge(int rssi) {
if (attributes != null && attributes.containsKey(ATTRIBUTES_KEY_BADGING_CURVE)) {
RssiCurve badgingCurve =
@@ -291,7 +305,7 @@
return badgingCurve.lookupScore(rssi);
}
- return BADGING_NONE;
+ return NetworkBadging.BADGING_NONE;
}
public static final Parcelable.Creator<ScoredNetwork> CREATOR =
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 7cdb3ce..39f4d42 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -311,11 +311,6 @@
}
/** {@hide} */
- public static File getDataAppEphemeralDirectory(String volumeUuid) {
- return new File(getDataDirectory(volumeUuid), "app-ephemeral");
- }
-
- /** {@hide} */
public static File getDataUserCeDirectory(String volumeUuid) {
return new File(getDataDirectory(volumeUuid), "user");
}
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 7c015de..280fa2c 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -432,14 +432,13 @@
*/
public static boolean contains(File dir, File file) {
if (dir == null || file == null) return false;
+ return contains(dir.getAbsolutePath(), file.getAbsolutePath());
+ }
- String dirPath = dir.getAbsolutePath();
- String filePath = file.getAbsolutePath();
-
+ public static boolean contains(String dirPath, String filePath) {
if (dirPath.equals(filePath)) {
return true;
}
-
if (!dirPath.endsWith("/")) {
dirPath += "/";
}
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 7702c17..8882672 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -546,6 +546,25 @@
}
/**
+ * Return the filesystem path of the real file on disk that is represented
+ * by the given {@link FileDescriptor}.
+ *
+ * @hide
+ */
+ public static File getFile(FileDescriptor fd) throws IOException {
+ try {
+ final String path = Os.readlink("/proc/self/fd/" + fd.getInt$());
+ if (OsConstants.S_ISREG(Os.stat(path).st_mode)) {
+ return new File(path);
+ } else {
+ throw new IOException("Not a regular file: " + path);
+ }
+ } catch (ErrnoException e) {
+ throw e.rethrowAsIOException();
+ }
+ }
+
+ /**
* Retrieve the actual FileDescriptor associated with this object.
*
* @return Returns the FileDescriptor associated with this object.
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 9e35bf6..2cc4b71 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -293,6 +293,6 @@
ParcelFileDescriptor openProxyFileDescriptor(int mountPointId, int fileId, int mode) = 74;
long getCacheQuotaBytes(String volumeUuid, int uid) = 75;
long getCacheSizeBytes(String volumeUuid, int uid) = 76;
- long getAllocatableBytes(String path, int flags) = 77;
- void allocateBytes(String path, long bytes, int flags) = 78;
+ long getAllocatableBytes(String volumeUuid, int flags) = 77;
+ void allocateBytes(String volumeUuid, long bytes, int flags) = 78;
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 2a3c03d..e070c6e 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -16,6 +16,7 @@
package android.os.storage;
+import static android.net.TrafficStats.GB_IN_BYTES;
import static android.net.TrafficStats.MB_IN_BYTES;
import android.annotation.IntDef;
@@ -675,6 +676,36 @@
}
/** {@hide} */
+ public @Nullable String findUuidForPath(File path) {
+ Preconditions.checkNotNull(path);
+ final String pathString = path.getAbsolutePath();
+ if (FileUtils.contains(Environment.getDataDirectory().getAbsolutePath(), pathString)) {
+ return StorageManager.UUID_PRIVATE_INTERNAL;
+ }
+ try {
+ for (VolumeInfo vol : mStorageManager.getVolumes(0)) {
+ if (vol.path != null && FileUtils.contains(vol.path, pathString)) {
+ // TODO: verify that emulated adopted devices have UUID of
+ // underlying volume
+ return vol.fsUuid;
+ }
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ throw new IllegalStateException("Failed to find a storage device for " + path);
+ }
+
+ /** {@hide} */
+ public @Nullable File findPathForUuid(String volumeUuid) {
+ final VolumeInfo vol = findVolumeByQualifiedUuid(volumeUuid);
+ if (vol != null) {
+ return vol.getPath();
+ }
+ throw new IllegalStateException("Failed to find a storage device for " + volumeUuid);
+ }
+
+ /** {@hide} */
public @NonNull List<VolumeInfo> getVolumes() {
try {
return Arrays.asList(mStorageManager.getVolumes(0));
@@ -1069,9 +1100,12 @@
throw new IllegalStateException("Missing primary storage");
}
- /** {@hide} */
- private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;
+ private static final int DEFAULT_THRESHOLD_PERCENTAGE = 5;
private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500 * MB_IN_BYTES;
+
+ private static final int DEFAULT_CACHE_PERCENTAGE = 10;
+ private static final long DEFAULT_CACHE_MAX_BYTES = 5 * GB_IN_BYTES;
+
private static final long DEFAULT_FULL_THRESHOLD_BYTES = MB_IN_BYTES;
/**
@@ -1102,6 +1136,23 @@
}
/**
+ * Return the minimum number of bytes of storage on the device that should
+ * be reserved for cached data.
+ *
+ * @hide
+ */
+ public long getStorageCacheBytes(File path) {
+ final long cachePercent = Settings.Global.getInt(mResolver,
+ Settings.Global.SYS_STORAGE_CACHE_PERCENTAGE, DEFAULT_CACHE_PERCENTAGE);
+ final long cacheBytes = (path.getTotalSpace() * cachePercent) / 100;
+
+ final long maxCacheBytes = Settings.Global.getLong(mResolver,
+ Settings.Global.SYS_STORAGE_CACHE_MAX_BYTES, DEFAULT_CACHE_MAX_BYTES);
+
+ return Math.min(cacheBytes, maxCacheBytes);
+ }
+
+ /**
* Return the number of available bytes at which the given path is
* considered full.
*
@@ -1409,40 +1460,37 @@
}
/**
- * Return quota size in bytes for cached data belonging to the calling app.
+ * Return quota size in bytes for all cached data belonging to the calling
+ * app on the filesystem that hosts the given path.
* <p>
* If your app goes above this quota, your cached files will be some of the
* first to be deleted when additional disk space is needed. Conversely, if
* your app stays under this quota, your cached files will be some of the
* last to be deleted when additional disk space is needed.
* <p>
- * This quota may change over time depending on how frequently the user
+ * This quota will change over time depending on how frequently the user
* interacts with your app, and depending on how much disk space is used.
- * <p>
- * Cached data tracked by this method always includes
- * {@link Context#getCacheDir()} and {@link Context#getCodeCacheDir()}, and
- * it also includes {@link Context#getExternalCacheDir()} if the primary
- * shared/external storage is hosted on the same storage device as your
- * private data.
* <p class="note">
* Note: if your app uses the {@code android:sharedUserId} manifest feature,
* then cached data for all packages in your shared UID is tracked together
* as a single unit.
* </p>
*
- * @see #getCacheSizeBytes()
+ * @see #getCacheSizeBytes(File)
*/
- public long getCacheQuotaBytes() {
+ public long getCacheQuotaBytes(File path) {
try {
+ final String volumeUuid = findUuidForPath(path);
final ApplicationInfo app = mContext.getApplicationInfo();
- return mStorageManager.getCacheQuotaBytes(app.volumeUuid, app.uid);
+ return mStorageManager.getCacheQuotaBytes(volumeUuid, app.uid);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Return total size in bytes of cached data belonging to the calling app.
+ * Return total size in bytes of all cached data belonging to the calling
+ * app on the filesystem that hosts the given path.
* <p>
* Cached data tracked by this method always includes
* {@link Context#getCacheDir()} and {@link Context#getCodeCacheDir()}, and
@@ -1457,67 +1505,38 @@
*
* @see #getCacheQuotaBytes()
*/
- public long getCacheSizeBytes() {
+ public long getCacheSizeBytes(File path) {
try {
+ final String volumeUuid = findUuidForPath(path);
final ApplicationInfo app = mContext.getApplicationInfo();
- return mStorageManager.getCacheSizeBytes(app.volumeUuid, app.uid);
+ return mStorageManager.getCacheSizeBytes(volumeUuid, app.uid);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
- /**
- * Return quota size in bytes for cached data on primary shared/external
- * storage belonging to the calling app.
- * <p>
- * If primary shared/external storage is hosted on the same storage device
- * as your private data, this method will return -1, since all data stored
- * under {@link Context#getExternalCacheDir()} will be counted under
- * {@link #getCacheQuotaBytes()}.
- * <p class="note">
- * Note: if your app uses the {@code android:sharedUserId} manifest feature,
- * then cached data for all packages in your shared UID is tracked together
- * as a single unit.
- * </p>
- */
+ /** @removed */
+ @Deprecated
+ public long getCacheQuotaBytes() {
+ return getCacheQuotaBytes(mContext.getCacheDir());
+ }
+
+ /** @removed */
+ @Deprecated
+ public long getCacheSizeBytes() {
+ return getCacheSizeBytes(mContext.getCacheDir());
+ }
+
+ /** @removed */
+ @Deprecated
public long getExternalCacheQuotaBytes() {
- final ApplicationInfo app = mContext.getApplicationInfo();
- final String primaryUuid = getPrimaryStorageUuid();
- if (Objects.equals(app.volumeUuid, primaryUuid)) {
- return -1;
- }
- try {
- return mStorageManager.getCacheQuotaBytes(primaryUuid, app.uid);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return getCacheQuotaBytes(mContext.getExternalCacheDir());
}
- /**
- * Return total size in bytes of cached data on primary shared/external
- * storage belonging to the calling app.
- * <p>
- * If primary shared/external storage is hosted on the same storage device
- * as your private data, this method will return -1, since all data stored
- * under {@link Context#getExternalCacheDir()} will be counted under
- * {@link #getCacheQuotaBytes()}.
- * <p class="note">
- * Note: if your app uses the {@code android:sharedUserId} manifest feature,
- * then cached data for all packages in your shared UID is tracked together
- * as a single unit.
- * </p>
- */
+ /** @removed */
+ @Deprecated
public long getExternalCacheSizeBytes() {
- final ApplicationInfo app = mContext.getApplicationInfo();
- final String primaryUuid = getPrimaryStorageUuid();
- if (Objects.equals(app.volumeUuid, primaryUuid)) {
- return -1;
- }
- try {
- return mStorageManager.getCacheSizeBytes(primaryUuid, app.uid);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return getCacheSizeBytes(mContext.getExternalCacheDir());
}
/**
@@ -1551,28 +1570,30 @@
* Return the maximum number of new bytes that your app can allocate for
* itself using {@link #allocateBytes(File, long, int)} at the given path.
* This value is typically larger than {@link File#getUsableSpace()}, since
- * the system may automatically delete cached files to satisfy your request.
+ * the system may be willing to delete cached files to satisfy an allocation
+ * request.
* <p>
* This method is best used as a pre-flight check, such as deciding if there
* is enough space to store an entire music album before you allocate space
* for each audio file in the album. Attempts to allocate disk space beyond
- * this value will fail.
+ * the returned value will fail.
* <p class="note">
* Note: if your app uses the {@code android:sharedUserId} manifest feature,
* then allocatable space for all packages in your shared UID is tracked
* together as a single unit.
* </p>
*
- * @param file the directory where you're considering allocating disk space,
+ * @param path the path where you're considering allocating disk space,
* since allocatable space can vary widely depending on the
* underlying storage device.
* @param flags to apply to the request.
* @return the maximum number of new bytes that the calling app can allocate
* using {@link #allocateBytes(File, long, int)}.
*/
- public long getAllocatableBytes(File file, @AllocateFlags int flags) throws IOException {
+ public long getAllocatableBytes(File path, @AllocateFlags int flags) throws IOException {
try {
- return mStorageManager.getAllocatableBytes(file.getAbsolutePath(), flags);
+ final String volumeUuid = findUuidForPath(path);
+ return mStorageManager.getAllocatableBytes(volumeUuid, flags);
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
@@ -1594,14 +1615,15 @@
* {@link #allocateBytes(FileDescriptor, long, int)} which will guarantee
* that bytes are allocated to an opened file.
*
- * @param file the directory where you'd like to allocate disk space.
+ * @param path the path where you'd like to allocate disk space.
* @param bytes the number of bytes to allocate.
* @param flags to apply to the request.
* @see #getAllocatableBytes(File, int)
*/
- public void allocateBytes(File file, long bytes, @AllocateFlags int flags) throws IOException {
+ public void allocateBytes(File path, long bytes, @AllocateFlags int flags) throws IOException {
try {
- mStorageManager.allocateBytes(file.getAbsolutePath(), bytes, flags);
+ final String volumeUuid = findUuidForPath(path);
+ mStorageManager.allocateBytes(volumeUuid, bytes, flags);
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
} catch (RemoteException e) {
@@ -1610,37 +1632,39 @@
}
/**
- * Allocate the requested number of bytes for your application to use at the
- * given path. This will cause the system to delete any cached files
+ * Allocate the requested number of bytes for your application to use in the
+ * given open file. This will cause the system to delete any cached files
* necessary to satisfy your request.
* <p>
* Attempts to allocate disk space beyond the value returned by
* {@link #getAllocatableBytes(File, int)} will fail.
* <p>
- * This method guarantees that bytes are allocated to the opened file,
- * otherwise it will throw if fast allocation not possible. Fast allocation
- * is typically only supported in private app data directories, and on
- * shared/external storage devices which are emulated.
+ * This method guarantees that bytes have been allocated to the opened file,
+ * otherwise it will throw if fast allocation is not possible. Fast
+ * allocation is typically only supported in private app data directories,
+ * and on shared/external storage devices which are emulated.
*
- * @param fd the directory where you'd like to allocate disk space.
- * @param bytes the number of bytes to allocate.
+ * @param fd the open file that you'd like to allocate disk space for.
+ * @param bytes the number of bytes to allocate. This is the desired final
+ * size of the open file.
* @param flags to apply to the request.
* @see #getAllocatableBytes(File, int)
* @see Environment#isExternalStorageEmulated(File)
*/
public void allocateBytes(FileDescriptor fd, long bytes, @AllocateFlags int flags)
throws IOException {
- final File file;
- try {
- file = new File(Os.readlink("/proc/self/fd/" + fd.getInt$()));
- } catch (ErrnoException e) {
- throw e.rethrowAsIOException();
- }
+ final File file = ParcelFileDescriptor.getFile(fd);
for (int i = 0; i < 3; i++) {
- allocateBytes(file, bytes, flags);
-
try {
+ final long haveBytes = Os.fstat(fd).st_blocks * 512;
+ final long needBytes = bytes - haveBytes;
+
+ if (needBytes > 0) {
+ allocateBytes(file, needBytes, flags);
+ }
+
Os.posix_fallocate(fd, 0, bytes);
+ return;
} catch (ErrnoException e) {
if (e.errno == OsConstants.ENOSPC) {
Log.w(TAG, "Odd, not enough space; let's try again?");
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index 600d82f..c0c5db6 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -200,6 +200,10 @@
private ViewGroup mPrefsContainer;
+ // Backup of the original activity title. This is used when navigating back to the headers list
+ // in onBackPress to restore the title.
+ private CharSequence mActivityTitle;
+
// Null if in legacy mode.
private ViewGroup mHeadersContainer;
@@ -569,6 +573,7 @@
Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);
int initialShortTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, 0);
+ mActivityTitle = getTitle();
if (savedInstanceState != null) {
// We are restarting from a previous saved state; used that to
@@ -704,6 +709,9 @@
mPrefsContainer.setVisibility(View.GONE);
mHeadersContainer.setVisibility(View.VISIBLE);
+ if (mActivityTitle != null) {
+ showBreadCrumbs(mActivityTitle, null);
+ }
getListView().clearChoices();
} else {
super.onBackPressed();
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index aca41b8..3ece9b3 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3882,6 +3882,7 @@
SCREEN_BRIGHTNESS,
SCREEN_BRIGHTNESS_MODE,
SCREEN_AUTO_BRIGHTNESS_ADJ,
+ SCREEN_BRIGHTNESS_FOR_VR,
VIBRATE_INPUT_DEVICES,
MODE_RINGER_STREAMS_AFFECTED,
TEXT_AUTO_REPLACE,
@@ -4128,20 +4129,20 @@
}
/**
- * System settings which can be accessed by ephemeral apps.
+ * System settings which can be accessed by instant apps.
* @hide
*/
- public static final Set<String> EPHEMERAL_SETTINGS = new ArraySet<>();
+ public static final Set<String> INSTANT_APP_SETTINGS = new ArraySet<>();
static {
- EPHEMERAL_SETTINGS.add(TEXT_AUTO_REPLACE);
- EPHEMERAL_SETTINGS.add(TEXT_AUTO_CAPS);
- EPHEMERAL_SETTINGS.add(TEXT_AUTO_PUNCTUATE);
- EPHEMERAL_SETTINGS.add(TEXT_SHOW_PASSWORD);
- EPHEMERAL_SETTINGS.add(DATE_FORMAT);
- EPHEMERAL_SETTINGS.add(FONT_SCALE);
- EPHEMERAL_SETTINGS.add(HAPTIC_FEEDBACK_ENABLED);
- EPHEMERAL_SETTINGS.add(TIME_12_24);
- EPHEMERAL_SETTINGS.add(SOUND_EFFECTS_ENABLED);
+ INSTANT_APP_SETTINGS.add(TEXT_AUTO_REPLACE);
+ INSTANT_APP_SETTINGS.add(TEXT_AUTO_CAPS);
+ INSTANT_APP_SETTINGS.add(TEXT_AUTO_PUNCTUATE);
+ INSTANT_APP_SETTINGS.add(TEXT_SHOW_PASSWORD);
+ INSTANT_APP_SETTINGS.add(DATE_FORMAT);
+ INSTANT_APP_SETTINGS.add(FONT_SCALE);
+ INSTANT_APP_SETTINGS.add(HAPTIC_FEEDBACK_ENABLED);
+ INSTANT_APP_SETTINGS.add(TIME_12_24);
+ INSTANT_APP_SETTINGS.add(SOUND_EFFECTS_ENABLED);
}
/**
@@ -5491,7 +5492,12 @@
/**
* Whether to speak passwords while in accessibility mode.
+ *
+ * @deprecated The speaking of passwords is controlled by individual accessibility services.
+ * Apps should ignore this setting and provide complete information to accessibility
+ * at all times, which was the behavior when this value was {@code true}.
*/
+ @Deprecated
public static final String ACCESSIBILITY_SPEAK_PASSWORD = "speak_password";
/**
@@ -7004,17 +7010,17 @@
}
/**
- * Secure settings which can be accessed by ephemeral apps.
+ * Secure settings which can be accessed by instant apps.
* @hide
*/
- public static final Set<String> EPHEMERAL_SETTINGS = new ArraySet<>();
+ public static final Set<String> INSTANT_APP_SETTINGS = new ArraySet<>();
static {
- EPHEMERAL_SETTINGS.add(ENABLED_ACCESSIBILITY_SERVICES);
- EPHEMERAL_SETTINGS.add(ACCESSIBILITY_SPEAK_PASSWORD);
- EPHEMERAL_SETTINGS.add(ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
+ INSTANT_APP_SETTINGS.add(ENABLED_ACCESSIBILITY_SERVICES);
+ INSTANT_APP_SETTINGS.add(ACCESSIBILITY_SPEAK_PASSWORD);
+ INSTANT_APP_SETTINGS.add(ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
- EPHEMERAL_SETTINGS.add(DEFAULT_INPUT_METHOD);
- EPHEMERAL_SETTINGS.add(ENABLED_INPUT_METHODS);
+ INSTANT_APP_SETTINGS.add(DEFAULT_INPUT_METHOD);
+ INSTANT_APP_SETTINGS.add(ENABLED_INPUT_METHODS);
}
/**
@@ -8577,6 +8583,24 @@
SYS_STORAGE_FULL_THRESHOLD_BYTES = "sys_storage_full_threshold_bytes";
/**
+ * Minimum percentage of storage on the device that is reserved for
+ * cached data.
+ *
+ * @hide
+ */
+ public static final String
+ SYS_STORAGE_CACHE_PERCENTAGE = "sys_storage_cache_percentage";
+
+ /**
+ * Maximum bytes of storage on the device that is reserved for cached
+ * data.
+ *
+ * @hide
+ */
+ public static final String
+ SYS_STORAGE_CACHE_MAX_BYTES = "sys_storage_cache_max_bytes";
+
+ /**
* The maximum reconnect delay for short network outages or when the
* network is suspended due to phone use.
*
@@ -10152,16 +10176,16 @@
public static final String CELL_ON = "cell_on";
/**
- * Global settings which can be accessed by ephemeral apps.
+ * Global settings which can be accessed by instant apps.
* @hide
*/
- public static final Set<String> EPHEMERAL_SETTINGS = new ArraySet<>();
+ public static final Set<String> INSTANT_APP_SETTINGS = new ArraySet<>();
static {
- EPHEMERAL_SETTINGS.add(WAIT_FOR_DEBUGGER);
- EPHEMERAL_SETTINGS.add(DEVICE_PROVISIONED);
- EPHEMERAL_SETTINGS.add(DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES);
- EPHEMERAL_SETTINGS.add(DEVELOPMENT_FORCE_RTL);
- EPHEMERAL_SETTINGS.add(EPHEMERAL_COOKIE_MAX_SIZE_BYTES);
+ INSTANT_APP_SETTINGS.add(WAIT_FOR_DEBUGGER);
+ INSTANT_APP_SETTINGS.add(DEVICE_PROVISIONED);
+ INSTANT_APP_SETTINGS.add(DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES);
+ INSTANT_APP_SETTINGS.add(DEVELOPMENT_FORCE_RTL);
+ INSTANT_APP_SETTINGS.add(EPHEMERAL_COOKIE_MAX_SIZE_BYTES);
}
/**
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index bd38c7f..59b494c 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -16,6 +16,8 @@
package android.service.autofill;
+import static android.view.autofill.Helper.DEBUG;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.IntentSender;
@@ -23,6 +25,7 @@
import android.os.Parcelable;
import android.view.autofill.AutoFillId;
import android.view.autofill.AutoFillValue;
+import android.widget.RemoteViews;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
@@ -33,34 +36,28 @@
* <p>It contains:
*
* <ol>
- * <li>A name used to identify the dataset in the UI.
- * <li>A list of id/value pairs for the fields that can be auto-filled.
- * <li>A list of savable ids in addition to the ones with a provided value.
+ * <li>A list of values for input fields.
+ * <li>A presentation view to visualize.
+ * <li>An optional intent to authenticate.
* </ol>
*
* @see android.service.autofill.FillResponse for examples.
*/
public final class Dataset implements Parcelable {
- private static final boolean DEBUG = false;
- private final CharSequence mName;
private final ArrayList<AutoFillId> mFieldIds;
private final ArrayList<AutoFillValue> mFieldValues;
+ private final RemoteViews mPresentation;
private final IntentSender mAuthentication;
private Dataset(Builder builder) {
- mName = builder.mName;
mFieldIds = builder.mFieldIds;
mFieldValues = builder.mFieldValues;
+ mPresentation = builder.mPresentation;
mAuthentication = builder.mAuthentication;
}
/** @hide */
- public @NonNull CharSequence getName() {
- return mName;
- }
-
- /** @hide */
public @Nullable ArrayList<AutoFillId> getFieldIds() {
return mFieldIds;
}
@@ -71,6 +68,11 @@
}
/** @hide */
+ public @Nullable RemoteViews getPresentation() {
+ return mPresentation;
+ }
+
+ /** @hide */
public @Nullable IntentSender getAuthentication() {
return mAuthentication;
}
@@ -84,11 +86,12 @@
public String toString() {
if (!DEBUG) return super.toString();
- final StringBuilder builder = new StringBuilder("Dataset [name=").append(mName)
+ return new StringBuilder("Dataset [")
.append(", fieldIds=").append(mFieldIds)
.append(", fieldValues=").append(mFieldValues)
- .append(", hasAuthentication=").append(mAuthentication != null);
- return builder.append(']').toString();
+ .append(", hasPresentation=").append(mPresentation != null)
+ .append(", hasAuthentication=").append(mAuthentication != null)
+ .append(']').toString();
}
/**
@@ -96,21 +99,22 @@
* one value for a field or set an authentication intent.
*/
public static final class Builder {
- private CharSequence mName;
private ArrayList<AutoFillId> mFieldIds;
private ArrayList<AutoFillValue> mFieldValues;
+ private RemoteViews mPresentation;
private IntentSender mAuthentication;
private boolean mDestroyed;
/**
- * Creates a new builder.
+ * Sets the presentation used to visualize this dataset.
*
- * @param name Name used to identify the dataset in the UI. Typically it's the same value as
- * the first field in the dataset (like username or email address) or a user-provided name
- * (like "My Work Address").
+ * @param presentation The presentation view.
+ *
+ * @return This builder.
*/
- public Builder(@NonNull CharSequence name) {
- mName = Preconditions.checkStringNotEmpty(name, "name cannot be empty or null");
+ public @NonNull Builder setPresentation(@Nullable RemoteViews presentation) {
+ mPresentation = presentation;
+ return this;
}
/**
@@ -119,7 +123,7 @@
* <p>This method is called when you need to provide an authentication
* UI for the data set. For example, when a data set contains credit card information
* (such as number, expiration date, and verification code), you can display UI
- * asking for the verification code to before filing in the data). Even if the
+ * asking for the verification code before filing in the data. Even if the
* data set is completely populated the system will launch the specified authentication
* intent and will need your approval to fill it in. Since the data set is "locked"
* until the user authenticates it, typically this data set name is masked
@@ -136,7 +140,7 @@
* android.app.Activity#RESULT_OK} and provide the fully populated {@link Dataset
* dataset} by setting it to the {@link
* android.view.autofill.AutoFillManager#EXTRA_AUTHENTICATION_RESULT} extra. For example,
- * if you provided an credit card information without the CVV for the data set in the
+ * if you provided credit card information without the CVV for the data set in the
* {@link FillResponse response} then the returned data set should contain the
* CVV entry.</p>
*
@@ -145,6 +149,7 @@
* platform needs to fill in the authentication arguments.</p>
*
* @param authentication Intent to an activity with your authentication flow.
+ * @return This builder.
*
* @see android.app.PendingIntent
*/
@@ -160,6 +165,7 @@
* @param id id returned by {@link
* android.app.assist.AssistStructure.ViewNode#getAutoFillId()}.
* @param value value to be auto filled.
+ * @return This builder.
*/
public @NonNull Builder setValue(@NonNull AutoFillId id, @NonNull AutoFillValue value) {
throwIfDestroyed();
@@ -182,14 +188,21 @@
/**
* Creates a new {@link Dataset} instance. You should not interact
- * with this builder once this method is called.
+ * with this builder once this method is called. It is required
+ * that you specified at least one field. Also it is mandatory to
+ * provide a presentation view to visualize the data set in the UI.
+ *
+ * @return The built dataset.
*/
public @NonNull Dataset build() {
throwIfDestroyed();
mDestroyed = true;
- if (mFieldIds == null && mAuthentication == null) {
+ if (mFieldIds == null) {
throw new IllegalArgumentException(
- "at least one value or an authentication must be set");
+ "at least one value must be set");
+ }
+ if (mPresentation == null) {
+ throw new IllegalArgumentException("presentation must be set");
}
return new Dataset(this);
}
@@ -212,9 +225,9 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeCharSequence(mName);
- parcel.writeTypedArrayList(mFieldIds, 0);
- parcel.writeTypedArrayList(mFieldValues, 0);
+ parcel.writeTypedArrayList(mFieldIds, flags);
+ parcel.writeTypedArrayList(mFieldValues, flags);
+ parcel.writeParcelable(mPresentation, flags);
parcel.writeParcelable(mAuthentication, flags);
}
@@ -224,7 +237,7 @@
// 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.readCharSequence());
+ final Builder builder = new Builder();
final ArrayList<AutoFillId> ids = parcel.readTypedArrayList(null);
final ArrayList<AutoFillValue> values = parcel.readTypedArrayList(null);
final int idCount = (ids != null) ? ids.size() : 0;
@@ -234,6 +247,7 @@
AutoFillValue value = (valueCount > i) ? values.get(i) : null;
builder.setValue(id, value);
}
+ builder.setPresentation(parcel.readParcelable(null));
builder.setAuthentication(parcel.readParcelable(null));
return builder.build();
}
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index ea36e64..86688d3 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -15,6 +15,8 @@
*/
package android.service.autofill;
+import static android.view.autofill.Helper.DEBUG;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.IntentSender;
@@ -24,12 +26,14 @@
import android.util.ArraySet;
import android.view.autofill.AutoFillId;
import android.view.autofill.AutoFillManager;
+import android.widget.RemoteViews;
+
+import java.util.ArrayList;
/**
* Response for a {@link
* AutoFillService#onFillRequest(android.app.assist.AssistStructure,
- * Bundle, android.os.CancellationSignal, FillCallback)} and
- * authentication requests.
+ * Bundle, android.os.CancellationSignal, FillCallback)}.
*
* <p>The response typically contains one or more {@link Dataset}s, each representing a set of
* fields that can be auto-filled together, and the Android system displays a dataset picker UI
@@ -41,7 +45,8 @@
*
* <pre class="prettyprint">
* new FillResponse.Builder()
- * .add(new Dataset.Builder("homer")
+ * .add(new Dataset.Builder()
+ * .setPresentation(createPresentation())
* .setTextFieldValue(id1, "homer")
* .setTextFieldValue(id2, "D'OH!")
* .build())
@@ -52,11 +57,13 @@
*
* <pre class="prettyprint">
* new FillResponse.Builder()
- * .add(new Dataset.Builder("Homer's Account")
+ * .add(new Dataset.Builder()
+ * .setPresentation(createFirstPresentation())
* .setTextFieldValue(id1, "homer")
* .setTextFieldValue(id2, "D'OH!")
* .build())
- * .add(new Dataset.Builder("Bart's Account")
+ * .add(new Dataset.Builder()
+ * .setPresentation(createSecondPresentation())
* .setTextFieldValue(id1, "elbarto")
* .setTextFieldValue(id2, "cowabonga")
* .build())
@@ -80,7 +87,8 @@
*
* <pre class="prettyprint">
* new FillResponse.Builder()
- * .add(new Dataset.Builder("Homer")
+ * .add(new Dataset.Builder(")
+ * .setPresentation(createPresentation())
* .setTextFieldValue(id1, "Homer") // first name
* .setTextFieldValue(id2, "Simpson") // last name
* .setTextFieldValue(id3, "742 Evergreen Terrace") // street
@@ -108,27 +116,31 @@
*
* <pre class="prettyprint">
* new FillResponse.Builder()
- * .add(new Dataset.Builder("Homer")
+ * .add(new Dataset.Builder()
+ * .setPresentation(createFirstPresentation())
* .setTextFieldValue(id1, "Homer")
* .setTextFieldValue(id2, "Simpson")
* .build())
- * .add(new Dataset.Builder("Bart")
+ * .add(new Dataset.Builder()
+ * .setPresentation(createSecondPresentation())
* .setTextFieldValue(id1, "Bart")
* .setTextFieldValue(id2, "Simpson")
* .build())
* .build();
* </pre>
*
- * <p>Then after the user picks the {@code Homer} dataset and taps the {@code Street} field to
+ * <p>Then after the user picks the second dataset and taps the street field to
* trigger another auto-fill request, the second response could be:
*
* <pre class="prettyprint">
* new FillResponse.Builder()
- * .add(new Dataset.Builder("Home")
+ * .add(new Dataset.Builder()
+ * .setPresentation(createThirdPresentation())
* .setTextFieldValue(id3, "742 Evergreen Terrace")
* .setTextFieldValue(id4, "Springfield")
* .build())
- * .add(new Dataset.Builder("Work")
+ * .add(new Dataset.Builder()
+ * .setPresentation(createFourthPresentation())
* .setTextFieldValue(id3, "Springfield Power Plant")
* .setTextFieldValue(id4, "Springfield")
* .build())
@@ -139,30 +151,31 @@
* {@link Dataset} level, prior to auto-filling an activity - see {@link FillResponse.Builder
* #setAuthentication(IntentSender)} and {@link Dataset.Builder#setAuthentication(IntentSender)}.
* It is recommended that you encrypt only the sensitive data but leave the labels unencrypted
- * which would allow you to provide the dataset names to the user and if they choose one
- * them challenge the user to onAuthenticate. For example, if the user has a home and a work
- * address the Home and Work labels could be stored unencrypted as they don't have any sensitive
- * data while the address data is in an encrypted storage. If the user chooses Home, then the
- * platform will start your authentication flow. If you encrypt all data and require auth
- * at the response level the user will have to interact with the fill UI to trigger a request
- * for the datasets as they don't see Home and Work options which will trigger your auth
- * flow and after successfully authenticating the user will be presented with the Home and
- * Work options where they can pick one. Hence, you have flexibility how to implement your
- * auth while storing labels non-encrypted and data encrypted provides a better user
- * experience.</p>
+ * which would allow you to provide a dataset presentation views with labels and if the user
+ * chooses one of them challenge the user to authenticate. For example, if the user has a
+ * home and a work address the Home and Work labels could be stored unencrypted as they don't
+ * have any sensitive data while the address data is in an encrypted storage. If the user
+ * chooses Home, then the platform will start your authentication flow. If you encrypt all
+ * data and require auth at the response level the user will have to interact with the fill
+ * UI to trigger a request for the datasets (as they don't see the presentation views for the
+ * possible options) which will start your auth flow and after successfully authenticating
+ * the user will be presented with the Home and Work options to pick one. Hence, you have
+ * flexibility how to implement your auth while storing labels non-encrypted and data
+ * encrypted provides a better user experience.</p>
*/
public final class FillResponse implements Parcelable {
- private static final boolean DEBUG = false;
- private final ArraySet<Dataset> mDatasets;
+ private final ArrayList<Dataset> mDatasets;
private final ArraySet<AutoFillId> mSavableIds;
private final Bundle mExtras;
+ private final RemoteViews mPresentation;
private final IntentSender mAuthentication;
private FillResponse(@NonNull Builder builder) {
mDatasets = builder.mDatasets;
mSavableIds = builder.mSavableIds;
mExtras = builder.mExtras;
+ mPresentation = builder.mPresentation;
mAuthentication = builder.mAuthentication;
}
@@ -172,7 +185,7 @@
}
/** @hide */
- public @Nullable ArraySet<Dataset> getDatasets() {
+ public @Nullable ArrayList<Dataset> getDatasets() {
return mDatasets;
}
@@ -182,26 +195,42 @@
}
/** @hide */
+ public @Nullable RemoteViews getPresentation() {
+ return mPresentation;
+ }
+
+ /** @hide */
public @Nullable IntentSender getAuthentication() {
return mAuthentication;
}
/**
* Builder for {@link FillResponse} objects. You must to provide at least
- * one dataset or set an authentication intent.
+ * one dataset or set an authentication intent with a presentation view.
*/
public static final class Builder {
- private ArraySet<Dataset> mDatasets;
+ private ArrayList<Dataset> mDatasets;
private ArraySet<AutoFillId> mSavableIds;
private Bundle mExtras;
+ private RemoteViews mPresentation;
private IntentSender mAuthentication;
private boolean mDestroyed;
/**
- * Creates a new {@link FillResponse} builder.
+ * Sets the presentation used to visualize this response. You should
+ * set this only if you need an authentication as this is the only
+ * case the response needs to be presented to the user.
+ *
+ * @param presentation The presentation view.
+ *
+ * @return This builder.
+ *
+ * @see #setAuthentication(IntentSender)
*/
- public Builder() {
-
+ public @NonNull
+ FillResponse.Builder setPresentation(@Nullable RemoteViews presentation) {
+ mPresentation = presentation;
+ return this;
}
/**
@@ -214,14 +243,15 @@
* auth on the data set level leading to a better user experience. Note that if you
* use sensitive data as a label, for example an email address, then it should also
* be encrypted. The provided {@link android.app.PendingIntent intent} must be an
- * activity which implements your authentication flow.</p>
+ * activity which implements your authentication flow. Also if you provide an auth
+ * intent you also need to specify the presentation view to be shown in the fill UI
+ * for the user to trigger your authentication flow.</p>
*
* <p>When a user triggers auto-fill, the system launches the provided intent
- * whose extras will have the {@link
- * AutoFillManager#EXTRA_ASSIST_STRUCTURE screen
+ * whose extras will have the {@link AutoFillManager#EXTRA_ASSIST_STRUCTURE screen
* content}. Once you complete your authentication flow you should set the activity
- * result to {@link android.app.Activity#RESULT_OK} and provide the fully populated {@link
- * FillResponse response} by setting it to the {@link
+ * result to {@link android.app.Activity#RESULT_OK} and provide the fully populated
+ * {@link FillResponse response} by setting it to the {@link
* AutoFillManager#EXTRA_AUTHENTICATION_RESULT} extra.
* For example, if you provided an empty {@link FillResponse resppnse} because the
* user's data was locked and marked that the response needs an authentication then
@@ -234,8 +264,10 @@
* platform needs to fill in the authentication arguments.</p>
*
* @param authentication Intent to an activity with your authentication flow.
+ * @return This builder.
*
* @see android.app.PendingIntent#getIntentSender()
+ * @see #setPresentation(RemoteViews)
*/
public @NonNull Builder setAuthentication(@Nullable IntentSender authentication) {
throwIfDestroyed();
@@ -244,10 +276,9 @@
}
/**
- * Adds a new {@link Dataset} to this response. Adding a dataset with the
- * same id updates the existing one.
+ * Adds a new {@link Dataset} to this response.
*
- * @throws IllegalArgumentException if a dataset with same {@code name} already exists.
+ * @return This builder.
*/
public@NonNull Builder addDataset(@Nullable Dataset dataset) {
throwIfDestroyed();
@@ -255,25 +286,20 @@
return this;
}
if (mDatasets == null) {
- mDatasets = new ArraySet<>();
- }
- final int datasetCount = mDatasets.size();
- for (int i = 0; i < datasetCount; i++) {
- if (mDatasets.valueAt(i).getName().equals(dataset.getName())) {
- throw new IllegalArgumentException("Duplicate dataset name: "
- + dataset.getName());
- }
+ mDatasets = new ArrayList<>();
}
if (!mDatasets.add(dataset)) {
return this;
}
- final int fieldCount = dataset.getFieldIds().size();
- for (int i = 0; i < fieldCount; i++) {
- final AutoFillId id = dataset.getFieldIds().get(i);
- if (mSavableIds == null) {
- mSavableIds = new ArraySet<>();
+ if (dataset.getFieldIds() != null) {
+ final int fieldCount = dataset.getFieldIds().size();
+ for (int i = 0; i < fieldCount; i++) {
+ final AutoFillId id = dataset.getFieldIds().get(i);
+ if (mSavableIds == null) {
+ mSavableIds = new ArraySet<>();
+ }
+ mSavableIds.add(id);
}
- mSavableIds.add(id);
}
return this;
}
@@ -284,7 +310,10 @@
* android.app.assist.AssistStructure, Bundle, SaveCallback)})
* but were not indirectly set through {@link #addDataset(Dataset)}.
*
- * <p>See {@link FillResponse} for examples.
+ * @param ids The savable ids.
+ * @return This builder.
+ *
+ * @see FillResponse
*/
public @NonNull Builder addSavableFields(@Nullable AutoFillId... ids) {
throwIfDestroyed();
@@ -305,10 +334,11 @@
* manipulate this response. For example, they are passed to subsequent
* calls to {@link AutoFillService#onFillRequest(
* android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal,
- * FillCallback)} and {@link
- * AutoFillService#onSaveRequest(
- * android.app.assist.AssistStructure, Bundle,
- * SaveCallback)}.
+ * FillCallback)} and {@link AutoFillService#onSaveRequest(
+ * android.app.assist.AssistStructure, Bundle, SaveCallback)}.
+ *
+ * @param extras The response extras.
+ * @return This builder.
*/
public Builder setExtras(Bundle extras) {
throwIfDestroyed();
@@ -317,10 +347,22 @@
}
/**
- * Builds a new {@link FillResponse} instance.
+ * Builds a new {@link FillResponse} instance. You must provide at least
+ * one dataset or some savable ids or an authentication with a presentation
+ * view.
+ *
+ * @return A built response.
*/
public FillResponse build() {
throwIfDestroyed();
+ if (mAuthentication == null ^ mPresentation == null) {
+ throw new IllegalArgumentException("authentication and presentation"
+ + " must be both non-null or null");
+ }
+ if (mAuthentication == null && mDatasets == null && mSavableIds == null) {
+ throw new IllegalArgumentException("need to provide at least one"
+ + " data set or savable ids or an authentication with a presentation");
+ }
mDestroyed = true;
return new FillResponse(this);
}
@@ -338,12 +380,13 @@
@Override
public String toString() {
if (!DEBUG) return super.toString();
- final StringBuilder builder = new StringBuilder(
+ return new StringBuilder(
"FillResponse: [datasets=").append(mDatasets)
.append(", savableIds=").append(mSavableIds)
.append(", hasExtras=").append(mExtras != null)
- .append(", hasAuthentication=").append(mAuthentication != null);
- return builder.append(']').toString();
+ .append(", hasPresentation=").append(mPresentation != null)
+ .append(", hasAuthentication=").append(mAuthentication != null)
+ .toString();
}
/////////////////////////////////////
@@ -357,10 +400,11 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeTypedArraySet(mDatasets, 0);
- parcel.writeTypedArraySet(mSavableIds, 0);
- parcel.writeParcelable(mExtras, 0);
- parcel.writeParcelable(mAuthentication, 0);
+ parcel.writeTypedArrayList(mDatasets, flags);
+ parcel.writeTypedArraySet(mSavableIds, flags);
+ parcel.writeParcelable(mExtras, flags);
+ parcel.writeParcelable(mPresentation, flags);
+ parcel.writeParcelable(mAuthentication, flags);
}
public static final Parcelable.Creator<FillResponse> CREATOR =
@@ -371,10 +415,10 @@
// the system obeys the contract of the builder to avoid attacks
// using specially crafted parcels.
final Builder builder = new Builder();
- final ArraySet<Dataset> datasets = parcel.readTypedArraySet(null);
+ final ArrayList<Dataset> datasets = parcel.readTypedArrayList(null);
final int datasetCount = (datasets != null) ? datasets.size() : 0;
for (int i = 0; i < datasetCount; i++) {
- builder.addDataset(datasets.valueAt(i));
+ builder.addDataset(datasets.get(i));
}
final ArraySet<AutoFillId> fillIds = parcel.readTypedArraySet(null);
final int fillIdCount = (fillIds != null) ? fillIds.size() : 0;
@@ -382,6 +426,7 @@
builder.addSavableFields(fillIds.valueAt(i));
}
builder.setExtras(parcel.readParcelable(null));
+ builder.setPresentation(parcel.readParcelable(null));
builder.setAuthentication(parcel.readParcelable(null));
return builder.build();
}
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index 9728fda..e39d53f 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -15,6 +15,8 @@
*/
package android.service.notification;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.app.NotificationChannel;
import android.os.Bundle;
import android.os.Parcel;
@@ -22,7 +24,10 @@
/**
* Ranking updates from the Assistant.
+ * @hide
*/
+@SystemApi
+@TestApi
public final class Adjustment implements Parcelable {
private final String mPackage;
private final String mKey;
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index de86b2d..46609df 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -19,6 +19,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.app.NotificationChannel;
import android.content.Context;
import android.content.Intent;
@@ -35,7 +37,10 @@
/**
* A service that helps the user manage notifications.
+ * @hide
*/
+@SystemApi
+@TestApi
public abstract class NotificationAssistantService extends NotificationListenerService {
private static final String TAG = "NotificationAssistants";
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index e5abdac..5f7ff67 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -16,6 +16,7 @@
package android.service.notification;
+import android.annotation.TestApi;
import android.app.NotificationChannel;
import android.os.Handler;
import android.os.Looper;
@@ -528,7 +529,10 @@
* @param key The key of the notification to snooze
* @param snoozeCriterionId The{@link SnoozeCriterion#getId()} of a context to snooze the
* notification until.
+ * @hide
*/
+ @SystemApi
+ @TestApi
public final void snoozeNotification(String key, String snoozeCriterionId) {
if (!isBound()) return;
try {
@@ -1257,7 +1261,10 @@
/**
* If the {@link NotificationAssistantService} has added people to this notification, then
* this will be non-null.
+ * @hide
*/
+ @SystemApi
+ @TestApi
public List<String> getAdditionalPeople() {
return mOverridePeople;
}
@@ -1266,7 +1273,10 @@
* Returns snooze criteria provided by the {@link NotificationAssistantService}. If your
* user interface displays options for snoozing notifications these criteria should be
* displayed as well.
+ * @hide
*/
+ @SystemApi
+ @TestApi
public List<SnoozeCriterion> getSnoozeCriteria() {
return mSnoozeCriteria;
}
diff --git a/core/java/android/service/notification/SnoozeCriterion.java b/core/java/android/service/notification/SnoozeCriterion.java
index f37f1ae..bd93eff 100644
--- a/core/java/android/service/notification/SnoozeCriterion.java
+++ b/core/java/android/service/notification/SnoozeCriterion.java
@@ -15,13 +15,18 @@
*/
package android.service.notification;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Represents an option to be shown to users for snoozing a notification until a given context
* instead of for a fixed amount of time.
+ * @hide
*/
+@SystemApi
+@TestApi
public final class SnoozeCriterion implements Parcelable {
private final String mId;
private final CharSequence mExplanation;
diff --git a/core/java/android/service/trust/ITrustAgentService.aidl b/core/java/android/service/trust/ITrustAgentService.aidl
index 22b4d09..21661db 100644
--- a/core/java/android/service/trust/ITrustAgentService.aidl
+++ b/core/java/android/service/trust/ITrustAgentService.aidl
@@ -16,6 +16,7 @@
package android.service.trust;
import android.os.PersistableBundle;
+import android.os.UserHandle;
import android.service.trust.ITrustAgentServiceCallback;
/**
@@ -30,4 +31,7 @@
oneway void onDeviceUnlocked();
oneway void onConfigure(in List<PersistableBundle> options, IBinder token);
oneway void setCallback(ITrustAgentServiceCallback callback);
+ oneway void onEscrowTokenAdded(in byte[] token, long handle, in UserHandle user);
+ oneway void onTokenStateReceived(long handle, int tokenState);
+ oneway void onEscrowTokenRemoved(long handle, boolean successful);
}
diff --git a/core/java/android/service/trust/ITrustAgentServiceCallback.aidl b/core/java/android/service/trust/ITrustAgentServiceCallback.aidl
index ec66cc8..14df7cb 100644
--- a/core/java/android/service/trust/ITrustAgentServiceCallback.aidl
+++ b/core/java/android/service/trust/ITrustAgentServiceCallback.aidl
@@ -28,4 +28,8 @@
void revokeTrust();
void setManagingTrust(boolean managingTrust);
void onConfigureCompleted(boolean result, IBinder token);
+ void addEscrowToken(in byte[] token, int userId);
+ void isEscrowTokenActive(long handle, int userId);
+ void removeEscrowToken(long handle, int userId);
+ void unlockUserWithToken(long handle, in byte[] token, int userId);
}
diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java
index 0d5177d..2b37a23 100644
--- a/core/java/android/service/trust/TrustAgentService.java
+++ b/core/java/android/service/trust/TrustAgentService.java
@@ -23,16 +23,20 @@
import android.app.Service;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Message;
import android.os.PersistableBundle;
import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.util.Log;
import android.util.Slog;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
@@ -118,13 +122,44 @@
public @interface GrantTrustFlags {}
+ /**
+ * Int enum indicating that escrow token is active.
+ * See {@link #onEscrowTokenStateReceived(long, int)}
+ *
+ */
+ public static final int TOKEN_STATE_ACTIVE = 1;
+
+ /**
+ * Int enum indicating that escow token is inactive.
+ * See {@link #onEscrowTokenStateReceived(long, int)}
+ *
+ */
+ public static final int TOKEN_STATE_INACTIVE = 0;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true,
+ value = {
+ TOKEN_STATE_ACTIVE,
+ TOKEN_STATE_INACTIVE,
+ })
+ public @interface TokenState {}
+
private static final int MSG_UNLOCK_ATTEMPT = 1;
private static final int MSG_CONFIGURE = 2;
private static final int MSG_TRUST_TIMEOUT = 3;
private static final int MSG_DEVICE_LOCKED = 4;
private static final int MSG_DEVICE_UNLOCKED = 5;
private static final int MSG_UNLOCK_LOCKOUT = 6;
+ private static final int MSG_ESCROW_TOKEN_ADDED = 7;
+ private static final int MSG_ESCROW_TOKEN_STATE_RECEIVED = 8;
+ private static final int MSG_ESCROW_TOKEN_REMOVED = 9;
+ private static final String EXTRA_TOKEN = "token";
+ private static final String EXTRA_TOKEN_HANDLE = "token_handle";
+ private static final String EXTRA_USER_HANDLE = "user_handle";
+ private static final String EXTRA_TOKEN_STATE = "token_state";
+ private static final String EXTRA_TOKEN_REMOVED_RESULT = "token_removed_result";
/**
* Class containing raw data for a given configuration request.
*/
@@ -155,7 +190,7 @@
case MSG_UNLOCK_LOCKOUT:
onDeviceUnlockLockout(msg.arg1);
break;
- case MSG_CONFIGURE:
+ case MSG_CONFIGURE: {
ConfigurationData data = (ConfigurationData) msg.obj;
boolean result = onConfigure(data.options);
if (data.token != null) {
@@ -168,6 +203,7 @@
}
}
break;
+ }
case MSG_TRUST_TIMEOUT:
onTrustTimeout();
break;
@@ -177,6 +213,28 @@
case MSG_DEVICE_UNLOCKED:
onDeviceUnlocked();
break;
+ case MSG_ESCROW_TOKEN_ADDED: {
+ Bundle data = msg.getData();
+ byte[] token = data.getByteArray(EXTRA_TOKEN);
+ long handle = data.getLong(EXTRA_TOKEN_HANDLE);
+ UserHandle user = (UserHandle) data.getParcelable(EXTRA_USER_HANDLE);
+ onEscrowTokenAdded(token, handle, user);
+ break;
+ }
+ case MSG_ESCROW_TOKEN_STATE_RECEIVED: {
+ Bundle data = msg.getData();
+ long handle = data.getLong(EXTRA_TOKEN_HANDLE);
+ int tokenState = data.getInt(EXTRA_TOKEN_STATE, TOKEN_STATE_INACTIVE);
+ onEscrowTokenStateReceived(handle, tokenState);
+ break;
+ }
+ case MSG_ESCROW_TOKEN_REMOVED: {
+ Bundle data = msg.getData();
+ long handle = data.getLong(EXTRA_TOKEN_HANDLE);
+ boolean success = data.getBoolean(EXTRA_TOKEN_REMOVED_RESULT);
+ onEscrowTokenRemoved(handle, success);
+ break;
+ }
}
}
};
@@ -245,6 +303,38 @@
public void onDeviceUnlockLockout(long timeoutMs) {
}
+ /**
+ * Called when an escrow token is added for user userId.
+ *
+ * @param token the added token
+ * @param handle the handle to the corresponding internal synthetic password. A user is unlocked
+ * by presenting both handle and escrow token.
+ * @param user the user to which the escrow token is added.
+ *
+ */
+ public void onEscrowTokenAdded(byte[] token, long handle, UserHandle user) {
+ }
+
+ /**
+ * Called when an escrow token state is received upon request.
+ *
+ * @param handle the handle to the internal synthetic password.
+ * @param state the state of the requested escrow token, see {@link TokenState}.
+ *
+ */
+ public void onEscrowTokenStateReceived(long handle, @TokenState int tokenState) {
+ }
+
+ /**
+ * Called when an escrow token is removed.
+ *
+ * @param handle the handle to the removed the synthetic password.
+ * @param successful whether the removing operaiton is achieved.
+ *
+ */
+ public void onEscrowTokenRemoved(long handle, boolean successful) {
+ }
+
private void onError(String msg) {
Slog.v(TAG, "Remote exception while " + msg);
}
@@ -257,7 +347,7 @@
* <p>Agents that support configuration options should overload this method and return 'true'.
*
* @param options The aggregated list of options or an empty list if no restrictions apply.
- * @return true if the {@link TrustAgentService} supports configuration options.
+ * @return true if the {@link } supports configuration options.
*/
public boolean onConfigure(List<PersistableBundle> options) {
return false;
@@ -373,6 +463,106 @@
}
}
+ /**
+ * Call to add an escrow token to derive a synthetic password. A synthetic password is an
+ * alternaive to the user-set password/pin/pattern in order to unlock encrypted disk. An escrow
+ * token can be taken and internally derive the synthetic password. The new added token will not
+ * be acivated until the user input the correct PIN/Passcode/Password once.
+ *
+ * Result will be return by callback {@link #onEscrowTokenAdded(long, int)}
+ *
+ * @param token an escrow token of high entropy.
+ * @param user the user which the escrow token will be added to.
+ *
+ */
+ public final void addEscrowToken(byte[] token, UserHandle user) {
+ synchronized (mLock) {
+ if (mCallback == null) {
+ Slog.w(TAG, "Cannot add escrow token if the agent is not connecting to framework");
+ throw new IllegalStateException("Trust agent is not connected");
+ }
+ try {
+ mCallback.addEscrowToken(token, user.getIdentifier());
+ } catch (RemoteException e) {
+ onError("calling addEscrowToken");
+ }
+ }
+ }
+
+ /**
+ * Call to check the active state of an escrow token.
+ *
+ * Result will be return in callback {@link #onEscrowTokenStateReceived(long, boolean)}
+ *
+ * @param handle the handle of escrow token to the internal synthetic password.
+ * @param user the user which the escrow token is added to.
+ *
+ */
+ public final void isEscrowTokenActive(long handle, UserHandle user) {
+ synchronized (mLock) {
+ if (mCallback == null) {
+ Slog.w(TAG, "Cannot add escrow token if the agent is not connecting to framework");
+ throw new IllegalStateException("Trust agent is not connected");
+ }
+ try {
+ mCallback.isEscrowTokenActive(handle, user.getIdentifier());
+ } catch (RemoteException e) {
+ onError("calling isEscrowTokenActive");
+ }
+ }
+ }
+
+ /**
+ * Call to remove the escrow token.
+ *
+ * Result will be return in callback {@link #onEscrowTokenRemoved(long, boolean)}
+ *
+ * @param handle the handle of escrow tokent to the internal synthetic password.
+ * @param user the user id which the escrow token is added to.
+ *
+ */
+ public final void removeEscrowToken(long handle, UserHandle user) {
+ synchronized (mLock) {
+ if (mCallback == null) {
+ Slog.w(TAG, "Cannot add escrow token if the agent is not connecting to framework");
+ throw new IllegalStateException("Trust agent is not connected");
+ }
+ try {
+ mCallback.removeEscrowToken(handle, user.getIdentifier());
+ } catch (RemoteException e) {
+ onError("callling removeEscrowToken");
+ }
+ }
+ }
+
+ /**
+ * Call to unlock user's FBE.
+ *
+ * @param handle the handle of escrow tokent to the internal synthetic password.
+ * @param token the escrow token
+ * @param user the user about to be unlocked.
+ *
+ */
+ public final void unlockUserWithToken(long handle, byte[] token, UserHandle user) {
+ UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
+ if (um.isUserUnlocked()) {
+ Slog.i(TAG, "User already unlocked");
+ return;
+ }
+
+ synchronized (mLock) {
+ if (mCallback == null) {
+ Slog.w(TAG, "Cannot add escrow token if the agent is not connecting to framework");
+ throw new IllegalStateException("Trust agent is not connected");
+ }
+ try {
+ mCallback.unlockUserWithToken(handle, token, user.getIdentifier());
+ } catch (RemoteException e) {
+ onError("calling unlockUserWithToken");
+ }
+ }
+ }
+
@Override
public final IBinder onBind(Intent intent) {
if (DEBUG) Slog.v(TAG, "onBind() intent = " + intent);
@@ -430,6 +620,28 @@
}
}
}
- }
+ @Override
+ public void onEscrowTokenAdded(byte[] token, long handle, UserHandle user) {
+ Message msg = mHandler.obtainMessage(MSG_ESCROW_TOKEN_ADDED);
+ msg.getData().putByteArray(EXTRA_TOKEN, token);
+ msg.getData().putLong(EXTRA_TOKEN_HANDLE, handle);
+ msg.getData().putParcelable(EXTRA_USER_HANDLE, user);
+ msg.sendToTarget();
+ }
+
+ public void onTokenStateReceived(long handle, int tokenState) {
+ Message msg = mHandler.obtainMessage(MSG_ESCROW_TOKEN_STATE_RECEIVED);
+ msg.getData().putLong(EXTRA_TOKEN_HANDLE, handle);
+ msg.getData().putInt(EXTRA_TOKEN_STATE, tokenState);
+ msg.sendToTarget();
+ }
+
+ public void onEscrowTokenRemoved(long handle, boolean successful) {
+ Message msg = mHandler.obtainMessage(MSG_ESCROW_TOKEN_REMOVED);
+ msg.getData().putLong(EXTRA_TOKEN_HANDLE, handle);
+ msg.getData().putBoolean(EXTRA_TOKEN_REMOVED_RESULT, successful);
+ msg.sendToTarget();
+ }
+ }
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 64f0222..483a49b 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -289,7 +289,7 @@
public void resized(Rect frame, Rect overscanInsets, Rect contentInsets,
Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw,
Configuration newConfig, Rect backDropRect, boolean forceLayout,
- boolean alwaysConsumeNavBar) {
+ boolean alwaysConsumeNavBar, int displayId) {
Message msg = mCaller.obtainMessageIO(MSG_WINDOW_RESIZED,
reportDraw ? 1 : 0, outsets);
mCaller.sendMessage(msg);
diff --git a/core/java/android/util/Spline.java b/core/java/android/util/Spline.java
index bed3a60..1037096 100644
--- a/core/java/android/util/Spline.java
+++ b/core/java/android/util/Spline.java
@@ -166,10 +166,10 @@
+ "monotonic Y values.");
}
float h = (float) Math.hypot(a, b);
- if (h > 9f) {
+ if (h > 3f) {
float t = 3f / h;
- m[i] = t * a * d[i];
- m[i + 1] = t * b * d[i];
+ m[i] *= t;
+ m[i + 1] *= t;
}
}
}
diff --git a/core/java/android/view/IGraphicsStats.aidl b/core/java/android/view/IGraphicsStats.aidl
index c235eb2..e6b750b 100644
--- a/core/java/android/view/IGraphicsStats.aidl
+++ b/core/java/android/view/IGraphicsStats.aidl
@@ -17,10 +17,11 @@
package android.view;
import android.os.ParcelFileDescriptor;
+import android.view.IGraphicsStatsCallback;
/**
* @hide
*/
interface IGraphicsStats {
- ParcelFileDescriptor requestBufferForProcess(String packageName, IBinder token);
+ ParcelFileDescriptor requestBufferForProcess(String packageName, IGraphicsStatsCallback callback);
}
diff --git a/core/java/android/view/IGraphicsStatsCallback.aidl b/core/java/android/view/IGraphicsStatsCallback.aidl
new file mode 100644
index 0000000..f70e141
--- /dev/null
+++ b/core/java/android/view/IGraphicsStatsCallback.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.view;
+
+/**
+ * @hide
+ */
+oneway interface IGraphicsStatsCallback {
+ void onRotateGraphicsStatsBuffer();
+}
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 10b1e19..14b2abe 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -50,7 +50,7 @@
void resized(in Rect frame, in Rect overscanInsets, in Rect contentInsets,
in Rect visibleInsets, in Rect stableInsets, in Rect outsets, boolean reportDraw,
in Configuration newConfig, in Rect backDropFrame, boolean forceLayout,
- boolean alwaysConsumeNavBar);
+ boolean alwaysConsumeNavBar, int displayId);
void moved(int newX, int newY);
void dispatchAppVisibility(boolean visible);
void dispatchGetNewSurface();
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 035d48f..ea2434e 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -268,7 +268,6 @@
* those of a scroll wheel.
*
* @see #SOURCE_CLASS_NONE
- * {@hide}
*/
public static final int SOURCE_ROTARY_ENCODER = 0x00400000 | SOURCE_CLASS_NONE;
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 018be86..d2577d4 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -818,7 +818,7 @@
public void resized(Rect frame, Rect overscanInsets, Rect contentInsets,
Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw,
Configuration newConfig, Rect backDropRect, boolean forceLayout,
- boolean alwaysConsumeNavBar) {
+ boolean alwaysConsumeNavBar, int displayId) {
SurfaceView surfaceView = mSurfaceView.get();
if (surfaceView != null) {
if (DEBUG) Log.v(TAG, surfaceView + " got resized: w=" + frame.width()
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 4ceb236..c66bf874 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -25,9 +25,9 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.AnimatedVectorDrawable;
-import android.os.Binder;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.Trace;
import android.util.Log;
@@ -164,6 +164,18 @@
public static final String OVERDRAW_PROPERTY_SHOW = "show";
/**
+ * Defines the rendering pipeline to be used by the ThreadedRenderer.
+ *
+ * Possible values:
+ * "opengl", will use the existing OpenGL renderer
+ * "skiagl", will use Skia's OpenGL renderer
+ * "skiavk", will use Skia's Vulkan renderer
+ *
+ * @hide
+ */
+ public static final String DEBUG_RENDERER_PROPERTY = "debug.hwui.renderer";
+
+ /**
* Turn on to debug non-rectangular clip operations.
*
* Possible values:
@@ -248,10 +260,10 @@
*
* @return A threaded renderer backed by OpenGL.
*/
- public static ThreadedRenderer create(Context context, boolean translucent) {
+ public static ThreadedRenderer create(Context context, boolean translucent, String name) {
ThreadedRenderer renderer = null;
if (isAvailable()) {
- renderer = new ThreadedRenderer(context, translucent);
+ renderer = new ThreadedRenderer(context, translucent, name);
}
return renderer;
}
@@ -275,10 +287,6 @@
nOverrideProperty(name, value);
}
- public static void dumpProfileData(byte[] data, FileDescriptor fd) {
- nDumpProfileData(data, fd);
- }
-
// Keep in sync with DrawFrameTask.h SYNC_* flags
// Nothing interesting to report
private static final int SYNC_OK = 0;
@@ -334,7 +342,7 @@
private boolean mEnabled;
private boolean mRequested = true;
- ThreadedRenderer(Context context, boolean translucent) {
+ ThreadedRenderer(Context context, boolean translucent, String name) {
final TypedArray a = context.obtainStyledAttributes(null, R.styleable.Lighting, 0, 0);
mLightY = a.getDimension(R.styleable.Lighting_lightY, 0);
mLightZ = a.getDimension(R.styleable.Lighting_lightZ, 0);
@@ -348,6 +356,7 @@
mRootNode = RenderNode.adopt(rootNodePtr);
mRootNode.setClipToBounds(false);
mNativeProxy = nCreateProxy(translucent, rootNodePtr);
+ nSetName(mNativeProxy, name);
ProcessInitializer.sInstance.init(context, mNativeProxy);
@@ -815,15 +824,6 @@
}
/**
- * Optional, sets the name of the renderer. Useful for debugging purposes.
- *
- * @param name The name of this renderer, can be null
- */
- void setName(String name) {
- nSetName(mNativeProxy, name);
- }
-
- /**
* Blocks until all previously queued work has completed.
*/
void fence() {
@@ -884,20 +884,29 @@
private static class ProcessInitializer {
static ProcessInitializer sInstance = new ProcessInitializer();
- private static IBinder sProcToken;
private boolean mInitialized = false;
+ private Context mAppContext;
+ private IGraphicsStats mGraphicsStatsService;
+ private IGraphicsStatsCallback mGraphicsStatsCallback = new IGraphicsStatsCallback.Stub() {
+ @Override
+ public void onRotateGraphicsStatsBuffer() throws RemoteException {
+ rotateBuffer();
+ }
+ };
+
private ProcessInitializer() {}
synchronized void init(Context context, long renderProxy) {
if (mInitialized) return;
mInitialized = true;
+ mAppContext = context.getApplicationContext();
initSched(context, renderProxy);
- initGraphicsStats(context, renderProxy);
+ initGraphicsStats();
}
- private static void initSched(Context context, long renderProxy) {
+ private void initSched(Context context, long renderProxy) {
try {
int tid = nGetRenderThreadTid(renderProxy);
ActivityManager.getService().setRenderThread(tid);
@@ -906,17 +915,28 @@
}
}
- private static void initGraphicsStats(Context context, long renderProxy) {
+ private void initGraphicsStats() {
try {
IBinder binder = ServiceManager.getService("graphicsstats");
if (binder == null) return;
- IGraphicsStats graphicsStatsService = IGraphicsStats.Stub
- .asInterface(binder);
- sProcToken = new Binder();
- final String pkg = context.getApplicationInfo().packageName;
- ParcelFileDescriptor pfd = graphicsStatsService.
- requestBufferForProcess(pkg, sProcToken);
- nSetProcessStatsBuffer(renderProxy, pfd.getFd());
+ mGraphicsStatsService = IGraphicsStats.Stub.asInterface(binder);
+ requestBuffer();
+ } catch (Throwable t) {
+ Log.w(LOG_TAG, "Could not acquire gfx stats buffer", t);
+ }
+ }
+
+ private void rotateBuffer() {
+ nRotateProcessStatsBuffer();
+ requestBuffer();
+ }
+
+ private void requestBuffer() {
+ try {
+ final String pkg = mAppContext.getApplicationInfo().packageName;
+ ParcelFileDescriptor pfd = mGraphicsStatsService
+ .requestBufferForProcess(pkg, mGraphicsStatsCallback);
+ nSetProcessStatsBuffer(pfd.getFd());
pfd.close();
} catch (Throwable t) {
Log.w(LOG_TAG, "Could not acquire gfx stats buffer", t);
@@ -936,7 +956,8 @@
static native void setupShadersDiskCache(String cacheFile);
- private static native void nSetProcessStatsBuffer(long nativeProxy, int fd);
+ private static native void nRotateProcessStatsBuffer();
+ private static native void nSetProcessStatsBuffer(int fd);
private static native int nGetRenderThreadTid(long nativeProxy);
private static native long nCreateRootRenderNode();
@@ -981,7 +1002,6 @@
private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd,
@DumpFlags int dumpFlags);
- private static native void nDumpProfileData(byte[] data, FileDescriptor fd);
private static native void nAddRenderNode(long nativeProxy, long rootRenderNode,
boolean placeFront);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ef5dc33..df0a161 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -108,6 +108,7 @@
import android.widget.ScrollBarDrawable;
import com.android.internal.R;
+import com.android.internal.util.Preconditions;
import com.android.internal.view.TooltipPopup;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.widget.ScrollBarUtils;
@@ -942,6 +943,37 @@
private static final int[] VISIBILITY_FLAGS = {VISIBLE, INVISIBLE, GONE};
+ /** @hide */
+ @IntDef({
+ AUTO_FILL_MODE_INHERIT,
+ AUTO_FILL_MODE_AUTO,
+ AUTO_FILL_MODE_MANUAL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AutoFillMode {}
+
+ /**
+ * This view inherits the autofill state from it's parent. If there is no parent it is
+ * {@link #AUTO_FILL_MODE_AUTO}.
+ * Use with {@link #setAutoFillMode(int)} and <a href="#attr_android:autoFillMode">
+ * {@code android:autoFillMode}.
+ */
+ public static final int AUTO_FILL_MODE_INHERIT = 0;
+
+ /**
+ * Allows this view to automatically trigger an auto-fill request when it get focus.
+ * Use with {@link #setAutoFillMode(int)} and <a href="#attr_android:autoFillMode">
+ * {@code android:autoFillMode}.
+ */
+ public static final int AUTO_FILL_MODE_AUTO = 1;
+
+ /**
+ * Require the user to manually force an auto-fill request.
+ * Use with {@link #setAutoFillMode(int)} and <a href="#attr_android:autoFillMode">{@code
+ * android:autoFillMode}.
+ */
+ public static final int AUTO_FILL_MODE_MANUAL = 2;
+
/**
* This view is enabled. Interpretation varies by subclass.
* Use with ENABLED_MASK when calling setFlags.
@@ -2512,7 +2544,8 @@
* x * NO LONGER NEEDED, SHOULD BE REUSED *
* 1 PFLAG3_FINGER_DOWN
* 1 PFLAG3_FOCUSED_BY_DEFAULT
- * xxxx * NO LONGER NEEDED, SHOULD BE REUSED *
+ * 11 PFLAG3_AUTO_FILL_MODE_MASK
+ * xx * NO LONGER NEEDED, SHOULD BE REUSED *
* 1 PFLAG3_OVERLAPPING_RENDERING_FORCED_VALUE
* 1 PFLAG3_HAS_OVERLAPPING_RENDERING_FORCED
* 1 PFLAG3_TEMPORARY_DETACH
@@ -2733,6 +2766,23 @@
private static final int PFLAG3_FOCUSED_BY_DEFAULT = 0x40000;
/**
+ * Shift for the place where the auto-fill mode is stored in the pflags
+ *
+ * @see #getAutoFillMode()
+ * @see #setAutoFillMode(int)
+ */
+ private static final int PFLAG3_AUTO_FILL_MODE_SHIFT = 19;
+
+ /**
+ * Mask for auto-fill modes
+ *
+ * @see #getAutoFillMode()
+ * @see #setAutoFillMode(int)
+ */
+ private static final int PFLAG3_AUTO_FILL_MODE_MASK = (AUTO_FILL_MODE_INHERIT
+ | AUTO_FILL_MODE_AUTO | AUTO_FILL_MODE_MANUAL) << PFLAG3_AUTO_FILL_MODE_SHIFT;
+
+ /**
* Whether this view has rendered elements that overlap (see {@link
* #hasOverlappingRendering()}, {@link #forceHasOverlappingRendering(boolean)}, and
* {@link #getHasOverlappingRendering()} ). The value in this bit is only valid when
@@ -4747,6 +4797,11 @@
setFocusedByDefault(a.getBoolean(attr, true));
}
break;
+ case com.android.internal.R.styleable.View_autoFillMode:
+ if (a.peekValue(attr) != null) {
+ setAutoFillMode(a.getInt(attr, AUTO_FILL_MODE_INHERIT));
+ }
+ break;
}
}
@@ -7405,16 +7460,19 @@
* This method only needs overloading if the node is marked as having extra data available.
* </p>
*
- * @param info The info to which to add the extra data
+ * @param info The info to which to add the extra data. Never {@code null}.
* @param extraDataKey A key specifying the type of extra data to add to the info. The
* extra data should be added to the {@link Bundle} returned by
- * the info's {@link AccessibilityNodeInfo#getExtras} method.
- * @param arguments A {@link Bundle} holding any arguments relevant for this request.
+ * the info's {@link AccessibilityNodeInfo#getExtras} method. Never
+ * {@code null}.
+ * @param arguments A {@link Bundle} holding any arguments relevant for this request. May be
+ * {@code null} if the service provided no arguments.
*
* @see AccessibilityNodeInfo#setExtraAvailableData
*/
public void addExtraDataToAccessibilityNodeInfo(
- AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
+ @NonNull AccessibilityNodeInfo info, @NonNull String extraDataKey,
+ @Nullable Bundle arguments) {
}
/**
@@ -8614,6 +8672,21 @@
}
/**
+ * Set auto-fill mode for the view.
+ *
+ * @param autoFillMode One of {@link #AUTO_FILL_MODE_INHERIT}, {@link #AUTO_FILL_MODE_AUTO},
+ * or {@link #AUTO_FILL_MODE_MANUAL}.
+ * @attr ref android.R.styleable#View_autoFillMode
+ */
+ public void setAutoFillMode(@AutoFillMode int autoFillMode) {
+ Preconditions.checkArgumentInRange(autoFillMode, AUTO_FILL_MODE_INHERIT,
+ AUTO_FILL_MODE_MANUAL, "autoFillMode");
+
+ mPrivateFlags3 &= ~PFLAG3_AUTO_FILL_MODE_MASK;
+ mPrivateFlags3 |= autoFillMode << PFLAG3_AUTO_FILL_MODE_SHIFT;
+ }
+
+ /**
* Set whether this view should have sound effects enabled for events such as
* clicking and touching.
*
@@ -9217,6 +9290,23 @@
}
/**
+ * Returns the auto-fill mode for this view.
+ *
+ * @return One of {@link #AUTO_FILL_MODE_INHERIT}, {@link #AUTO_FILL_MODE_AUTO}, or
+ * {@link #AUTO_FILL_MODE_MANUAL}.
+ * @attr ref android.R.styleable#View_autoFillMode
+ */
+ @ViewDebug.ExportedProperty(mapping = {
+ @ViewDebug.IntToString(from = AUTO_FILL_MODE_INHERIT, to = "AUTO_FILL_MODE_INHERIT"),
+ @ViewDebug.IntToString(from = AUTO_FILL_MODE_AUTO, to = "AUTO_FILL_MODE_AUTO"),
+ @ViewDebug.IntToString(from = AUTO_FILL_MODE_MANUAL, to = "AUTO_FILL_MODE_MANUAL")
+ })
+ @AutoFillMode
+ public int getAutoFillMode() {
+ return (mPrivateFlags3 & PFLAG3_AUTO_FILL_MODE_MASK) >> PFLAG3_AUTO_FILL_MODE_SHIFT;
+ }
+
+ /**
* Find the nearest view in the specified direction that can take focus.
* This does not actually give focus to that view.
*
@@ -9491,7 +9581,10 @@
public void addKeyboardNavigationClusters(
@NonNull Collection<View> views,
int direction) {
- if (!(isKeyboardNavigationCluster())) {
+ if (!isKeyboardNavigationCluster()) {
+ return;
+ }
+ if (!hasFocusable()) {
return;
}
views.add(this);
@@ -9698,11 +9791,12 @@
}
/**
- * Public for testing. This will request focus for whichever View was last focused within this
+ * This will request focus for whichever View was last focused within this
* cluster before a focus-jump out of it.
*
* @hide
*/
+ @TestApi
public boolean restoreFocusInCluster(@FocusRealDirection int direction) {
// Prioritize focusableByDefault over algorithmic focus selection.
if (restoreDefaultFocus()) {
@@ -9712,6 +9806,18 @@
}
/**
+ * This will request focus for whichever View not in a cluster was last focused before a
+ * focus-jump to a cluster. If no non-cluster View has previously had focus, this will focus
+ * the "first" focusable view it finds.
+ *
+ * @hide
+ */
+ @TestApi
+ public boolean restoreFocusNotInCluster() {
+ return requestFocus(View.FOCUS_DOWN);
+ }
+
+ /**
* Gives focus to the default-focus view in the view hierarchy that has this view as a root.
* If the default-focus view cannot be found, falls back to calling {@link #requestFocus(int)}.
*
@@ -15869,6 +15975,32 @@
}
/**
+ * @see #onMovedToDisplay(int)
+ */
+ void dispatchMovedToDisplay(Display display) {
+ mAttachInfo.mDisplay = display;
+ mAttachInfo.mDisplayState = display.getState();
+ onMovedToDisplay(display.getDisplayId());
+ }
+
+ /**
+ * Called by the system when the hosting activity is moved from one display to another without
+ * recreation. This means that the activity is declared to handle all changes to configuration
+ * that happened when it was switched to another display, so it wasn't destroyed and created
+ * again. This call will be followed by {@link #onConfigurationChanged(Configuration)} if the
+ * applied configuration actually changed.
+ *
+ * <p>Use this callback to track changes to the displays if some functionality relies on an
+ * association with some display properties.
+ *
+ * @param displayId The id of the display to which the view was moved.
+ *
+ * @see #onConfigurationChanged(Configuration)
+ */
+ public void onMovedToDisplay(int displayId) {
+ }
+
+ /**
* Return true if the application tag in the AndroidManifest has set "supportRtl" to true
*/
private boolean hasRtlSupport() {
@@ -23756,7 +23888,7 @@
final IBinder mWindowToken;
- final Display mDisplay;
+ Display mDisplay;
final Callbacks mRootCallbacks;
@@ -24518,17 +24650,20 @@
* the case where no accessibility delegate is set.
* </p>
*
- * @param host The View hosting the delegate.
- * @param info The info to which to add the extra data
+ * @param host The View hosting the delegate. Never {@code null}.
+ * @param info The info to which to add the extra data. Never {@code null}.
* @param extraDataKey A key specifying the type of extra data to add to the info. The
* extra data should be added to the {@link Bundle} returned by
- * the info's {@link AccessibilityNodeInfo#getExtras} method.
+ * the info's {@link AccessibilityNodeInfo#getExtras} method. Never
+ * {@code null}.
* @param arguments A {@link Bundle} holding any arguments relevant for this request.
+ * May be {@code null} if the if the service provided no arguments.
*
* @see AccessibilityNodeInfo#setExtraAvailableData
*/
- public void addExtraDataToAccessibilityNodeInfo(
- View host, AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
+ public void addExtraDataToAccessibilityNodeInfo(@NonNull View host,
+ @NonNull AccessibilityNodeInfo info, @NonNull String extraDataKey,
+ @Nullable Bundle arguments) {
host.addExtraDataToAccessibilityNodeInfo(info, extraDataKey, arguments);
}
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 5d01b416..f16fcc9 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -35,7 +35,7 @@
* Defines the width of the horizontal scrollbar and the height of the vertical scrollbar in
* dips
*/
- private static final int SCROLL_BAR_SIZE = 10;
+ private static final int SCROLL_BAR_SIZE = 4;
/**
* Duration of the fade when scrollbars fade away in milliseconds
@@ -346,7 +346,8 @@
mEdgeSlop = (int) (sizeAndDensity * EDGE_SLOP + 0.5f);
mFadingEdgeLength = (int) (sizeAndDensity * FADING_EDGE_LENGTH + 0.5f);
- mScrollbarSize = (int) (density * SCROLL_BAR_SIZE + 0.5f);
+ mScrollbarSize = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_scrollbarSize);
mDoubleTapSlop = (int) (sizeAndDensity * DOUBLE_TAP_SLOP + 0.5f);
mWindowTouchSlop = (int) (sizeAndDensity * WINDOW_TOUCH_SLOP + 0.5f);
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index fd3ff82..7aa2168 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -22,6 +22,7 @@
import android.annotation.CallSuper;
import android.annotation.IdRes;
import android.annotation.NonNull;
+import android.annotation.TestApi;
import android.annotation.UiThread;
import android.content.ClipData;
import android.content.Context;
@@ -3166,6 +3167,7 @@
/**
* @hide
*/
+ @TestApi
@Override
public boolean restoreFocusInCluster(@FocusRealDirection int direction) {
if (mFocusedInCluster != null && !mFocusedInCluster.isKeyboardNavigationCluster()
@@ -3178,6 +3180,40 @@
}
/**
+ * @hide
+ */
+ @Override
+ public boolean restoreFocusNotInCluster() {
+ if (mFocusedInCluster != null) {
+ // since clusters don't nest; we can assume that a non-null mFocusedInCluster
+ // will refer to a view not-in a cluster.
+ return restoreFocusInCluster(View.FOCUS_DOWN);
+ }
+ if (isKeyboardNavigationCluster()) {
+ return false;
+ }
+ int descendentFocusability = getDescendantFocusability();
+ if (descendentFocusability == FOCUS_BLOCK_DESCENDANTS) {
+ return super.requestFocus(FOCUS_DOWN, null);
+ }
+ if (descendentFocusability == FOCUS_BEFORE_DESCENDANTS
+ && super.requestFocus(FOCUS_DOWN, null)) {
+ return true;
+ }
+ for (int i = 0; i < mChildrenCount; ++i) {
+ View child = mChildren[i];
+ if (!child.isKeyboardNavigationCluster()
+ && child.restoreFocusNotInCluster()) {
+ return true;
+ }
+ }
+ if (descendentFocusability == FOCUS_AFTER_DESCENDANTS) {
+ return super.requestFocus(FOCUS_DOWN, null);
+ }
+ return false;
+ }
+
+ /**
* {@inheritDoc}
*
* @hide
@@ -3239,6 +3275,17 @@
}
}
+ @Override
+ void dispatchMovedToDisplay(Display display) {
+ super.dispatchMovedToDisplay(display);
+
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ children[i].dispatchMovedToDisplay(display);
+ }
+ }
+
/** @hide */
@Override
public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 20d8a11..c81e938 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -887,9 +887,9 @@
final boolean hasSurfaceInsets = insets.left != 0 || insets.right != 0
|| insets.top != 0 || insets.bottom != 0;
final boolean translucent = attrs.format != PixelFormat.OPAQUE || hasSurfaceInsets;
- mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent);
+ mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent,
+ attrs.getTitle().toString());
if (mAttachInfo.mThreadedRenderer != null) {
- mAttachInfo.mThreadedRenderer.setName(attrs.getTitle().toString());
mAttachInfo.mHardwareAccelerated =
mAttachInfo.mHardwareAccelerationRequested = true;
}
@@ -1034,6 +1034,26 @@
}
};
+ /**
+ * Notify about move to a different display.
+ * @param displayId The id of the display where this view root is moved to.
+ *
+ * @hide
+ */
+ public void onMovedToDisplay(int displayId) {
+ if (mDisplay.getDisplayId() == displayId) {
+ return;
+ }
+
+ // Get new instance of display based on current display adjustments. It may be updated later
+ // if moving between the displays also involved a configuration change.
+ final DisplayAdjustments displayAdjustments = mView.getResources().getDisplayAdjustments();
+ mDisplay = ResourcesManager.getInstance().getAdjustedDisplay(displayId, displayAdjustments);
+ mAttachInfo.mDisplayState = mDisplay.getState();
+ // Internal state updated, now notify the view hierarchy.
+ mView.dispatchMovedToDisplay(mDisplay);
+ }
+
void pokeDrawLockIfNeeded() {
final int displayState = mAttachInfo.mDisplayState;
if (mView != null && mAdded && mTraversalScheduled
@@ -3523,7 +3543,8 @@
&& mPendingOutsets.equals(args.arg7)
&& mPendingBackDropFrame.equals(args.arg8)
&& args.arg4 == null
- && args.argi1 == 0) {
+ && args.argi1 == 0
+ && mDisplay.getDisplayId() == args.argi3) {
break;
}
} // fall through...
@@ -3531,6 +3552,10 @@
if (mAdded) {
SomeArgs args = (SomeArgs) msg.obj;
+ if (mDisplay.getDisplayId() != args.argi3) {
+ onMovedToDisplay(args.argi3);
+ }
+
Configuration config = (Configuration) args.arg4;
if (config != null) {
updateConfiguration(config, false);
@@ -4440,7 +4465,7 @@
private boolean performKeyboardGroupNavigation(int direction) {
final View focused = mView.findFocus();
- final View cluster = focused != null
+ View cluster = focused != null
? focused.keyboardNavigationClusterSearch(null, direction)
: keyboardNavigationClusterSearch(null, direction);
@@ -4451,6 +4476,15 @@
realDirection = View.FOCUS_DOWN;
}
+ if (cluster != null && cluster.isRootNamespace()) {
+ // the default cluster. Try to find a non-clustered view to focus.
+ if (cluster.restoreFocusNotInCluster()) {
+ return true;
+ }
+ // otherwise skip to next actual cluster
+ cluster = keyboardNavigationClusterSearch(null, direction);
+ }
+
if (cluster != null && cluster.restoreFocusInCluster(realDirection)) {
return true;
}
@@ -6138,7 +6172,7 @@
public void dispatchResized(Rect frame, Rect overscanInsets, Rect contentInsets,
Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw,
Configuration newConfig, Rect backDropFrame, boolean forceLayout,
- boolean alwaysConsumeNavBar) {
+ boolean alwaysConsumeNavBar, int displayId) {
if (DEBUG_LAYOUT) Log.v(mTag, "Resizing " + this + ": frame=" + frame.toShortString()
+ " contentInsets=" + contentInsets.toShortString()
+ " visibleInsets=" + visibleInsets.toShortString()
@@ -6176,6 +6210,7 @@
args.arg8 = sameProcessCall ? new Rect(backDropFrame) : backDropFrame;
args.argi1 = forceLayout ? 1 : 0;
args.argi2 = alwaysConsumeNavBar ? 1 : 0;
+ args.argi3 = displayId;
msg.obj = args;
mHandler.sendMessage(msg);
}
@@ -7188,12 +7223,12 @@
public void resized(Rect frame, Rect overscanInsets, Rect contentInsets,
Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw,
Configuration newConfig, Rect backDropFrame, boolean forceLayout,
- boolean alwaysConsumeNavBar) {
+ boolean alwaysConsumeNavBar, int displayId) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.dispatchResized(frame, overscanInsets, contentInsets,
visibleInsets, stableInsets, outsets, reportDraw, newConfig, backDropFrame,
- forceLayout, alwaysConsumeNavBar);
+ forceLayout, alwaysConsumeNavBar, displayId);
}
}
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 9ce23e6..bc2725f 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -31,16 +31,6 @@
public abstract class ViewStructure {
/**
- * Flag used when adding virtual views for auto-fill, it indicates the contents of the view
- * (such as * {@link android.app.assist.AssistStructure.ViewNode#getText()} and
- * {@link android.app.assist.AssistStructure.ViewNode#getAutoFillValue()})
- * can be passed to the {@link
- * android.service.autofill.AutoFillService#onFillRequest(android.app.assist.AssistStructure,
- * Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback)} call.
- */
- public static final int AUTO_FILL_FLAG_SANITIZED = 0x1;
-
- /**
* Set the identifier for this view.
*
* @param id The view's identifier, as per {@link View#getId View.getId()}.
@@ -278,7 +268,7 @@
*
* @param index child index
* @param virtualId id identifying the virtual child inside the custom view.
- * @param flags currently {@code 0} or {@link #AUTO_FILL_FLAG_SANITIZED}.
+ * @param flags currently {@code 0}.
*/
// TODO(b/33197203, b/33802548): add CTS/unit test
public abstract ViewStructure newChild(int index, int virtualId, int flags);
@@ -299,7 +289,7 @@
*
* @param index child index
* @param virtualId id identifying the virtual child inside the custom view.
- * @param flags currently {@code 0} or {@link #AUTO_FILL_FLAG_SANITIZED}.
+ * @param flags currently {@code 0}.
*/
// TODO(b/33197203, b/33802548): add CTS/unit test
public abstract ViewStructure asyncNewChild(int index, int virtualId, int flags);
@@ -317,12 +307,19 @@
public abstract void setAutoFillValue(AutoFillValue value);
/**
- * @hide
+ * Marks this node as sanitized so its content are sent on {@link
+ * android.service.autofill.AutoFillService#onFillRequest(android.app.assist.AssistStructure,
+ * Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback)}.
*
- * TODO(b/33197203, b/33269702): temporary set it as not sanitized until
- * AssistStructure automaticaly sets sanitization based on text coming from resources
+ * <p>Only nodes that does not have PII (Personally Identifiable Information - sensitive data
+ * such as email addresses, credit card numbers, passwords, etc...) should be marked
+ * as sanitized; a good rule of thumb is to mark as sanitized nodes whose value were statically
+ * set from resources.
+ *
+ * <p>Should only be set when the node is used for AutoFill purposes - it will be ignored
+ * when used for Assist.
*/
- public abstract void setSanitized(boolean sensitive);
+ public abstract void setSanitized(boolean sanitized);
/**
* Call when done populating a {@link ViewStructure} returned by
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 67d7ff8..d866927 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -28,6 +28,7 @@
import android.os.Parcelable;
import android.text.InputType;
import android.text.Spannable;
+import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.AccessibilityClickableSpan;
@@ -2421,7 +2422,7 @@
ClickableSpan[] spans =
((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
if (spans.length > 0) {
- Spannable spannable = Spannable.Factory.getInstance().newSpannable(text);
+ Spannable spannable = new SpannableStringBuilder(text);
for (int i = 0; i < spans.length; i++) {
ClickableSpan span = spans[i];
if ((span instanceof AccessibilityClickableSpan)
diff --git a/core/java/android/view/autofill/AutoFillManager.java b/core/java/android/view/autofill/AutoFillManager.java
index f7a1b61..2168444 100644
--- a/core/java/android/view/autofill/AutoFillManager.java
+++ b/core/java/android/view/autofill/AutoFillManager.java
@@ -16,6 +16,8 @@
package android.view.autofill;
+import static android.view.autofill.Helper.DEBUG;
+
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
@@ -37,7 +39,6 @@
// TODO(b/33197203): improve this javadoc
//TODO(b/33197203): restrict manager calls to activity
public final class AutoFillManager {
- private static final boolean DEBUG = false;
private static final String TAG = "AutoFillManager";
@@ -178,8 +179,6 @@
* @param view view whose focus changed.
*/
public void valueChanged(View view) {
- ensureServiceClientAddedIfNeeded();
-
if (!mEnabled || !mHasSession) {
return;
}
@@ -198,8 +197,6 @@
* @param value new value of the child.
*/
public void virtualValueChanged(View parent, int childId, AutoFillValue value) {
- ensureServiceClientAddedIfNeeded();
-
if (!mEnabled || !mHasSession) {
return;
}
@@ -215,8 +212,6 @@
* call this method after the form is submitted and another page is rendered.
*/
public void reset() {
- ensureServiceClientAddedIfNeeded();
-
if (!mEnabled && !mHasSession) {
return;
}
@@ -247,12 +242,18 @@
/** @hide */
public void onAuthenticationResult(Intent data) {
+ // TODO(b/33197203): the result code is being ignored, so this method is not reliably
+ // handling the cases where it's not RESULT_OK: it works fine if the service does not
+ // set the EXTRA_AUTHENTICATION_RESULT extra, but it could cause weird results if the
+ // service set the extra and returned RESULT_CANCELED...
+
+ if (DEBUG) Log.d(TAG, "onAuthenticationResult(): d=" + data);
+
if (data == null) {
return;
}
- Parcelable result = data.getParcelableExtra(
- EXTRA_AUTHENTICATION_RESULT);
- Bundle responseData = new Bundle();
+ final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT);
+ final Bundle responseData = new Bundle();
responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result);
try {
mService.setAuthenticationResult(responseData,
diff --git a/core/java/android/view/autofill/AutoFillValue.java b/core/java/android/view/autofill/AutoFillValue.java
index af70361..d9afa3b 100644
--- a/core/java/android/view/autofill/AutoFillValue.java
+++ b/core/java/android/view/autofill/AutoFillValue.java
@@ -32,7 +32,6 @@
* {@code sub-type} define its semantics (like a postal address).
*/
public final class AutoFillValue implements Parcelable {
-
private final String mText;
private final int mListIndex;
private final boolean mToggle;
@@ -100,6 +99,12 @@
return true;
}
+ /** @hide */
+ public String coerceToString() {
+ // TODO(b/33197203): How can we filter on toggles or list values?
+ return mText;
+ }
+
@Override
public String toString() {
if (!DEBUG) return super.toString();
diff --git a/core/java/android/view/autofill/Helper.java b/core/java/android/view/autofill/Helper.java
index b1c9efa..aa94de0 100644
--- a/core/java/android/view/autofill/Helper.java
+++ b/core/java/android/view/autofill/Helper.java
@@ -25,9 +25,9 @@
/** @hide */
public final class Helper {
- static final boolean DEBUG = true; // TODO(b/33197203): set to false when stable
- static final boolean VERBOSE = false;
- static final String REDACTED = "[REDACTED]";
+ public static final boolean DEBUG = true; // TODO(b/33197203): set to false when stable
+ public static final boolean VERBOSE = false;
+ public static final String REDACTED = "[REDACTED]";
static StringBuilder append(StringBuilder builder, Bundle bundle) {
if (bundle == null) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 5572cbb..ab29257 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -63,7 +63,6 @@
import android.os.Parcelable;
import android.os.ParcelableParcel;
import android.os.SystemClock;
-import android.os.UserHandle;
import android.provider.Settings;
import android.text.BoringLayout;
import android.text.DynamicLayout;
@@ -113,6 +112,7 @@
import android.text.style.UpdateAppearance;
import android.text.util.Linkify;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
import android.util.IntArray;
import android.util.Log;
import android.util.TypedValue;
@@ -706,16 +706,18 @@
private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112;
// Default value for the step size in pixels.
private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1;
+ // Use this to specify that any of the auto-size configuration int values have not been set.
+ private static final int UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1;
// Auto-size text type.
private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
// Specify if auto-size text is needed.
private boolean mNeedsAutoSizeText = false;
// Step size for auto-sizing in pixels.
- private int mAutoSizeStepGranularityInPx = 0;
+ private int mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
// Minimum text size for auto-sizing in pixels.
- private int mAutoSizeMinTextSizeInPx = 0;
+ private int mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
// Maximum text size for auto-sizing in pixels.
- private int mAutoSizeMaxTextSizeInPx = 0;
+ private int mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
// Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from
// when auto-sizing text.
private int[] mAutoSizeTextSizesInPx = EmptyArray.INT;
@@ -727,6 +729,10 @@
// Watcher used to notify changes to auto-fill manager.
private AutoFillChangeWatcher mAutoFillChangeWatcher;
+ // Indicates whether the text was set from resources or dynamically, so it can be used to
+ // sanitize auto-fill request.
+ private boolean mTextFromResource = false;
+
/**
* Kick-start the font cache for the zygote process (to pay the cost of
* initializing freetype for our default font only once).
@@ -937,11 +943,16 @@
CharSequence text = "";
CharSequence hint = null;
boolean password = false;
+ int autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
+ int autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
+ int autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
int inputType = EditorInfo.TYPE_NULL;
a = theme.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
int n = a.getIndexCount();
+
+ boolean fromResourceId = false;
for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
@@ -1083,6 +1094,7 @@
break;
case com.android.internal.R.styleable.TextView_text:
+ fromResourceId = true;
text = a.getText(attr);
break;
@@ -1304,15 +1316,18 @@
break;
case com.android.internal.R.styleable.TextView_autoSizeStepGranularity:
- mAutoSizeStepGranularityInPx = a.getDimensionPixelSize(attr, 0);
+ autoSizeStepGranularityInPx = a.getDimensionPixelSize(attr,
+ UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
break;
case com.android.internal.R.styleable.TextView_autoSizeMinTextSize:
- mAutoSizeMinTextSizeInPx = a.getDimensionPixelSize(attr, 0);
+ autoSizeMinTextSizeInPx = a.getDimensionPixelSize(attr,
+ UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
break;
case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize:
- mAutoSizeMaxTextSizeInPx = a.getDimensionPixelSize(attr, 0);
+ autoSizeMaxTextSizeInPx = a.getDimensionPixelSize(attr,
+ UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
break;
case com.android.internal.R.styleable.TextView_autoSizePresetSizes:
@@ -1320,7 +1335,7 @@
if (autoSizeStepSizeArrayResId > 0) {
final TypedArray autoSizePreDefTextSizes = a.getResources()
.obtainTypedArray(autoSizeStepSizeArrayResId);
- setupAutoSizePresetSizes(autoSizePreDefTextSizes);
+ setupAutoSizeUniformPresetSizes(autoSizePreDefTextSizes);
autoSizePreDefTextSizes.recycle();
}
break;
@@ -1558,6 +1573,10 @@
}
setText(text, bufferType);
+ if (fromResourceId) {
+ mTextFromResource = true;
+ }
+
if (hint != null) setHint(hint);
/*
@@ -1598,12 +1617,47 @@
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
- setupAutoSizeText();
+ if (supportsAutoSizeText()) {
+ if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
+ // If uniform auto-size has been specified but preset values have not been set then
+ // replace the auto-size configuration values that have not been specified with the
+ // defaults.
+ if (!mHasPresetAutoSizeValues) {
+ final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
+
+ if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
+ autoSizeMinTextSizeInPx = (int) TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_SP,
+ DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
+ displayMetrics);
+ }
+
+ if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
+ autoSizeMaxTextSizeInPx = (int) TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_SP,
+ DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
+ displayMetrics);
+ }
+
+ if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
+ autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX;
+ }
+
+ validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
+ autoSizeMaxTextSizeInPx,
+ autoSizeStepGranularityInPx);
+ }
+
+ setupAutoSizeText();
+ }
+ } else {
+ mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
+ }
}
/**
* Specify whether this widget should automatically scale the text to try to perfectly fit
- * within the layout bounds by taking into account the auto-size configuration.
+ * within the layout bounds by using the default auto-size configuration.
*
* @param autoSizeTextType the type of auto-size. Must be one of
* {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
@@ -1613,25 +1667,28 @@
*
* @see #getAutoSizeTextType()
*/
- public void setAutoSizeTextType(@AutoSizeTextType int autoSizeTextType) {
+ public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) {
if (supportsAutoSizeText()) {
switch (autoSizeTextType) {
case AUTO_SIZE_TEXT_TYPE_NONE:
- if (mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE) {
- // Clear all auto-size configuration
- mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
- mAutoSizeMinTextSizeInPx = 0;
- mAutoSizeMaxTextSizeInPx = 0;
- mAutoSizeStepGranularityInPx = 0;
- mAutoSizeTextSizesInPx = EmptyArray.INT;
- mNeedsAutoSizeText = false;
- }
+ clearAutoSizeConfiguration();
break;
case AUTO_SIZE_TEXT_TYPE_UNIFORM:
- if (mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_UNIFORM) {
- mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
- setupAutoSizeText();
- }
+ final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
+ final int autoSizeMinTextSizeInPx = (int) TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_SP,
+ DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
+ displayMetrics);
+ final int autoSizeMaxTextSizeInPx = (int) TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_SP,
+ DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
+ displayMetrics);
+
+ validateAndSetAutoSizeTextTypeUniformConfiguration(
+ autoSizeMinTextSizeInPx,
+ autoSizeMaxTextSizeInPx,
+ DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX);
+ setupAutoSizeText();
break;
default:
throw new IllegalArgumentException(
@@ -1641,6 +1698,111 @@
}
/**
+ * Specify whether this widget should automatically scale the text to try to perfectly fit
+ * within the layout bounds. If all the configuration params are valid the type of auto-size is
+ * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
+ *
+ * @param autoSizeMinTextSize the minimum text size available for auto-size
+ * @param autoSizeMaxTextSize the maximum text size available for auto-size
+ * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with
+ * the minimum and maximum text size in order to build the set of
+ * text sizes the system uses to choose from when auto-sizing
+ * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the
+ * possible dimension units
+ *
+ * @throws IllegalArgumentException if any of the configuration params are invalid.
+ *
+ * @attr ref android.R.styleable#TextView_autoSizeText
+ * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
+ * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
+ * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
+ *
+ * @see #setAutoSizeTextTypeWithDefaults(int)
+ * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
+ * @see #getAutoSizeMinTextSize()
+ * @see #getAutoSizeMaxTextSize()
+ * @see #getAutoSizeStepGranularity()
+ * @see #getAutoSizeTextAvailableSizes()
+ */
+ public void setAutoSizeTextTypeUniformWithConfiguration(
+ int autoSizeMinTextSize,
+ int autoSizeMaxTextSize,
+ int autoSizeStepGranularity,
+ int unit) throws IllegalArgumentException {
+ if (supportsAutoSizeText()) {
+ final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
+ final int autoSizeMinTextSizeInPx = (int) TypedValue.applyDimension(
+ unit, autoSizeMinTextSize, displayMetrics);
+ final int autoSizeMaxTextSizeInPx = (int) TypedValue.applyDimension(
+ unit, autoSizeMaxTextSize, displayMetrics);
+ final int autoSizeStepGranularityInPx = (int) TypedValue.applyDimension(
+ unit, autoSizeStepGranularity, displayMetrics);
+
+ validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
+ autoSizeMaxTextSizeInPx,
+ autoSizeStepGranularityInPx);
+ setupAutoSizeText();
+ }
+ }
+
+ /**
+ * Specify whether this widget should automatically scale the text to try to perfectly fit
+ * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid
+ * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
+ *
+ * @param presetSizes an {@code int} array of sizes in pixels
+ * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for
+ * the possible dimension units
+ *
+ * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid.
+ *
+ * @attr ref android.R.styleable#TextView_autoSizeText
+ * @attr ref android.R.styleable#TextView_autoSizePresetSizes
+ *
+ * @see #setAutoSizeTextTypeWithDefaults(int)
+ * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
+ * @see #getAutoSizeMinTextSize()
+ * @see #getAutoSizeMaxTextSize()
+ * @see #getAutoSizeTextAvailableSizes()
+ */
+ public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit)
+ throws IllegalArgumentException {
+ if (supportsAutoSizeText()) {
+ final int presetSizesLength = presetSizes.length;
+ if (presetSizesLength > 0) {
+ int[] presetSizesInPx = new int[presetSizesLength];
+
+ if (unit == TypedValue.COMPLEX_UNIT_PX) {
+ presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength);
+ } else {
+ final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
+ // Convert all to sizes to pixels.
+ for (int i = 0; i < presetSizesLength; i++) {
+ presetSizesInPx[i] = (int) TypedValue.applyDimension(unit, presetSizes[i],
+ displayMetrics);
+ }
+ }
+
+ mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx);
+ final int sizesLength = mAutoSizeTextSizesInPx.length;
+ mHasPresetAutoSizeValues = sizesLength > 0;
+ if (mHasPresetAutoSizeValues) {
+ mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
+ mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0];
+ mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1];
+ mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
+ } else {
+ throw new IllegalArgumentException("None of the preset sizes is valid: "
+ + Arrays.toString(presetSizes));
+ }
+ } else {
+ mHasPresetAutoSizeValues = false;
+ }
+ setupAutoSizeText();
+ }
+ }
+
+ /**
* Returns the type of auto-size set for this widget.
*
* @return an {@code int} corresponding to one of the auto-size types:
@@ -1649,7 +1811,9 @@
*
* @attr ref android.R.styleable#TextView_autoSizeText
*
- * @see #setAutoSizeTextType(int)
+ * @see #setAutoSizeTextTypeWithDefaults(int)
+ * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
+ * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
*/
@AutoSizeTextType
public int getAutoSizeTextType() {
@@ -1657,155 +1821,53 @@
}
/**
- * Sets the auto-size step granularity. It is used in conjunction with auto-size minimum
- * and maximum text size in order to build the set of text sizes the system uses to choose
- * from when auto-sizing.
- *
- * @param unit the desired dimension unit. See {@link TypedValue} for the possible
- * dimension units
- * @param size the desired size in the given units
+ * @return the current auto-size step granularity in pixels.
*
* @attr ref android.R.styleable#TextView_autoSizeStepGranularity
*
- * @see #getAutoSizeStepGranularity()
- * @see #setAutoSizeMinTextSize(int, float)
- * @see #setAutoSizeMaxTextSize(int, float)
- */
- public void setAutoSizeStepGranularity(int unit, float size) {
- if (supportsAutoSizeText()) {
- mAutoSizeStepGranularityInPx = (int) TypedValue.applyDimension(
- unit, size, getResources().getDisplayMetrics());
- mHasPresetAutoSizeValues = false;
- setupAutoSizeText();
- }
- }
-
- /**
- * @return the current auto-size step granularity in pixels.
- *
- * @see #setAutoSizeStepGranularity(int, float)
+ * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
*/
public int getAutoSizeStepGranularity() {
return mAutoSizeStepGranularityInPx;
}
/**
- * Sets the minimum text size to be used in conjunction with auto-size maximum text size and
- * auto-size step granularity in order to build the set of text sizes the system uses to choose
- * from when auto-sizing.
- *
- * @param unit the desired dimension unit. See {@link TypedValue} for the possible
- * dimension units
- * @param size the desired size in the given units
+ * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that
+ * if auto-size has not been configured this function returns {@code -1}.
*
* @attr ref android.R.styleable#TextView_autoSizeMinTextSize
*
- * @see #getAutoSizeMinTextSize()
- * @see #setAutoSizeMaxTextSize(int, float)
- * @see #setAutoSizeStepGranularity(int, float)
- */
- public void setAutoSizeMinTextSize(int unit, float size) {
- if (supportsAutoSizeText()) {
- mAutoSizeMinTextSizeInPx = (int) TypedValue.applyDimension(
- unit, size, getResources().getDisplayMetrics());
- mHasPresetAutoSizeValues = false;
- setupAutoSizeText();
- }
- }
-
- /**
- * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that
- * if auto-size has not been configured this function returns {@code 0}.
- *
- * @see #setAutoSizeMinTextSize(int, float)
+ * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
+ * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
*/
public int getAutoSizeMinTextSize() {
return mAutoSizeMinTextSizeInPx;
}
/**
- * Sets the maximum text size to be used in conjunction with auto-size minimum text size and
- * auto-size step granularity in order to build the set of text sizes the system uses to choose
- * from when auto-sizing.
- *
- * @param unit the desired dimension unit. See {@link TypedValue} for the possible
- * dimension units
- * @param size the desired size in the given units
+ * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that
+ * if auto-size has not been configured this function returns {@code -1}.
*
* @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
*
- * @see #getAutoSizeMaxTextSize()
- * @see #setAutoSizeMinTextSize(int, float)
- * @see #setAutoSizeStepGranularity(int, float)
- */
- public void setAutoSizeMaxTextSize(int unit, float size) {
- if (supportsAutoSizeText()) {
- mAutoSizeMaxTextSizeInPx = (int) TypedValue.applyDimension(
- unit, size, getResources().getDisplayMetrics());
- mHasPresetAutoSizeValues = false;
- setupAutoSizeText();
- }
- }
-
- /**
- * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that
- * if auto-size has not been configured this function returns {@code 0}.
- *
- * @see #setAutoSizeMaxTextSize(int, float)
+ * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
+ * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
*/
public int getAutoSizeMaxTextSize() {
return mAutoSizeMaxTextSizeInPx;
}
/**
- * Sets a predefined array of sizes to be used when auto-sizing.
- *
- * <ul>Note:
- * <li>when <code>presetSizes</code> is not empty then the auto-size algorithm will use the
- * values provided here instead of calculating the values based on min, max and step size. Also
- * the values will be de-duplicated, sorted and negative or zero values will be removed.
- * <li>when <code>presetSizes</code> is empty then the auto-size algorithm will use the min, max
- * and step size to build the set of available sizes to choose from. Note that if no values have
- * been provided for any of min, max or step size then defaults will be used.
- * </ul>
- *
- * @param presetSizes an {@code int} array of sizes in pixels
- *
- * @attr ref android.R.styleable#TextView_autoSizePresetSizes
- *
- * @see #getAutoSizeTextAvailableSizes()
- */
- public void setAutoSizeTextPresetSizes(@NonNull int[] presetSizes) {
- if (supportsAutoSizeText()) {
- if (presetSizes.length > 0) {
- mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizes);
- final int sizesLength = mAutoSizeTextSizesInPx.length;
- mHasPresetAutoSizeValues = sizesLength > 0;
- if (mHasPresetAutoSizeValues) {
- mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0];
- mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1];
- mAutoSizeStepGranularityInPx = 0;
- }
- } else {
- mHasPresetAutoSizeValues = false;
- }
- setupAutoSizeText();
- }
- }
-
- /**
* @return the current auto-size {@code int} sizes array (in pixels).
*
- * @see #setAutoSizeTextPresetSizes(int[])
- * @see #setAutoSizeMinTextSize(int, float)
- * @see #setAutoSizeMaxTextSize(int, float)
- * @see #setAutoSizeStepGranularity(int, float)
+ * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
+ * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
*/
public int[] getAutoSizeTextAvailableSizes() {
return mAutoSizeTextSizesInPx;
}
- private void setupAutoSizePresetSizes(TypedArray textSizes) {
+ private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) {
final int textSizesLength = textSizes.length();
final int[] parsedSizes = new int[textSizesLength];
@@ -1818,6 +1880,49 @@
}
}
+ /**
+ * If all params are valid then save the auto-size configuration.
+ *
+ * @throws IllegalArgumentException if any of the params are invalid
+ */
+ private void validateAndSetAutoSizeTextTypeUniformConfiguration(
+ int autoSizeMinTextSizeInPx,
+ int autoSizeMaxTextSizeInPx,
+ int autoSizeStepGranularityInPx) throws IllegalArgumentException {
+ // First validate.
+ if (autoSizeMinTextSizeInPx <= 0) {
+ throw new IllegalArgumentException("Minimum auto-size text size ("
+ + autoSizeMinTextSizeInPx + "px) is less or equal to 0px)");
+ }
+
+ if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) {
+ throw new IllegalArgumentException("Maximum auto-size text size ("
+ + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size "
+ + "text size (" + autoSizeMinTextSizeInPx + "px)");
+ }
+
+ if (autoSizeStepGranularityInPx <= 0) {
+ throw new IllegalArgumentException("Minimum auto-size text size ("
+ + autoSizeStepGranularityInPx + "px) is less or equal to 0px)");
+ }
+
+ // All good, persist the configuration.
+ mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
+ mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx;
+ mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx;
+ mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx;
+ mHasPresetAutoSizeValues = false;
+ }
+
+ private void clearAutoSizeConfiguration() {
+ mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
+ mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
+ mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
+ mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
+ mAutoSizeTextSizesInPx = EmptyArray.INT;
+ mNeedsAutoSizeText = false;
+ }
+
// Returns distinct sorted positive values.
private int[] cleanupAutoSizePresetSizes(int[] presetValues) {
final int presetValuesLength = presetValues.length;
@@ -1846,33 +1951,6 @@
// Calculate the sizes set based on minimum size, maximum size and step size if we do
// not have a predefined set of sizes or if the current sizes array is empty.
if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) {
- // Set valid defaults.
- if (mAutoSizeMinTextSizeInPx <= 0) {
- mAutoSizeMinTextSizeInPx = (int) TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_SP,
- DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
- getResources().getDisplayMetrics());
- }
-
- if (mAutoSizeMaxTextSizeInPx <= 0) {
- mAutoSizeMaxTextSizeInPx = (int) TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_SP,
- DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
- getResources().getDisplayMetrics());
- }
-
- if (mAutoSizeStepGranularityInPx <= 0) {
- mAutoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX;
- }
-
- // Validate.
- if (mAutoSizeMaxTextSizeInPx <= mAutoSizeMinTextSizeInPx) {
- throw new IllegalStateException("Maximum auto-size text size ("
- + mAutoSizeMaxTextSizeInPx
- + "px) is less or equal to minimum auto-size "
- + "text size (" + mAutoSizeMinTextSizeInPx + "px)");
- }
-
// Calculate sizes to choose from based on the current auto-size configuration.
int autoSizeValuesLength = (int) Math.ceil(
(mAutoSizeMaxTextSizeInPx - mAutoSizeMinTextSizeInPx)
@@ -4999,6 +5077,7 @@
private void setText(CharSequence text, BufferType type,
boolean notifyBefore, int oldlen) {
+ mTextFromResource = false;
if (text == null) {
text = "";
}
@@ -5233,6 +5312,7 @@
@android.view.RemotableViewMethod
public final void setText(@StringRes int resid) {
setText(getContext().getResources().getText(resid));
+ mTextFromResource = true;
}
/**
@@ -5259,6 +5339,7 @@
*/
public final void setText(@StringRes int resid, BufferType type) {
setText(getContext().getResources().getText(resid), type);
+ mTextFromResource = true;
}
/**
@@ -7953,9 +8034,9 @@
TEMP_RECTF.setEmpty();
TEMP_RECTF.right = maxWidth;
TEMP_RECTF.bottom = maxHeight;
- final float textSize = findLargestTextSizeWhichFits(TEMP_RECTF);
- if (textSize != getTextSize()) {
- setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, textSize);
+ final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF);
+ if (optimalTextSize != getTextSize()) {
+ setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize);
}
}
}
@@ -9772,16 +9853,6 @@
}
}
- /**
- * @return true if the user has explicitly allowed accessibility services
- * to speak passwords.
- */
- private boolean shouldSpeakPasswordsForAccessibility() {
- return (Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0,
- UserHandle.USER_CURRENT_OR_SELF) == 1);
- }
-
@Override
public CharSequence getAccessibilityClassName() {
return TextView.class.getName();
@@ -9804,9 +9875,7 @@
final boolean isPassword = hasPasswordTransformationMethod()
|| isPasswordInputType(getInputType());
if (forAutoFill) {
- // TODO(b/33197203, b/33269702): temporary set it as not sanitized until
- // AssistStructure automaticaly sets sanitization based on text coming from resources
- structure.setSanitized(!isPassword);
+ structure.setSanitized(mTextFromResource);
if (mAutoFillChangeWatcher == null && isTextEditable()) {
mAutoFillChangeWatcher = new AutoFillChangeWatcher();
addTextChangedListener(mAutoFillChangeWatcher);
@@ -10035,6 +10104,10 @@
@Override
public void addExtraDataToAccessibilityNodeInfo(
AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
+ // The only extra data we support requires arguments.
+ if (arguments == null) {
+ return;
+ }
if (extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) {
int positionInfoStartIndex = arguments.getInt(
EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1);
@@ -10352,13 +10425,7 @@
return mHint;
}
- // Check whether we need to bypass the transformation
- // method and expose unobscured text.
- if (hasPasswordTransformationMethod() && shouldSpeakPasswordsForAccessibility()) {
- return mText;
- }
-
- // Otherwise, speak whatever text is being displayed.
+ // Otherwise, return whatever text is being displayed.
return mTransformed;
}
@@ -11475,9 +11542,7 @@
+ " before=" + before + " after=" + after + ": " + buffer);
}
- if (AccessibilityManager.getInstance(mContext).isEnabled()
- && ((!isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod())
- || shouldSpeakPasswordsForAccessibility())) {
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
mBeforeText = buffer.toString();
}
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java
index eec3cb0..e088717 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/PackageHelper.java
@@ -534,7 +534,7 @@
final int prefer;
final boolean checkBoth;
boolean ephemeral = false;
- if ((installFlags & PackageManager.INSTALL_EPHEMERAL) != 0) {
+ if ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
prefer = RECOMMEND_INSTALL_INTERNAL;
ephemeral = true;
checkBoth = false;
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 9dca5ea..12d96e2 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -7914,12 +7914,11 @@
}
public void noteStartSensor(int sensor, long elapsedRealtimeMs) {
- StopwatchTimer t = getSensorTimerLocked(sensor, true);
- if (t != null) {
- t.startRunningLocked(elapsedRealtimeMs);
- }
- Counter c = getSensorBgCounterLocked(sensor, true);
- if (c != null && mProcessState >= PROCESS_STATE_BACKGROUND) {
+ StopwatchTimer t = getSensorTimerLocked(sensor, /* create= */ true);
+ t.startRunningLocked(elapsedRealtimeMs);
+
+ Counter c = getSensorBgCounterLocked(sensor, /* create= */ true);
+ if (mProcessState >= PROCESS_STATE_BACKGROUND && t.mNesting == 1) {
c.stepAtomic();
}
}
@@ -7933,17 +7932,17 @@
}
public void noteStartGps(long elapsedRealtimeMs) {
- StopwatchTimer t = getSensorTimerLocked(Sensor.GPS, true);
- if (t != null) {
- t.startRunningLocked(elapsedRealtimeMs);
- }
- Counter c = getSensorBgCounterLocked(Sensor.GPS, true);
- if (c != null && mProcessState >= PROCESS_STATE_BACKGROUND) {
+ StopwatchTimer t = getSensorTimerLocked(Sensor.GPS, /* create= */ true);
+ t.startRunningLocked(elapsedRealtimeMs);
+
+ Counter c = getSensorBgCounterLocked(Sensor.GPS, /* create= */ true);
+ if (mProcessState >= PROCESS_STATE_BACKGROUND && t.mNesting == 1) {
c.stepAtomic();
}
}
public void noteStopGps(long elapsedRealtimeMs) {
+ // Don't create a timer if one doesn't already exist
StopwatchTimer t = getSensorTimerLocked(Sensor.GPS, false);
if (t != null) {
t.stopRunningLocked(elapsedRealtimeMs);
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index cb3a250..2b16254 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -260,10 +260,19 @@
*/
@SuppressWarnings("unchecked")
public static @NonNull <T> T[] appendElement(Class<T> kind, @Nullable T[] array, T element) {
+ return appendElement(kind, array, element, false);
+ }
+
+ /**
+ * Adds value to given array.
+ */
+ @SuppressWarnings("unchecked")
+ public static @NonNull <T> T[] appendElement(Class<T> kind, @Nullable T[] array, T element,
+ boolean allowDuplicates) {
final T[] result;
final int end;
if (array != null) {
- if (contains(array, element)) return array;
+ if (!allowDuplicates && contains(array, element)) return array;
end = array.length;
result = (T[])Array.newInstance(kind, end + 1);
System.arraycopy(array, 0, result, 0, end);
@@ -299,17 +308,19 @@
}
/**
- * Adds value to given array if not already present, providing set-like
- * behavior.
+ * Adds value to given array.
*/
- public static @NonNull int[] appendInt(@Nullable int[] cur, int val) {
+ public static @NonNull int[] appendInt(@Nullable int[] cur, int val,
+ boolean allowDuplicates) {
if (cur == null) {
return new int[] { val };
}
final int N = cur.length;
- for (int i = 0; i < N; i++) {
- if (cur[i] == val) {
- return cur;
+ if (!allowDuplicates) {
+ for (int i = 0; i < N; i++) {
+ if (cur[i] == val) {
+ return cur;
+ }
}
}
int[] ret = new int[N + 1];
@@ -319,6 +330,14 @@
}
/**
+ * Adds value to given array if not already present, providing set-like
+ * behavior.
+ */
+ public static @NonNull int[] appendInt(@Nullable int[] cur, int val) {
+ return appendInt(cur, val, false);
+ }
+
+ /**
* Removes value from given array if present, providing set-like behavior.
*/
public static @Nullable int[] removeInt(@Nullable int[] cur, int val) {
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index dd91d2f..ce51dc4 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -40,7 +40,7 @@
@Override
public void resized(Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets,
Rect stableInsets, Rect outsets, boolean reportDraw, Configuration newConfig,
- Rect backDropFrame, boolean forceLayout, boolean alwaysConsumeNavBar) {
+ Rect backDropFrame, boolean forceLayout, boolean alwaysConsumeNavBar, int displayId) {
if (reportDraw) {
try {
mSession.finishDrawing(this);
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index 4ba19f4..ae2e0ac 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -1489,21 +1489,10 @@
return bounds;
}
- private boolean shouldSpeakPassword() {
- final boolean speakPassword = Settings.Secure.getIntForUser(
- mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0,
- UserHandle.USER_CURRENT_OR_SELF) != 0;
- final boolean hasHeadphones = mAudioManager != null ?
- (mAudioManager.isWiredHeadsetOn() || mAudioManager.isBluetoothA2dpOn())
- : false;
- return speakPassword || hasHeadphones;
- }
-
private CharSequence getTextForVirtualView(int virtualViewId) {
final Resources res = getResources();
- return shouldSpeakPassword() ? res.getString(
- R.string.lockscreen_access_pattern_cell_added_verbose, virtualViewId)
- : res.getString(R.string.lockscreen_access_pattern_cell_added);
+ return res.getString(R.string.lockscreen_access_pattern_cell_added_verbose,
+ virtualViewId);
}
/**
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 2aa16b2..30d6337 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -455,10 +455,17 @@
const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(),
decodeColorType, decodeAlphaType, codec->computeOutputColorSpace(decodeColorType));
- // When supported by the colorType, we will decode to sRGB (or linear sRGB). However,
- // we only want to mark the bitmap as sRGB when linear blending is enabled.
- SkImageInfo bitmapInfo = decodeInfo.makeAlphaType(alphaType)
- .makeColorSpace(GraphicsJNI::colorSpaceForType(decodeColorType));
+ SkImageInfo bitmapInfo = decodeInfo.makeAlphaType(alphaType);
+
+ // For wide gamut images, we will leave the color space on the SkBitmap. Otherwise,
+ // use the default.
+ sk_sp<SkColorSpace> srgb =
+ SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
+ SkColorSpace::kSRGB_Gamut,
+ SkColorSpace::kNonLinearBlending_ColorSpaceFlag);
+ if (decodeInfo.colorSpace() == srgb.get()) {
+ bitmapInfo = bitmapInfo.makeColorSpace(GraphicsJNI::colorSpaceForType(decodeColorType));
+ }
if (decodeColorType == kGray_8_SkColorType) {
// The legacy implementation of BitmapFactory used kAlpha8 for
diff --git a/core/jni/android/graphics/SurfaceTexture.cpp b/core/jni/android/graphics/SurfaceTexture.cpp
index dc9b656..2b1da25 100644
--- a/core/jni/android/graphics/SurfaceTexture.cpp
+++ b/core/jni/android/graphics/SurfaceTexture.cpp
@@ -25,6 +25,7 @@
#include <gui/GLConsumer.h>
#include <gui/Surface.h>
+#include <gui/BufferQueue.h>
#include "core_jni_helpers.h"
@@ -33,6 +34,7 @@
#include "jni.h"
#include "JNIHelp.h"
+#include "ScopedLocalRef.h"
// ----------------------------------------------------------------------------
@@ -384,7 +386,6 @@
// ----------------------------------------------------------------------------
static const JNINativeMethod gSurfaceTextureMethods[] = {
- {"nativeClassInit", "()V", (void*)SurfaceTexture_classInit },
{"nativeInit", "(ZIZLjava/lang/ref/WeakReference;)V", (void*)SurfaceTexture_init },
{"nativeFinalize", "()V", (void*)SurfaceTexture_finalize },
{"nativeSetDefaultBufferSize", "(II)V", (void*)SurfaceTexture_setDefaultBufferSize },
@@ -400,6 +401,10 @@
int register_android_graphics_SurfaceTexture(JNIEnv* env)
{
+ // Cache some fields.
+ ScopedLocalRef<jclass> klass(env, FindClassOrDie(env, kSurfaceTextureClassPathName));
+ SurfaceTexture_classInit(env, klass.get());
+
return RegisterMethodsOrDie(env, kSurfaceTextureClassPathName, gSurfaceTextureMethods,
NELEM(gSurfaceTextureMethods));
}
diff --git a/core/jni/android_hardware_HardwareBuffer.cpp b/core/jni/android_hardware_HardwareBuffer.cpp
index 6cf5ccf..5f0664b 100644
--- a/core/jni/android_hardware_HardwareBuffer.cpp
+++ b/core/jni/android_hardware_HardwareBuffer.cpp
@@ -239,6 +239,8 @@
return AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM;
case HAL_PIXEL_FORMAT_RGBA_FP16:
return AHARDWAREBUFFER_FORMAT_R16G16B16A16_SFLOAT;
+ case HAL_PIXEL_FORMAT_RGBA_1010102:
+ return AHARDWAREBUFFER_FORMAT_A2R10G10B10_UNORM_PACK32;
case HAL_PIXEL_FORMAT_BLOB:
return AHARDWAREBUFFER_FORMAT_BLOB;
default:
@@ -259,6 +261,8 @@
return HAL_PIXEL_FORMAT_RGB_888;
case AHARDWAREBUFFER_FORMAT_R16G16B16A16_SFLOAT:
return HAL_PIXEL_FORMAT_RGBA_FP16;
+ case AHARDWAREBUFFER_FORMAT_A2R10G10B10_UNORM_PACK32:
+ return HAL_PIXEL_FORMAT_RGBA_1010102;
case AHARDWAREBUFFER_FORMAT_BLOB:
return HAL_PIXEL_FORMAT_BLOB;
default:
diff --git a/core/jni/android_hardware_camera2_CameraMetadata.cpp b/core/jni/android_hardware_camera2_CameraMetadata.cpp
index 518f99e..78a5735 100644
--- a/core/jni/android_hardware_camera2_CameraMetadata.cpp
+++ b/core/jni/android_hardware_camera2_CameraMetadata.cpp
@@ -161,7 +161,6 @@
extern "C" {
-static void CameraMetadata_classInit(JNIEnv *env, jobject thiz);
static jobject CameraMetadata_getAllVendorKeys(JNIEnv* env, jobject thiz, jclass keyType);
static jint CameraMetadata_getTagFromKey(JNIEnv *env, jobject thiz, jstring keyName);
static jint CameraMetadata_getTypeFromTag(JNIEnv *env, jobject thiz, jint tag);
@@ -529,9 +528,6 @@
static const JNINativeMethod gCameraMetadataMethods[] = {
// static methods
- { "nativeClassInit",
- "()V",
- (void *)CameraMetadata_classInit },
{ "nativeGetAllVendorKeys",
"(Ljava/lang/Class;)Ljava/util/ArrayList;",
(void *)CameraMetadata_getAllVendorKeys},
@@ -581,35 +577,6 @@
(void *)CameraMetadata_writeToParcel },
};
-struct field {
- const char *class_name;
- const char *field_name;
- const char *field_type;
- jfieldID *jfield;
-};
-
-static int find_fields(JNIEnv *env, field *fields, int count)
-{
- for (int i = 0; i < count; i++) {
- field *f = &fields[i];
- jclass clazz = env->FindClass(f->class_name);
- if (clazz == NULL) {
- ALOGE("Can't find %s", f->class_name);
- return -1;
- }
-
- jfieldID field = env->GetFieldID(clazz, f->field_name, f->field_type);
- if (field == NULL) {
- ALOGE("Can't find %s.%s", f->class_name, f->field_name);
- return -1;
- }
-
- *(f->jfield) = field;
- }
-
- return 0;
-}
-
// Get all the required offsets in java class and register native functions
int register_android_hardware_camera2_CameraMetadata(JNIEnv *env)
{
@@ -651,6 +618,9 @@
gMetadataOffsets.mArrayListAdd = GetMethodIDOrDie(env, gMetadataOffsets.mArrayList,
"add", "(Ljava/lang/Object;)Z");
+ jclass cameraMetadataClazz = FindClassOrDie(env, CAMERA_METADATA_CLASS_NAME);
+ fields.metadata_ptr = GetFieldIDOrDie(env, cameraMetadataClazz, "mMetadataPtr", "J");
+
// Register native functions
return RegisterMethodsOrDie(env,
CAMERA_METADATA_CLASS_NAME,
@@ -660,22 +630,6 @@
extern "C" {
-static void CameraMetadata_classInit(JNIEnv *env, jobject thiz) {
- // XX: Why do this separately instead of doing it in the register function?
- ALOGV("%s", __FUNCTION__);
-
- field fields_to_find[] = {
- { CAMERA_METADATA_CLASS_NAME, "mMetadataPtr", "J", &fields.metadata_ptr },
- };
-
- // Do this here instead of in register_native_methods,
- // since otherwise it will fail to find the fields.
- if (find_fields(env, fields_to_find, NELEM(fields_to_find)) < 0)
- return;
-
- env->FindClass(CAMERA_METADATA_CLASS_NAME);
-}
-
static jobject CameraMetadata_getAllVendorKeys(JNIEnv* env, jobject thiz, jclass keyType) {
// Get all vendor tags
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index b57f2362..a03d3c5 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -252,25 +252,26 @@
if (t_pri >= ANDROID_PRIORITY_BACKGROUND) {
// This task wants to stay at background
// update its cpuset so it doesn't only run on bg core(s)
-#ifdef ENABLE_CPUSETS
- int err = set_cpuset_policy(t_pid, sp);
- if (err != NO_ERROR) {
- signalExceptionForGroupError(env, -err, t_pid);
- break;
+ if (cpusets_enabled()) {
+ int err = set_cpuset_policy(t_pid, sp);
+ if (err != NO_ERROR) {
+ signalExceptionForGroupError(env, -err, t_pid);
+ break;
+ }
}
-#endif
continue;
}
}
int err;
-#ifdef ENABLE_CPUSETS
- // set both cpuset and cgroup for general threads
- err = set_cpuset_policy(t_pid, sp);
- if (err != NO_ERROR) {
- signalExceptionForGroupError(env, -err, t_pid);
- break;
+
+ if (cpusets_enabled()) {
+ // set both cpuset and cgroup for general threads
+ err = set_cpuset_policy(t_pid, sp);
+ if (err != NO_ERROR) {
+ signalExceptionForGroupError(env, -err, t_pid);
+ break;
+ }
}
-#endif
err = set_sched_policy(t_pid, sp);
if (err != NO_ERROR) {
@@ -291,7 +292,6 @@
return (int) sp;
}
-#ifdef ENABLE_CPUSETS
/** Sample CPUset list format:
* 0-3,4,6-8
*/
@@ -367,7 +367,6 @@
}
return;
}
-#endif
/**
@@ -376,22 +375,21 @@
* them in the passed in cpu_set_t
*/
void get_exclusive_cpuset_cores(SchedPolicy policy, cpu_set_t *cpu_set) {
-#ifdef ENABLE_CPUSETS
- int i;
- cpu_set_t tmp_set;
- get_cpuset_cores_for_policy(policy, cpu_set);
- for (i = 0; i < SP_CNT; i++) {
- if ((SchedPolicy) i == policy) continue;
- get_cpuset_cores_for_policy((SchedPolicy)i, &tmp_set);
- // First get cores exclusive to one set or the other
- CPU_XOR(&tmp_set, cpu_set, &tmp_set);
- // Then get the ones only in cpu_set
- CPU_AND(cpu_set, cpu_set, &tmp_set);
+ if (cpusets_enabled()) {
+ int i;
+ cpu_set_t tmp_set;
+ get_cpuset_cores_for_policy(policy, cpu_set);
+ for (i = 0; i < SP_CNT; i++) {
+ if ((SchedPolicy) i == policy) continue;
+ get_cpuset_cores_for_policy((SchedPolicy)i, &tmp_set);
+ // First get cores exclusive to one set or the other
+ CPU_XOR(&tmp_set, cpu_set, &tmp_set);
+ // Then get the ones only in cpu_set
+ CPU_AND(cpu_set, cpu_set, &tmp_set);
+ }
+ } else {
+ CPU_ZERO(cpu_set);
}
-#else
- (void) policy;
- CPU_ZERO(cpu_set);
-#endif
return;
}
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 4a9cd24..6192271 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -170,6 +170,7 @@
case HAL_PIXEL_FORMAT_RGBA_8888:
case HAL_PIXEL_FORMAT_RGBX_8888:
case HAL_PIXEL_FORMAT_RGBA_FP16:
+ case HAL_PIXEL_FORMAT_RGBA_1010102:
case HAL_PIXEL_FORMAT_RGB_888:
case HAL_PIXEL_FORMAT_RGB_565:
case HAL_PIXEL_FORMAT_Y8:
diff --git a/core/jni/android_view_TextureView.cpp b/core/jni/android_view_TextureView.cpp
index 613e040..18a1360 100644
--- a/core/jni/android_view_TextureView.cpp
+++ b/core/jni/android_view_TextureView.cpp
@@ -83,7 +83,7 @@
colorType = kN32_SkColorType;
alphaType = kOpaque_SkAlphaType;
break;
- case WINDOW_FORMAT_RGBA_FP16:
+ case AHARDWAREBUFFER_FORMAT_R16G16B16A16_SFLOAT:
colorType = kRGBA_F16_SkColorType;
alphaType = kPremul_SkAlphaType;
break;
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index d37f96a..37eae48a 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -43,7 +43,6 @@
#include <FrameInfo.h>
#include <FrameMetricsObserver.h>
#include <IContextFactory.h>
-#include <JankTracker.h>
#include <PropertyValuesAnimatorSet.h>
#include <RenderNode.h>
#include <renderthread/CanvasContext.h>
@@ -587,10 +586,13 @@
return atoi(prop) > 0 ? JNI_TRUE : JNI_FALSE;
}
+static void android_view_ThreadedRenderer_rotateProcessStatsBuffer(JNIEnv* env, jobject clazz) {
+ RenderProxy::rotateProcessStatsBuffer();
+}
+
static void android_view_ThreadedRenderer_setProcessStatsBuffer(JNIEnv* env, jobject clazz,
- jlong proxyPtr, jint fd) {
- RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- proxy->setProcessStatsBuffer(fd);
+ jint fd) {
+ RenderProxy::setProcessStatsBuffer(fd);
}
static jint android_view_ThreadedRenderer_getRenderThreadTid(JNIEnv* env, jobject clazz,
@@ -817,15 +819,6 @@
proxy->dumpProfileInfo(fd, dumpFlags);
}
-static void android_view_ThreadedRenderer_dumpProfileData(JNIEnv* env, jobject clazz,
- jbyteArray jdata, jobject javaFileDescriptor) {
- int fd = jniGetFDFromFileDescriptor(env, javaFileDescriptor);
- ScopedByteArrayRO buffer(env, jdata);
- if (buffer.get()) {
- JankTracker::dumpBuffer(buffer.get(), buffer.size(), fd);
- }
-}
-
static void android_view_ThreadedRenderer_addRenderNode(JNIEnv* env, jobject clazz,
jlong proxyPtr, jlong renderNodePtr, jboolean placeFront) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
@@ -910,7 +903,8 @@
static const JNINativeMethod gMethods[] = {
{ "nSupportsOpenGL", "()Z", (void*) android_view_ThreadedRenderer_supportsOpenGL },
- { "nSetProcessStatsBuffer", "(JI)V", (void*) android_view_ThreadedRenderer_setProcessStatsBuffer },
+ { "nRotateProcessStatsBuffer", "()V", (void*) android_view_ThreadedRenderer_rotateProcessStatsBuffer },
+ { "nSetProcessStatsBuffer", "(I)V", (void*) android_view_ThreadedRenderer_setProcessStatsBuffer },
{ "nGetRenderThreadTid", "(J)I", (void*) android_view_ThreadedRenderer_getRenderThreadTid },
{ "nCreateRootRenderNode", "()J", (void*) android_view_ThreadedRenderer_createRootRenderNode },
{ "nCreateProxy", "(ZJ)J", (void*) android_view_ThreadedRenderer_createProxy },
@@ -943,7 +937,6 @@
{ "nNotifyFramePending", "(J)V", (void*) android_view_ThreadedRenderer_notifyFramePending },
{ "nSerializeDisplayListTree", "(J)V", (void*) android_view_ThreadedRenderer_serializeDisplayListTree },
{ "nDumpProfileInfo", "(JLjava/io/FileDescriptor;I)V", (void*) android_view_ThreadedRenderer_dumpProfileInfo },
- { "nDumpProfileData", "([BLjava/io/FileDescriptor;)V", (void*) android_view_ThreadedRenderer_dumpProfileData },
{ "setupShadersDiskCache", "(Ljava/lang/String;)V",
(void*) android_view_ThreadedRenderer_setupShadersDiskCache },
{ "nAddRenderNode", "(JJZ)V", (void*) android_view_ThreadedRenderer_addRenderNode},
diff --git a/core/jni/include/android_runtime/android_view_Surface.h b/core/jni/include/android_runtime/android_view_Surface.h
index 07e29cb..3f1bdff 100644
--- a/core/jni/include/android_runtime/android_view_Surface.h
+++ b/core/jni/include/android_runtime/android_view_Surface.h
@@ -50,6 +50,7 @@
RAW_PRIVATE = 0x24,
RAW10 = 0x25,
RAW12 = 0x26,
+ RGBA_1010102 = 0x2b,
JPEG = 0x100,
DEPTH_POINT_CLOUD = 0x101,
YV12 = 0x32315659,
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index ba1d664..a2f07d9 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -21,6 +21,7 @@
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/service/appwidget.proto";
+import "frameworks/base/core/proto/android/service/graphicsstats.proto";
import "frameworks/base/core/proto/android/service/fingerprint.proto";
import "frameworks/base/core/proto/android/service/netstats.proto";
import "frameworks/base/core/proto/android/service/notification.proto";
@@ -57,4 +58,5 @@
android.providers.settings.SettingsServiceDumpProto settings = 3002;
android.service.appwidget.AppWidgetServiceDumpProto appwidget = 3003;
android.service.notification.NotificationServiceDumpProto notification = 3004;
+ android.service.GraphicsStatsServiceDumpProto graphicsstats = 3005;
}
diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto
new file mode 100644
index 0000000..6dbfe48
--- /dev/null
+++ b/core/proto/android/service/graphicsstats.proto
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+syntax = "proto3";
+
+package android.service;
+
+option java_multiple_files = true;
+option java_outer_classname = "GraphicsStatsServiceProto";
+
+message GraphicsStatsServiceDumpProto {
+ repeated GraphicsStatsProto stats = 1;
+}
+
+message GraphicsStatsProto {
+
+ // The package name of the app
+ string package_name = 1;
+
+ // The version code of the app
+ int32 version_code = 2;
+
+ // The start & end timestamps in UTC as
+ // milliseconds since January 1, 1970
+ // Compatible with java.util.Date#setTime()
+ int64 stats_start = 3;
+ int64 stats_end = 4;
+
+ // The aggregated statistics for the package
+ GraphicsStatsJankSummaryProto summary = 5;
+
+ // The frame time histogram for the package
+ repeated GraphicsStatsHistogramBucketProto histogram = 6;
+}
+
+message GraphicsStatsJankSummaryProto {
+ // Distinct frame count.
+ int32 total_frames = 1;
+
+ // Number of frames with slow render time. Frames are considered janky if
+ // they took more than a vsync interval (typically 16.667ms) to be rendered.
+ int32 janky_frames = 2;
+
+ // Number of "missed vsync" events.
+ int32 missed_vsync_count = 3;
+
+ // Number of "high input latency" events.
+ int32 high_input_latency_count = 4;
+
+ // Number of "slow UI thread" events.
+ int32 slow_ui_thread_count = 5;
+
+ // Number of "slow bitmap upload" events.
+ int32 slow_bitmap_upload_count = 6;
+
+ // Number of "slow draw" events.
+ int32 slow_draw_count = 7;
+}
+
+message GraphicsStatsHistogramBucketProto {
+ // Lower bound of render time in milliseconds.
+ int32 render_millis = 1;
+ // Number of frames in the bucket.
+ int32 frame_count = 2;
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 97fbfa5..9108f4a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -520,6 +520,7 @@
<!-- TODO: temporary broadcast used by AutoFillManagerServiceImpl; will be removed -->
<protected-broadcast android:name="com.android.internal.autofill.action.REQUEST_AUTOFILL" />
<protected-broadcast android:name="android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED" />
+ <protected-broadcast android:name="com.android.server.wm.ACTION_REVOKE_SYSTEM_ALERT_WINDOW_PERMISSION" />
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
@@ -3076,10 +3077,11 @@
<permission android:name="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
android:protectionLevel="signature" />
- <!-- Must be required by an {@link
+ <!-- @SystemApi Must be required by an {@link
android.service.notification.NotificationAssistantService} to ensure that only the system
can bind to it.
<p>Protection level: signature
+ @hide
-->
<permission android:name="android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE"
android:protectionLevel="signature" />
@@ -3107,6 +3109,13 @@
<permission android:name="android.permission.BIND_DREAM_SERVICE"
android:protectionLevel="signature" />
+ <!-- Must be required by an {@link android.app.usage.CacheQuotaService} to ensure that only the
+ system can bind to it.
+ @hide This is not a third-party API (intended for OEMs and system apps).
+ -->
+ <permission android:name="android.permission.BIND_CACHE_QUOTA_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows an application to call into a carrier setup flow. It is up to the
carrier setup application to enforce that this permission is required
@hide This is not a third-party API (intended for OEMs and system apps). -->
diff --git a/core/res/res/drawable-nodpi/alert_window_layer.xml b/core/res/res/drawable-nodpi/alert_window_layer.xml
new file mode 100644
index 0000000..f9b38c8
--- /dev/null
+++ b/core/res/res/drawable-nodpi/alert_window_layer.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+<path
+ android:fillColor="#FF000000"
+ android:pathData="M11.99,18.54l-7.37,-5.73L3,14.07l9,7 9,-7 -1.63,-1.27 -7.38,5.74zM12,16l7.36,-5.73L21,9l-9,-7 -9,7 1.63,1.27L12,16z"/>
+</vector>
diff --git a/core/res/res/drawable/scrollbar_handle_material.xml b/core/res/res/drawable/scrollbar_handle_material.xml
index 33efbba..f020112 100644
--- a/core/res/res/drawable/scrollbar_handle_material.xml
+++ b/core/res/res/drawable/scrollbar_handle_material.xml
@@ -19,7 +19,4 @@
android:shape="rectangle">
<solid
android:color="#84ffffff" />
- <size
- android:width="4dp"
- android:height="4dp" />
</shape>
diff --git a/core/res/res/layout/autofill_dataset_picker.xml b/core/res/res/layout/autofill_dataset_picker.xml
new file mode 100644
index 0000000..40cce7b
--- /dev/null
+++ b/core/res/res/layout/autofill_dataset_picker.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<ListView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/list"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:divider="?android:attr/listDivider"
+ android:background="#ffffffff">
+</ListView>
diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index c4e8e9c..40c9941 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -45,7 +45,7 @@
android:textColor="?attr/colorAccent"
android:gravity="center_vertical"
android:layout_alignParentTop="true"
- android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true"
android:singleLine="true" />
<TextView
@@ -59,7 +59,7 @@
android:paddingEnd="?attr/dialogPreferredPadding"
android:paddingTop="8dp"
android:layout_below="@id/profile_button"
- android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true"
android:paddingBottom="8dp" />
</RelativeLayout>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 4d5e45b..8031f19 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2287,6 +2287,17 @@
<enum name="auto" value="0x00000010" />
</attr>
+ <!-- Controls the auto-fill behavior for this view -->
+ <attr name="autoFillMode">
+ <!-- Inherit the behavior from the parent. If there is no parent it is auto. -->
+ <enum name="inherit" value="0" />
+ <!-- Allows this view to automatically trigger an auto-fill request when it get focus.
+ -->
+ <enum name="auto" value="1" />
+ <!-- The user has to manually force an auto-fill request for this view. -->
+ <enum name="manual" value="2" />
+ </attr>
+
<!-- Boolean that controls whether a view can take focus while in touch mode.
If this is true for a view, that view can gain focus when clicked on, and can keep
focus if another view is clicked on that doesn't have this attribute set to true. -->
@@ -5844,8 +5855,8 @@
<attr name="color" />
</declare-styleable>
- <!-- Drawable used to draw masked icons with foreground and background layers. -->
- <declare-styleable name="MaskableIconDrawableLayer">
+ <!-- Drawable used to draw adaptive icons with foreground and background layers. -->
+ <declare-styleable name="AdaptiveIconDrawableLayer">
<!-- The drawable to use for the layer. -->
<attr name="drawable" />
</declare-styleable>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index fcb0e08..d5ffdd0 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1873,6 +1873,10 @@
<!-- Amount of time in ms the user needs to press the relevant key to bring up the global actions dialog -->
<integer name="config_globalActionsKeyTimeout">500</integer>
+ <!-- Default width of a vertical scrollbar and height of a horizontal scrollbar.
+ Takes effect only if the scrollbar drawables have no intrinsic size. -->
+ <dimen name="config_scrollbarSize">4dp</dimen>
+
<!-- Distance that should be scrolled in response to a {@link MotionEvent#ACTION_SCROLL event}
with an axis value of 1. -->
<dimen name="config_scrollFactor">64dp</dimen>
@@ -2743,7 +2747,7 @@
<!-- Component name of the default cell broadcast receiver -->
<string name="config_defaultCellBroadcastReceiverComponent" translatable="false">com.android.cellbroadcastreceiver/.PrivilegedCellBroadcastReceiver</string>
- <!-- Specifies the path that is used by MaskableIconDrawable class to crop launcher icons. -->
+ <!-- Specifies the path that is used by AdaptiveIconDrawable class to crop launcher icons. -->
<string name="config_icon_mask" translatable="false">"M50,0L100,0 100,100 0,100 0,0z"</string>
<!-- The component name, flattened to a string, for the default accessibility service to be
@@ -2751,4 +2755,13 @@
without explicit consent of the user. If no accessibility service with the specified name
exists on the device, the accessibility shortcut will be disabled by default. -->
<string name="config_defaultAccessibilityService" translatable="false"></string>
+
+ <!-- Flag indicates that whether escrow token API is enabled for TrustAgent -->
+ <!-- Warning: This API can be dangerous when not implemented properly. In particular,
+ escrow token must NOT be retrievable from device storage. In other words, either
+ escrow token is not stored on device or its ciphertext is stored on device while
+ the decryption key is not. Before enabling this feature, please ensure you've read
+ and followed the pertinent sections of the escrow tokens section of the CDD <link>-->
+ <!-- TODO(b/35230407) complete the link field -->
+ <bool name="config_allowEscrowTokenForTrustAgent">false</bool>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 78489eb..78549b5 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2796,6 +2796,7 @@
<public name="numericModifiers" />
<public name="fontProviderAuthority" />
<public name="fontProviderQuery" />
+ <public name="autoFillMode" />
</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 8faa76c..f6da660 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3116,6 +3116,21 @@
<string name="fast_scroll_alphabet">\u0020ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
<string name="fast_scroll_numeric_alphabet">\u00200123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
+ <!-- Alert windows notification strings -->
+ <skip />
+ <!-- Name of notification channel the system post notification to inform the use about apps
+ that are drawing ui on-top of other apps (alert-windows) [CHAR LIMIT=NONE] -->
+ <string name="alert_windows_notification_channel_name"><xliff:g id="name" example="Google Maps">%s</xliff:g> draw over other apps</string>
+ <!-- Notification title when an application is displaying ui on-top of other apps
+ [CHAR LIMIT=30] -->
+ <string name="alert_windows_notification_title"><xliff:g id="name" example="Google Maps">%s</xliff:g> app displaying on top.</string>
+ <!-- Notification body when an application is displaying ui on-top of other apps
+ [CHAR LIMIT=NONE] -->
+ <string name="alert_windows_notification_message">Parts of this app may remain visible at all times. If this feature isn\'t working correctly, turn it off.</string>
+ <!-- Notification action to turn-off app displaying on-top of other apps. [CHAR LIMIT=20] -->
+ <string name="alert_windows_notification_turn_off_action">TURN OFF</string>
+
+
<!-- External media notification strings -->
<skip />
@@ -3512,11 +3527,6 @@
<!-- Description of the unlock handle in the Slide unlock screen for tablets. [CHAR LIMIT=NONE] -->
<string name="description_target_unlock_tablet">Swipe to unlock.</string>
- <!-- Announce that a headset is required to hear keyboard keys while typing a password. [CHAR LIMIT=NONE] -->
- <string name="keyboard_headset_required_to_hear_password">Plug in a headset to hear password keys spoken.</string>
- <!-- The value of a keyboard key announced when accessibility is enabled and no headsed is used. [CHAR LIMIT=NONE] -->
- <string name="keyboard_password_character_no_headset">Dot.</string>
-
<!-- Content description for the action bar "home" affordance. [CHAR LIMIT=NONE] -->
<string name="action_bar_home_description">Navigate home</string>
<!-- Content description for the action bar "up" affordance. [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0d63a1e..db0298a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -428,6 +428,7 @@
<java-symbol type="dimen" name="config_viewConfigurationTouchSlop" />
<java-symbol type="dimen" name="config_viewMinFlingVelocity" />
<java-symbol type="dimen" name="config_viewMaxFlingVelocity" />
+ <java-symbol type="dimen" name="config_scrollbarSize" />
<java-symbol type="dimen" name="config_scrollFactor" />
<java-symbol type="dimen" name="default_app_widget_padding_bottom" />
<java-symbol type="dimen" name="default_app_widget_padding_left" />
@@ -707,8 +708,6 @@
<java-symbol type="string" name="js_dialog_before_unload" />
<java-symbol type="string" name="js_dialog_title" />
<java-symbol type="string" name="js_dialog_title_default" />
- <java-symbol type="string" name="keyboard_headset_required_to_hear_password" />
- <java-symbol type="string" name="keyboard_password_character_no_headset" />
<java-symbol type="string" name="keyboardview_keycode_alt" />
<java-symbol type="string" name="keyboardview_keycode_cancel" />
<java-symbol type="string" name="keyboardview_keycode_delete" />
@@ -2840,6 +2839,7 @@
<java-symbol type="dimen" name="autofill_fill_item_height" />
<java-symbol type="dimen" name="autofill_fill_min_margin" />
<java-symbol type="layout" name="autofill_save"/>
+ <java-symbol type="layout" name="autofill_dataset_picker"/>
<java-symbol type="id" name="autofill_save_title" />
<java-symbol type="id" name="autofill_save_no" />
<java-symbol type="id" name="autofill_save_yes" />
@@ -2848,6 +2848,9 @@
<java-symbol type="string" name="capability_title_canCaptureFingerprintGestures" />
<java-symbol type="string" name="capability_desc_canCaptureFingerprintGestures" />
+ <!-- android.service.trust -->
+ <java-symbol type="bool" name="config_allowEscrowTokenForTrustAgent"/>
+
<!-- Time picker -->
<java-symbol type="id" name="toggle_mode"/>
<java-symbol type="id" name="input_mode"/>
@@ -2867,4 +2870,12 @@
<!-- resolver activity -->
<java-symbol type="drawable" name="resolver_icon_placeholder" />
+
+ <!-- Alert windows notification -->
+ <java-symbol type="string" name="alert_windows_notification_channel_name" />
+ <java-symbol type="string" name="alert_windows_notification_title" />
+ <java-symbol type="string" name="alert_windows_notification_message" />
+ <java-symbol type="string" name="alert_windows_notification_turn_off_action" />
+ <java-symbol type="drawable" name="alert_window_layer" />
+
</resources>
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index b063baf..400fb47 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -214,7 +214,7 @@
<!-- Scrollbar attributes -->
<item name="scrollbarFadeDuration">250</item>
<item name="scrollbarDefaultDelayBeforeFade">400</item>
- <item name="scrollbarSize">10dp</item>
+ <item name="scrollbarSize">@dimen/config_scrollbarSize</item>
<item name="scrollbarThumbHorizontal">@drawable/scrollbar_handle_material</item>
<item name="scrollbarThumbVertical">@drawable/config_scrollbarThumbVertical</item>
<item name="scrollbarTrackHorizontal">@null</item>
@@ -583,7 +583,7 @@
<!-- Scrollbar attributes -->
<item name="scrollbarFadeDuration">250</item>
<item name="scrollbarDefaultDelayBeforeFade">400</item>
- <item name="scrollbarSize">10dp</item>
+ <item name="scrollbarSize">@dimen/config_scrollbarSize</item>
<item name="scrollbarThumbHorizontal">@drawable/scrollbar_handle_material</item>
<item name="scrollbarThumbVertical">@drawable/config_scrollbarThumbVertical</item>
<item name="scrollbarTrackHorizontal">@null</item>
diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk
index 48b78d4..ef09c35 100644
--- a/core/tests/coretests/Android.mk
+++ b/core/tests/coretests/Android.mk
@@ -21,6 +21,7 @@
$(call all-java-files-under, DisabledTestApp/src) \
$(call all-java-files-under, EnabledTestApp/src)
+LOCAL_DX_FLAGS := --core-library
LOCAL_AAPT_FLAGS = -0 dat -0 gld -c fa
LOCAL_STATIC_JAVA_LIBRARIES := \
core-tests-support \
diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
index 4e70bb1..b2ff927 100644
--- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
+++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
@@ -197,7 +197,8 @@
final Configuration overrideConfig = new Configuration();
overrideConfig.orientation = Configuration.ORIENTATION_LANDSCAPE;
- mResourcesManager.updateResourcesForActivity(activity1, overrideConfig);
+ mResourcesManager.updateResourcesForActivity(activity1, overrideConfig,
+ Display.DEFAULT_DISPLAY, false /* movedToDifferentDisplay */);
assertSame(resources1, theme.getResources());
// Make sure we can still access the data.
@@ -246,7 +247,8 @@
// Now update the Activity base override, and both resources should update.
config1.orientation = Configuration.ORIENTATION_LANDSCAPE;
- mResourcesManager.updateResourcesForActivity(activity1, config1);
+ mResourcesManager.updateResourcesForActivity(activity1, config1, Display.DEFAULT_DISPLAY,
+ false /* movedToDifferentDisplay */);
expectedConfig1.orientation = Configuration.ORIENTATION_LANDSCAPE;
assertEquals(expectedConfig1, resources1.getConfiguration());
diff --git a/core/tests/coretests/src/android/net/ScoredNetworkTest.java b/core/tests/coretests/src/android/net/ScoredNetworkTest.java
index e818c56..dc17da2 100644
--- a/core/tests/coretests/src/android/net/ScoredNetworkTest.java
+++ b/core/tests/coretests/src/android/net/ScoredNetworkTest.java
@@ -18,13 +18,10 @@
import static org.junit.Assert.*;
-import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
-import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -170,41 +167,41 @@
@Test
public void calculateBadgeShouldReturnNoBadgeWhenNoAttributesBundle() {
ScoredNetwork network = new ScoredNetwork(KEY, CURVE);
- assertEquals(ScoredNetwork.BADGING_NONE, network.calculateBadge(TEST_RSSI));
+ assertEquals(NetworkBadging.BADGING_NONE, network.calculateBadge(TEST_RSSI));
}
@Test
public void calculateBadgeShouldReturnNoBadgeWhenNoBadgingCurveInBundle() {
ScoredNetwork network = new ScoredNetwork(KEY, CURVE, false /* meteredHint */, ATTRIBUTES);
- assertEquals(ScoredNetwork.BADGING_NONE, network.calculateBadge(TEST_RSSI));
+ assertEquals(NetworkBadging.BADGING_NONE, network.calculateBadge(TEST_RSSI));
}
@Test
public void calculateBadgeShouldReturn4kBadge() {
ScoredNetwork network =
- buildScoredNetworkWithGivenBadgeForTestRssi(ScoredNetwork.BADGING_4K);
- assertEquals(ScoredNetwork.BADGING_4K, network.calculateBadge(TEST_RSSI));
+ buildScoredNetworkWithGivenBadgeForTestRssi(NetworkBadging.BADGING_4K);
+ assertEquals(NetworkBadging.BADGING_4K, network.calculateBadge(TEST_RSSI));
}
@Test
public void calculateBadgeShouldReturnHdBadge() {
ScoredNetwork network =
- buildScoredNetworkWithGivenBadgeForTestRssi(ScoredNetwork.BADGING_HD);
- assertEquals(ScoredNetwork.BADGING_HD, network.calculateBadge(TEST_RSSI));
+ buildScoredNetworkWithGivenBadgeForTestRssi(NetworkBadging.BADGING_HD);
+ assertEquals(NetworkBadging.BADGING_HD, network.calculateBadge(TEST_RSSI));
}
@Test
public void calculateBadgeShouldReturnSdBadge() {
ScoredNetwork network =
- buildScoredNetworkWithGivenBadgeForTestRssi(ScoredNetwork.BADGING_SD);
- assertEquals(ScoredNetwork.BADGING_SD, network.calculateBadge(TEST_RSSI));
+ buildScoredNetworkWithGivenBadgeForTestRssi(NetworkBadging.BADGING_SD);
+ assertEquals(NetworkBadging.BADGING_SD, network.calculateBadge(TEST_RSSI));
}
@Test
public void calculateBadgeShouldReturnNoBadge() {
ScoredNetwork network =
- buildScoredNetworkWithGivenBadgeForTestRssi(ScoredNetwork.BADGING_NONE);
- assertEquals(ScoredNetwork.BADGING_NONE, network.calculateBadge(TEST_RSSI));
+ buildScoredNetworkWithGivenBadgeForTestRssi(NetworkBadging.BADGING_NONE);
+ assertEquals(NetworkBadging.BADGING_NONE, network.calculateBadge(TEST_RSSI));
}
private ScoredNetwork buildScoredNetworkWithGivenBadgeForTestRssi(int badge) {
diff --git a/core/tests/coretests/src/android/provider/SettingsTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
similarity index 98%
rename from core/tests/coretests/src/android/provider/SettingsTest.java
rename to core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 019f837..48435bd 100644
--- a/core/tests/coretests/src/android/provider/SettingsTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -24,8 +24,7 @@
import static java.lang.reflect.Modifier.isPublic;
import static java.lang.reflect.Modifier.isStatic;
-import android.annotation.TargetApi;
-
+import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -36,11 +35,11 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-/** Unit test for Settings. */
-@TargetApi(25)
+/** Tests that ensure appropriate settings are backed up. */
@RunWith(AndroidJUnit4.class)
+@Presubmit
@SmallTest
-public class SettingsTest {
+public class SettingsBackupTest {
/**
* The following blacklists contain settings that should *not* be backed up and restored to
@@ -64,7 +63,6 @@
Settings.System.NOTIFICATION_SOUND_CACHE, // internal cache
Settings.System.POINTER_LOCATION, // backup candidate?
Settings.System.RINGTONE_CACHE, // internal cache
- Settings.System.SCREEN_BRIGHTNESS_FOR_VR, // bug?
Settings.System.SETUP_WIZARD_HAS_RUN, // Only used by SuW
Settings.System.SHOW_GTALK_SERVICE_STATUS, // candidate for backup?
Settings.System.SHOW_TOUCHES, // bug?
@@ -246,6 +244,7 @@
Settings.Global.NETWORK_AVOID_BAD_WIFI,
Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE,
Settings.Global.NETWORK_PREFERENCE,
+ Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE,
Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS,
Settings.Global.NETWORK_SCORER_APP,
Settings.Global.NETWORK_SCORING_PROVISIONED,
@@ -306,6 +305,8 @@
Settings.Global.STORAGE_BENCHMARK_INTERVAL,
Settings.Global.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
Settings.Global.SYS_FREE_STORAGE_LOG_INTERVAL,
+ Settings.Global.SYS_STORAGE_CACHE_MAX_BYTES,
+ Settings.Global.SYS_STORAGE_CACHE_PERCENTAGE,
Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES,
Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES,
Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE,
@@ -323,6 +324,7 @@
Settings.Global.USE_GOOGLE_MAIL,
Settings.Global.VT_IMS_ENABLED,
Settings.Global.WAIT_FOR_DEBUGGER,
+ Settings.Global.WAIT_FOR_NETWORK_TIMEOUT_MS,
Settings.Global.WARNING_TEMPERATURE,
Settings.Global.WEBVIEW_DATA_REDUCTION_PROXY_KEY,
Settings.Global.WEBVIEW_FALLBACK_LOGIC_ENABLED,
@@ -383,7 +385,6 @@
Settings.Secure.ASSIST_STRUCTURE_ENABLED,
Settings.Secure.AUTO_FILL_SERVICE,
Settings.Secure.AUTOMATIC_STORAGE_MANAGER_BYTES_CLEARED,
- Settings.Secure.AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN,
Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED,
Settings.Secure.AUTOMATIC_STORAGE_MANAGER_LAST_RUN,
Settings.Secure.BACKUP_AUTO_RESTORE,
@@ -422,6 +423,7 @@
Settings.Secure.MANAGED_PROFILE_CONTACT_REMOTE_SEARCH,
Settings.Secure.MULTI_PRESS_TIMEOUT,
Settings.Secure.NFC_PAYMENT_FOREGROUND,
+ Settings.Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME,
Settings.Secure.PACKAGE_VERIFIER_STATE,
Settings.Secure.PACKAGE_VERIFIER_USER_CONSENT,
Settings.Secure.PARENTAL_CONTROL_LAST_UPDATE,
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
index 0bdf7ca..e152163 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
@@ -48,9 +48,11 @@
bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_RECEIVER);
bi.noteStartSensorLocked(UID, SENSOR_ID);
+ bi.noteStartSensorLocked(UID, SENSOR_ID);
clocks.realtime = 400;
clocks.uptime = 400;
bi.noteStopSensorLocked(UID, SENSOR_ID);
+ bi.noteStopSensorLocked(UID, SENSOR_ID);
BatteryStats.Timer sensorTimer = bi.getUidStats().get(UID).getSensorStats()
.get(SENSOR_ID).getSensorTime();
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk
index 836ede6..14b032e 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk
@@ -31,9 +31,28 @@
LOCAL_JAVACFLAGS := -nowarn
+mainDexList:= \
+ $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
+
+LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex
LOCAL_JACK_FLAGS := -D jack.dex.output.policy=minimal-multidex -D jack.preprocessor=true\
-D jack.preprocessor.file=$(LOCAL_PATH)/test.jpp -D jack.dex.output.multidex.legacy=true
+#################################
+include $(BUILD_SYSTEM)/configure_local_jack.mk
+#################################
+
+ifdef LOCAL_JACK_ENABLED
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/test.jpp
+endif
include $(BUILD_PACKAGE)
+
+ifndef LOCAL_JACK_ENABLED
+$(mainDexList): $(full_classes_proguard_jar) | $(MAINDEXCLASSES)
+ $(hide) mkdir -p $(dir $@)
+ $(MAINDEXCLASSES) $< 1>$@
+ echo "com/android/multidexlegacyandexception/Test.class" >> $@
+
+$(built_dex_intermediate): $(mainDexList)
+endif
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.mk
index 2915914..208eceb 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.mk
@@ -29,13 +29,32 @@
LOCAL_DEX_PREOPT := false
+mainDexList:= \
+ $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
+
+LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex
LOCAL_JACK_FLAGS := -D jack.dex.output.policy=minimal-multidex -D jack.preprocessor=true\
-D jack.preprocessor.file=$(LOCAL_PATH)/test.jpp -D jack.dex.output.multidex.legacy=true
+#################################
+include $(BUILD_SYSTEM)/configure_local_jack.mk
+#################################
+
+ifdef LOCAL_JACK_ENABLED
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/test.jpp
+endif
include $(BUILD_PACKAGE)
+ifndef LOCAL_JACK_ENABLED
+$(mainDexList): $(full_classes_proguard_jar) | $(MAINDEXCLASSES)
+ $(hide) mkdir -p $(dir $@)
+ $(MAINDEXCLASSES) $< 1>$@
+ echo "com/android/multidexlegacytestapp/Test.class" >> $@
+
+$(built_dex_intermediate): $(mainDexList)
+endif
+
## The application with a full main dex
include $(CLEAR_VARS)
@@ -51,9 +70,28 @@
LOCAL_DEX_PREOPT := false
+mainDexList2:= \
+ $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
+
+LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList2)
LOCAL_JACK_FLAGS := -D jack.dex.output.policy=multidex -D jack.preprocessor=true\
-D jack.preprocessor.file=$(LOCAL_PATH)/test.jpp -D jack.dex.output.multidex.legacy=true
+#################################
+include $(BUILD_SYSTEM)/configure_local_jack.mk
+#################################
+
+ifdef LOCAL_JACK_ENABLED
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/test.jpp
+endif
include $(BUILD_PACKAGE)
+
+ifndef LOCAL_JACK_ENABLED
+$(mainDexList2): $(full_classes_proguard_jar) | $(MAINDEXCLASSES)
+ $(hide) mkdir -p $(dir $@)
+ $(MAINDEXCLASSES) $< 1>$@
+ echo "com/android/multidexlegacytestapp/Test.class" >> $@
+
+$(built_dex_intermediate): $(mainDexList2)
+endif
\ No newline at end of file
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk
index 2732372..99bcd6c 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk
@@ -26,8 +26,20 @@
LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex
+mainDexList:= \
+ $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
+
+LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex
LOCAL_JACK_FLAGS := -D jack.dex.output.policy=minimal-multidex -D jack.dex.output.multidex.legacy=true
LOCAL_DEX_PREOPT := false
include $(BUILD_PACKAGE)
+
+ifndef LOCAL_JACK_ENABLED
+$(mainDexList): $(full_classes_proguard_jar) | $(MAINDEXCLASSES)
+ $(hide) mkdir -p $(dir $@)
+ $(MAINDEXCLASSES) $< 1>$@
+
+$(built_dex_intermediate): $(mainDexList)
+endif
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/Android.mk
index b4a666f..1c7d807 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/Android.mk
@@ -28,9 +28,28 @@
LOCAL_DEX_PREOPT := false
+mainDexList:= \
+ $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
+
+LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex
LOCAL_JACK_FLAGS := -D jack.dex.output.policy=minimal-multidex -D jack.preprocessor=true\
-D jack.preprocessor.file=$(LOCAL_PATH)/test.jpp -D jack.dex.output.multidex.legacy=true
+#################################
+include $(BUILD_SYSTEM)/configure_local_jack.mk
+#################################
+
+ifdef LOCAL_JACK_ENABLED
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/test.jpp
+endif
include $(BUILD_PACKAGE)
+
+ifndef LOCAL_JACK_ENABLED
+$(mainDexList): $(full_classes_proguard_jar) | $(MAINDEXCLASSES)
+ $(hide) mkdir -p $(dir $@)
+ $(MAINDEXCLASSES) $< 1>$@
+ echo "com/android/framework/multidexlegacyversionedtestapp/MultiDexUpdateTest.class" >> $@
+
+$(built_dex_intermediate): $(mainDexList)
+endif
\ No newline at end of file
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/Android.mk
index f38bd4f..b77cf31 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/Android.mk
@@ -28,9 +28,28 @@
LOCAL_DEX_PREOPT := false
+mainDexList:= \
+ $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
+
+LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex
LOCAL_JACK_FLAGS := -D jack.dex.output.policy=minimal-multidex -D jack.preprocessor=true\
-D jack.preprocessor.file=$(LOCAL_PATH)/test.jpp -D jack.dex.output.multidex.legacy=true
+#################################
+include $(BUILD_SYSTEM)/configure_local_jack.mk
+#################################
+
+ifdef LOCAL_JACK_ENABLED
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/test.jpp
+endif
include $(BUILD_PACKAGE)
+
+ifndef LOCAL_JACK_ENABLED
+$(mainDexList): $(full_classes_proguard_jar) | $(MAINDEXCLASSES)
+ $(hide) mkdir -p $(dir $@)
+ $(MAINDEXCLASSES) $< 1>$@
+ echo "com/android/framework/multidexlegacyversionedtestapp/MultiDexUpdateTest.class" >> $@
+
+$(built_dex_intermediate): $(mainDexList)
+endif
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/Android.mk
index 5bc2c95..3631626 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/Android.mk
@@ -26,11 +26,31 @@
LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex
+mainDexList:= \
+ $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
+
LOCAL_DEX_PREOPT := false
+LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex
LOCAL_JACK_FLAGS := -D jack.dex.output.policy=minimal-multidex -D jack.preprocessor=true\
-D jack.preprocessor.file=$(LOCAL_PATH)/test.jpp -D jack.dex.output.multidex.legacy=true
+#################################
+include $(BUILD_SYSTEM)/configure_local_jack.mk
+#################################
+
+ifdef LOCAL_JACK_ENABLED
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/test.jpp
+endif
include $(BUILD_PACKAGE)
+
+ifndef LOCAL_JACK_ENABLED
+$(mainDexList): $(full_classes_proguard_jar) | $(MAINDEXCLASSES)
+ $(hide) mkdir -p $(dir $@)
+ $(MAINDEXCLASSES) $< 1>$@
+ echo "com/android/framework/multidexlegacyversionedtestapp/MultiDexUpdateTest.class" >> $@
+
+$(built_dex_intermediate): $(mainDexList)
+endif
+
diff --git a/core/tests/systemproperties/Android.mk b/core/tests/systemproperties/Android.mk
index e16c367..4c2e224 100644
--- a/core/tests/systemproperties/Android.mk
+++ b/core/tests/systemproperties/Android.mk
@@ -8,6 +8,7 @@
LOCAL_SRC_FILES := \
$(call all-java-files-under, src)
+LOCAL_DX_FLAGS := --core-library
LOCAL_STATIC_JAVA_LIBRARIES := android-common frameworks-core-util-lib
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_PACKAGE_NAME := FrameworksCoreSystemPropertiesTests
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 039ab1f..5226fe5 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -264,6 +264,7 @@
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
<permission name="android.permission.MANAGE_ACTIVITY_STACKS"/>
<permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
+ <permission name="android.permission.MANAGE_USB"/>
<permission name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"/>
<permission name="android.permission.MODIFY_PHONE_STATE"/>
<permission name="android.permission.MOUNT_FORMAT_FILESYSTEMS"/>
diff --git a/graphics/java/android/graphics/PixelFormat.java b/graphics/java/android/graphics/PixelFormat.java
index c3a9443..f93886d 100644
--- a/graphics/java/android/graphics/PixelFormat.java
+++ b/graphics/java/android/graphics/PixelFormat.java
@@ -29,65 +29,66 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef({RGBA_8888, RGBX_8888, RGBA_F16, RGB_888, RGB_565})
- public @interface Format { };
+ @IntDef({RGBA_8888, RGBX_8888, RGBA_F16, RGBA_1010102, RGB_888, RGB_565})
+ public @interface Format { }
// NOTE: these constants must match the values from graphics/common/x.x/types.hal
- public static final int UNKNOWN = 0;
+ public static final int UNKNOWN = 0;
/** System chooses a format that supports translucency (many alpha bits) */
- public static final int TRANSLUCENT = -3;
+ public static final int TRANSLUCENT = -3;
/**
* System chooses a format that supports transparency
* (at least 1 alpha bit)
*/
- public static final int TRANSPARENT = -2;
+ public static final int TRANSPARENT = -2;
/** System chooses an opaque format (no alpha bits required) */
- public static final int OPAQUE = -1;
+ public static final int OPAQUE = -1;
- public static final int RGBA_8888 = 1;
- public static final int RGBX_8888 = 2;
- public static final int RGB_888 = 3;
- public static final int RGB_565 = 4;
+ public static final int RGBA_8888 = 1;
+ public static final int RGBX_8888 = 2;
+ public static final int RGB_888 = 3;
+ public static final int RGB_565 = 4;
@Deprecated
- public static final int RGBA_5551 = 6;
+ public static final int RGBA_5551 = 6;
@Deprecated
- public static final int RGBA_4444 = 7;
+ public static final int RGBA_4444 = 7;
@Deprecated
- public static final int A_8 = 8;
+ public static final int A_8 = 8;
@Deprecated
- public static final int L_8 = 9;
+ public static final int L_8 = 9;
@Deprecated
- public static final int LA_88 = 0xA;
+ public static final int LA_88 = 0xA;
@Deprecated
- public static final int RGB_332 = 0xB;
+ public static final int RGB_332 = 0xB;
/**
* @deprecated use {@link android.graphics.ImageFormat#NV16
* ImageFormat.NV16} instead.
*/
@Deprecated
- public static final int YCbCr_422_SP= 0x10;
+ public static final int YCbCr_422_SP = 0x10;
/**
* @deprecated use {@link android.graphics.ImageFormat#NV21
* ImageFormat.NV21} instead.
*/
@Deprecated
- public static final int YCbCr_420_SP= 0x11;
+ public static final int YCbCr_420_SP = 0x11;
/**
* @deprecated use {@link android.graphics.ImageFormat#YUY2
* ImageFormat.YUY2} instead.
*/
@Deprecated
- public static final int YCbCr_422_I = 0x14;
+ public static final int YCbCr_422_I = 0x14;
- public static final int RGBA_F16 = 0x16;
+ public static final int RGBA_F16 = 0x16;
+ public static final int RGBA_1010102 = 0x2B;
/**
* @deprecated use {@link android.graphics.ImageFormat#JPEG
@@ -103,6 +104,7 @@
switch (format) {
case RGBA_8888:
case RGBX_8888:
+ case RGBA_1010102:
info.bitsPerPixel = 32;
info.bytesPerPixel = 4;
break;
@@ -149,6 +151,7 @@
case PixelFormat.RGBA_5551:
case PixelFormat.RGBA_8888:
case PixelFormat.RGBA_F16:
+ case PixelFormat.RGBA_1010102:
case PixelFormat.TRANSLUCENT:
case PixelFormat.TRANSPARENT:
return true;
@@ -176,6 +179,7 @@
case RGB_888:
case RGB_565:
case RGBA_F16:
+ case RGBA_1010102:
return true;
}
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index efb46b90..90bdd81 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -403,11 +403,4 @@
private native int nativeGetQueuedCount();
private native void nativeRelease();
private native boolean nativeIsReleased();
-
- /*
- * We use a class initializer to allow the native code to cache some
- * field offsets.
- */
- private static native void nativeClassInit();
- static { nativeClassInit(); }
}
diff --git a/graphics/java/android/graphics/drawable/MaskableIconDrawable.java b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
similarity index 97%
rename from graphics/java/android/graphics/drawable/MaskableIconDrawable.java
rename to graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
index 472b229..9896a88 100644
--- a/graphics/java/android/graphics/drawable/MaskableIconDrawable.java
+++ b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
@@ -29,7 +29,6 @@
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Outline;
@@ -56,10 +55,10 @@
*
* <p>The layers are clipped when rendering using the mask path defined in the device configuration.
*
- * <p>This class can also be created via XML inflation using <code><maskable-icon></code> tag
+ * <p>This class can also be created via XML inflation using <code><adaptive-icon></code> tag
* in addition to dynamic creation.
*/
-public class MaskableIconDrawable extends Drawable implements Drawable.Callback {
+public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback {
/**
* Mask path is defined inside device configuration in following dimension: [100 x 100]
@@ -122,7 +121,7 @@
/**
* Constructor used for xml inflation.
*/
- MaskableIconDrawable() {
+ AdaptiveIconDrawable() {
this((LayerState) null, null);
}
@@ -130,7 +129,7 @@
* The one constructor to rule them all. This is called by all public
* constructors to set the state and initialize local properties.
*/
- MaskableIconDrawable(@Nullable LayerState state, @Nullable Resources res) {
+ AdaptiveIconDrawable(@Nullable LayerState state, @Nullable Resources res) {
mLayerState = createConstantState(state, res);
if (sMask == null) {
@@ -163,7 +162,7 @@
* @param foregroundDrawable drawable that should be rendered in the foreground
* @hide
*/
- public MaskableIconDrawable(Drawable backgroundDrawable,
+ public AdaptiveIconDrawable(Drawable backgroundDrawable,
Drawable foregroundDrawable) {
this((LayerState)null, null);
if (backgroundDrawable != null) {
@@ -367,7 +366,7 @@
if (layer.mThemeAttrs != null) {
final TypedArray a = t.resolveAttributes(
- layer.mThemeAttrs, R.styleable.MaskableIconDrawableLayer);
+ layer.mThemeAttrs, R.styleable.AdaptiveIconDrawableLayer);
updateLayerFromTypedArray(layer, a);
a.recycle();
}
@@ -414,7 +413,7 @@
final ChildDrawable layer = new ChildDrawable(state.mDensity);
final TypedArray a = obtainAttributes(r, theme, attrs,
- R.styleable.MaskableIconDrawableLayer);
+ R.styleable.AdaptiveIconDrawableLayer);
updateLayerFromTypedArray(layer, a);
a.recycle();
@@ -450,7 +449,7 @@
// Extract the theme attributes, if any.
layer.mThemeAttrs = a.extractThemeAttrs();
- Drawable dr = a.getDrawable(R.styleable.MaskableIconDrawableLayer_drawable);
+ Drawable dr = a.getDrawable(R.styleable.AdaptiveIconDrawableLayer_drawable);
if (dr != null) {
if (layer.mDrawable != null) {
// It's possible that a drawable was already set, in which case
@@ -841,7 +840,7 @@
mDensity = density;
}
- ChildDrawable(@NonNull ChildDrawable orig, @NonNull MaskableIconDrawable owner,
+ ChildDrawable(@NonNull ChildDrawable orig, @NonNull AdaptiveIconDrawable owner,
@Nullable Resources res) {
final Drawable dr = orig.mDrawable;
@@ -899,7 +898,7 @@
private boolean mIsStateful;
private boolean mAutoMirrored = false;
- LayerState(@Nullable LayerState orig, @NonNull MaskableIconDrawable owner,
+ LayerState(@Nullable LayerState orig, @NonNull AdaptiveIconDrawable owner,
@Nullable Resources res) {
mDensity = Drawable.resolveDensity(res, orig != null ? orig.mDensity : 0);
mChildren = new ChildDrawable[N_CHILDREN];
@@ -952,12 +951,12 @@
@Override
public Drawable newDrawable() {
- return new MaskableIconDrawable(this, null);
+ return new AdaptiveIconDrawable(this, null);
}
@Override
public Drawable newDrawable(@Nullable Resources res) {
- return new MaskableIconDrawable(this, res);
+ return new AdaptiveIconDrawable(this, res);
}
@Override
diff --git a/graphics/java/android/graphics/drawable/DrawableInflater.java b/graphics/java/android/graphics/drawable/DrawableInflater.java
index 6d0bbdf..3404d8c 100644
--- a/graphics/java/android/graphics/drawable/DrawableInflater.java
+++ b/graphics/java/android/graphics/drawable/DrawableInflater.java
@@ -147,8 +147,8 @@
return new TransitionDrawable();
case "ripple":
return new RippleDrawable();
- case "maskable-icon":
- return new MaskableIconDrawable();
+ case "adaptive-icon":
+ return new AdaptiveIconDrawable();
case "color":
return new ColorDrawable();
case "shape":
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index 60c3b1c..ff1312a 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -290,7 +290,7 @@
case TYPE_BITMAP:
return new BitmapDrawable(context.getResources(), getBitmap());
case TYPE_BITMAP_MASKABLE:
- return new MaskableIconDrawable(null,
+ return new AdaptiveIconDrawable(null,
new BitmapDrawable(context.getResources(), getBitmap()));
case TYPE_RESOURCE:
if (getResources() == null) {
@@ -563,7 +563,7 @@
/**
* Create an Icon pointing to a bitmap in memory that follows the icon design guideline defined
- * by {@link MaskableIconDrawable}.
+ * by {@link AdaptiveIconDrawable}.
* @param bits A valid {@link android.graphics.Bitmap} object
*/
public static Icon createWithMaskableBitmap(Bitmap bits) {
diff --git a/graphics/tests/graphicstests/src/android/graphics/drawable/IconTest.java b/graphics/tests/graphicstests/src/android/graphics/drawable/IconTest.java
index 50c498b..d3e1a43 100644
--- a/graphics/tests/graphicstests/src/android/graphics/drawable/IconTest.java
+++ b/graphics/tests/graphicstests/src/android/graphics/drawable/IconTest.java
@@ -117,16 +117,16 @@
final Icon im1 = Icon.createWithMaskableBitmap(bm1);
- final MaskableIconDrawable draw1 = (MaskableIconDrawable) im1.loadDrawable(mContext);
+ final AdaptiveIconDrawable draw1 = (AdaptiveIconDrawable) im1.loadDrawable(mContext);
final Bitmap test1 = Bitmap.createBitmap(
- (int)(draw1.getIntrinsicWidth() * (1 + 2 * MaskableIconDrawable.getExtraInsetPercentage())),
- (int)(draw1.getIntrinsicHeight() * (1 + 2 * MaskableIconDrawable.getExtraInsetPercentage())),
+ (int)(draw1.getIntrinsicWidth() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetPercentage())),
+ (int)(draw1.getIntrinsicHeight() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetPercentage())),
Bitmap.Config.ARGB_8888);
draw1.setBounds(0, 0,
- (int) (draw1.getIntrinsicWidth() * (1 + 2 * MaskableIconDrawable.getExtraInsetPercentage())),
- (int) (draw1.getIntrinsicHeight() * (1 + 2 * MaskableIconDrawable.getExtraInsetPercentage())));
+ (int) (draw1.getIntrinsicWidth() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetPercentage())),
+ (int) (draw1.getIntrinsicHeight() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetPercentage())));
draw1.draw(new Canvas(test1));
final File dir = getContext().getExternalFilesDir(null);
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index fe68ec0..a5b1d29 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -18,7 +18,10 @@
#include "androidfw/ApkAssets.h"
+#include <algorithm>
+
#include "android-base/logging.h"
+#include "utils/FileMap.h"
#include "utils/Trace.h"
#include "ziparchive/zip_archive.h"
@@ -62,13 +65,13 @@
LOG(WARNING) << "resources.arsc is compressed.";
}
+ loaded_apk->path_ = path;
loaded_apk->resources_asset_ =
loaded_apk->Open("resources.arsc", Asset::AccessMode::ACCESS_BUFFER);
if (loaded_apk->resources_asset_ == nullptr) {
return {};
}
- loaded_apk->path_ = path;
loaded_apk->loaded_arsc_ =
LoadedArsc::Load(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/),
loaded_apk->resources_asset_->getLength(), system, load_as_shared_library);
@@ -80,37 +83,93 @@
return std::move(loaded_apk);
}
-std::unique_ptr<Asset> ApkAssets::Open(const std::string& path, Asset::AccessMode /*mode*/) const {
- ATRACE_NAME("ApkAssets::Open");
+std::unique_ptr<Asset> ApkAssets::Open(const std::string& path, Asset::AccessMode mode) const {
+ ATRACE_CALL();
CHECK(zip_handle_ != nullptr);
::ZipString name(path.c_str());
::ZipEntry entry;
int32_t result = ::FindEntry(zip_handle_.get(), name, &entry);
if (result != 0) {
- LOG(ERROR) << "No entry '" << path << "' found in APK.";
+ LOG(ERROR) << "No entry '" << path << "' found in APK '" << path_ << "'";
return {};
}
if (entry.method == kCompressDeflated) {
- auto compressed_asset = util::make_unique<_CompressedAsset>();
- if (compressed_asset->openChunk(::GetFileDescriptor(zip_handle_.get()), entry.offset,
- entry.method, entry.uncompressed_length,
- entry.compressed_length) != NO_ERROR) {
+ std::unique_ptr<FileMap> map = util::make_unique<FileMap>();
+ if (!map->create(path_.c_str(), ::GetFileDescriptor(zip_handle_.get()), entry.offset,
+ entry.compressed_length, true /*readOnly*/)) {
+ LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << path_ << "'";
+ return {};
+ }
+
+ std::unique_ptr<Asset> asset =
+ Asset::createFromCompressedMap(std::move(map), entry.uncompressed_length, mode);
+ if (asset == nullptr) {
LOG(ERROR) << "Failed to decompress '" << path << "'.";
return {};
}
- return std::move(compressed_asset);
+ return asset;
} else {
- auto uncompressed_asset = util::make_unique<_FileAsset>();
- if (uncompressed_asset->openChunk(path.c_str(), ::GetFileDescriptor(zip_handle_.get()),
- entry.offset, entry.uncompressed_length) != NO_ERROR) {
- LOG(ERROR) << "Failed to mmap '" << path << "'.";
+ std::unique_ptr<FileMap> map = util::make_unique<FileMap>();
+ if (!map->create(path_.c_str(), ::GetFileDescriptor(zip_handle_.get()), entry.offset,
+ entry.uncompressed_length, true /*readOnly*/)) {
+ LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << path_ << "'";
return {};
}
- return std::move(uncompressed_asset);
+
+ std::unique_ptr<Asset> asset = Asset::createFromUncompressedMap(std::move(map), mode);
+ if (asset == nullptr) {
+ LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << path_ << "'";
+ return {};
+ }
+ return asset;
}
- return {};
+}
+
+bool ApkAssets::ForEachFile(const std::string& root_path,
+ const std::function<void(const StringPiece&, FileType)>& f) const {
+ CHECK(zip_handle_ != nullptr);
+
+ std::string root_path_full = root_path;
+ if (root_path_full.back() != '/') {
+ root_path_full += '/';
+ }
+
+ ::ZipString prefix(root_path_full.c_str());
+ void* cookie;
+ if (::StartIteration(zip_handle_.get(), &cookie, &prefix, nullptr) != 0) {
+ return false;
+ }
+
+ ::ZipString name;
+ ::ZipEntry entry;
+
+ // We need to hold back directories because many paths will contain them and we want to only
+ // surface one.
+ std::set<std::string> dirs;
+
+ int32_t result;
+ while ((result = ::Next(cookie, &entry, &name)) == 0) {
+ StringPiece full_file_path(reinterpret_cast<const char*>(name.name), name.name_length);
+ StringPiece leaf_file_path = full_file_path.substr(root_path_full.size());
+ auto iter = std::find(leaf_file_path.begin(), leaf_file_path.end(), '/');
+ if (iter != leaf_file_path.end()) {
+ dirs.insert(
+ leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)).to_string());
+ } else if (!leaf_file_path.empty()) {
+ f(leaf_file_path, kFileTypeRegular);
+ }
+ }
+ ::EndIteration(cookie);
+
+ // Now present the unique directories.
+ for (const std::string& dir : dirs) {
+ f(dir, kFileTypeDirectory);
+ }
+
+ // -1 is end of iteration, anything else is an error.
+ return result == -1;
}
} // namespace android
diff --git a/libs/androidfw/Asset.cpp b/libs/androidfw/Asset.cpp
index 8e8c6a2..247458d 100644
--- a/libs/androidfw/Asset.cpp
+++ b/libs/androidfw/Asset.cpp
@@ -23,6 +23,7 @@
#include <androidfw/Asset.h>
#include <androidfw/StreamingZipInflater.h>
+#include <androidfw/Util.h>
#include <androidfw/ZipFileRO.h>
#include <androidfw/ZipUtils.h>
#include <utils/Atomic.h>
@@ -298,6 +299,22 @@
return pAsset;
}
+/*static*/ std::unique_ptr<Asset> Asset::createFromUncompressedMap(std::unique_ptr<FileMap> dataMap,
+ AccessMode mode)
+{
+ std::unique_ptr<_FileAsset> pAsset = util::make_unique<_FileAsset>();
+
+ status_t result = pAsset->openChunk(dataMap.get());
+ if (result != NO_ERROR) {
+ return NULL;
+ }
+
+ // We succeeded, so relinquish control of dataMap
+ (void) dataMap.release();
+ pAsset->mAccessMode = mode;
+ return std::move(pAsset);
+}
+
/*
* Create a new Asset from compressed data in a memory mapping.
*/
@@ -316,6 +333,21 @@
return pAsset;
}
+/*static*/ std::unique_ptr<Asset> Asset::createFromCompressedMap(std::unique_ptr<FileMap> dataMap,
+ size_t uncompressedLen, AccessMode mode)
+{
+ std::unique_ptr<_CompressedAsset> pAsset = util::make_unique<_CompressedAsset>();
+
+ status_t result = pAsset->openChunk(dataMap.get(), uncompressedLen);
+ if (result != NO_ERROR) {
+ return NULL;
+ }
+
+ // We succeeded, so relinquish control of dataMap
+ (void) dataMap.release();
+ pAsset->mAccessMode = mode;
+ return std::move(pAsset);
+}
/*
* Do generic seek() housekeeping. Pass in the offset/whence values from
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index ef0c967..5667f92 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -138,6 +138,17 @@
return &package_groups_[idx].dynamic_ref_table;
}
+const DynamicRefTable* AssetManager2::GetDynamicRefTableForCookie(ApkAssetsCookie cookie) const {
+ for (const PackageGroup& package_group : package_groups_) {
+ for (const ApkAssetsCookie& package_cookie : package_group.cookies_) {
+ if (package_cookie == cookie) {
+ return &package_group.dynamic_ref_table;
+ }
+ }
+ }
+ return nullptr;
+}
+
void AssetManager2::SetConfiguration(const ResTable_config& configuration) {
const int diff = configuration_.diff(configuration);
configuration_ = configuration;
@@ -188,6 +199,35 @@
return OpenNonAsset(new_path, cookie, mode);
}
+std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) {
+ ATRACE_CALL();
+
+ std::string full_path = "assets/" + dirname;
+ std::unique_ptr<SortedVector<AssetDir::FileInfo>> files =
+ util::make_unique<SortedVector<AssetDir::FileInfo>>();
+
+ // Start from the back.
+ for (auto iter = apk_assets_.rbegin(); iter != apk_assets_.rend(); ++iter) {
+ const ApkAssets* apk_assets = *iter;
+
+ auto func = [&](const StringPiece& name, FileType type) {
+ AssetDir::FileInfo info;
+ info.setFileName(String8(name.data(), name.size()));
+ info.setFileType(type);
+ info.setSourceName(String8(apk_assets->GetPath().c_str()));
+ files->add(info);
+ };
+
+ if (!apk_assets->ForEachFile(full_path, func)) {
+ return {};
+ }
+ }
+
+ std::unique_ptr<AssetDir> asset_dir = util::make_unique<AssetDir>();
+ asset_dir->setFileList(files.release());
+ return asset_dir;
+}
+
// Search in reverse because that's how we used to do it and we need to preserve behaviour.
// This is unfortunate, because ClassLoaders delegate to the parent first, so the order
// is inconsistent for split APKs.
@@ -237,15 +277,15 @@
desired_config = &density_override_config;
}
- const uint32_t package_id = get_package_id(resid);
- const uint8_t type_id = get_type_id(resid);
- const uint16_t entry_id = get_entry_id(resid);
-
- if (type_id == 0) {
+ if (!is_valid_resid(resid)) {
LOG(ERROR) << base::StringPrintf("Invalid ID 0x%08x.", resid);
return kInvalidCookie;
}
+ const uint32_t package_id = get_package_id(resid);
+ const uint8_t type_idx = get_type_id(resid) - 1;
+ const uint16_t entry_id = get_entry_id(resid);
+
const uint8_t idx = package_ids_[package_id];
if (idx == 0xff) {
LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.", package_id, resid);
@@ -265,7 +305,7 @@
uint32_t current_flags = 0;
const LoadedPackage* loaded_package = package_group.packages_[i];
- if (!loaded_package->FindEntry(type_id - 1, entry_id, *desired_config, ¤t_entry,
+ if (!loaded_package->FindEntry(type_idx, entry_id, *desired_config, ¤t_entry,
¤t_config, ¤t_flags)) {
continue;
}
@@ -385,16 +425,16 @@
ApkAssetsCookie AssetManager2::ResolveReference(ApkAssetsCookie cookie, Res_value* in_out_value,
ResTable_config* in_out_selected_config,
uint32_t* in_out_flags,
- ResTable_ref* out_last_reference) {
+ uint32_t* out_last_reference) {
ATRACE_CALL();
constexpr const int kMaxIterations = 20;
- out_last_reference->ident = 0u;
+ *out_last_reference = 0u;
for (size_t iteration = 0u; in_out_value->dataType == Res_value::TYPE_REFERENCE &&
in_out_value->data != 0u && iteration < kMaxIterations;
iteration++) {
if (out_last_reference != nullptr) {
- out_last_reference->ident = in_out_value->data;
+ *out_last_reference = in_out_value->data;
}
uint32_t new_flags = 0u;
cookie = GetResource(in_out_value->data, true /*may_be_bag*/, 0u /*density_override*/,
@@ -405,7 +445,7 @@
if (in_out_flags != nullptr) {
*in_out_flags |= new_flags;
}
- if (out_last_reference->ident == in_out_value->data) {
+ if (*out_last_reference == in_out_value->data) {
// This reference can't be resolved, so exit now and let the caller deal with it.
return cookie;
}
@@ -832,6 +872,25 @@
return kInvalidCookie;
}
+ApkAssetsCookie Theme::ResolveAttributeReference(ApkAssetsCookie cookie, Res_value* in_out_value,
+ ResTable_config* in_out_selected_config,
+ uint32_t* in_out_type_spec_flags,
+ uint32_t* out_last_ref) {
+ if (in_out_value->dataType == Res_value::TYPE_ATTRIBUTE) {
+ uint32_t new_flags;
+ cookie = GetAttribute(in_out_value->data, in_out_value, &new_flags);
+ if (cookie == kInvalidCookie) {
+ return kInvalidCookie;
+ }
+
+ if (in_out_type_spec_flags != nullptr) {
+ *in_out_type_spec_flags |= new_flags;
+ }
+ }
+ return asset_manager_->ResolveReference(cookie, in_out_value, in_out_selected_config,
+ in_out_type_spec_flags, out_last_ref);
+}
+
void Theme::Clear() {
type_spec_flags_ = 0u;
for (std::unique_ptr<Package>& package : packages_) {
diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp
index 166863c..5694115 100644
--- a/libs/androidfw/CursorWindow.cpp
+++ b/libs/androidfw/CursorWindow.cpp
@@ -98,9 +98,14 @@
if (dupAshmemFd < 0) {
result = -errno;
} else {
+ // the size of the ashmem descriptor can be modified between ashmem_get_size_region
+ // call and mmap, so we'll check again immediately after memory is mapped
void* data = ::mmap(NULL, size, PROT_READ, MAP_SHARED, dupAshmemFd, 0);
if (data == MAP_FAILED) {
result = -errno;
+ } else if (ashmem_get_size_region(dupAshmemFd) != size) {
+ ::munmap(data, size);
+ result = BAD_VALUE;
} else {
CursorWindow* window = new CursorWindow(name, dupAshmemFd,
data, size, true /*readOnly*/);
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 763a178..b8d95e4 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -6074,6 +6074,10 @@
return true;
}
+static bool keyCompare(const ResTable_sparseTypeEntry& entry , uint16_t entryIdx) {
+ return dtohs(entry.idx) < entryIdx;
+}
+
status_t ResTable::getEntry(
const PackageGroup* packageGroup, int typeIndex, int entryIndex,
const ResTable_config* config,
@@ -6115,6 +6119,9 @@
currentTypeIsOverlay = true;
}
+ // Check that the entry idx is within range of the declared entry count (ResTable_typeSpec).
+ // Particular types (ResTable_type) may be encoded with sparse entries, and so their
+ // entryCount do not need to match.
if (static_cast<size_t>(realEntryIndex) >= typeSpec->entryCount) {
ALOGW("For resource 0x%08x, entry index(%d) is beyond type entryCount(%d)",
Res_MAKEID(packageGroup->id - 1, typeIndex, entryIndex),
@@ -6169,11 +6176,37 @@
continue;
}
- // Check if there is the desired entry in this type.
const uint32_t* const eindex = reinterpret_cast<const uint32_t*>(
reinterpret_cast<const uint8_t*>(thisType) + dtohs(thisType->header.headerSize));
- uint32_t thisOffset = dtohl(eindex[realEntryIndex]);
+ uint32_t thisOffset;
+
+ // Check if there is the desired entry in this type.
+ if (thisType->flags & ResTable_type::FLAG_SPARSE) {
+ // This is encoded as a sparse map, so perform a binary search.
+ const ResTable_sparseTypeEntry* sparseIndices =
+ reinterpret_cast<const ResTable_sparseTypeEntry*>(eindex);
+ const ResTable_sparseTypeEntry* result = std::lower_bound(
+ sparseIndices, sparseIndices + dtohl(thisType->entryCount), realEntryIndex,
+ keyCompare);
+ if (result == sparseIndices + dtohl(thisType->entryCount)
+ || dtohs(result->idx) != realEntryIndex) {
+ // No entry found.
+ continue;
+ }
+
+ // Extract the offset from the entry. Each offset must be a multiple of 4
+ // so we store it as the real offset divided by 4.
+ thisOffset = dtohs(result->offset) * 4u;
+ } else {
+ if (static_cast<uint32_t>(realEntryIndex) >= dtohl(thisType->entryCount)) {
+ // Entry does not exist.
+ continue;
+ }
+
+ thisOffset = dtohl(eindex[realEntryIndex]);
+ }
+
if (thisOffset == ResTable_type::NO_ENTRY) {
// There is no entry for this index and configuration.
continue;
@@ -6480,12 +6513,6 @@
}
Type* t = typeList.editItemAt(typeList.size() - 1);
- if (newEntryCount != t->entryCount) {
- ALOGE("ResTable_type entry count inconsistent: given %d, previously %d",
- (int)newEntryCount, (int)t->entryCount);
- return (mError=BAD_TYPE);
- }
-
if (t->package != package) {
ALOGE("No TypeSpec for type %d", type->id);
return (mError=BAD_TYPE);
@@ -6532,6 +6559,8 @@
return NO_ERROR;
}
+DynamicRefTable::DynamicRefTable() : DynamicRefTable(0, false) {}
+
DynamicRefTable::DynamicRefTable(uint8_t packageId, bool appAsLib)
: mAssignedPackageId(packageId)
, mAppAsLib(appAsLib)
@@ -6637,11 +6666,11 @@
// Do a proper lookup.
uint8_t translatedId = mLookupTable[packageId];
if (translatedId == 0) {
- ALOGV("DynamicRefTable(0x%02x): No mapping for build-time package ID 0x%02x.",
+ ALOGW("DynamicRefTable(0x%02x): No mapping for build-time package ID 0x%02x.",
(uint8_t)mAssignedPackageId, (uint8_t)packageId);
for (size_t i = 0; i < 256; i++) {
if (mLookupTable[i] != 0) {
- ALOGV("e[0x%02x] -> 0x%02x", (uint8_t)i, mLookupTable[i]);
+ ALOGW("e[0x%02x] -> 0x%02x", (uint8_t)i, mLookupTable[i]);
}
}
return UNKNOWN_ERROR;
@@ -7096,8 +7125,17 @@
thisConfig.copyFromDtoH(type->config);
String8 configStr = thisConfig.toString();
- printf(" config %s:\n", configStr.size() > 0
+ printf(" config %s", configStr.size() > 0
? configStr.string() : "(default)");
+ if (type->flags != 0u) {
+ printf(" flags=0x%02x", type->flags);
+ if (type->flags & ResTable_type::FLAG_SPARSE) {
+ printf(" [sparse]");
+ }
+ }
+
+ printf(":\n");
+
size_t entryCount = dtohl(type->entryCount);
uint32_t entriesStart = dtohl(type->entriesStart);
if ((entriesStart&0x3) != 0) {
@@ -7109,18 +7147,30 @@
printf(" NON-INTEGER ResTable_type header.size: 0x%x\n", typeSize);
continue;
}
- for (size_t entryIndex=0; entryIndex<entryCount; entryIndex++) {
- const uint32_t* const eindex = (const uint32_t*)
- (((const uint8_t*)type) + dtohs(type->header.headerSize));
- uint32_t thisOffset = dtohl(eindex[entryIndex]);
- if (thisOffset == ResTable_type::NO_ENTRY) {
- continue;
+ const uint32_t* const eindex = (const uint32_t*)
+ (((const uint8_t*)type) + dtohs(type->header.headerSize));
+ for (size_t entryIndex=0; entryIndex<entryCount; entryIndex++) {
+ size_t entryId;
+ uint32_t thisOffset;
+ if (type->flags & ResTable_type::FLAG_SPARSE) {
+ const ResTable_sparseTypeEntry* entry =
+ reinterpret_cast<const ResTable_sparseTypeEntry*>(
+ eindex + entryIndex);
+ entryId = dtohs(entry->idx);
+ // Offsets are encoded as divided by 4.
+ thisOffset = static_cast<uint32_t>(dtohs(entry->offset)) * 4u;
+ } else {
+ entryId = entryIndex;
+ thisOffset = dtohl(eindex[entryIndex]);
+ if (thisOffset == ResTable_type::NO_ENTRY) {
+ continue;
+ }
}
uint32_t resID = (0xff000000 & ((packageId)<<24))
| (0x00ff0000 & ((typeIndex+1)<<16))
- | (0x0000ffff & (entryIndex));
+ | (0x0000ffff & (entryId));
if (packageId == 0) {
pg->dynamicRefTable.lookupResourceId(&resID);
}
diff --git a/libs/androidfw/TypeWrappers.cpp b/libs/androidfw/TypeWrappers.cpp
index 06b4040..647aa19 100644
--- a/libs/androidfw/TypeWrappers.cpp
+++ b/libs/androidfw/TypeWrappers.cpp
@@ -16,23 +16,45 @@
#include <androidfw/TypeWrappers.h>
+#include <algorithm>
+
namespace android {
+TypeVariant::TypeVariant(const ResTable_type* data) : data(data), mLength(dtohl(data->entryCount)) {
+ if (data->flags & ResTable_type::FLAG_SPARSE) {
+ const uint32_t entryCount = dtohl(data->entryCount);
+ const uintptr_t containerEnd = reinterpret_cast<uintptr_t>(data) + dtohl(data->header.size);
+ const uint32_t* const entryIndices = reinterpret_cast<const uint32_t*>(
+ reinterpret_cast<uintptr_t>(data) + dtohs(data->header.headerSize));
+ if (reinterpret_cast<uintptr_t>(entryIndices) + (sizeof(uint32_t) * entryCount)
+ > containerEnd) {
+ ALOGE("Type's entry indices extend beyond its boundaries");
+ mLength = 0;
+ } else {
+ mLength = ResTable_sparseTypeEntry{entryIndices[entryCount - 1]}.idx + 1;
+ }
+ }
+}
+
TypeVariant::iterator& TypeVariant::iterator::operator++() {
mIndex++;
- if (mIndex > dtohl(mTypeVariant->data->entryCount)) {
- mIndex = dtohl(mTypeVariant->data->entryCount);
+ if (mIndex > mTypeVariant->mLength) {
+ mIndex = mTypeVariant->mLength;
}
return *this;
}
+static bool keyCompare(uint32_t entry, uint16_t index) {
+ return dtohs(ResTable_sparseTypeEntry{entry}.idx) < index;
+}
+
const ResTable_entry* TypeVariant::iterator::operator*() const {
const ResTable_type* type = mTypeVariant->data;
- const uint32_t entryCount = dtohl(type->entryCount);
- if (mIndex >= entryCount) {
+ if (mIndex >= mTypeVariant->mLength) {
return NULL;
}
+ const uint32_t entryCount = dtohl(mTypeVariant->data->entryCount);
const uintptr_t containerEnd = reinterpret_cast<uintptr_t>(type)
+ dtohl(type->header.size);
const uint32_t* const entryIndices = reinterpret_cast<const uint32_t*>(
@@ -42,7 +64,19 @@
return NULL;
}
- const uint32_t entryOffset = dtohl(entryIndices[mIndex]);
+ uint32_t entryOffset;
+ if (type->flags & ResTable_type::FLAG_SPARSE) {
+ auto iter = std::lower_bound(entryIndices, entryIndices + entryCount, mIndex, keyCompare);
+ if (iter == entryIndices + entryCount
+ || dtohs(ResTable_sparseTypeEntry{*iter}.idx) != mIndex) {
+ return NULL;
+ }
+
+ entryOffset = static_cast<uint32_t>(dtohs(ResTable_sparseTypeEntry{*iter}.offset)) * 4u;
+ } else {
+ entryOffset = dtohl(entryIndices[mIndex]);
+ }
+
if (entryOffset == ResTable_type::NO_ENTRY) {
return NULL;
}
diff --git a/libs/androidfw/Util.cpp b/libs/androidfw/Util.cpp
index 202bc8e..575cd18 100644
--- a/libs/androidfw/Util.cpp
+++ b/libs/androidfw/Util.cpp
@@ -41,5 +41,31 @@
}
}
+std::u16string Utf8ToUtf16(const StringPiece& utf8) {
+ ssize_t utf16_length =
+ utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length());
+ if (utf16_length <= 0) {
+ return {};
+ }
+
+ std::u16string utf16;
+ utf16.resize(utf16_length);
+ utf8_to_utf16(reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length(), &*utf16.begin(),
+ utf16_length + 1);
+ return utf16;
+}
+
+std::string Utf16ToUtf8(const StringPiece16& utf16) {
+ ssize_t utf8_length = utf16_to_utf8_length(utf16.data(), utf16.length());
+ if (utf8_length <= 0) {
+ return {};
+ }
+
+ std::string utf8;
+ utf8.resize(utf8_length);
+ utf16_to_utf8(utf16.data(), utf16.length(), &*utf8.begin(), utf8_length + 1);
+ return utf8;
+}
+
} // namespace util
} // namespace android
diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h
index 6d1578c..b7e66fb 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -25,6 +25,7 @@
#include "androidfw/Asset.h"
#include "androidfw/LoadedArsc.h"
+#include "androidfw/misc.h"
namespace android {
@@ -38,6 +39,9 @@
std::unique_ptr<Asset> Open(const std::string& path,
Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM) const;
+ bool ForEachFile(const std::string& path,
+ const std::function<void(const StringPiece&, FileType)>& f) const;
+
inline const std::string& GetPath() const { return path_; }
inline const LoadedArsc* GetLoadedArsc() const { return loaded_arsc_.get(); }
@@ -56,6 +60,7 @@
using ZipArchivePtr =
std::unique_ptr<typename std::remove_pointer<::ZipArchiveHandle>::type, ZipArchivePtrCloser>;
+
ZipArchivePtr zip_handle_;
std::string path_;
std::unique_ptr<Asset> resources_asset_;
diff --git a/libs/androidfw/include/androidfw/Asset.h b/libs/androidfw/include/androidfw/Asset.h
index 461e773..9d12a35 100644
--- a/libs/androidfw/include/androidfw/Asset.h
+++ b/libs/androidfw/include/androidfw/Asset.h
@@ -24,6 +24,8 @@
#include <stdio.h>
#include <sys/types.h>
+#include <memory>
+
#include <utils/Compat.h>
#include <utils/Errors.h>
#include <utils/String8.h>
@@ -150,6 +152,7 @@
/* AssetManager needs access to our "create" functions */
friend class AssetManager;
+ friend class ApkAssets;
/*
* Create the asset from a named file on disk.
@@ -194,6 +197,9 @@
*/
static Asset* createFromUncompressedMap(FileMap* dataMap, AccessMode mode);
+ static std::unique_ptr<Asset> createFromUncompressedMap(std::unique_ptr<FileMap> dataMap,
+ AccessMode mode);
+
/*
* Create the asset from a memory-mapped file segment with compressed
* data.
@@ -203,6 +209,9 @@
static Asset* createFromCompressedMap(FileMap* dataMap,
size_t uncompressedLen, AccessMode mode);
+ static std::unique_ptr<Asset> createFromCompressedMap(std::unique_ptr<FileMap> dataMap,
+ size_t uncompressedLen, AccessMode mode);
+
/*
* Create from a reference-counted chunk of shared memory.
diff --git a/libs/androidfw/include/androidfw/AssetDir.h b/libs/androidfw/include/androidfw/AssetDir.h
index bd89d7d..7aef02d 100644
--- a/libs/androidfw/include/androidfw/AssetDir.h
+++ b/libs/androidfw/include/androidfw/AssetDir.h
@@ -70,6 +70,7 @@
const AssetDir& operator=(const AssetDir& src);
friend class AssetManager;
+ friend class AssetManager2;
/*
* This holds information about files in the asset hierarchy.
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index 81cdc46..d2bc6ee 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -107,6 +107,9 @@
// Returns the DynamicRefTable for the given package ID.
const DynamicRefTable* GetDynamicRefTableForPackage(uint32_t package_id) const;
+ // Returns the DynamicRefTable for the ApkAssets represented by the cookie.
+ const DynamicRefTable* GetDynamicRefTableForCookie(ApkAssetsCookie cookie) const;
+
// Sets/resets the configuration for this AssetManager. This will cause all
// caches that are related to the configuration change to be invalidated.
void SetConfiguration(const ResTable_config& configuration);
@@ -143,6 +146,11 @@
std::unique_ptr<Asset> Open(const std::string& filename, ApkAssetsCookie cookie,
Asset::AccessMode mode);
+ // Opens the directory specified by `dirname`. The result is an AssetDir that is the combination
+ // of all directories matching `dirname` under the assets/ directory of every ApkAssets loaded.
+ // The entries are sorted by their ASCII name.
+ std::unique_ptr<AssetDir> OpenDir(const std::string& dirname);
+
// Searches the set of APKs loaded by this AssetManager and opens the first one found.
// `mode` controls how the file is opened.
// `out_cookie` is populated with the cookie of the APK this file was found in.
@@ -203,7 +211,7 @@
// it was not found.
ApkAssetsCookie ResolveReference(ApkAssetsCookie cookie, Res_value* in_out_value,
ResTable_config* in_out_selected_config, uint32_t* in_out_flags,
- ResTable_ref* out_last_reference);
+ uint32_t* out_last_reference);
// Retrieves the best matching bag/map resource with ID `resid`.
// This method will resolve all parent references for this bag and merge keys with the child.
@@ -298,6 +306,8 @@
inline const AssetManager2* GetAssetManager() const { return asset_manager_; }
+ inline AssetManager2* GetAssetManager() { return asset_manager_; }
+
// Returns a bit mask of configuration changes that will impact this
// theme (and thus require completely reloading it).
inline uint32_t GetChangingConfigurations() const { return type_spec_flags_; }
@@ -318,10 +328,10 @@
// This is like AssetManager2::ResolveReference(), but also takes
// care of resolving attribute references to the theme.
- ApkAssetsCookie ResolveAttributeReference(Res_value* in_out_value, ApkAssetsCookie src_cookie,
- uint32_t* out_last_ref = nullptr,
+ ApkAssetsCookie ResolveAttributeReference(ApkAssetsCookie cookie, Res_value* in_out_value,
+ ResTable_config* in_out_selected_config = nullptr,
uint32_t* in_out_type_spec_flags = nullptr,
- ResTable_config* out_selected_config = nullptr) const;
+ uint32_t* out_last_ref = nullptr);
private:
DISALLOW_COPY_AND_ASSIGN(Theme);
diff --git a/libs/androidfw/include/androidfw/CursorWindow.h b/libs/androidfw/include/androidfw/CursorWindow.h
index f543565..ad64b24 100644
--- a/libs/androidfw/include/androidfw/CursorWindow.h
+++ b/libs/androidfw/include/androidfw/CursorWindow.h
@@ -17,6 +17,7 @@
#ifndef _ANDROID__DATABASE_WINDOW_H
#define _ANDROID__DATABASE_WINDOW_H
+#include <inttypes.h>
#include <stddef.h>
#include <stdint.h>
@@ -128,12 +129,13 @@
inline const char* getFieldSlotValueString(FieldSlot* fieldSlot,
size_t* outSizeIncludingNull) {
*outSizeIncludingNull = fieldSlot->data.buffer.size;
- return static_cast<char*>(offsetToPtr(fieldSlot->data.buffer.offset));
+ return static_cast<char*>(offsetToPtr(
+ fieldSlot->data.buffer.offset, fieldSlot->data.buffer.size));
}
inline const void* getFieldSlotValueBlob(FieldSlot* fieldSlot, size_t* outSize) {
*outSize = fieldSlot->data.buffer.size;
- return offsetToPtr(fieldSlot->data.buffer.offset);
+ return offsetToPtr(fieldSlot->data.buffer.offset, fieldSlot->data.buffer.size);
}
private:
@@ -166,7 +168,16 @@
bool mReadOnly;
Header* mHeader;
- inline void* offsetToPtr(uint32_t offset) {
+ inline void* offsetToPtr(uint32_t offset, uint32_t bufferSize = 0) {
+ if (offset >= mSize) {
+ ALOGE("Offset %" PRIu32 " out of bounds, max value %zu", offset, mSize);
+ return NULL;
+ }
+ if (offset + bufferSize > mSize) {
+ ALOGE("End offset %" PRIu32 " out of bounds, max value %zu",
+ offset + bufferSize, mSize);
+ return NULL;
+ }
return static_cast<uint8_t*>(mData) + offset;
}
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 04a5d95..d982a35 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1339,12 +1339,21 @@
/**
* A collection of resource entries for a particular resource data
- * type. Followed by an array of uint32_t defining the resource
+ * type.
+ *
+ * If the flag FLAG_SPARSE is not set in `flags`, then this struct is
+ * followed by an array of uint32_t defining the resource
* values, corresponding to the array of type strings in the
* ResTable_package::typeStrings string block. Each of these hold an
* index from entriesStart; a value of NO_ENTRY means that entry is
* not defined.
*
+ * If the flag FLAG_SPARSE is set in `flags`, then this struct is followed
+ * by an array of ResTable_sparseTypeEntry defining only the entries that
+ * have values for this type. Each entry is sorted by their entry ID such
+ * that a binary search can be performed over the entries. The ID and offset
+ * are encoded in a uint32_t. See ResTabe_sparseTypeEntry.
+ *
* There may be multiple of these chunks for a particular resource type,
* supply different configuration variations for the resource values of
* that type.
@@ -1365,10 +1374,17 @@
// resource identifier). 0 is invalid.
uint8_t id;
+ enum {
+ // If set, the entry is sparse, and encodes both the entry ID and offset into each entry,
+ // and a binary search is used to find the key. Only available on platforms >= O.
+ // Mark any types that use this with a v26 qualifier to prevent runtime issues on older
+ // platforms.
+ FLAG_SPARSE = 0x01,
+ };
+ uint8_t flags;
+
// Must be 0.
- uint8_t res0;
- // Must be 0.
- uint16_t res1;
+ uint16_t reserved;
// Number of uint32_t entry indices that follow.
uint32_t entryCount;
@@ -1381,6 +1397,24 @@
};
/**
+ * An entry in a ResTable_type with the flag `FLAG_SPARSE` set.
+ */
+union ResTable_sparseTypeEntry {
+ // Holds the raw uint32_t encoded value. Do not read this.
+ uint32_t entry;
+ struct {
+ // The index of the entry.
+ uint16_t idx;
+
+ // The offset from ResTable_type::entriesStart, divided by 4.
+ uint16_t offset;
+ };
+};
+
+static_assert(sizeof(ResTable_sparseTypeEntry) == sizeof(uint32_t),
+ "ResTable_sparseTypeEntry must be 4 bytes in size");
+
+/**
* This is the beginning of information about an entry in the resource
* table. It holds the reference to the name of this entry, and is
* immediately followed by one of:
@@ -1554,7 +1588,7 @@
{
friend class AssetManager2;
public:
- DynamicRefTable() = default;
+ DynamicRefTable();
DynamicRefTable(uint8_t packageId, bool appAsLib);
// Loads an unmapped reference table from the package.
@@ -1577,10 +1611,10 @@
}
private:
- uint8_t mAssignedPackageId = 0;
+ uint8_t mAssignedPackageId;
uint8_t mLookupTable[256];
KeyedVector<String16, uint8_t> mEntries;
- bool mAppAsLib = false;
+ bool mAppAsLib;
};
bool U16StringToInt(const char16_t* s, size_t len, Res_value* outValue);
diff --git a/libs/androidfw/include/androidfw/TypeWrappers.h b/libs/androidfw/include/androidfw/TypeWrappers.h
index f1daf33..5cfe54e5 100644
--- a/libs/androidfw/include/androidfw/TypeWrappers.h
+++ b/libs/androidfw/include/androidfw/TypeWrappers.h
@@ -23,8 +23,7 @@
namespace android {
struct TypeVariant {
- TypeVariant(const ResTable_type* data)
- : data(data) {}
+ TypeVariant(const ResTable_type* data);
class iterator {
public:
@@ -72,10 +71,13 @@
}
iterator endEntries() const {
- return iterator(this, dtohl(data->entryCount));
+ return iterator(this, mLength);
}
const ResTable_type* data;
+
+private:
+ size_t mLength;
};
} // namespace android
diff --git a/libs/androidfw/include/androidfw/Util.h b/libs/androidfw/include/androidfw/Util.h
index 3950cf2..e4cd6a8 100644
--- a/libs/androidfw/include/androidfw/Util.h
+++ b/libs/androidfw/include/androidfw/Util.h
@@ -22,6 +22,8 @@
#include "android-base/macros.h"
+#include "androidfw/StringPiece.h"
+
namespace android {
namespace util {
@@ -108,6 +110,12 @@
void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out);
+// Converts a UTF-8 string to a UTF-16 string.
+std::u16string Utf8ToUtf16(const StringPiece& utf8);
+
+// Converts a UTF-16 string to a UTF-8 string.
+std::string Utf16ToUtf8(const StringPiece16& utf16);
+
} // namespace util
} // namespace android
diff --git a/libs/androidfw/tests/Android.mk b/libs/androidfw/tests/Android.mk
index 19527c5..921fd14 100644
--- a/libs/androidfw/tests/Android.mk
+++ b/libs/androidfw/tests/Android.mk
@@ -45,6 +45,8 @@
benchmarkFiles := \
AssetManager2_bench.cpp \
BenchMain.cpp \
+ BenchmarkHelpers.cpp \
+ SparseEntry_bench.cpp \
TestHelpers.cpp \
Theme_bench.cpp
diff --git a/libs/androidfw/tests/ApkAssets_test.cpp b/libs/androidfw/tests/ApkAssets_test.cpp
index 6b4a719..c85b0b9 100644
--- a/libs/androidfw/tests/ApkAssets_test.cpp
+++ b/libs/androidfw/tests/ApkAssets_test.cpp
@@ -16,6 +16,9 @@
#include "androidfw/ApkAssets.h"
+#include "android-base/file.h"
+#include "android-base/unique_fd.h"
+
#include "TestHelpers.h"
#include "data/basic/R.h"
@@ -51,4 +54,41 @@
EXPECT_TRUE(loaded_arsc->GetPackages()[0]->IsDynamic());
}
+TEST(ApkAssetsTest, CreateAndDestroyAssetKeepsApkAssetsOpen) {
+ std::unique_ptr<const ApkAssets> loaded_apk =
+ ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
+ ASSERT_NE(nullptr, loaded_apk);
+
+ {
+ std::unique_ptr<Asset> assets = loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER);
+ ASSERT_NE(nullptr, assets);
+ }
+
+ {
+ std::unique_ptr<Asset> assets = loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER);
+ ASSERT_NE(nullptr, assets);
+ }
+}
+
+TEST(ApkAssetsTest, OpenUncompressedAssetFd) {
+ std::unique_ptr<const ApkAssets> loaded_apk =
+ ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
+ ASSERT_NE(nullptr, loaded_apk);
+
+ auto asset = loaded_apk->Open("assets/uncompressed.txt", Asset::ACCESS_UNKNOWN);
+ ASSERT_NE(nullptr, asset);
+
+ off64_t start, length;
+ base::unique_fd fd(asset->openFileDescriptor(&start, &length));
+ EXPECT_GE(fd.get(), 0);
+
+ lseek64(fd.get(), start, SEEK_SET);
+
+ std::string buffer;
+ buffer.resize(length);
+ ASSERT_TRUE(base::ReadFully(fd.get(), &*buffer.begin(), length));
+
+ EXPECT_EQ("This should be uncompressed.\n\n", buffer);
+}
+
} // namespace android
diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp
index 273290a..67de741 100644
--- a/libs/androidfw/tests/AssetManager2_bench.cpp
+++ b/libs/androidfw/tests/AssetManager2_bench.cpp
@@ -22,6 +22,7 @@
#include "androidfw/AssetManager2.h"
#include "androidfw/ResourceTypes.h"
+#include "BenchmarkHelpers.h"
#include "TestHelpers.h"
#include "data/basic/R.h"
#include "data/libclient/R.h"
@@ -112,34 +113,6 @@
}
}
-static void GetResourceBenchmarkOld(const std::vector<std::string>& paths,
- const ResTable_config* config, uint32_t resid,
- benchmark::State& state) {
- AssetManager assetmanager;
- for (const std::string& path : paths) {
- if (!assetmanager.addAssetPath(String8(path.c_str()), nullptr /* cookie */,
- false /* appAsLib */, false /* isSystemAssets */)) {
- state.SkipWithError(base::StringPrintf("Failed to load assets %s", path.c_str()).c_str());
- return;
- }
- }
-
- if (config != nullptr) {
- assetmanager.setConfiguration(*config);
- }
-
- const ResTable& table = assetmanager.getResources(true);
-
- Res_value value;
- ResTable_config selected_config;
- uint32_t flags;
-
- while (state.KeepRunning()) {
- table.getResource(resid, &value, false /*may_be_bag*/, 0u /*density*/, &flags,
- &selected_config);
- }
-}
-
static void BM_AssetManagerGetResource(benchmark::State& state) {
GetResourceBenchmark({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/,
basic::R::integer::number1, state);
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index 78fbb0f..d8e5abf 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -312,12 +312,12 @@
EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType);
EXPECT_EQ(basic::R::integer::ref2, value.data);
- ResTable_ref last_ref;
+ uint32_t last_ref;
cookie = assetmanager.ResolveReference(cookie, &value, &selected_config, &flags, &last_ref);
ASSERT_NE(kInvalidCookie, cookie);
EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
EXPECT_EQ(12000u, value.data);
- EXPECT_EQ(basic::R::integer::ref2, last_ref.ident);
+ EXPECT_EQ(basic::R::integer::ref2, last_ref);
}
TEST_F(AssetManager2Test, ResolveReferenceToBag) {
@@ -335,12 +335,12 @@
EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType);
EXPECT_EQ(basic::R::array::integerArray1, value.data);
- ResTable_ref last_ref;
+ uint32_t last_ref;
cookie = assetmanager.ResolveReference(cookie, &value, &selected_config, &flags, &last_ref);
ASSERT_NE(kInvalidCookie, cookie);
EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType);
EXPECT_EQ(basic::R::array::integerArray1, value.data);
- EXPECT_EQ(basic::R::array::integerArray1, last_ref.ident);
+ EXPECT_EQ(basic::R::array::integerArray1, last_ref);
}
static bool IsConfigurationPresent(const std::set<ResTable_config>& configurations,
diff --git a/libs/androidfw/tests/BenchmarkHelpers.cpp b/libs/androidfw/tests/BenchmarkHelpers.cpp
new file mode 100644
index 0000000..3619b7e
--- /dev/null
+++ b/libs/androidfw/tests/BenchmarkHelpers.cpp
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+#include "BenchmarkHelpers.h"
+
+#include "android-base/stringprintf.h"
+#include "androidfw/AssetManager.h"
+
+namespace android {
+
+void GetResourceBenchmarkOld(const std::vector<std::string>& paths, const ResTable_config* config,
+ uint32_t resid, benchmark::State& state) {
+ AssetManager assetmanager;
+ for (const std::string& path : paths) {
+ if (!assetmanager.addAssetPath(String8(path.c_str()), nullptr /* cookie */,
+ false /* appAsLib */, false /* isSystemAssets */)) {
+ state.SkipWithError(base::StringPrintf("Failed to load assets %s", path.c_str()).c_str());
+ return;
+ }
+ }
+
+ if (config != nullptr) {
+ assetmanager.setConfiguration(*config);
+ }
+
+ const ResTable& table = assetmanager.getResources(true);
+
+ Res_value value;
+ ResTable_config selected_config;
+ uint32_t flags;
+
+ while (state.KeepRunning()) {
+ table.getResource(resid, &value, false /*may_be_bag*/, 0u /*density*/, &flags,
+ &selected_config);
+ }
+}
+
+} // namespace android
diff --git a/libs/androidfw/tests/BenchmarkHelpers.h b/libs/androidfw/tests/BenchmarkHelpers.h
new file mode 100644
index 0000000..fc36664
--- /dev/null
+++ b/libs/androidfw/tests/BenchmarkHelpers.h
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+#ifndef TESTS_BENCHMARKHELPERS_H_
+#define TESTS_BENCHMARKHELPERS_H_
+
+#include <string>
+#include <vector>
+
+#include "benchmark/benchmark.h"
+
+#include "androidfw/ResourceTypes.h"
+
+namespace android {
+
+void GetResourceBenchmarkOld(const std::vector<std::string>& paths, const ResTable_config* config,
+ uint32_t resid, benchmark::State& state);
+
+} // namespace android
+
+#endif /* TESTS_BENCHMARKHELPERS_H_ */
diff --git a/libs/androidfw/tests/ResTable_test.cpp b/libs/androidfw/tests/ResTable_test.cpp
index ad1cd2b..2df4130 100644
--- a/libs/androidfw/tests/ResTable_test.cpp
+++ b/libs/androidfw/tests/ResTable_test.cpp
@@ -41,6 +41,34 @@
ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
}
+TEST(ResTableTest, ShouldLoadSparseEntriesSuccessfully) {
+ std::string contents;
+ ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc",
+ &contents));
+
+ ResTable table;
+ ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
+
+ ResTable_config config;
+ memset(&config, 0, sizeof(config));
+ config.sdkVersion = 26;
+ table.setParameters(&config);
+
+ String16 name(u"com.android.sparse:integer/foo_9");
+ uint32_t flags;
+ uint32_t resid =
+ table.identifierForName(name.string(), name.size(), nullptr, 0, nullptr, 0, &flags);
+ ASSERT_NE(0u, resid);
+
+ Res_value val;
+ ResTable_config selected_config;
+ ASSERT_GE(
+ table.getResource(resid, &val, false /*mayBeBag*/, 0u /*density*/, &flags, &selected_config),
+ 0);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
+ EXPECT_EQ(900u, val.data);
+}
+
TEST(ResTableTest, SimpleTypeIsRetrievedCorrectly) {
std::string contents;
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk",
diff --git a/libs/androidfw/tests/SparseEntry_bench.cpp b/libs/androidfw/tests/SparseEntry_bench.cpp
new file mode 100644
index 0000000..1ebf7ce
--- /dev/null
+++ b/libs/androidfw/tests/SparseEntry_bench.cpp
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+#include "androidfw/AssetManager.h"
+#include "androidfw/ResourceTypes.h"
+
+#include "BenchmarkHelpers.h"
+#include "TestHelpers.h"
+#include "data/sparse/R.h"
+
+namespace sparse = com::android::sparse;
+
+namespace android {
+
+static void BM_SparseEntryGetResourceSparseSmall(benchmark::State& state) {
+ ResTable_config config;
+ memset(&config, 0, sizeof(config));
+ config.sdkVersion = 26;
+ GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/sparse.apk"}, &config,
+ sparse::R::integer::foo_9, state);
+}
+BENCHMARK(BM_SparseEntryGetResourceSparseSmall);
+
+static void BM_SparseEntryGetResourceNotSparseSmall(benchmark::State& state) {
+ ResTable_config config;
+ memset(&config, 0, sizeof(config));
+ config.sdkVersion = 26;
+ GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/not_sparse.apk"}, &config,
+ sparse::R::integer::foo_9, state);
+}
+BENCHMARK(BM_SparseEntryGetResourceNotSparseSmall);
+
+static void BM_SparseEntryGetResourceSparseLarge(benchmark::State& state) {
+ ResTable_config config;
+ memset(&config, 0, sizeof(config));
+ config.sdkVersion = 26;
+ GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/sparse.apk"}, &config,
+ sparse::R::string::foo_999, state);
+}
+BENCHMARK(BM_SparseEntryGetResourceSparseLarge);
+
+static void BM_SparseEntryGetResourceNotSparseLarge(benchmark::State& state) {
+ ResTable_config config;
+ memset(&config, 0, sizeof(config));
+ config.sdkVersion = 26;
+ GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/not_sparse.apk"}, &config,
+ sparse::R::string::foo_999, state);
+}
+BENCHMARK(BM_SparseEntryGetResourceNotSparseLarge);
+
+} // namespace android
diff --git a/libs/androidfw/tests/TestHelpers.h b/libs/androidfw/tests/TestHelpers.h
index a11ea84..ec78b2a 100644
--- a/libs/androidfw/tests/TestHelpers.h
+++ b/libs/androidfw/tests/TestHelpers.h
@@ -19,6 +19,7 @@
#include <ostream>
#include <string>
+#include <vector>
#include "androidfw/ResourceTypes.h"
#include "gtest/gtest.h"
diff --git a/libs/androidfw/tests/data/basic/assets/uncompressed.txt b/libs/androidfw/tests/data/basic/assets/uncompressed.txt
new file mode 100644
index 0000000..c3d39c5
--- /dev/null
+++ b/libs/androidfw/tests/data/basic/assets/uncompressed.txt
@@ -0,0 +1,2 @@
+This should be uncompressed.
+
diff --git a/libs/androidfw/tests/data/basic/basic.apk b/libs/androidfw/tests/data/basic/basic.apk
index 7ee6734..0c17328 100644
--- a/libs/androidfw/tests/data/basic/basic.apk
+++ b/libs/androidfw/tests/data/basic/basic.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/build b/libs/androidfw/tests/data/basic/build
index af0fd87..d619800 100755
--- a/libs/androidfw/tests/data/basic/build
+++ b/libs/androidfw/tests/data/basic/build
@@ -19,4 +19,11 @@
PATH_TO_FRAMEWORK_RES=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/android.jar
-aapt package -M AndroidManifest.xml -S res -I $PATH_TO_FRAMEWORK_RES --split hdpi --split xhdpi --split xxhdpi --split fr,de -F basic.apk -f
+aapt package \
+ -M AndroidManifest.xml \
+ -S res \
+ -A assets \
+ -I $PATH_TO_FRAMEWORK_RES \
+ --split hdpi --split xhdpi --split xxhdpi --split fr,de \
+ -F basic.apk \
+ -f
diff --git a/libs/androidfw/tests/data/sparse/.gitignore b/libs/androidfw/tests/data/sparse/.gitignore
new file mode 100644
index 0000000..52e32d4
--- /dev/null
+++ b/libs/androidfw/tests/data/sparse/.gitignore
@@ -0,0 +1 @@
+*.flata
diff --git a/libs/androidfw/tests/data/sparse/AndroidManifest.xml b/libs/androidfw/tests/data/sparse/AndroidManifest.xml
new file mode 100644
index 0000000..27911b6
--- /dev/null
+++ b/libs/androidfw/tests/data/sparse/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.sparse">
+ <application />
+</manifest>
diff --git a/libs/androidfw/tests/data/sparse/R.h b/libs/androidfw/tests/data/sparse/R.h
new file mode 100644
index 0000000..243e74f
--- /dev/null
+++ b/libs/androidfw/tests/data/sparse/R.h
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+#ifndef TESTS_DATA_SPARSE_R_H_
+#define TESTS_DATA_SPARSE_R_H_
+
+#include <cstdint>
+
+namespace com {
+namespace android {
+namespace sparse {
+
+struct R {
+ struct integer {
+ enum : uint32_t {
+ foo_0 = 0x7f010000,
+ foo_1 = 0x7f010000,
+ foo_2 = 0x7f010000,
+ foo_3 = 0x7f010000,
+ foo_4 = 0x7f010000,
+ foo_5 = 0x7f010000,
+ foo_6 = 0x7f010000,
+ foo_7 = 0x7f010000,
+ foo_8 = 0x7f010000,
+ foo_9 = 0x7f010000,
+ };
+ };
+
+ struct string {
+ enum : uint32_t {
+ foo_999 = 0x7f0203e7,
+ };
+ };
+};
+
+} // namespace sparse
+} // namespace android
+} // namespace com
+
+#endif /* TESTS_DATA_SPARSE_R_H_ */
diff --git a/libs/androidfw/tests/data/sparse/build b/libs/androidfw/tests/data/sparse/build
new file mode 100755
index 0000000..305593f
--- /dev/null
+++ b/libs/androidfw/tests/data/sparse/build
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# 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.
+#
+
+set -e
+
+PATH_TO_FRAMEWORK_RES=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/android.jar
+
+aapt2 compile --dir res -o compiled.flata
+aapt2 link --manifest AndroidManifest.xml -I $PATH_TO_FRAMEWORK_RES -o sparse.apk --enable-sparse-encoding compiled.flata
+aapt2 link --manifest AndroidManifest.xml -I $PATH_TO_FRAMEWORK_RES -o not_sparse.apk compiled.flata
diff --git a/libs/androidfw/tests/data/sparse/gen_strings.sh b/libs/androidfw/tests/data/sparse/gen_strings.sh
new file mode 100755
index 0000000..e7e1d60
--- /dev/null
+++ b/libs/androidfw/tests/data/sparse/gen_strings.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+OUTPUT_default=res/values/strings.xml
+OUTPUT_v26=res/values-v26/strings.xml
+
+echo "<resources>" > $OUTPUT_default
+echo "<resources>" > $OUTPUT_v26
+for i in {0..999}
+do
+ echo " <string name=\"foo_$i\">$i</string>" >> $OUTPUT_default
+ if [ "$(($i % 3))" -eq "0" ]
+ then
+ echo " <string name=\"foo_$i\">$(($i * 10))</string>" >> $OUTPUT_v26
+ fi
+done
+echo "</resources>" >> $OUTPUT_default
+echo "</resources>" >> $OUTPUT_v26
+
diff --git a/libs/androidfw/tests/data/sparse/not_sparse.apk b/libs/androidfw/tests/data/sparse/not_sparse.apk
new file mode 100644
index 0000000..599a370
--- /dev/null
+++ b/libs/androidfw/tests/data/sparse/not_sparse.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml b/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml
new file mode 100644
index 0000000..b6f8299
--- /dev/null
+++ b/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml
@@ -0,0 +1,336 @@
+<resources>
+ <string name="foo_0">0</string>
+ <string name="foo_3">30</string>
+ <string name="foo_6">60</string>
+ <string name="foo_9">90</string>
+ <string name="foo_12">120</string>
+ <string name="foo_15">150</string>
+ <string name="foo_18">180</string>
+ <string name="foo_21">210</string>
+ <string name="foo_24">240</string>
+ <string name="foo_27">270</string>
+ <string name="foo_30">300</string>
+ <string name="foo_33">330</string>
+ <string name="foo_36">360</string>
+ <string name="foo_39">390</string>
+ <string name="foo_42">420</string>
+ <string name="foo_45">450</string>
+ <string name="foo_48">480</string>
+ <string name="foo_51">510</string>
+ <string name="foo_54">540</string>
+ <string name="foo_57">570</string>
+ <string name="foo_60">600</string>
+ <string name="foo_63">630</string>
+ <string name="foo_66">660</string>
+ <string name="foo_69">690</string>
+ <string name="foo_72">720</string>
+ <string name="foo_75">750</string>
+ <string name="foo_78">780</string>
+ <string name="foo_81">810</string>
+ <string name="foo_84">840</string>
+ <string name="foo_87">870</string>
+ <string name="foo_90">900</string>
+ <string name="foo_93">930</string>
+ <string name="foo_96">960</string>
+ <string name="foo_99">990</string>
+ <string name="foo_102">1020</string>
+ <string name="foo_105">1050</string>
+ <string name="foo_108">1080</string>
+ <string name="foo_111">1110</string>
+ <string name="foo_114">1140</string>
+ <string name="foo_117">1170</string>
+ <string name="foo_120">1200</string>
+ <string name="foo_123">1230</string>
+ <string name="foo_126">1260</string>
+ <string name="foo_129">1290</string>
+ <string name="foo_132">1320</string>
+ <string name="foo_135">1350</string>
+ <string name="foo_138">1380</string>
+ <string name="foo_141">1410</string>
+ <string name="foo_144">1440</string>
+ <string name="foo_147">1470</string>
+ <string name="foo_150">1500</string>
+ <string name="foo_153">1530</string>
+ <string name="foo_156">1560</string>
+ <string name="foo_159">1590</string>
+ <string name="foo_162">1620</string>
+ <string name="foo_165">1650</string>
+ <string name="foo_168">1680</string>
+ <string name="foo_171">1710</string>
+ <string name="foo_174">1740</string>
+ <string name="foo_177">1770</string>
+ <string name="foo_180">1800</string>
+ <string name="foo_183">1830</string>
+ <string name="foo_186">1860</string>
+ <string name="foo_189">1890</string>
+ <string name="foo_192">1920</string>
+ <string name="foo_195">1950</string>
+ <string name="foo_198">1980</string>
+ <string name="foo_201">2010</string>
+ <string name="foo_204">2040</string>
+ <string name="foo_207">2070</string>
+ <string name="foo_210">2100</string>
+ <string name="foo_213">2130</string>
+ <string name="foo_216">2160</string>
+ <string name="foo_219">2190</string>
+ <string name="foo_222">2220</string>
+ <string name="foo_225">2250</string>
+ <string name="foo_228">2280</string>
+ <string name="foo_231">2310</string>
+ <string name="foo_234">2340</string>
+ <string name="foo_237">2370</string>
+ <string name="foo_240">2400</string>
+ <string name="foo_243">2430</string>
+ <string name="foo_246">2460</string>
+ <string name="foo_249">2490</string>
+ <string name="foo_252">2520</string>
+ <string name="foo_255">2550</string>
+ <string name="foo_258">2580</string>
+ <string name="foo_261">2610</string>
+ <string name="foo_264">2640</string>
+ <string name="foo_267">2670</string>
+ <string name="foo_270">2700</string>
+ <string name="foo_273">2730</string>
+ <string name="foo_276">2760</string>
+ <string name="foo_279">2790</string>
+ <string name="foo_282">2820</string>
+ <string name="foo_285">2850</string>
+ <string name="foo_288">2880</string>
+ <string name="foo_291">2910</string>
+ <string name="foo_294">2940</string>
+ <string name="foo_297">2970</string>
+ <string name="foo_300">3000</string>
+ <string name="foo_303">3030</string>
+ <string name="foo_306">3060</string>
+ <string name="foo_309">3090</string>
+ <string name="foo_312">3120</string>
+ <string name="foo_315">3150</string>
+ <string name="foo_318">3180</string>
+ <string name="foo_321">3210</string>
+ <string name="foo_324">3240</string>
+ <string name="foo_327">3270</string>
+ <string name="foo_330">3300</string>
+ <string name="foo_333">3330</string>
+ <string name="foo_336">3360</string>
+ <string name="foo_339">3390</string>
+ <string name="foo_342">3420</string>
+ <string name="foo_345">3450</string>
+ <string name="foo_348">3480</string>
+ <string name="foo_351">3510</string>
+ <string name="foo_354">3540</string>
+ <string name="foo_357">3570</string>
+ <string name="foo_360">3600</string>
+ <string name="foo_363">3630</string>
+ <string name="foo_366">3660</string>
+ <string name="foo_369">3690</string>
+ <string name="foo_372">3720</string>
+ <string name="foo_375">3750</string>
+ <string name="foo_378">3780</string>
+ <string name="foo_381">3810</string>
+ <string name="foo_384">3840</string>
+ <string name="foo_387">3870</string>
+ <string name="foo_390">3900</string>
+ <string name="foo_393">3930</string>
+ <string name="foo_396">3960</string>
+ <string name="foo_399">3990</string>
+ <string name="foo_402">4020</string>
+ <string name="foo_405">4050</string>
+ <string name="foo_408">4080</string>
+ <string name="foo_411">4110</string>
+ <string name="foo_414">4140</string>
+ <string name="foo_417">4170</string>
+ <string name="foo_420">4200</string>
+ <string name="foo_423">4230</string>
+ <string name="foo_426">4260</string>
+ <string name="foo_429">4290</string>
+ <string name="foo_432">4320</string>
+ <string name="foo_435">4350</string>
+ <string name="foo_438">4380</string>
+ <string name="foo_441">4410</string>
+ <string name="foo_444">4440</string>
+ <string name="foo_447">4470</string>
+ <string name="foo_450">4500</string>
+ <string name="foo_453">4530</string>
+ <string name="foo_456">4560</string>
+ <string name="foo_459">4590</string>
+ <string name="foo_462">4620</string>
+ <string name="foo_465">4650</string>
+ <string name="foo_468">4680</string>
+ <string name="foo_471">4710</string>
+ <string name="foo_474">4740</string>
+ <string name="foo_477">4770</string>
+ <string name="foo_480">4800</string>
+ <string name="foo_483">4830</string>
+ <string name="foo_486">4860</string>
+ <string name="foo_489">4890</string>
+ <string name="foo_492">4920</string>
+ <string name="foo_495">4950</string>
+ <string name="foo_498">4980</string>
+ <string name="foo_501">5010</string>
+ <string name="foo_504">5040</string>
+ <string name="foo_507">5070</string>
+ <string name="foo_510">5100</string>
+ <string name="foo_513">5130</string>
+ <string name="foo_516">5160</string>
+ <string name="foo_519">5190</string>
+ <string name="foo_522">5220</string>
+ <string name="foo_525">5250</string>
+ <string name="foo_528">5280</string>
+ <string name="foo_531">5310</string>
+ <string name="foo_534">5340</string>
+ <string name="foo_537">5370</string>
+ <string name="foo_540">5400</string>
+ <string name="foo_543">5430</string>
+ <string name="foo_546">5460</string>
+ <string name="foo_549">5490</string>
+ <string name="foo_552">5520</string>
+ <string name="foo_555">5550</string>
+ <string name="foo_558">5580</string>
+ <string name="foo_561">5610</string>
+ <string name="foo_564">5640</string>
+ <string name="foo_567">5670</string>
+ <string name="foo_570">5700</string>
+ <string name="foo_573">5730</string>
+ <string name="foo_576">5760</string>
+ <string name="foo_579">5790</string>
+ <string name="foo_582">5820</string>
+ <string name="foo_585">5850</string>
+ <string name="foo_588">5880</string>
+ <string name="foo_591">5910</string>
+ <string name="foo_594">5940</string>
+ <string name="foo_597">5970</string>
+ <string name="foo_600">6000</string>
+ <string name="foo_603">6030</string>
+ <string name="foo_606">6060</string>
+ <string name="foo_609">6090</string>
+ <string name="foo_612">6120</string>
+ <string name="foo_615">6150</string>
+ <string name="foo_618">6180</string>
+ <string name="foo_621">6210</string>
+ <string name="foo_624">6240</string>
+ <string name="foo_627">6270</string>
+ <string name="foo_630">6300</string>
+ <string name="foo_633">6330</string>
+ <string name="foo_636">6360</string>
+ <string name="foo_639">6390</string>
+ <string name="foo_642">6420</string>
+ <string name="foo_645">6450</string>
+ <string name="foo_648">6480</string>
+ <string name="foo_651">6510</string>
+ <string name="foo_654">6540</string>
+ <string name="foo_657">6570</string>
+ <string name="foo_660">6600</string>
+ <string name="foo_663">6630</string>
+ <string name="foo_666">6660</string>
+ <string name="foo_669">6690</string>
+ <string name="foo_672">6720</string>
+ <string name="foo_675">6750</string>
+ <string name="foo_678">6780</string>
+ <string name="foo_681">6810</string>
+ <string name="foo_684">6840</string>
+ <string name="foo_687">6870</string>
+ <string name="foo_690">6900</string>
+ <string name="foo_693">6930</string>
+ <string name="foo_696">6960</string>
+ <string name="foo_699">6990</string>
+ <string name="foo_702">7020</string>
+ <string name="foo_705">7050</string>
+ <string name="foo_708">7080</string>
+ <string name="foo_711">7110</string>
+ <string name="foo_714">7140</string>
+ <string name="foo_717">7170</string>
+ <string name="foo_720">7200</string>
+ <string name="foo_723">7230</string>
+ <string name="foo_726">7260</string>
+ <string name="foo_729">7290</string>
+ <string name="foo_732">7320</string>
+ <string name="foo_735">7350</string>
+ <string name="foo_738">7380</string>
+ <string name="foo_741">7410</string>
+ <string name="foo_744">7440</string>
+ <string name="foo_747">7470</string>
+ <string name="foo_750">7500</string>
+ <string name="foo_753">7530</string>
+ <string name="foo_756">7560</string>
+ <string name="foo_759">7590</string>
+ <string name="foo_762">7620</string>
+ <string name="foo_765">7650</string>
+ <string name="foo_768">7680</string>
+ <string name="foo_771">7710</string>
+ <string name="foo_774">7740</string>
+ <string name="foo_777">7770</string>
+ <string name="foo_780">7800</string>
+ <string name="foo_783">7830</string>
+ <string name="foo_786">7860</string>
+ <string name="foo_789">7890</string>
+ <string name="foo_792">7920</string>
+ <string name="foo_795">7950</string>
+ <string name="foo_798">7980</string>
+ <string name="foo_801">8010</string>
+ <string name="foo_804">8040</string>
+ <string name="foo_807">8070</string>
+ <string name="foo_810">8100</string>
+ <string name="foo_813">8130</string>
+ <string name="foo_816">8160</string>
+ <string name="foo_819">8190</string>
+ <string name="foo_822">8220</string>
+ <string name="foo_825">8250</string>
+ <string name="foo_828">8280</string>
+ <string name="foo_831">8310</string>
+ <string name="foo_834">8340</string>
+ <string name="foo_837">8370</string>
+ <string name="foo_840">8400</string>
+ <string name="foo_843">8430</string>
+ <string name="foo_846">8460</string>
+ <string name="foo_849">8490</string>
+ <string name="foo_852">8520</string>
+ <string name="foo_855">8550</string>
+ <string name="foo_858">8580</string>
+ <string name="foo_861">8610</string>
+ <string name="foo_864">8640</string>
+ <string name="foo_867">8670</string>
+ <string name="foo_870">8700</string>
+ <string name="foo_873">8730</string>
+ <string name="foo_876">8760</string>
+ <string name="foo_879">8790</string>
+ <string name="foo_882">8820</string>
+ <string name="foo_885">8850</string>
+ <string name="foo_888">8880</string>
+ <string name="foo_891">8910</string>
+ <string name="foo_894">8940</string>
+ <string name="foo_897">8970</string>
+ <string name="foo_900">9000</string>
+ <string name="foo_903">9030</string>
+ <string name="foo_906">9060</string>
+ <string name="foo_909">9090</string>
+ <string name="foo_912">9120</string>
+ <string name="foo_915">9150</string>
+ <string name="foo_918">9180</string>
+ <string name="foo_921">9210</string>
+ <string name="foo_924">9240</string>
+ <string name="foo_927">9270</string>
+ <string name="foo_930">9300</string>
+ <string name="foo_933">9330</string>
+ <string name="foo_936">9360</string>
+ <string name="foo_939">9390</string>
+ <string name="foo_942">9420</string>
+ <string name="foo_945">9450</string>
+ <string name="foo_948">9480</string>
+ <string name="foo_951">9510</string>
+ <string name="foo_954">9540</string>
+ <string name="foo_957">9570</string>
+ <string name="foo_960">9600</string>
+ <string name="foo_963">9630</string>
+ <string name="foo_966">9660</string>
+ <string name="foo_969">9690</string>
+ <string name="foo_972">9720</string>
+ <string name="foo_975">9750</string>
+ <string name="foo_978">9780</string>
+ <string name="foo_981">9810</string>
+ <string name="foo_984">9840</string>
+ <string name="foo_987">9870</string>
+ <string name="foo_990">9900</string>
+ <string name="foo_993">9930</string>
+ <string name="foo_996">9960</string>
+ <string name="foo_999">9990</string>
+</resources>
diff --git a/libs/androidfw/tests/data/sparse/res/values-v26/values.xml b/libs/androidfw/tests/data/sparse/res/values-v26/values.xml
new file mode 100644
index 0000000..b396ad2
--- /dev/null
+++ b/libs/androidfw/tests/data/sparse/res/values-v26/values.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <integer name="foo_0">0</integer>
+ <integer name="foo_4">400</integer>
+ <integer name="foo_5">500</integer>
+ <integer name="foo_9">900</integer>
+</resources>
diff --git a/libs/androidfw/tests/data/sparse/res/values/strings.xml b/libs/androidfw/tests/data/sparse/res/values/strings.xml
new file mode 100644
index 0000000..6ff839a
--- /dev/null
+++ b/libs/androidfw/tests/data/sparse/res/values/strings.xml
@@ -0,0 +1,1002 @@
+<resources>
+ <string name="foo_0">0</string>
+ <string name="foo_1">1</string>
+ <string name="foo_2">2</string>
+ <string name="foo_3">3</string>
+ <string name="foo_4">4</string>
+ <string name="foo_5">5</string>
+ <string name="foo_6">6</string>
+ <string name="foo_7">7</string>
+ <string name="foo_8">8</string>
+ <string name="foo_9">9</string>
+ <string name="foo_10">10</string>
+ <string name="foo_11">11</string>
+ <string name="foo_12">12</string>
+ <string name="foo_13">13</string>
+ <string name="foo_14">14</string>
+ <string name="foo_15">15</string>
+ <string name="foo_16">16</string>
+ <string name="foo_17">17</string>
+ <string name="foo_18">18</string>
+ <string name="foo_19">19</string>
+ <string name="foo_20">20</string>
+ <string name="foo_21">21</string>
+ <string name="foo_22">22</string>
+ <string name="foo_23">23</string>
+ <string name="foo_24">24</string>
+ <string name="foo_25">25</string>
+ <string name="foo_26">26</string>
+ <string name="foo_27">27</string>
+ <string name="foo_28">28</string>
+ <string name="foo_29">29</string>
+ <string name="foo_30">30</string>
+ <string name="foo_31">31</string>
+ <string name="foo_32">32</string>
+ <string name="foo_33">33</string>
+ <string name="foo_34">34</string>
+ <string name="foo_35">35</string>
+ <string name="foo_36">36</string>
+ <string name="foo_37">37</string>
+ <string name="foo_38">38</string>
+ <string name="foo_39">39</string>
+ <string name="foo_40">40</string>
+ <string name="foo_41">41</string>
+ <string name="foo_42">42</string>
+ <string name="foo_43">43</string>
+ <string name="foo_44">44</string>
+ <string name="foo_45">45</string>
+ <string name="foo_46">46</string>
+ <string name="foo_47">47</string>
+ <string name="foo_48">48</string>
+ <string name="foo_49">49</string>
+ <string name="foo_50">50</string>
+ <string name="foo_51">51</string>
+ <string name="foo_52">52</string>
+ <string name="foo_53">53</string>
+ <string name="foo_54">54</string>
+ <string name="foo_55">55</string>
+ <string name="foo_56">56</string>
+ <string name="foo_57">57</string>
+ <string name="foo_58">58</string>
+ <string name="foo_59">59</string>
+ <string name="foo_60">60</string>
+ <string name="foo_61">61</string>
+ <string name="foo_62">62</string>
+ <string name="foo_63">63</string>
+ <string name="foo_64">64</string>
+ <string name="foo_65">65</string>
+ <string name="foo_66">66</string>
+ <string name="foo_67">67</string>
+ <string name="foo_68">68</string>
+ <string name="foo_69">69</string>
+ <string name="foo_70">70</string>
+ <string name="foo_71">71</string>
+ <string name="foo_72">72</string>
+ <string name="foo_73">73</string>
+ <string name="foo_74">74</string>
+ <string name="foo_75">75</string>
+ <string name="foo_76">76</string>
+ <string name="foo_77">77</string>
+ <string name="foo_78">78</string>
+ <string name="foo_79">79</string>
+ <string name="foo_80">80</string>
+ <string name="foo_81">81</string>
+ <string name="foo_82">82</string>
+ <string name="foo_83">83</string>
+ <string name="foo_84">84</string>
+ <string name="foo_85">85</string>
+ <string name="foo_86">86</string>
+ <string name="foo_87">87</string>
+ <string name="foo_88">88</string>
+ <string name="foo_89">89</string>
+ <string name="foo_90">90</string>
+ <string name="foo_91">91</string>
+ <string name="foo_92">92</string>
+ <string name="foo_93">93</string>
+ <string name="foo_94">94</string>
+ <string name="foo_95">95</string>
+ <string name="foo_96">96</string>
+ <string name="foo_97">97</string>
+ <string name="foo_98">98</string>
+ <string name="foo_99">99</string>
+ <string name="foo_100">100</string>
+ <string name="foo_101">101</string>
+ <string name="foo_102">102</string>
+ <string name="foo_103">103</string>
+ <string name="foo_104">104</string>
+ <string name="foo_105">105</string>
+ <string name="foo_106">106</string>
+ <string name="foo_107">107</string>
+ <string name="foo_108">108</string>
+ <string name="foo_109">109</string>
+ <string name="foo_110">110</string>
+ <string name="foo_111">111</string>
+ <string name="foo_112">112</string>
+ <string name="foo_113">113</string>
+ <string name="foo_114">114</string>
+ <string name="foo_115">115</string>
+ <string name="foo_116">116</string>
+ <string name="foo_117">117</string>
+ <string name="foo_118">118</string>
+ <string name="foo_119">119</string>
+ <string name="foo_120">120</string>
+ <string name="foo_121">121</string>
+ <string name="foo_122">122</string>
+ <string name="foo_123">123</string>
+ <string name="foo_124">124</string>
+ <string name="foo_125">125</string>
+ <string name="foo_126">126</string>
+ <string name="foo_127">127</string>
+ <string name="foo_128">128</string>
+ <string name="foo_129">129</string>
+ <string name="foo_130">130</string>
+ <string name="foo_131">131</string>
+ <string name="foo_132">132</string>
+ <string name="foo_133">133</string>
+ <string name="foo_134">134</string>
+ <string name="foo_135">135</string>
+ <string name="foo_136">136</string>
+ <string name="foo_137">137</string>
+ <string name="foo_138">138</string>
+ <string name="foo_139">139</string>
+ <string name="foo_140">140</string>
+ <string name="foo_141">141</string>
+ <string name="foo_142">142</string>
+ <string name="foo_143">143</string>
+ <string name="foo_144">144</string>
+ <string name="foo_145">145</string>
+ <string name="foo_146">146</string>
+ <string name="foo_147">147</string>
+ <string name="foo_148">148</string>
+ <string name="foo_149">149</string>
+ <string name="foo_150">150</string>
+ <string name="foo_151">151</string>
+ <string name="foo_152">152</string>
+ <string name="foo_153">153</string>
+ <string name="foo_154">154</string>
+ <string name="foo_155">155</string>
+ <string name="foo_156">156</string>
+ <string name="foo_157">157</string>
+ <string name="foo_158">158</string>
+ <string name="foo_159">159</string>
+ <string name="foo_160">160</string>
+ <string name="foo_161">161</string>
+ <string name="foo_162">162</string>
+ <string name="foo_163">163</string>
+ <string name="foo_164">164</string>
+ <string name="foo_165">165</string>
+ <string name="foo_166">166</string>
+ <string name="foo_167">167</string>
+ <string name="foo_168">168</string>
+ <string name="foo_169">169</string>
+ <string name="foo_170">170</string>
+ <string name="foo_171">171</string>
+ <string name="foo_172">172</string>
+ <string name="foo_173">173</string>
+ <string name="foo_174">174</string>
+ <string name="foo_175">175</string>
+ <string name="foo_176">176</string>
+ <string name="foo_177">177</string>
+ <string name="foo_178">178</string>
+ <string name="foo_179">179</string>
+ <string name="foo_180">180</string>
+ <string name="foo_181">181</string>
+ <string name="foo_182">182</string>
+ <string name="foo_183">183</string>
+ <string name="foo_184">184</string>
+ <string name="foo_185">185</string>
+ <string name="foo_186">186</string>
+ <string name="foo_187">187</string>
+ <string name="foo_188">188</string>
+ <string name="foo_189">189</string>
+ <string name="foo_190">190</string>
+ <string name="foo_191">191</string>
+ <string name="foo_192">192</string>
+ <string name="foo_193">193</string>
+ <string name="foo_194">194</string>
+ <string name="foo_195">195</string>
+ <string name="foo_196">196</string>
+ <string name="foo_197">197</string>
+ <string name="foo_198">198</string>
+ <string name="foo_199">199</string>
+ <string name="foo_200">200</string>
+ <string name="foo_201">201</string>
+ <string name="foo_202">202</string>
+ <string name="foo_203">203</string>
+ <string name="foo_204">204</string>
+ <string name="foo_205">205</string>
+ <string name="foo_206">206</string>
+ <string name="foo_207">207</string>
+ <string name="foo_208">208</string>
+ <string name="foo_209">209</string>
+ <string name="foo_210">210</string>
+ <string name="foo_211">211</string>
+ <string name="foo_212">212</string>
+ <string name="foo_213">213</string>
+ <string name="foo_214">214</string>
+ <string name="foo_215">215</string>
+ <string name="foo_216">216</string>
+ <string name="foo_217">217</string>
+ <string name="foo_218">218</string>
+ <string name="foo_219">219</string>
+ <string name="foo_220">220</string>
+ <string name="foo_221">221</string>
+ <string name="foo_222">222</string>
+ <string name="foo_223">223</string>
+ <string name="foo_224">224</string>
+ <string name="foo_225">225</string>
+ <string name="foo_226">226</string>
+ <string name="foo_227">227</string>
+ <string name="foo_228">228</string>
+ <string name="foo_229">229</string>
+ <string name="foo_230">230</string>
+ <string name="foo_231">231</string>
+ <string name="foo_232">232</string>
+ <string name="foo_233">233</string>
+ <string name="foo_234">234</string>
+ <string name="foo_235">235</string>
+ <string name="foo_236">236</string>
+ <string name="foo_237">237</string>
+ <string name="foo_238">238</string>
+ <string name="foo_239">239</string>
+ <string name="foo_240">240</string>
+ <string name="foo_241">241</string>
+ <string name="foo_242">242</string>
+ <string name="foo_243">243</string>
+ <string name="foo_244">244</string>
+ <string name="foo_245">245</string>
+ <string name="foo_246">246</string>
+ <string name="foo_247">247</string>
+ <string name="foo_248">248</string>
+ <string name="foo_249">249</string>
+ <string name="foo_250">250</string>
+ <string name="foo_251">251</string>
+ <string name="foo_252">252</string>
+ <string name="foo_253">253</string>
+ <string name="foo_254">254</string>
+ <string name="foo_255">255</string>
+ <string name="foo_256">256</string>
+ <string name="foo_257">257</string>
+ <string name="foo_258">258</string>
+ <string name="foo_259">259</string>
+ <string name="foo_260">260</string>
+ <string name="foo_261">261</string>
+ <string name="foo_262">262</string>
+ <string name="foo_263">263</string>
+ <string name="foo_264">264</string>
+ <string name="foo_265">265</string>
+ <string name="foo_266">266</string>
+ <string name="foo_267">267</string>
+ <string name="foo_268">268</string>
+ <string name="foo_269">269</string>
+ <string name="foo_270">270</string>
+ <string name="foo_271">271</string>
+ <string name="foo_272">272</string>
+ <string name="foo_273">273</string>
+ <string name="foo_274">274</string>
+ <string name="foo_275">275</string>
+ <string name="foo_276">276</string>
+ <string name="foo_277">277</string>
+ <string name="foo_278">278</string>
+ <string name="foo_279">279</string>
+ <string name="foo_280">280</string>
+ <string name="foo_281">281</string>
+ <string name="foo_282">282</string>
+ <string name="foo_283">283</string>
+ <string name="foo_284">284</string>
+ <string name="foo_285">285</string>
+ <string name="foo_286">286</string>
+ <string name="foo_287">287</string>
+ <string name="foo_288">288</string>
+ <string name="foo_289">289</string>
+ <string name="foo_290">290</string>
+ <string name="foo_291">291</string>
+ <string name="foo_292">292</string>
+ <string name="foo_293">293</string>
+ <string name="foo_294">294</string>
+ <string name="foo_295">295</string>
+ <string name="foo_296">296</string>
+ <string name="foo_297">297</string>
+ <string name="foo_298">298</string>
+ <string name="foo_299">299</string>
+ <string name="foo_300">300</string>
+ <string name="foo_301">301</string>
+ <string name="foo_302">302</string>
+ <string name="foo_303">303</string>
+ <string name="foo_304">304</string>
+ <string name="foo_305">305</string>
+ <string name="foo_306">306</string>
+ <string name="foo_307">307</string>
+ <string name="foo_308">308</string>
+ <string name="foo_309">309</string>
+ <string name="foo_310">310</string>
+ <string name="foo_311">311</string>
+ <string name="foo_312">312</string>
+ <string name="foo_313">313</string>
+ <string name="foo_314">314</string>
+ <string name="foo_315">315</string>
+ <string name="foo_316">316</string>
+ <string name="foo_317">317</string>
+ <string name="foo_318">318</string>
+ <string name="foo_319">319</string>
+ <string name="foo_320">320</string>
+ <string name="foo_321">321</string>
+ <string name="foo_322">322</string>
+ <string name="foo_323">323</string>
+ <string name="foo_324">324</string>
+ <string name="foo_325">325</string>
+ <string name="foo_326">326</string>
+ <string name="foo_327">327</string>
+ <string name="foo_328">328</string>
+ <string name="foo_329">329</string>
+ <string name="foo_330">330</string>
+ <string name="foo_331">331</string>
+ <string name="foo_332">332</string>
+ <string name="foo_333">333</string>
+ <string name="foo_334">334</string>
+ <string name="foo_335">335</string>
+ <string name="foo_336">336</string>
+ <string name="foo_337">337</string>
+ <string name="foo_338">338</string>
+ <string name="foo_339">339</string>
+ <string name="foo_340">340</string>
+ <string name="foo_341">341</string>
+ <string name="foo_342">342</string>
+ <string name="foo_343">343</string>
+ <string name="foo_344">344</string>
+ <string name="foo_345">345</string>
+ <string name="foo_346">346</string>
+ <string name="foo_347">347</string>
+ <string name="foo_348">348</string>
+ <string name="foo_349">349</string>
+ <string name="foo_350">350</string>
+ <string name="foo_351">351</string>
+ <string name="foo_352">352</string>
+ <string name="foo_353">353</string>
+ <string name="foo_354">354</string>
+ <string name="foo_355">355</string>
+ <string name="foo_356">356</string>
+ <string name="foo_357">357</string>
+ <string name="foo_358">358</string>
+ <string name="foo_359">359</string>
+ <string name="foo_360">360</string>
+ <string name="foo_361">361</string>
+ <string name="foo_362">362</string>
+ <string name="foo_363">363</string>
+ <string name="foo_364">364</string>
+ <string name="foo_365">365</string>
+ <string name="foo_366">366</string>
+ <string name="foo_367">367</string>
+ <string name="foo_368">368</string>
+ <string name="foo_369">369</string>
+ <string name="foo_370">370</string>
+ <string name="foo_371">371</string>
+ <string name="foo_372">372</string>
+ <string name="foo_373">373</string>
+ <string name="foo_374">374</string>
+ <string name="foo_375">375</string>
+ <string name="foo_376">376</string>
+ <string name="foo_377">377</string>
+ <string name="foo_378">378</string>
+ <string name="foo_379">379</string>
+ <string name="foo_380">380</string>
+ <string name="foo_381">381</string>
+ <string name="foo_382">382</string>
+ <string name="foo_383">383</string>
+ <string name="foo_384">384</string>
+ <string name="foo_385">385</string>
+ <string name="foo_386">386</string>
+ <string name="foo_387">387</string>
+ <string name="foo_388">388</string>
+ <string name="foo_389">389</string>
+ <string name="foo_390">390</string>
+ <string name="foo_391">391</string>
+ <string name="foo_392">392</string>
+ <string name="foo_393">393</string>
+ <string name="foo_394">394</string>
+ <string name="foo_395">395</string>
+ <string name="foo_396">396</string>
+ <string name="foo_397">397</string>
+ <string name="foo_398">398</string>
+ <string name="foo_399">399</string>
+ <string name="foo_400">400</string>
+ <string name="foo_401">401</string>
+ <string name="foo_402">402</string>
+ <string name="foo_403">403</string>
+ <string name="foo_404">404</string>
+ <string name="foo_405">405</string>
+ <string name="foo_406">406</string>
+ <string name="foo_407">407</string>
+ <string name="foo_408">408</string>
+ <string name="foo_409">409</string>
+ <string name="foo_410">410</string>
+ <string name="foo_411">411</string>
+ <string name="foo_412">412</string>
+ <string name="foo_413">413</string>
+ <string name="foo_414">414</string>
+ <string name="foo_415">415</string>
+ <string name="foo_416">416</string>
+ <string name="foo_417">417</string>
+ <string name="foo_418">418</string>
+ <string name="foo_419">419</string>
+ <string name="foo_420">420</string>
+ <string name="foo_421">421</string>
+ <string name="foo_422">422</string>
+ <string name="foo_423">423</string>
+ <string name="foo_424">424</string>
+ <string name="foo_425">425</string>
+ <string name="foo_426">426</string>
+ <string name="foo_427">427</string>
+ <string name="foo_428">428</string>
+ <string name="foo_429">429</string>
+ <string name="foo_430">430</string>
+ <string name="foo_431">431</string>
+ <string name="foo_432">432</string>
+ <string name="foo_433">433</string>
+ <string name="foo_434">434</string>
+ <string name="foo_435">435</string>
+ <string name="foo_436">436</string>
+ <string name="foo_437">437</string>
+ <string name="foo_438">438</string>
+ <string name="foo_439">439</string>
+ <string name="foo_440">440</string>
+ <string name="foo_441">441</string>
+ <string name="foo_442">442</string>
+ <string name="foo_443">443</string>
+ <string name="foo_444">444</string>
+ <string name="foo_445">445</string>
+ <string name="foo_446">446</string>
+ <string name="foo_447">447</string>
+ <string name="foo_448">448</string>
+ <string name="foo_449">449</string>
+ <string name="foo_450">450</string>
+ <string name="foo_451">451</string>
+ <string name="foo_452">452</string>
+ <string name="foo_453">453</string>
+ <string name="foo_454">454</string>
+ <string name="foo_455">455</string>
+ <string name="foo_456">456</string>
+ <string name="foo_457">457</string>
+ <string name="foo_458">458</string>
+ <string name="foo_459">459</string>
+ <string name="foo_460">460</string>
+ <string name="foo_461">461</string>
+ <string name="foo_462">462</string>
+ <string name="foo_463">463</string>
+ <string name="foo_464">464</string>
+ <string name="foo_465">465</string>
+ <string name="foo_466">466</string>
+ <string name="foo_467">467</string>
+ <string name="foo_468">468</string>
+ <string name="foo_469">469</string>
+ <string name="foo_470">470</string>
+ <string name="foo_471">471</string>
+ <string name="foo_472">472</string>
+ <string name="foo_473">473</string>
+ <string name="foo_474">474</string>
+ <string name="foo_475">475</string>
+ <string name="foo_476">476</string>
+ <string name="foo_477">477</string>
+ <string name="foo_478">478</string>
+ <string name="foo_479">479</string>
+ <string name="foo_480">480</string>
+ <string name="foo_481">481</string>
+ <string name="foo_482">482</string>
+ <string name="foo_483">483</string>
+ <string name="foo_484">484</string>
+ <string name="foo_485">485</string>
+ <string name="foo_486">486</string>
+ <string name="foo_487">487</string>
+ <string name="foo_488">488</string>
+ <string name="foo_489">489</string>
+ <string name="foo_490">490</string>
+ <string name="foo_491">491</string>
+ <string name="foo_492">492</string>
+ <string name="foo_493">493</string>
+ <string name="foo_494">494</string>
+ <string name="foo_495">495</string>
+ <string name="foo_496">496</string>
+ <string name="foo_497">497</string>
+ <string name="foo_498">498</string>
+ <string name="foo_499">499</string>
+ <string name="foo_500">500</string>
+ <string name="foo_501">501</string>
+ <string name="foo_502">502</string>
+ <string name="foo_503">503</string>
+ <string name="foo_504">504</string>
+ <string name="foo_505">505</string>
+ <string name="foo_506">506</string>
+ <string name="foo_507">507</string>
+ <string name="foo_508">508</string>
+ <string name="foo_509">509</string>
+ <string name="foo_510">510</string>
+ <string name="foo_511">511</string>
+ <string name="foo_512">512</string>
+ <string name="foo_513">513</string>
+ <string name="foo_514">514</string>
+ <string name="foo_515">515</string>
+ <string name="foo_516">516</string>
+ <string name="foo_517">517</string>
+ <string name="foo_518">518</string>
+ <string name="foo_519">519</string>
+ <string name="foo_520">520</string>
+ <string name="foo_521">521</string>
+ <string name="foo_522">522</string>
+ <string name="foo_523">523</string>
+ <string name="foo_524">524</string>
+ <string name="foo_525">525</string>
+ <string name="foo_526">526</string>
+ <string name="foo_527">527</string>
+ <string name="foo_528">528</string>
+ <string name="foo_529">529</string>
+ <string name="foo_530">530</string>
+ <string name="foo_531">531</string>
+ <string name="foo_532">532</string>
+ <string name="foo_533">533</string>
+ <string name="foo_534">534</string>
+ <string name="foo_535">535</string>
+ <string name="foo_536">536</string>
+ <string name="foo_537">537</string>
+ <string name="foo_538">538</string>
+ <string name="foo_539">539</string>
+ <string name="foo_540">540</string>
+ <string name="foo_541">541</string>
+ <string name="foo_542">542</string>
+ <string name="foo_543">543</string>
+ <string name="foo_544">544</string>
+ <string name="foo_545">545</string>
+ <string name="foo_546">546</string>
+ <string name="foo_547">547</string>
+ <string name="foo_548">548</string>
+ <string name="foo_549">549</string>
+ <string name="foo_550">550</string>
+ <string name="foo_551">551</string>
+ <string name="foo_552">552</string>
+ <string name="foo_553">553</string>
+ <string name="foo_554">554</string>
+ <string name="foo_555">555</string>
+ <string name="foo_556">556</string>
+ <string name="foo_557">557</string>
+ <string name="foo_558">558</string>
+ <string name="foo_559">559</string>
+ <string name="foo_560">560</string>
+ <string name="foo_561">561</string>
+ <string name="foo_562">562</string>
+ <string name="foo_563">563</string>
+ <string name="foo_564">564</string>
+ <string name="foo_565">565</string>
+ <string name="foo_566">566</string>
+ <string name="foo_567">567</string>
+ <string name="foo_568">568</string>
+ <string name="foo_569">569</string>
+ <string name="foo_570">570</string>
+ <string name="foo_571">571</string>
+ <string name="foo_572">572</string>
+ <string name="foo_573">573</string>
+ <string name="foo_574">574</string>
+ <string name="foo_575">575</string>
+ <string name="foo_576">576</string>
+ <string name="foo_577">577</string>
+ <string name="foo_578">578</string>
+ <string name="foo_579">579</string>
+ <string name="foo_580">580</string>
+ <string name="foo_581">581</string>
+ <string name="foo_582">582</string>
+ <string name="foo_583">583</string>
+ <string name="foo_584">584</string>
+ <string name="foo_585">585</string>
+ <string name="foo_586">586</string>
+ <string name="foo_587">587</string>
+ <string name="foo_588">588</string>
+ <string name="foo_589">589</string>
+ <string name="foo_590">590</string>
+ <string name="foo_591">591</string>
+ <string name="foo_592">592</string>
+ <string name="foo_593">593</string>
+ <string name="foo_594">594</string>
+ <string name="foo_595">595</string>
+ <string name="foo_596">596</string>
+ <string name="foo_597">597</string>
+ <string name="foo_598">598</string>
+ <string name="foo_599">599</string>
+ <string name="foo_600">600</string>
+ <string name="foo_601">601</string>
+ <string name="foo_602">602</string>
+ <string name="foo_603">603</string>
+ <string name="foo_604">604</string>
+ <string name="foo_605">605</string>
+ <string name="foo_606">606</string>
+ <string name="foo_607">607</string>
+ <string name="foo_608">608</string>
+ <string name="foo_609">609</string>
+ <string name="foo_610">610</string>
+ <string name="foo_611">611</string>
+ <string name="foo_612">612</string>
+ <string name="foo_613">613</string>
+ <string name="foo_614">614</string>
+ <string name="foo_615">615</string>
+ <string name="foo_616">616</string>
+ <string name="foo_617">617</string>
+ <string name="foo_618">618</string>
+ <string name="foo_619">619</string>
+ <string name="foo_620">620</string>
+ <string name="foo_621">621</string>
+ <string name="foo_622">622</string>
+ <string name="foo_623">623</string>
+ <string name="foo_624">624</string>
+ <string name="foo_625">625</string>
+ <string name="foo_626">626</string>
+ <string name="foo_627">627</string>
+ <string name="foo_628">628</string>
+ <string name="foo_629">629</string>
+ <string name="foo_630">630</string>
+ <string name="foo_631">631</string>
+ <string name="foo_632">632</string>
+ <string name="foo_633">633</string>
+ <string name="foo_634">634</string>
+ <string name="foo_635">635</string>
+ <string name="foo_636">636</string>
+ <string name="foo_637">637</string>
+ <string name="foo_638">638</string>
+ <string name="foo_639">639</string>
+ <string name="foo_640">640</string>
+ <string name="foo_641">641</string>
+ <string name="foo_642">642</string>
+ <string name="foo_643">643</string>
+ <string name="foo_644">644</string>
+ <string name="foo_645">645</string>
+ <string name="foo_646">646</string>
+ <string name="foo_647">647</string>
+ <string name="foo_648">648</string>
+ <string name="foo_649">649</string>
+ <string name="foo_650">650</string>
+ <string name="foo_651">651</string>
+ <string name="foo_652">652</string>
+ <string name="foo_653">653</string>
+ <string name="foo_654">654</string>
+ <string name="foo_655">655</string>
+ <string name="foo_656">656</string>
+ <string name="foo_657">657</string>
+ <string name="foo_658">658</string>
+ <string name="foo_659">659</string>
+ <string name="foo_660">660</string>
+ <string name="foo_661">661</string>
+ <string name="foo_662">662</string>
+ <string name="foo_663">663</string>
+ <string name="foo_664">664</string>
+ <string name="foo_665">665</string>
+ <string name="foo_666">666</string>
+ <string name="foo_667">667</string>
+ <string name="foo_668">668</string>
+ <string name="foo_669">669</string>
+ <string name="foo_670">670</string>
+ <string name="foo_671">671</string>
+ <string name="foo_672">672</string>
+ <string name="foo_673">673</string>
+ <string name="foo_674">674</string>
+ <string name="foo_675">675</string>
+ <string name="foo_676">676</string>
+ <string name="foo_677">677</string>
+ <string name="foo_678">678</string>
+ <string name="foo_679">679</string>
+ <string name="foo_680">680</string>
+ <string name="foo_681">681</string>
+ <string name="foo_682">682</string>
+ <string name="foo_683">683</string>
+ <string name="foo_684">684</string>
+ <string name="foo_685">685</string>
+ <string name="foo_686">686</string>
+ <string name="foo_687">687</string>
+ <string name="foo_688">688</string>
+ <string name="foo_689">689</string>
+ <string name="foo_690">690</string>
+ <string name="foo_691">691</string>
+ <string name="foo_692">692</string>
+ <string name="foo_693">693</string>
+ <string name="foo_694">694</string>
+ <string name="foo_695">695</string>
+ <string name="foo_696">696</string>
+ <string name="foo_697">697</string>
+ <string name="foo_698">698</string>
+ <string name="foo_699">699</string>
+ <string name="foo_700">700</string>
+ <string name="foo_701">701</string>
+ <string name="foo_702">702</string>
+ <string name="foo_703">703</string>
+ <string name="foo_704">704</string>
+ <string name="foo_705">705</string>
+ <string name="foo_706">706</string>
+ <string name="foo_707">707</string>
+ <string name="foo_708">708</string>
+ <string name="foo_709">709</string>
+ <string name="foo_710">710</string>
+ <string name="foo_711">711</string>
+ <string name="foo_712">712</string>
+ <string name="foo_713">713</string>
+ <string name="foo_714">714</string>
+ <string name="foo_715">715</string>
+ <string name="foo_716">716</string>
+ <string name="foo_717">717</string>
+ <string name="foo_718">718</string>
+ <string name="foo_719">719</string>
+ <string name="foo_720">720</string>
+ <string name="foo_721">721</string>
+ <string name="foo_722">722</string>
+ <string name="foo_723">723</string>
+ <string name="foo_724">724</string>
+ <string name="foo_725">725</string>
+ <string name="foo_726">726</string>
+ <string name="foo_727">727</string>
+ <string name="foo_728">728</string>
+ <string name="foo_729">729</string>
+ <string name="foo_730">730</string>
+ <string name="foo_731">731</string>
+ <string name="foo_732">732</string>
+ <string name="foo_733">733</string>
+ <string name="foo_734">734</string>
+ <string name="foo_735">735</string>
+ <string name="foo_736">736</string>
+ <string name="foo_737">737</string>
+ <string name="foo_738">738</string>
+ <string name="foo_739">739</string>
+ <string name="foo_740">740</string>
+ <string name="foo_741">741</string>
+ <string name="foo_742">742</string>
+ <string name="foo_743">743</string>
+ <string name="foo_744">744</string>
+ <string name="foo_745">745</string>
+ <string name="foo_746">746</string>
+ <string name="foo_747">747</string>
+ <string name="foo_748">748</string>
+ <string name="foo_749">749</string>
+ <string name="foo_750">750</string>
+ <string name="foo_751">751</string>
+ <string name="foo_752">752</string>
+ <string name="foo_753">753</string>
+ <string name="foo_754">754</string>
+ <string name="foo_755">755</string>
+ <string name="foo_756">756</string>
+ <string name="foo_757">757</string>
+ <string name="foo_758">758</string>
+ <string name="foo_759">759</string>
+ <string name="foo_760">760</string>
+ <string name="foo_761">761</string>
+ <string name="foo_762">762</string>
+ <string name="foo_763">763</string>
+ <string name="foo_764">764</string>
+ <string name="foo_765">765</string>
+ <string name="foo_766">766</string>
+ <string name="foo_767">767</string>
+ <string name="foo_768">768</string>
+ <string name="foo_769">769</string>
+ <string name="foo_770">770</string>
+ <string name="foo_771">771</string>
+ <string name="foo_772">772</string>
+ <string name="foo_773">773</string>
+ <string name="foo_774">774</string>
+ <string name="foo_775">775</string>
+ <string name="foo_776">776</string>
+ <string name="foo_777">777</string>
+ <string name="foo_778">778</string>
+ <string name="foo_779">779</string>
+ <string name="foo_780">780</string>
+ <string name="foo_781">781</string>
+ <string name="foo_782">782</string>
+ <string name="foo_783">783</string>
+ <string name="foo_784">784</string>
+ <string name="foo_785">785</string>
+ <string name="foo_786">786</string>
+ <string name="foo_787">787</string>
+ <string name="foo_788">788</string>
+ <string name="foo_789">789</string>
+ <string name="foo_790">790</string>
+ <string name="foo_791">791</string>
+ <string name="foo_792">792</string>
+ <string name="foo_793">793</string>
+ <string name="foo_794">794</string>
+ <string name="foo_795">795</string>
+ <string name="foo_796">796</string>
+ <string name="foo_797">797</string>
+ <string name="foo_798">798</string>
+ <string name="foo_799">799</string>
+ <string name="foo_800">800</string>
+ <string name="foo_801">801</string>
+ <string name="foo_802">802</string>
+ <string name="foo_803">803</string>
+ <string name="foo_804">804</string>
+ <string name="foo_805">805</string>
+ <string name="foo_806">806</string>
+ <string name="foo_807">807</string>
+ <string name="foo_808">808</string>
+ <string name="foo_809">809</string>
+ <string name="foo_810">810</string>
+ <string name="foo_811">811</string>
+ <string name="foo_812">812</string>
+ <string name="foo_813">813</string>
+ <string name="foo_814">814</string>
+ <string name="foo_815">815</string>
+ <string name="foo_816">816</string>
+ <string name="foo_817">817</string>
+ <string name="foo_818">818</string>
+ <string name="foo_819">819</string>
+ <string name="foo_820">820</string>
+ <string name="foo_821">821</string>
+ <string name="foo_822">822</string>
+ <string name="foo_823">823</string>
+ <string name="foo_824">824</string>
+ <string name="foo_825">825</string>
+ <string name="foo_826">826</string>
+ <string name="foo_827">827</string>
+ <string name="foo_828">828</string>
+ <string name="foo_829">829</string>
+ <string name="foo_830">830</string>
+ <string name="foo_831">831</string>
+ <string name="foo_832">832</string>
+ <string name="foo_833">833</string>
+ <string name="foo_834">834</string>
+ <string name="foo_835">835</string>
+ <string name="foo_836">836</string>
+ <string name="foo_837">837</string>
+ <string name="foo_838">838</string>
+ <string name="foo_839">839</string>
+ <string name="foo_840">840</string>
+ <string name="foo_841">841</string>
+ <string name="foo_842">842</string>
+ <string name="foo_843">843</string>
+ <string name="foo_844">844</string>
+ <string name="foo_845">845</string>
+ <string name="foo_846">846</string>
+ <string name="foo_847">847</string>
+ <string name="foo_848">848</string>
+ <string name="foo_849">849</string>
+ <string name="foo_850">850</string>
+ <string name="foo_851">851</string>
+ <string name="foo_852">852</string>
+ <string name="foo_853">853</string>
+ <string name="foo_854">854</string>
+ <string name="foo_855">855</string>
+ <string name="foo_856">856</string>
+ <string name="foo_857">857</string>
+ <string name="foo_858">858</string>
+ <string name="foo_859">859</string>
+ <string name="foo_860">860</string>
+ <string name="foo_861">861</string>
+ <string name="foo_862">862</string>
+ <string name="foo_863">863</string>
+ <string name="foo_864">864</string>
+ <string name="foo_865">865</string>
+ <string name="foo_866">866</string>
+ <string name="foo_867">867</string>
+ <string name="foo_868">868</string>
+ <string name="foo_869">869</string>
+ <string name="foo_870">870</string>
+ <string name="foo_871">871</string>
+ <string name="foo_872">872</string>
+ <string name="foo_873">873</string>
+ <string name="foo_874">874</string>
+ <string name="foo_875">875</string>
+ <string name="foo_876">876</string>
+ <string name="foo_877">877</string>
+ <string name="foo_878">878</string>
+ <string name="foo_879">879</string>
+ <string name="foo_880">880</string>
+ <string name="foo_881">881</string>
+ <string name="foo_882">882</string>
+ <string name="foo_883">883</string>
+ <string name="foo_884">884</string>
+ <string name="foo_885">885</string>
+ <string name="foo_886">886</string>
+ <string name="foo_887">887</string>
+ <string name="foo_888">888</string>
+ <string name="foo_889">889</string>
+ <string name="foo_890">890</string>
+ <string name="foo_891">891</string>
+ <string name="foo_892">892</string>
+ <string name="foo_893">893</string>
+ <string name="foo_894">894</string>
+ <string name="foo_895">895</string>
+ <string name="foo_896">896</string>
+ <string name="foo_897">897</string>
+ <string name="foo_898">898</string>
+ <string name="foo_899">899</string>
+ <string name="foo_900">900</string>
+ <string name="foo_901">901</string>
+ <string name="foo_902">902</string>
+ <string name="foo_903">903</string>
+ <string name="foo_904">904</string>
+ <string name="foo_905">905</string>
+ <string name="foo_906">906</string>
+ <string name="foo_907">907</string>
+ <string name="foo_908">908</string>
+ <string name="foo_909">909</string>
+ <string name="foo_910">910</string>
+ <string name="foo_911">911</string>
+ <string name="foo_912">912</string>
+ <string name="foo_913">913</string>
+ <string name="foo_914">914</string>
+ <string name="foo_915">915</string>
+ <string name="foo_916">916</string>
+ <string name="foo_917">917</string>
+ <string name="foo_918">918</string>
+ <string name="foo_919">919</string>
+ <string name="foo_920">920</string>
+ <string name="foo_921">921</string>
+ <string name="foo_922">922</string>
+ <string name="foo_923">923</string>
+ <string name="foo_924">924</string>
+ <string name="foo_925">925</string>
+ <string name="foo_926">926</string>
+ <string name="foo_927">927</string>
+ <string name="foo_928">928</string>
+ <string name="foo_929">929</string>
+ <string name="foo_930">930</string>
+ <string name="foo_931">931</string>
+ <string name="foo_932">932</string>
+ <string name="foo_933">933</string>
+ <string name="foo_934">934</string>
+ <string name="foo_935">935</string>
+ <string name="foo_936">936</string>
+ <string name="foo_937">937</string>
+ <string name="foo_938">938</string>
+ <string name="foo_939">939</string>
+ <string name="foo_940">940</string>
+ <string name="foo_941">941</string>
+ <string name="foo_942">942</string>
+ <string name="foo_943">943</string>
+ <string name="foo_944">944</string>
+ <string name="foo_945">945</string>
+ <string name="foo_946">946</string>
+ <string name="foo_947">947</string>
+ <string name="foo_948">948</string>
+ <string name="foo_949">949</string>
+ <string name="foo_950">950</string>
+ <string name="foo_951">951</string>
+ <string name="foo_952">952</string>
+ <string name="foo_953">953</string>
+ <string name="foo_954">954</string>
+ <string name="foo_955">955</string>
+ <string name="foo_956">956</string>
+ <string name="foo_957">957</string>
+ <string name="foo_958">958</string>
+ <string name="foo_959">959</string>
+ <string name="foo_960">960</string>
+ <string name="foo_961">961</string>
+ <string name="foo_962">962</string>
+ <string name="foo_963">963</string>
+ <string name="foo_964">964</string>
+ <string name="foo_965">965</string>
+ <string name="foo_966">966</string>
+ <string name="foo_967">967</string>
+ <string name="foo_968">968</string>
+ <string name="foo_969">969</string>
+ <string name="foo_970">970</string>
+ <string name="foo_971">971</string>
+ <string name="foo_972">972</string>
+ <string name="foo_973">973</string>
+ <string name="foo_974">974</string>
+ <string name="foo_975">975</string>
+ <string name="foo_976">976</string>
+ <string name="foo_977">977</string>
+ <string name="foo_978">978</string>
+ <string name="foo_979">979</string>
+ <string name="foo_980">980</string>
+ <string name="foo_981">981</string>
+ <string name="foo_982">982</string>
+ <string name="foo_983">983</string>
+ <string name="foo_984">984</string>
+ <string name="foo_985">985</string>
+ <string name="foo_986">986</string>
+ <string name="foo_987">987</string>
+ <string name="foo_988">988</string>
+ <string name="foo_989">989</string>
+ <string name="foo_990">990</string>
+ <string name="foo_991">991</string>
+ <string name="foo_992">992</string>
+ <string name="foo_993">993</string>
+ <string name="foo_994">994</string>
+ <string name="foo_995">995</string>
+ <string name="foo_996">996</string>
+ <string name="foo_997">997</string>
+ <string name="foo_998">998</string>
+ <string name="foo_999">999</string>
+</resources>
diff --git a/libs/androidfw/tests/data/sparse/res/values/values.xml b/libs/androidfw/tests/data/sparse/res/values/values.xml
new file mode 100644
index 0000000..5cc42d8
--- /dev/null
+++ b/libs/androidfw/tests/data/sparse/res/values/values.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <integer name="foo_0">0</integer>
+ <integer name="foo_1">1</integer>
+ <integer name="foo_2">2</integer>
+ <integer name="foo_3">3</integer>
+ <integer name="foo_4">4</integer>
+ <integer name="foo_5">5</integer>
+ <integer name="foo_6">6</integer>
+ <integer name="foo_7">7</integer>
+ <integer name="foo_8">8</integer>
+ <integer name="foo_9">9</integer>
+</resources>
diff --git a/libs/androidfw/tests/data/sparse/sparse.apk b/libs/androidfw/tests/data/sparse/sparse.apk
new file mode 100644
index 0000000..1f9bba3
--- /dev/null
+++ b/libs/androidfw/tests/data/sparse/sparse.apk
Binary files differ
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index ff40c8a..9515b82 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -47,6 +47,7 @@
renderthread/RenderThread.cpp \
renderthread/TimeLord.cpp \
renderthread/Frame.cpp \
+ service/GraphicsStatsService.cpp \
thread/TaskManager.cpp \
utils/Blur.cpp \
utils/GLUtils.cpp \
@@ -293,6 +294,7 @@
tests/unit/GlopBuilderTests.cpp \
tests/unit/GpuMemoryTrackerTests.cpp \
tests/unit/GradientCacheTests.cpp \
+ tests/unit/GraphicsStatsServiceTests.cpp \
tests/unit/LayerUpdateQueueTests.cpp \
tests/unit/LeakCheckTests.cpp \
tests/unit/LinearAllocatorTests.cpp \
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 2132c2b..7be71ee 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -110,7 +110,7 @@
}
// Only called when dumping stats, less performance sensitive
-static uint32_t frameTimeForFrameCountIndex(uint32_t index) {
+int32_t JankTracker::frameTimeForFrameCountIndex(uint32_t index) {
index = index + kBucketMinThreshold;
if (index > kBucket2msIntervals) {
index += (index - kBucket2msIntervals);
@@ -123,6 +123,10 @@
return index;
}
+int32_t JankTracker::frameTimeForSlowFrameCountIndex(uint32_t index) {
+ return (index * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs;
+}
+
JankTracker::JankTracker(const DisplayInfo& displayInfo) {
// By default this will use malloc memory. It may be moved later to ashmem
// if there is shared space for it and a request comes in to do that.
@@ -161,8 +165,25 @@
mData = nullptr;
}
+void JankTracker::rotateStorage() {
+ // If we are mapped we want to stop using the ashmem backend and switch to malloc
+ // We are expecting a switchStorageToAshmem call to follow this, but it's not guaranteed
+ // If we aren't sitting on top of ashmem then just do a reset() as it's functionally
+ // equivalent do a free, malloc, reset.
+ if (mIsMapped) {
+ freeData();
+ mData = new ProfileData;
+ }
+ reset();
+}
+
void JankTracker::switchStorageToAshmem(int ashmemfd) {
int regionSize = ashmem_get_size_region(ashmemfd);
+ if (regionSize < 0) {
+ int err = errno;
+ ALOGW("Failed to get ashmem region size from fd %d, err %d %s", ashmemfd, err, strerror(err));
+ return;
+ }
if (regionSize < static_cast<int>(sizeof(ProfileData))) {
ALOGW("Ashmem region is too small! Received %d, required %u",
regionSize, static_cast<unsigned int>(sizeof(ProfileData)));
@@ -279,15 +300,19 @@
}
}
-void JankTracker::dumpBuffer(const void* buffer, size_t bufsize, int fd) {
- if (bufsize < sizeof(ProfileData)) {
- return;
+void JankTracker::dumpData(int fd, const ProfileDataDescription* description, const ProfileData* data) {
+ if (description) {
+ switch (description->type) {
+ case JankTrackerType::Generic:
+ break;
+ case JankTrackerType::Package:
+ dprintf(fd, "\nPackage: %s", description->name.c_str());
+ break;
+ case JankTrackerType::Window:
+ dprintf(fd, "\nWindow: %s", description->name.c_str());
+ break;
+ }
}
- const ProfileData* data = reinterpret_cast<const ProfileData*>(buffer);
- dumpData(data, fd);
-}
-
-void JankTracker::dumpData(const ProfileData* data, int fd) {
if (sFrameStart != FrameInfoIndex::IntendedVsync) {
dprintf(fd, "\nNote: Data has been filtered!");
}
@@ -308,7 +333,7 @@
data->frameCounts[i]);
}
for (size_t i = 0; i < data->slowFrameCounts.size(); i++) {
- dprintf(fd, " %zums=%u", (i * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs,
+ dprintf(fd, " %ums=%u", frameTimeForSlowFrameCountIndex(i),
data->slowFrameCounts[i]);
}
dprintf(fd, "\n");
diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h
index 8b482d5..6ff5d89 100644
--- a/libs/hwui/JankTracker.h
+++ b/libs/hwui/JankTracker.h
@@ -54,29 +54,50 @@
nsecs_t statStartTime;
};
+enum class JankTrackerType {
+ // The default, means there's no description set
+ Generic,
+ // The profile data represents a package
+ Package,
+ // The profile data is for a specific window
+ Window,
+};
+
+// Metadata about the ProfileData being collected
+struct ProfileDataDescription {
+ JankTrackerType type;
+ std::string name;
+};
+
// TODO: Replace DrawProfiler with this
class JankTracker {
public:
explicit JankTracker(const DisplayInfo& displayInfo);
~JankTracker();
+ void setDescription(JankTrackerType type, const std::string&& name) {
+ mDescription.type = type;
+ mDescription.name = name;
+ }
+
void addFrame(const FrameInfo& frame);
- void dump(int fd) { dumpData(mData, fd); }
+ void dump(int fd) { dumpData(fd, &mDescription, mData); }
void reset();
+ void rotateStorage();
void switchStorageToAshmem(int ashmemfd);
uint32_t findPercentile(int p) { return findPercentile(mData, p); }
-
- ANDROID_API static void dumpBuffer(const void* buffer, size_t bufsize, int fd);
+ static int32_t frameTimeForFrameCountIndex(uint32_t index);
+ static int32_t frameTimeForSlowFrameCountIndex(uint32_t index);
private:
void freeData();
void setFrameInterval(nsecs_t frameIntervalNanos);
static uint32_t findPercentile(const ProfileData* data, int p);
- static void dumpData(const ProfileData* data, int fd);
+ static void dumpData(int fd, const ProfileDataDescription* description, const ProfileData* data);
std::array<int64_t, NUM_BUCKETS> mThresholds;
int64_t mFrameInterval;
@@ -90,6 +111,7 @@
nsecs_t mDequeueTimeForgiveness = 0;
ProfileData* mData;
bool mIsMapped = false;
+ ProfileDataDescription mDescription;
};
} /* namespace uirenderer */
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 09e34bf..2931255 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -211,7 +211,7 @@
return sRenderPipelineType;
}
char prop[PROPERTY_VALUE_MAX];
- property_get(PROPERTY_DEFAULT_RENDERER, prop, "opengl");
+ property_get(PROPERTY_RENDERER, prop, "opengl");
if (!strcmp(prop, "skiagl") ) {
sRenderPipelineType = RenderPipelineType::SkiaGL;
} else if (!strcmp(prop, "skiavk") ) {
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 6dc0cb3..9db6449 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -156,7 +156,7 @@
* Allows to set rendering pipeline mode to OpenGL (default), Skia OpenGL
* or Vulkan.
*/
-#define PROPERTY_DEFAULT_RENDERER "debug.hwui.default_renderer"
+#define PROPERTY_RENDERER "debug.hwui.renderer"
///////////////////////////////////////////////////////////////////////////////
// Runtime configuration properties
diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp
index 9239986..760c10c 100644
--- a/libs/hwui/SkiaShader.cpp
+++ b/libs/hwui/SkiaShader.cpp
@@ -217,6 +217,7 @@
const float height = outData->bitmapTexture->height();
description->hasBitmap = true;
+ description->hasLinearTexture = outData->bitmapTexture->isLinear();
description->isShaderBitmapExternal = hwuiBitmap->isHardware();
// gralloc doesn't support non-clamp modes
if (hwuiBitmap->isHardware() || (!caches.extensions().hasNPot()
diff --git a/libs/hwui/hwui_static_deps.mk b/libs/hwui/hwui_static_deps.mk
index a75fd6a..8826cfc 100644
--- a/libs/hwui/hwui_static_deps.mk
+++ b/libs/hwui/hwui_static_deps.mk
@@ -22,11 +22,12 @@
libskia \
libui \
libgui \
- libprotobuf-cpp-lite \
+ libprotobuf-cpp-full \
libharfbuzz_ng \
libft2 \
libminikin \
- libandroidfw
+ libandroidfw \
+ libRScpp
-# enable RENDERSCRIPT
-LOCAL_SHARED_LIBRARIES += libRScpp
+LOCAL_STATIC_LIBRARIES += \
+ libplatformprotos
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index a53e5e0..02a9ffa 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -590,6 +590,7 @@
}
void CanvasContext::dumpFrames(int fd) {
+ mJankTracker.dump(fd);
FILE* file = fdopen(fd, "a");
fprintf(file, "\n\n---PROFILEDATA---\n");
for (size_t i = 0; i < static_cast<size_t>(FrameInfoIndex::NumIndexes); i++) {
@@ -615,6 +616,10 @@
mRenderThread.jankTracker().reset();
}
+void CanvasContext::setName(const std::string&& name) {
+ mJankTracker.setDescription(JankTrackerType::Window, std::move(name));
+}
+
void CanvasContext::serializeDisplayListTree() {
#if ENABLE_RENDERNODE_SERIALIZATION
using namespace google::protobuf::io;
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index aa01caa..738c091 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -155,8 +155,7 @@
void dumpFrames(int fd);
void resetFrameStats();
- void setName(const std::string&& name) { mName = name; }
- const std::string& name() { return mName; }
+ void setName(const std::string&& name);
void serializeDisplayListTree();
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 7020be0..860725b 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -106,7 +106,10 @@
// Now that extensions are loaded, pick a swap behavior
if (Properties::enablePartialUpdates) {
- if (Properties::useBufferAge && EglExtensions.bufferAge) {
+ // An Adreno driver bug is causing rendering problems for SkiaGL with
+ // buffer age swap behavior (b/31957043). To temporarily workaround,
+ // we will use preserved swap behavior.
+ if (Properties::useBufferAge && EglExtensions.bufferAge && !Properties::isSkiaEnabled()) {
mSwapBehavior = SwapBehavior::BufferAge;
} else {
mSwapBehavior = SwapBehavior::Preserved;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 11614fa..f4a4773 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -487,9 +487,22 @@
void RenderProxy::setProcessStatsBuffer(int fd) {
SETUP_TASK(setProcessStatsBuffer);
- args->thread = &mRenderThread;
+ auto& rt = RenderThread::getInstance();
+ args->thread = &rt;
args->fd = dup(fd);
- post(task);
+ rt.queue(task);
+}
+
+CREATE_BRIDGE1(rotateProcessStatsBuffer, RenderThread* thread) {
+ args->thread->jankTracker().rotateStorage();
+ return nullptr;
+}
+
+void RenderProxy::rotateProcessStatsBuffer() {
+ SETUP_TASK(rotateProcessStatsBuffer);
+ auto& rt = RenderThread::getInstance();
+ args->thread = &rt;
+ rt.queue(task);
}
int RenderProxy::getRenderThreadTid() {
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 1629090..a60ed55 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -113,7 +113,8 @@
uint32_t frameTimePercentile(int p);
ANDROID_API static void dumpGraphicsMemory(int fd);
- ANDROID_API void setProcessStatsBuffer(int fd);
+ ANDROID_API static void rotateProcessStatsBuffer();
+ ANDROID_API static void setProcessStatsBuffer(int fd);
ANDROID_API int getRenderThreadTid();
ANDROID_API void serializeDisplayListTree();
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index d121bcf..9bc5985 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -74,6 +74,7 @@
};
class ANDROID_API RenderThread : public Thread {
+ PREVENT_COPY_AND_ASSIGN(RenderThread);
public:
// RenderThread takes complete ownership of tasks that are queued
// and will delete them after they are run
diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp
new file mode 100644
index 0000000..ab6420e
--- /dev/null
+++ b/libs/hwui/service/GraphicsStatsService.cpp
@@ -0,0 +1,287 @@
+/*
+ * 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.
+ */
+
+#include "GraphicsStatsService.h"
+
+#include "JankTracker.h"
+
+#include <frameworks/base/core/proto/android/service/graphicsstats.pb.h>
+#include <google/protobuf/io/zero_copy_stream_impl.h>
+#include <log/log.h>
+
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+namespace android {
+namespace uirenderer {
+
+using namespace google::protobuf;
+
+constexpr int32_t sCurrentFileVersion = 1;
+constexpr int32_t sHeaderSize = 4;
+static_assert(sizeof(sCurrentFileVersion) == sHeaderSize, "Header size is wrong");
+
+constexpr int sHistogramSize =
+ std::tuple_size<decltype(ProfileData::frameCounts)>::value +
+ std::tuple_size<decltype(ProfileData::slowFrameCounts)>::value;
+
+static void mergeProfileDataIntoProto(service::GraphicsStatsProto* proto,
+ const std::string& package, int versionCode, int64_t startTime, int64_t endTime,
+ const ProfileData* data);
+static void dumpAsTextToFd(service::GraphicsStatsProto* proto, int outFd);
+
+bool GraphicsStatsService::parseFromFile(const std::string& path, service::GraphicsStatsProto* output) {
+
+ int fd = open(path.c_str(), O_RDONLY);
+ if (fd == -1) {
+ int err = errno;
+ // The file not existing is normal for addToDump(), so only log if
+ // we get an unexpected error
+ if (err != ENOENT) {
+ ALOGW("Failed to open '%s', errno=%d (%s)", path.c_str(), err, strerror(err));
+ }
+ return false;
+ }
+ uint32_t file_version;
+ ssize_t bytesRead = read(fd, &file_version, sHeaderSize);
+ if (bytesRead != sHeaderSize || file_version != sCurrentFileVersion) {
+ ALOGW("Failed to read '%s', bytesRead=%zd file_version=%d", path.c_str(), bytesRead,
+ file_version);
+ close(fd);
+ return false;
+ }
+
+ io::FileInputStream input(fd);
+ bool success = output->ParseFromZeroCopyStream(&input);
+ if (input.GetErrno() != 0) {
+ ALOGW("Error reading from fd=%d, path='%s' err=%d (%s)",
+ fd, path.c_str(), input.GetErrno(), strerror(input.GetErrno()));
+ success = false;
+ } else if (!success) {
+ ALOGW("Parse failed on '%s' error='%s'",
+ path.c_str(), output->InitializationErrorString().c_str());
+ }
+ close(fd);
+ return success;
+}
+
+void mergeProfileDataIntoProto(service::GraphicsStatsProto* proto, const std::string& package,
+ int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) {
+ if (proto->stats_start() == 0 || proto->stats_start() > startTime) {
+ proto->set_stats_start(startTime);
+ }
+ if (proto->stats_end() == 0 || proto->stats_end() < endTime) {
+ proto->set_stats_end(endTime);
+ }
+ proto->set_package_name(package);
+ proto->set_version_code(versionCode);
+ auto summary = proto->mutable_summary();
+ summary->set_total_frames(summary->total_frames() + data->totalFrameCount);
+ summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount);
+ summary->set_missed_vsync_count(
+ summary->missed_vsync_count() + data->jankTypeCounts[kMissedVsync]);
+ summary->set_high_input_latency_count(
+ summary->high_input_latency_count() + data->jankTypeCounts[kHighInputLatency]);
+ summary->set_slow_ui_thread_count(
+ summary->slow_ui_thread_count() + data->jankTypeCounts[kSlowUI]);
+ summary->set_slow_bitmap_upload_count(
+ summary->slow_bitmap_upload_count() + data->jankTypeCounts[kSlowSync]);
+ summary->set_slow_draw_count(
+ summary->slow_draw_count() + data->jankTypeCounts[kSlowRT]);
+
+ bool creatingHistogram = false;
+ if (proto->histogram_size() == 0) {
+ proto->mutable_histogram()->Reserve(sHistogramSize);
+ creatingHistogram = true;
+ } else if (proto->histogram_size() != sHistogramSize) {
+ LOG_ALWAYS_FATAL("Histogram size mismatch, proto is %d expected %d",
+ proto->histogram_size(), sHistogramSize);
+ }
+ for (size_t i = 0; i < data->frameCounts.size(); i++) {
+ service::GraphicsStatsHistogramBucketProto* bucket;
+ int32_t renderTime = JankTracker::frameTimeForFrameCountIndex(i);
+ if (creatingHistogram) {
+ bucket = proto->add_histogram();
+ bucket->set_render_millis(renderTime);
+ } else {
+ bucket = proto->mutable_histogram(i);
+ LOG_ALWAYS_FATAL_IF(bucket->render_millis() != renderTime,
+ "Frame time mistmatch %d vs. %d", bucket->render_millis(), renderTime);
+ }
+ bucket->set_frame_count(bucket->frame_count() + data->frameCounts[i]);
+ }
+ for (size_t i = 0; i < data->slowFrameCounts.size(); i++) {
+ service::GraphicsStatsHistogramBucketProto* bucket;
+ int32_t renderTime = JankTracker::frameTimeForSlowFrameCountIndex(i);
+ if (creatingHistogram) {
+ bucket = proto->add_histogram();
+ bucket->set_render_millis(renderTime);
+ } else {
+ constexpr int offset = std::tuple_size<decltype(ProfileData::frameCounts)>::value;
+ bucket = proto->mutable_histogram(offset + i);
+ LOG_ALWAYS_FATAL_IF(bucket->render_millis() != renderTime,
+ "Frame time mistmatch %d vs. %d", bucket->render_millis(), renderTime);
+ }
+ bucket->set_frame_count(bucket->frame_count() + data->slowFrameCounts[i]);
+ }
+}
+
+static int32_t findPercentile(service::GraphicsStatsProto* proto, int percentile) {
+ int32_t pos = percentile * proto->summary().total_frames() / 100;
+ int32_t remaining = proto->summary().total_frames() - pos;
+ for (auto it = proto->histogram().rbegin(); it != proto->histogram().rend(); ++it) {
+ remaining -= it->frame_count();
+ if (remaining <= 0) {
+ return it->render_millis();
+ }
+ }
+ return 0;
+}
+
+void dumpAsTextToFd(service::GraphicsStatsProto* proto, int fd) {
+ // This isn't a full validation, just enough that we can deref at will
+ LOG_ALWAYS_FATAL_IF(proto->package_name().empty()
+ || !proto->has_summary(), "package_name() '%s' summary %d",
+ proto->package_name().c_str(), proto->has_summary());
+ dprintf(fd, "\nPackage: %s", proto->package_name().c_str());
+ dprintf(fd, "\nVersion: %d", proto->version_code());
+ dprintf(fd, "\nStats since: %lldns", proto->stats_start());
+ dprintf(fd, "\nStats end: %lldns", proto->stats_end());
+ auto summary = proto->summary();
+ dprintf(fd, "\nTotal frames rendered: %d", summary.total_frames());
+ dprintf(fd, "\nJanky frames: %d (%.2f%%)", summary.janky_frames(),
+ (float) summary.janky_frames() / (float) summary.total_frames() * 100.0f);
+ dprintf(fd, "\n50th percentile: %dms", findPercentile(proto, 50));
+ dprintf(fd, "\n90th percentile: %dms", findPercentile(proto, 90));
+ dprintf(fd, "\n95th percentile: %dms", findPercentile(proto, 95));
+ dprintf(fd, "\n99th percentile: %dms", findPercentile(proto, 99));
+ dprintf(fd, "\nNumber Missed Vsync: %d", summary.missed_vsync_count());
+ dprintf(fd, "\nNumber High input latency: %d", summary.high_input_latency_count());
+ dprintf(fd, "\nNumber Slow UI thread: %d", summary.slow_ui_thread_count());
+ dprintf(fd, "\nNumber Slow bitmap uploads: %d", summary.slow_bitmap_upload_count());
+ dprintf(fd, "\nNumber Slow issue draw commands: %d", summary.slow_draw_count());
+ dprintf(fd, "\nHISTOGRAM:");
+ for (const auto& it : proto->histogram()) {
+ dprintf(fd, " %dms=%d", it.render_millis(), it.frame_count());
+ }
+ dprintf(fd, "\n");
+}
+
+void GraphicsStatsService::saveBuffer(const std::string& path, const std::string& package,
+ int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) {
+ service::GraphicsStatsProto statsProto;
+ if (!parseFromFile(path, &statsProto)) {
+ statsProto.Clear();
+ }
+ mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data);
+ // Although we might not have read any data from the file, merging the existing data
+ // should always fully-initialize the proto
+ LOG_ALWAYS_FATAL_IF(!statsProto.IsInitialized(), "%s",
+ statsProto.InitializationErrorString().c_str());
+ LOG_ALWAYS_FATAL_IF(statsProto.package_name().empty()
+ || !statsProto.has_summary(), "package_name() '%s' summary %d",
+ statsProto.package_name().c_str(), statsProto.has_summary());
+ int outFd = open(path.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0660);
+ if (outFd <= 0) {
+ int err = errno;
+ ALOGW("Failed to open '%s', error=%d (%s)", path.c_str(), err, strerror(err));
+ return;
+ }
+ int wrote = write(outFd, &sCurrentFileVersion, sHeaderSize);
+ if (wrote != sHeaderSize) {
+ int err = errno;
+ ALOGW("Failed to write header to '%s', returned=%d errno=%d (%s)",
+ path.c_str(), wrote, err, strerror(err));
+ close(outFd);
+ return;
+ }
+ {
+ io::FileOutputStream output(outFd);
+ bool success = statsProto.SerializeToZeroCopyStream(&output) && output.Flush();
+ if (output.GetErrno() != 0) {
+ ALOGW("Error writing to fd=%d, path='%s' err=%d (%s)",
+ outFd, path.c_str(), output.GetErrno(), strerror(output.GetErrno()));
+ success = false;
+ } else if (!success) {
+ ALOGW("Serialize failed on '%s' unknown error", path.c_str());
+ }
+ }
+ close(outFd);
+}
+
+class GraphicsStatsService::Dump {
+public:
+ Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {}
+ int fd() { return mFd; }
+ DumpType type() { return mType; }
+ service::GraphicsStatsServiceDumpProto& proto() { return mProto; }
+private:
+ int mFd;
+ DumpType mType;
+ service::GraphicsStatsServiceDumpProto mProto;
+};
+
+GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) {
+ return new Dump(outFd, type);
+}
+
+void GraphicsStatsService::addToDump(Dump* dump, const std::string& path, const std::string& package,
+ int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) {
+ service::GraphicsStatsProto statsProto;
+ if (!path.empty() && !parseFromFile(path, &statsProto)) {
+ statsProto.Clear();
+ }
+ if (data) {
+ mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data);
+ }
+ if (!statsProto.IsInitialized()) {
+ ALOGW("Failed to load profile data from path '%s' and data %p",
+ path.empty() ? "<empty>" : path.c_str(), data);
+ return;
+ }
+
+ if (dump->type() == DumpType::Protobuf) {
+ dump->proto().add_stats()->CopyFrom(statsProto);
+ } else {
+ dumpAsTextToFd(&statsProto, dump->fd());
+ }
+}
+
+void GraphicsStatsService::addToDump(Dump* dump, const std::string& path) {
+ service::GraphicsStatsProto statsProto;
+ if (!parseFromFile(path, &statsProto)) {
+ return;
+ }
+ if (dump->type() == DumpType::Protobuf) {
+ dump->proto().add_stats()->CopyFrom(statsProto);
+ } else {
+ dumpAsTextToFd(&statsProto, dump->fd());
+ }
+}
+
+void GraphicsStatsService::finishDump(Dump* dump) {
+ if (dump->type() == DumpType::Protobuf) {
+ io::FileOutputStream stream(dump->fd());
+ dump->proto().SerializeToZeroCopyStream(&stream);
+ }
+ delete dump;
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
\ No newline at end of file
diff --git a/libs/hwui/service/GraphicsStatsService.h b/libs/hwui/service/GraphicsStatsService.h
new file mode 100644
index 0000000..d0fd60e
--- /dev/null
+++ b/libs/hwui/service/GraphicsStatsService.h
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <string>
+
+#include "JankTracker.h"
+#include "utils/Macros.h"
+
+namespace android {
+namespace service {
+class GraphicsStatsProto;
+}
+
+namespace uirenderer {
+
+/*
+ * The exported entry points used by GraphicsStatsService.java in f/b/services/core
+ *
+ * NOTE: Avoid exporting a requirement on the protobuf itself. Keep the usage
+ * of the generated protobuf classes internal to libhwui.so to minimize library
+ * bloat.
+ */
+class GraphicsStatsService {
+public:
+ class Dump;
+ enum class DumpType {
+ Text,
+ Protobuf,
+ };
+
+ ANDROID_API static void saveBuffer(const std::string& path, const std::string& package,
+ int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data);
+
+ ANDROID_API static Dump* createDump(int outFd, DumpType type);
+ ANDROID_API static void addToDump(Dump* dump, const std::string& path, const std::string& package,
+ int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data);
+ ANDROID_API static void addToDump(Dump* dump, const std::string& path);
+ ANDROID_API static void finishDump(Dump* dump);
+
+ // Visible for testing
+ static bool parseFromFile(const std::string& path, service::GraphicsStatsProto* output);
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
\ No newline at end of file
diff --git a/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
new file mode 100644
index 0000000..cfe1134
--- /dev/null
+++ b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "service/GraphicsStatsService.h"
+
+#include <frameworks/base/core/proto/android/service/graphicsstats.pb.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+std::string findRootPath() {
+ char path[1024];
+ ssize_t r = readlink("/proc/self/exe", path, 1024);
+ // < 1023 because we need room for the null terminator
+ if (r <= 0 || r > 1023) {
+ int err = errno;
+ fprintf(stderr, "Failed to read from /proc/self/exe; r=%zd, err=%d (%s)\n",
+ r, err, strerror(err));
+ exit(EXIT_FAILURE);
+ }
+ while (--r > 0) {
+ if (path[r] == '/') {
+ path[r] = '\0';
+ return std::string(path);
+ }
+ }
+ return std::string();
+}
+
+// No code left untested
+TEST(GraphicsStats, findRootPath) {
+ std::string expected = "/data/nativetest/hwui_unit_tests";
+ EXPECT_EQ(expected, findRootPath());
+}
+
+TEST(GraphicsStats, saveLoad) {
+ std::string path = findRootPath() + "/test_saveLoad";
+ std::string packageName = "com.test.saveLoad";
+ ProfileData mockData;
+ mockData.jankFrameCount = 20;
+ mockData.totalFrameCount = 100;
+ mockData.statStartTime = 10000;
+ // Fill with patterned data we can recognize but which won't map to a
+ // memset or basic for iteration count
+ for (size_t i = 0; i < mockData.frameCounts.size(); i++) {
+ mockData.frameCounts[i] = ((i % 10) + 1) * 2;
+ }
+ for (size_t i = 0; i < mockData.slowFrameCounts.size(); i++) {
+ mockData.slowFrameCounts[i] = (i % 5) + 1;
+ }
+ GraphicsStatsService::saveBuffer(path, packageName, 5, 3000, 7000, &mockData);
+ service::GraphicsStatsProto loadedProto;
+ EXPECT_TRUE(GraphicsStatsService::parseFromFile(path, &loadedProto));
+ // Clean up the file
+ unlink(path.c_str());
+
+ EXPECT_EQ(packageName, loadedProto.package_name());
+ EXPECT_EQ(5, loadedProto.version_code());
+ EXPECT_EQ(3000, loadedProto.stats_start());
+ EXPECT_EQ(7000, loadedProto.stats_end());
+ // ASSERT here so we don't continue with a nullptr deref crash if this is false
+ ASSERT_TRUE(loadedProto.has_summary());
+ EXPECT_EQ(20, loadedProto.summary().janky_frames());
+ EXPECT_EQ(100, loadedProto.summary().total_frames());
+ EXPECT_EQ(mockData.frameCounts.size() + mockData.slowFrameCounts.size(),
+ (size_t) loadedProto.histogram_size());
+ for (size_t i = 0; i < (size_t) loadedProto.histogram_size(); i++) {
+ int expectedCount, expectedBucket;
+ if (i < mockData.frameCounts.size()) {
+ expectedCount = ((i % 10) + 1) * 2;
+ expectedBucket = JankTracker::frameTimeForFrameCountIndex(i);
+ } else {
+ int temp = i - mockData.frameCounts.size();
+ expectedCount = (temp % 5) + 1;
+ expectedBucket = JankTracker::frameTimeForSlowFrameCountIndex(temp);
+ }
+ EXPECT_EQ(expectedCount, loadedProto.histogram().Get(i).frame_count());
+ EXPECT_EQ(expectedBucket, loadedProto.histogram().Get(i).render_millis());
+ }
+}
+
+TEST(GraphicsStats, merge) {
+ std::string path = findRootPath() + "/test_merge";
+ std::string packageName = "com.test.merge";
+ ProfileData mockData;
+ mockData.jankFrameCount = 20;
+ mockData.totalFrameCount = 100;
+ mockData.statStartTime = 10000;
+ // Fill with patterned data we can recognize but which won't map to a
+ // memset or basic for iteration count
+ for (size_t i = 0; i < mockData.frameCounts.size(); i++) {
+ mockData.frameCounts[i] = ((i % 10) + 1) * 2;
+ }
+ for (size_t i = 0; i < mockData.slowFrameCounts.size(); i++) {
+ mockData.slowFrameCounts[i] = (i % 5) + 1;
+ }
+ GraphicsStatsService::saveBuffer(path, packageName, 5, 3000, 7000, &mockData);
+ mockData.jankFrameCount = 50;
+ mockData.totalFrameCount = 500;
+ for (size_t i = 0; i < mockData.frameCounts.size(); i++) {
+ mockData.frameCounts[i] = (i % 5) + 1;
+ }
+ for (size_t i = 0; i < mockData.slowFrameCounts.size(); i++) {
+ mockData.slowFrameCounts[i] = ((i % 10) + 1) * 2;
+ }
+ GraphicsStatsService::saveBuffer(path, packageName, 5, 7050, 10000, &mockData);
+
+ service::GraphicsStatsProto loadedProto;
+ EXPECT_TRUE(GraphicsStatsService::parseFromFile(path, &loadedProto));
+ // Clean up the file
+ unlink(path.c_str());
+
+ EXPECT_EQ(packageName, loadedProto.package_name());
+ EXPECT_EQ(5, loadedProto.version_code());
+ EXPECT_EQ(3000, loadedProto.stats_start());
+ EXPECT_EQ(10000, loadedProto.stats_end());
+ // ASSERT here so we don't continue with a nullptr deref crash if this is false
+ ASSERT_TRUE(loadedProto.has_summary());
+ EXPECT_EQ(20 + 50, loadedProto.summary().janky_frames());
+ EXPECT_EQ(100 + 500, loadedProto.summary().total_frames());
+ EXPECT_EQ(mockData.frameCounts.size() + mockData.slowFrameCounts.size(),
+ (size_t) loadedProto.histogram_size());
+ for (size_t i = 0; i < (size_t) loadedProto.histogram_size(); i++) {
+ int expectedCount, expectedBucket;
+ if (i < mockData.frameCounts.size()) {
+ expectedCount = ((i % 10) + 1) * 2;
+ expectedCount += (i % 5) + 1;
+ expectedBucket = JankTracker::frameTimeForFrameCountIndex(i);
+ } else {
+ int temp = i - mockData.frameCounts.size();
+ expectedCount = (temp % 5) + 1;
+ expectedCount += ((temp % 10) + 1) * 2;
+ expectedBucket = JankTracker::frameTimeForSlowFrameCountIndex(temp);
+ }
+ EXPECT_EQ(expectedCount, loadedProto.histogram().Get(i).frame_count());
+ EXPECT_EQ(expectedBucket, loadedProto.histogram().Get(i).render_millis());
+ }
+}
\ No newline at end of file
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 7a6499d..075a84f 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -20,6 +20,7 @@
import android.annotation.SystemApi;
import android.app.ActivityThread;
import android.hardware.Camera;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -1257,6 +1258,93 @@
private native void setParameter(String nameValuePair);
+ /**
+ * Returns Metrics data about the current media container.
+ *
+ * @return the set of keys and values available for the media being
+ * handled by this instance of MediaExtractor. The keys, data types,
+ * and meaning are described in the following table.
+ *
+ * <table style="width: 0%">
+ * <thead>
+ * <tr>
+ * <th>Key</th>
+ * <th>Type</th>
+ * <th>Description</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td>{@code "ht"}</td>
+ * <td>Integer</td>
+ * <td>Height of the recorded video (pixels)</td>
+ * </tr><tr>
+ * <td>{@code "wid"}</td>
+ * <td>Integer</td>
+ * <td>Width of the recorded video (pixels)</td>
+ * </tr><tr>
+ * <td>{@code "frame-rate"}</td>
+ * <td>Integer</td>
+ * <td>Framerate of captured Video (frames per second)</td>
+ * </tr><tr>
+ * <td>{@code "video-bitrate"}</td>
+ * <td>Integer</td>
+ * <td>Bit rate of encoded video (bits per second)</td>
+ * </tr><tr>
+ * <td>{@code "video-iframe-interval"}</td>
+ * <td>Integer</td>
+ * <td>Interval between encoded IFrames (seconds)</td>
+ * </tr><tr>
+ * <td>{@code "video-timescale"}</td>
+ * <td>Integer</td>
+ * <td></td>
+ * </tr><tr>
+ * <td>{@code "video-encoder-profile"}</td>
+ * <td>Integer</td>
+ * <td>Video Encoder Profile, as defined in OpenMAX IL</td>
+ * </tr><tr>
+ * <td>{@code "video-encoder-level"}</td>
+ * <td>Integer</td>
+ * <td>Video Encoder Level, as defined in OpenMAX IL</td>
+ * </tr><tr>
+ * <td>{@code "audio-bitrate"}</td>
+ * <td>Integer</td>
+ * <td>Bitrate of encoded audio (bits per second)</td>
+ * </tr><tr>
+ * <td>{@code "audio-samplerate"}</td>
+ * <td>Integer</td>
+ * <td></td>
+ * </tr><tr>
+ * <td>{@code "audio-channels"}</td>
+ * <td>Integer</td>
+ * <td>Number of Audio Channels Captured</td>
+ * </tr><tr>
+ * <td>{@code "audio-timescale"}</td>
+ * <td>Integer</td>
+ * <td></td>
+ * </tr><tr>
+ * <td>{@code "movie-timescale"}</td>
+ * <td>Integer</td>
+ * <td></td>
+ * </tr><tr>
+ * <td>{@code "movie-timescale"}</td>
+ * <td>Integer</td>
+ * <td></td>
+ * </tr><tr>
+ * <td>{@code "capture-fps"}</td>
+ * <td>Integer</td>
+ * <td></td>
+ * </tr><tr>
+ * <td>{@code "rotation"}</td>
+ * <td>Integer</td>
+ * <td>Orientation of the Video (degrees)</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ */
+
+ public native Bundle getMetrics();
+
@Override
protected void finalize() { native_finalize(); }
}
diff --git a/media/java/android/media/VolumeShaper.java b/media/java/android/media/VolumeShaper.java
index 77af359..4a2c4d8 100644
--- a/media/java/android/media/VolumeShaper.java
+++ b/media/java/android/media/VolumeShaper.java
@@ -37,8 +37,9 @@
public final class VolumeShaper {
/* member variables */
private int mId;
- private final WeakReference<PlayerBase> mPlayerBase;
- private final WeakReference<PlayerProxy> mPlayerProxy;
+ private final WeakReference<PlayerBase> mWeakPlayerBase;
+ private final WeakReference<PlayerProxy> mWeakPlayerProxy;
+ private PlayerProxy mPlayerProxy;
/**
* Constructs a {@code VolumeShaper} from a {@link VolumeShaper.Configuration} and an
@@ -64,14 +65,14 @@
/* package */ VolumeShaper(
@NonNull Configuration configuration, @NonNull PlayerBase playerBase) {
- mPlayerBase = new WeakReference<PlayerBase>(playerBase);
+ mWeakPlayerBase = new WeakReference<PlayerBase>(playerBase);
mPlayerProxy = null;
+ mWeakPlayerProxy = null;
mId = applyPlayer(configuration, new Operation.Builder().defer().build());
}
/**
* @hide
- * TODO SystemApi
* Constructs a {@code VolumeShaper} from a {@link VolumeShaper.Configuration} and a
* {@code PlayerProxy} object. The PlayerProxy object requires that the configuration
* be set with a system VolumeShaper id (this is a reserved value).
@@ -80,12 +81,20 @@
* @param playerProxy
*/
public VolumeShaper(
- @NonNull Configuration configuration, @NonNull PlayerProxy playerProxy) {
+ @NonNull Configuration configuration,
+ @NonNull PlayerProxy playerProxy,
+ boolean keepReference) {
if (configuration.getId() < 0) {
throw new IllegalArgumentException("playerProxy configuration id must be specified");
}
- mPlayerProxy = new WeakReference<PlayerProxy>(playerProxy);
- mPlayerBase = null;
+ if (keepReference) {
+ mPlayerProxy = playerProxy;
+ mWeakPlayerProxy = null;
+ } else {
+ mWeakPlayerProxy = new WeakReference<PlayerProxy>(playerProxy);
+ mPlayerProxy = null;
+ }
+ mWeakPlayerBase = null;
mId = applyPlayer(configuration, new Operation.Builder().defer().build());
}
@@ -143,12 +152,13 @@
} catch (IllegalStateException ise) {
; // ok
}
- if (mPlayerBase != null) {
- mPlayerBase.clear();
+ if (mWeakPlayerBase != null) {
+ mWeakPlayerBase.clear();
}
- if (mPlayerProxy != null) {
- mPlayerProxy.clear();
+ if (mWeakPlayerProxy != null) {
+ mWeakPlayerProxy.clear();
}
+ mPlayerProxy = null;
}
@Override
@@ -167,11 +177,11 @@
@NonNull VolumeShaper.Configuration configuration,
@NonNull VolumeShaper.Operation operation) {
final int id;
- if (mPlayerProxy != null) {
+ if (mPlayerProxy != null || mWeakPlayerProxy != null) {
// The PlayerProxy accepts only one way transactions so
// the Configuration must have an id set to one of the system
// ids (a positive value less than 16).
- PlayerProxy player = mPlayerProxy.get();
+ PlayerProxy player = mWeakPlayerProxy != null ? mWeakPlayerProxy.get() : mPlayerProxy;
if (player == null) {
throw new IllegalStateException("player deallocated");
}
@@ -180,8 +190,8 @@
throw new IllegalArgumentException("proxy requires configuration with id");
}
player.applyVolumeShaper(configuration, operation);
- } else if (mPlayerBase != null) {
- PlayerBase player = mPlayerBase.get();
+ } else if (mWeakPlayerBase != null) {
+ PlayerBase player = mWeakPlayerBase.get();
if (player == null) {
throw new IllegalStateException("player deallocated");
}
@@ -210,14 +220,10 @@
*/
private @NonNull VolumeShaper.State getStatePlayer(int id) {
final VolumeShaper.State state;
- if (mPlayerProxy != null) {
- PlayerProxy player = mPlayerProxy.get();
- if (player == null) {
- throw new IllegalStateException("player deallocated");
- }
+ if (mPlayerProxy != null || mWeakPlayerProxy != null) {
throw new IllegalStateException("getStatePlayer not permitted through proxy");
- } else if (mPlayerBase != null) {
- PlayerBase player = mPlayerBase.get();
+ } else if (mWeakPlayerBase != null) {
+ PlayerBase player = mWeakPlayerBase.get();
if (player == null) {
throw new IllegalStateException("player deallocated");
}
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index 77946a6..6d8296a 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -2474,7 +2474,7 @@
*
* <p>This indicates the number of times interaction has happened.
*
- * <p>Type: INTEGER
+ * <p>Type: INTEGER (long)
* @see #COLUMN_INTERACTION_TYPE
*/
public static final String COLUMN_INTERACTION_COUNT = "interaction_count";
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 3b74ee7..35988d4 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -694,10 +694,16 @@
* {@link TvInputService}.
*/
public Builder(Context context, ComponentName component) {
- mContext = context;
+ if (context == null) {
+ throw new IllegalArgumentException("context cannot be null.");
+ }
Intent intent = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component);
mResolveInfo = context.getPackageManager().resolveService(intent,
PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+ if (mResolveInfo == null) {
+ throw new IllegalArgumentException("Invalid component. Can't find the service.");
+ }
+ mContext = context;
}
/**
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index ce4a453..6f44e6d 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -284,8 +284,8 @@
private boolean mOverlayViewEnabled;
private IBinder mWindowToken;
private Rect mOverlayFrame;
- private long mStartPositionMs;
- private long mCurrentPositionMs;
+ private long mStartPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
+ private long mCurrentPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
private final TimeShiftPositionTrackingRunnable
mTimeShiftPositionTrackingRunnable = new TimeShiftPositionTrackingRunnable();
@@ -304,7 +304,6 @@
mContext = context;
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mHandler = new Handler(context.getMainLooper());
- mCurrentPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
}
/**
@@ -631,6 +630,8 @@
@MainThread
@Override
public void run() {
+ timeShiftEnablePositionTracking(
+ status == TvInputManager.TIME_SHIFT_STATUS_AVAILABLE);
try {
if (DEBUG) Log.d(TAG, "notifyTimeShiftStatusChanged");
if (mSessionCallback != null) {
@@ -1465,7 +1466,8 @@
@Override
public void run() {
long startPositionMs = onTimeShiftGetStartPosition();
- if (mStartPositionMs != startPositionMs) {
+ if (mStartPositionMs == TvInputManager.TIME_SHIFT_INVALID_TIME
+ || mStartPositionMs != startPositionMs) {
mStartPositionMs = startPositionMs;
notifyTimeShiftStartPositionChanged(startPositionMs);
}
@@ -1476,7 +1478,8 @@
+ "position.");
currentPositionMs = mStartPositionMs;
}
- if (mCurrentPositionMs != currentPositionMs) {
+ if (mCurrentPositionMs == TvInputManager.TIME_SHIFT_INVALID_TIME
+ || mCurrentPositionMs != currentPositionMs) {
mCurrentPositionMs = currentPositionMs;
notifyTimeShiftCurrentPositionChanged(currentPositionMs);
}
diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp
index 7c509d2..77544eb 100644
--- a/media/jni/android_media_MediaRecorder.cpp
+++ b/media/jni/android_media_MediaRecorder.cpp
@@ -28,6 +28,7 @@
#include <gui/Surface.h>
#include <camera/Camera.h>
#include <media/mediarecorder.h>
+#include <media/MediaAnalyticsItem.h>
#include <media/stagefright/PersistentSurface.h>
#include <utils/threads.h>
@@ -35,6 +36,7 @@
#include "jni.h"
#include "JNIHelp.h"
+#include "android_media_MediaMetricsJNI.h"
#include "android_runtime/AndroidRuntime.h"
#include <system/audio.h>
@@ -625,6 +627,36 @@
"java/lang/IllegalArgumentException", "native_setInputSurface failed.");
}
+static jobject
+android_media_MediaRecorder_getMetrics(JNIEnv *env, jobject thiz)
+{
+ ALOGV("android_media_MediaRecorder_getMetrics");
+
+ sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
+ if (mr == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ // get what we have for the metrics from the codec
+ Parcel reply;
+ status_t err = mr->getMetrics(&reply);
+ if (err != OK) {
+ ALOGE("getMetrics failed");
+ return (jobject) NULL;
+ }
+
+ // build and return the Bundle
+ MediaAnalyticsItem *item = new MediaAnalyticsItem;
+ item->readFromParcel(reply);
+ jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item, NULL);
+
+ // housekeeping
+ delete item;
+ item = NULL;
+ return mybundle;
+
+}
// ----------------------------------------------------------------------------
static const JNINativeMethod gMethods[] = {
@@ -655,6 +687,8 @@
(void *)android_media_MediaRecorder_native_setup},
{"native_finalize", "()V", (void *)android_media_MediaRecorder_native_finalize},
{"native_setInputSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaRecorder_setInputSurface },
+
+ {"getMetrics", "()Landroid/os/Bundle;", (void *)android_media_MediaRecorder_getMetrics},
};
// This function only registers the native methods, and is called from
diff --git a/media/mca/filterfw/jni/Android.mk b/media/mca/filterfw/jni/Android.mk
index 40576a0..9842e70 100644
--- a/media/mca/filterfw/jni/Android.mk
+++ b/media/mca/filterfw/jni/Android.mk
@@ -43,6 +43,6 @@
LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code -Wno-unused-parameter
-LOCAL_SHARED_LIBRARIES := libmedia
+LOCAL_SHARED_LIBRARIES := libmedia libgui libandroid
include $(BUILD_STATIC_LIBRARY)
diff --git a/media/mca/filterfw/native/Android.mk b/media/mca/filterfw/native/Android.mk
index 7c4703f..2e900fe 100644
--- a/media/mca/filterfw/native/Android.mk
+++ b/media/mca/filterfw/native/Android.mk
@@ -41,6 +41,8 @@
LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
+LOCAL_STATIC_LIBRARIES := libarect
+
# TODO: Build a shared library as well?
include $(BUILD_STATIC_LIBRARY)
diff --git a/media/mca/filterfw/native/core/gl_env.cpp b/media/mca/filterfw/native/core/gl_env.cpp
index c9d33da..1bb82f8 100644
--- a/media/mca/filterfw/native/core/gl_env.cpp
+++ b/media/mca/filterfw/native/core/gl_env.cpp
@@ -26,7 +26,10 @@
#include <string>
#include <EGL/eglext.h>
+#include <gui/BufferQueue.h>
+#include <gui/Surface.h>
#include <gui/GLConsumer.h>
+#include <gui/IGraphicBufferProducer.h>
namespace android {
namespace filterfw {
diff --git a/media/mca/filterfw/native/core/gl_env.h b/media/mca/filterfw/native/core/gl_env.h
index a709638..0445301 100644
--- a/media/mca/filterfw/native/core/gl_env.h
+++ b/media/mca/filterfw/native/core/gl_env.h
@@ -27,8 +27,9 @@
#include <GLES2/gl2.h>
#include <EGL/egl.h>
-#include <gui/IGraphicBufferProducer.h>
-#include <gui/Surface.h>
+#include <utils/StrongPointer.h>
+
+struct ANativeWindow;
namespace android {
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketEncoder.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketEncoder.java
index 5fb162c..8ac6b56 100644
--- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketEncoder.java
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketEncoder.java
@@ -48,7 +48,7 @@
private boolean mWritePending;
- private final Object mLock = new Object();
+ private final Object mLock = new Object();
// This receives normalized data from mMidiFramer and accumulates it into a packet buffer
private final MidiReceiver mFramedDataReceiver = new MidiReceiver() {
@@ -60,6 +60,8 @@
int milliTimestamp = (int)(timestamp / MILLISECOND_NANOS) & MILLISECOND_MASK;
byte status = msg[offset];
boolean isSysExStart = (status == MidiConstants.STATUS_SYSTEM_EXCLUSIVE);
+ // Because of the MidiFramer, if it is not a status byte then it
+ // must be a continuation.
boolean isSysExContinuation = ((status & 0x80) == 0);
int bytesNeeded;
@@ -70,7 +72,9 @@
bytesNeeded = count;
}
- boolean needsTimestamp = (milliTimestamp != mPacketTimestamp);
+ // Status bytes must be preceded by a timestamp
+ boolean needsTimestamp = (status != mRunningStatus)
+ || (milliTimestamp != mPacketTimestamp);
if (isSysExStart) {
// SysEx start byte must be preceded by a timestamp
needsTimestamp = true;
@@ -78,6 +82,7 @@
// SysEx continuation packets must not have timestamp byte
needsTimestamp = false;
}
+
if (needsTimestamp) bytesNeeded++; // add one for timestamp byte
if (status == mRunningStatus) bytesNeeded--; // subtract one for status byte
diff --git a/native/android/Android.mk b/native/android/Android.mk
index f8405ef..69544f5 100644
--- a/native/android/Android.mk
+++ b/native/android/Android.mk
@@ -37,7 +37,8 @@
libnetd_client \
LOCAL_STATIC_LIBRARIES := \
- libstorage
+ libstorage \
+ libarect
LOCAL_C_INCLUDES += \
frameworks/base/native/include \
@@ -45,6 +46,8 @@
bionic/libc/dns/include \
system/netd/include \
+LOCAL_EXPORT_STATIC_LIBRARY_HEADERS := libarect
+
LOCAL_MODULE := libandroid
LOCAL_CFLAGS += $(common_cflags)
diff --git a/native/graphics/jni/Android.mk b/native/graphics/jni/Android.mk
index 4c8a9db..ec4b35a 100644
--- a/native/graphics/jni/Android.mk
+++ b/native/graphics/jni/Android.mk
@@ -21,6 +21,7 @@
LOCAL_SHARED_LIBRARIES := \
libandroid_runtime \
libskia \
+ libui \
libandroidfw
LOCAL_C_INCLUDES += \
diff --git a/packages/ExtServices/Android.mk b/packages/ExtServices/Android.mk
index e8a4007..d0c2b9f 100644
--- a/packages/ExtServices/Android.mk
+++ b/packages/ExtServices/Android.mk
@@ -34,7 +34,8 @@
include $(BUILD_PACKAGE)
-
-
-
+# Use the following include to make our test apk.
+ifeq ($(strip $(LOCAL_PACKAGE_OVERRIDES)),)
+include $(call all-makefiles-under, $(LOCAL_PATH))
+endif
diff --git a/packages/ExtServices/AndroidManifest.xml b/packages/ExtServices/AndroidManifest.xml
index f442219..f3d8983 100644
--- a/packages/ExtServices/AndroidManifest.xml
+++ b/packages/ExtServices/AndroidManifest.xml
@@ -25,6 +25,13 @@
android:defaultToDeviceProtectedStorage="true"
android:directBootAware="true">
+ <service android:name=".storage.CacheQuotaServiceImpl"
+ android:permission="android.permission.BIND_CACHE_QUOTA_SERVICE">
+ <intent-filter>
+ <action android:name="android.app.usage.CacheQuotaService" />
+ </intent-filter>
+ </service>
+
<library android:name="android.ext.services"/>
</application>
diff --git a/packages/ExtServices/src/android/ext/services/storage/CacheQuotaServiceImpl.java b/packages/ExtServices/src/android/ext/services/storage/CacheQuotaServiceImpl.java
new file mode 100644
index 0000000..18863ca
--- /dev/null
+++ b/packages/ExtServices/src/android/ext/services/storage/CacheQuotaServiceImpl.java
@@ -0,0 +1,144 @@
+
+/*
+ * 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.ext.services.storage;
+
+import android.app.usage.CacheQuotaHint;
+import android.app.usage.CacheQuotaService;
+import android.os.Environment;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.util.ArrayMap;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * CacheQuotaServiceImpl implements the CacheQuotaService with a strategy for populating the quota
+ * of {@link CacheQuotaHint}.
+ */
+public class CacheQuotaServiceImpl extends CacheQuotaService {
+ private static final double CACHE_RESERVE_RATIO = 0.15;
+
+ @Override
+ public List<CacheQuotaHint> onComputeCacheQuotaHints(List<CacheQuotaHint> requests) {
+ ArrayMap<String, List<CacheQuotaHint>> byUuid = new ArrayMap<>();
+ final int requestCount = requests.size();
+ for (int i = 0; i < requestCount; i++) {
+ CacheQuotaHint request = requests.get(i);
+ String uuid = request.getVolumeUuid();
+ List<CacheQuotaHint> listForUuid = byUuid.get(uuid);
+ if (listForUuid == null) {
+ listForUuid = new ArrayList<>();
+ byUuid.put(uuid, listForUuid);
+ }
+ listForUuid.add(request);
+ }
+
+ List<CacheQuotaHint> processed = new ArrayList<>();
+ byUuid.entrySet().forEach(
+ requestListEntry -> {
+ // Collapse all usage stats to the same uid.
+ Map<Integer, List<CacheQuotaHint>> byUid = requestListEntry.getValue()
+ .stream()
+ .collect(Collectors.groupingBy(CacheQuotaHint::getUid));
+ byUid.values().forEach(uidGroupedList -> {
+ int size = uidGroupedList.size();
+ if (size < 2) {
+ return;
+ }
+ CacheQuotaHint first = uidGroupedList.get(0);
+ for (int i = 1; i < size; i++) {
+ /* Note: We can't use the UsageStats built-in addition function because
+ UIDs may span multiple packages and usage stats adding has
+ matching package names as a precondition. */
+ first.getUsageStats().mTotalTimeInForeground +=
+ uidGroupedList.get(i).getUsageStats().mTotalTimeInForeground;
+ }
+ });
+
+ // Because the foreground stats have been added to the first element, we need
+ // a list of only the first values (which contain the merged foreground time).
+ List<CacheQuotaHint> flattenedRequests =
+ byUid.values()
+ .stream()
+ .map(entryList -> entryList.get(0))
+ .filter(entry -> entry.getUsageStats().mTotalTimeInForeground != 0)
+ .sorted(sCacheQuotaRequestComparator)
+ .collect(Collectors.toList());
+
+ // Because the elements are sorted, we can use the index to also be the sorted
+ // index for cache quota calculation.
+ double sum = getSumOfFairShares(flattenedRequests.size());
+ String uuid = requestListEntry.getKey();
+ long reservedSize = getReservedCacheSize(uuid);
+ for (int count = 0; count < flattenedRequests.size(); count++) {
+ double share = getFairShareForPosition(count) / sum;
+ CacheQuotaHint entry = flattenedRequests.get(count);
+ CacheQuotaHint.Builder builder = new CacheQuotaHint.Builder(entry);
+ builder.setQuota(Math.round(share * reservedSize));
+ processed.add(builder.build());
+ }
+ }
+ );
+
+ return processed.stream()
+ .filter(request -> request.getQuota() > 0).collect(Collectors.toList());
+ }
+
+ private double getFairShareForPosition(int position) {
+ double value = 1.0 / Math.log(position + 3) - 0.285;
+ return (value > 0.01) ? value : 0.01;
+ }
+
+ private double getSumOfFairShares(int size) {
+ double sum = 0;
+ for (int i = 0; i < size; i++) {
+ sum += getFairShareForPosition(i);
+ }
+ return sum;
+ }
+
+ private long getReservedCacheSize(String uuid) {
+ // TODO: Revisit the cache size after running more storage tests.
+ // TODO: Figure out how to ensure ExtServices has the permissions to call
+ // StorageStatsManager, because this is ignoring the cache...
+ StorageManager storageManager = getSystemService(StorageManager.class);
+ long freeBytes = 0;
+ if (uuid == StorageManager.UUID_PRIVATE_INTERNAL) { // regular equals because of null
+ freeBytes = Environment.getDataDirectory().getFreeSpace();
+ } else {
+ final VolumeInfo vol = storageManager.findVolumeByUuid(uuid);
+ freeBytes = vol.getPath().getFreeSpace();
+ }
+ return Math.round(freeBytes * CACHE_RESERVE_RATIO);
+ }
+
+ // Compares based upon foreground time.
+ private static Comparator<CacheQuotaHint> sCacheQuotaRequestComparator =
+ new Comparator<CacheQuotaHint>() {
+ @Override
+ public int compare(CacheQuotaHint o, CacheQuotaHint t1) {
+ long x = t1.getUsageStats().getTotalTimeInForeground();
+ long y = o.getUsageStats().getTotalTimeInForeground();
+ return (x < y) ? -1 : ((x == y) ? 0 : 1);
+ }
+ };
+}
diff --git a/packages/ExtServices/tests/Android.mk b/packages/ExtServices/tests/Android.mk
new file mode 100644
index 0000000..cb3c352
--- /dev/null
+++ b/packages/ExtServices/tests/Android.mk
@@ -0,0 +1,24 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+LOCAL_CERTIFICATE := platform
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ mockito-target-minus-junit4 \
+ espresso-core \
+ truth-prebuilt \
+ legacy-android-test
+
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := ExtServicesUnitTests
+
+LOCAL_INSTRUMENTATION_FOR := ExtServices
+
+include $(BUILD_PACKAGE)
diff --git a/packages/ExtServices/tests/AndroidManifest.xml b/packages/ExtServices/tests/AndroidManifest.xml
new file mode 100644
index 0000000..e6c7b97
--- /dev/null
+++ b/packages/ExtServices/tests/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.ext.services.tests.unit">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.ext.services"
+ android:label="ExtServices Test Cases">
+ </instrumentation>
+
+</manifest>
\ No newline at end of file
diff --git a/packages/ExtServices/tests/src/android/ext/services/storage/CacheQuotaServiceImplTest.java b/packages/ExtServices/tests/src/android/ext/services/storage/CacheQuotaServiceImplTest.java
new file mode 100644
index 0000000..cc1699a
--- /dev/null
+++ b/packages/ExtServices/tests/src/android/ext/services/storage/CacheQuotaServiceImplTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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.ext.services.storage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.app.usage.CacheQuotaHint;
+import android.app.usage.UsageStats;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.test.ServiceTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class CacheQuotaServiceImplTest extends ServiceTestCase<CacheQuotaServiceImpl> {
+ private static final String sTestVolUuid = "uuid";
+ private static final String sSecondTestVolUuid = "otherUuid";
+
+ @Mock private Context mContext;
+ @Mock private File mFile;
+ @Mock private VolumeInfo mVolume;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS) private StorageManager mStorageManager;
+
+ public CacheQuotaServiceImplTest() {
+ super(CacheQuotaServiceImpl.class);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ mContext = Mockito.spy(new ContextWrapper(getSystemContext()));
+ setContext(mContext);
+ when(mContext.getSystemService(Context.STORAGE_SERVICE)).thenReturn(mStorageManager);
+
+ when(mFile.getFreeSpace()).thenReturn(10000L);
+ when(mVolume.getPath()).thenReturn(mFile);
+ when(mStorageManager.findVolumeByUuid(sTestVolUuid)).thenReturn(mVolume);
+ when(mStorageManager.findVolumeByUuid(sSecondTestVolUuid)).thenReturn(mVolume);
+
+ Intent intent = new Intent(getContext(), CacheQuotaServiceImpl.class);
+ startService(intent);
+ }
+
+ @Test
+ public void testNoApps() {
+ CacheQuotaServiceImpl service = getService();
+ assertEquals(service.onComputeCacheQuotaHints(new ArrayList()).size(), 0);
+ }
+
+ @Test
+ public void testOneApp() throws Exception {
+ ArrayList<CacheQuotaHint> requests = new ArrayList<>();
+ CacheQuotaHint request = makeNewRequest("com.test", sTestVolUuid, 1001, 100L);
+ requests.add(request);
+
+ List<CacheQuotaHint> output = getService().onComputeCacheQuotaHints(requests);
+
+ assertThat(output).hasSize(1);
+ assertThat(output.get(0).getQuota()).isEqualTo(1500L);
+ }
+
+ @Test
+ public void testTwoAppsOneVolume() throws Exception {
+ ArrayList<CacheQuotaHint> requests = new ArrayList<>();
+ requests.add(makeNewRequest("com.test", sTestVolUuid, 1001, 100L));
+ requests.add(makeNewRequest("com.test2", sTestVolUuid, 1002, 99L));
+
+ List<CacheQuotaHint> output = getService().onComputeCacheQuotaHints(requests);
+
+ // Note that the sizes are just the cache area split up.
+ assertThat(output).hasSize(2);
+ assertThat(output.get(0).getQuota()).isEqualTo(883);
+ assertThat(output.get(1).getQuota()).isEqualTo(1500 - 883);
+ }
+
+ @Test
+ public void testTwoAppsTwoVolumes() throws Exception {
+ ArrayList<CacheQuotaHint> requests = new ArrayList<>();
+ requests.add(makeNewRequest("com.test", sTestVolUuid, 1001, 100L));
+ requests.add(makeNewRequest("com.test2", sSecondTestVolUuid, 1002, 99L));
+
+ List<CacheQuotaHint> output = getService().onComputeCacheQuotaHints(requests);
+
+ assertThat(output).hasSize(2);
+ assertThat(output.get(0).getQuota()).isEqualTo(1500);
+ assertThat(output.get(1).getQuota()).isEqualTo(1500);
+ }
+
+ @Test
+ public void testMultipleAppsPerUidIsCollated() throws Exception {
+ ArrayList<CacheQuotaHint> requests = new ArrayList<>();
+ requests.add(makeNewRequest("com.test", sTestVolUuid, 1001, 100L));
+ requests.add(makeNewRequest("com.test2", sTestVolUuid, 1001, 99L));
+
+ List<CacheQuotaHint> output = getService().onComputeCacheQuotaHints(requests);
+
+ assertThat(output).hasSize(1);
+ assertThat(output.get(0).getQuota()).isEqualTo(1500);
+ }
+
+ @Test
+ public void testTwoAppsTwoVolumesTwoUuidsShouldBESeparate() throws Exception {
+ ArrayList<CacheQuotaHint> requests = new ArrayList<>();
+ requests.add(makeNewRequest("com.test", sTestVolUuid, 1001, 100L));
+ requests.add(makeNewRequest("com.test2", sSecondTestVolUuid, 1001, 99L));
+
+ List<CacheQuotaHint> output = getService().onComputeCacheQuotaHints(requests);
+
+ assertThat(output).hasSize(2);
+ assertThat(output.get(0).getQuota()).isEqualTo(1500);
+ assertThat(output.get(1).getQuota()).isEqualTo(1500);
+ }
+
+ private CacheQuotaHint makeNewRequest(String packageName, String uuid, int uid, long foregroundTime) {
+ UsageStats stats = new UsageStats();
+ stats.mPackageName = packageName;
+ stats.mTotalTimeInForeground = foregroundTime;
+ return new CacheQuotaHint.Builder()
+ .setVolumeUuid(uuid).setUid(uid).setUsageStats(stats).setQuota(-1).build();
+ }
+}
diff --git a/packages/PrintSpooler/res/layout/no_print_services_message.xml b/packages/PrintSpooler/res/layout/no_print_services_message.xml
new file mode 100644
index 0000000..7872658
--- /dev/null
+++ b/packages/PrintSpooler/res/layout/no_print_services_message.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="?android:attr/listPreferredItemHeightSmall"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:orientation="horizontal"
+ android:gravity="start|center_vertical">
+
+ <RelativeLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="32dip">
+ <HorizontalScrollView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:text="@string/print_no_print_services"
+ android:scrollHorizontally="true"
+ android:singleLine="true" />
+ </HorizontalScrollView>
+ </RelativeLayout>
+
+</LinearLayout>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/AddPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/AddPrinterActivity.java
index 7b0a291..696376e 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/AddPrinterActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/AddPrinterActivity.java
@@ -26,6 +26,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.Loader;
+import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.DataSetObserver;
import android.net.Uri;
@@ -97,20 +98,39 @@
*/
private RecommendedServicesAdapter mRecommendedServicesAdapter;
+ private static final String PKG_NAME_VENDING = "com.android.vending";
+ private boolean mHasVending;
+ private NoPrintServiceMessageAdapter mNoPrintServiceMessageAdapter;
+
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.add_printer_activity);
+ try {
+ getPackageManager().getPackageInfo(PKG_NAME_VENDING, 0);
+ mHasVending = true;
+ } catch (PackageManager.NameNotFoundException e) {
+ mHasVending = false;
+ }
mEnabledServicesAdapter = new EnabledServicesAdapter();
mDisabledServicesAdapter = new DisabledServicesAdapter();
- mRecommendedServicesAdapter = new RecommendedServicesAdapter();
+ if (mHasVending) {
+ mRecommendedServicesAdapter = new RecommendedServicesAdapter();
+ } else {
+ mNoPrintServiceMessageAdapter = new NoPrintServiceMessageAdapter();
+ }
ArrayList<ActionAdapter> adapterList = new ArrayList<>(3);
adapterList.add(mEnabledServicesAdapter);
- adapterList.add(mRecommendedServicesAdapter);
+ if (mHasVending) {
+ adapterList.add(mRecommendedServicesAdapter);
+ }
adapterList.add(mDisabledServicesAdapter);
+ if (!mHasVending) {
+ adapterList.add(mNoPrintServiceMessageAdapter);
+ }
setListAdapter(new CombinedAdapter(adapterList));
@@ -121,8 +141,10 @@
getLoaderManager().initLoader(LOADER_ID_ENABLED_SERVICES, null, printServiceLoaderCallbacks);
getLoaderManager().initLoader(LOADER_ID_DISABLED_SERVICES, null, printServiceLoaderCallbacks);
- getLoaderManager().initLoader(LOADER_ID_RECOMMENDED_SERVICES, null,
- new PrintServicePrintServiceRecommendationLoaderCallbacks());
+ if (mHasVending) {
+ getLoaderManager().initLoader(LOADER_ID_RECOMMENDED_SERVICES, null,
+ new PrintServicePrintServiceRecommendationLoaderCallbacks());
+ }
getLoaderManager().initLoader(LOADER_ID_ALL_SERVICES, null, printServiceLoaderCallbacks);
}
@@ -174,7 +196,11 @@
mDisabledServicesAdapter.updateData(data);
break;
case LOADER_ID_ALL_SERVICES:
- mRecommendedServicesAdapter.updateInstalledServices(data);
+ if (mHasVending) {
+ mRecommendedServicesAdapter.updateInstalledServices(data);
+ } else {
+ mNoPrintServiceMessageAdapter.updateInstalledServices(data);
+ }
default:
// not reached
}
@@ -191,7 +217,11 @@
mDisabledServicesAdapter.updateData(null);
break;
case LOADER_ID_ALL_SERVICES:
- mRecommendedServicesAdapter.updateInstalledServices(null);
+ if (mHasVending) {
+ mRecommendedServicesAdapter.updateInstalledServices(null);
+ } else {
+ mNoPrintServiceMessageAdapter.updateInstalledServices(null);
+ }
break;
default:
// not reached
@@ -804,4 +834,61 @@
filterRecommendations();
}
}
+
+ private class NoPrintServiceMessageAdapter extends ActionAdapter {
+ private boolean mHasPrintService;
+
+ void updateInstalledServices(@Nullable List<PrintServiceInfo> services) {
+ if (services == null || services.isEmpty()) {
+ mHasPrintService = false;
+ } else {
+ mHasPrintService = true;
+ }
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getCount() {
+ return mHasPrintService ? 0 : 1;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 1;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return 0;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = getLayoutInflater().inflate(R.layout.no_print_services_message,
+ parent, false);
+ }
+ return convertView;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return position != 0;
+ }
+
+ @Override
+ public void performAction(@IntRange(from = 0) int position) {
+ return;
+ }
+ }
}
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index eb64b3a..d207c35 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -444,6 +444,18 @@
<item>show_deuteranomaly</item>
</string-array>
+ <!-- Titles for debug renderer preference. [CHAR LIMIT=50] -->
+ <string-array name="debug_hw_renderer_entries">
+ <item>OpenGL (Default)</item>
+ <item>OpenGL (Skia)</item>
+ </string-array>
+
+ <!-- Values for debug renderer preference. -->
+ <string-array name="debug_hw_renderer_values" translatable="false" >
+ <item>opengl</item>
+ <item>skiagl</item>
+ </string-array>
+
<!-- Titles for app process limit preference. [CHAR LIMIT=35] -->
<string-array name="app_process_limit_entries">
<item>Standard limit</item>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 5475b32..961d0e5 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -57,6 +57,8 @@
<string name="wifi_disabled_generic">Disabled</string>
<!-- Status for networked disabled from a DNS or DHCP failure -->
<string name="wifi_disabled_network_failure">IP Configuration Failure</string>
+ <!-- Status for networks disabled by the network recommendation provider -->
+ <string name="wifi_disabled_by_recommendation_provider">Not connected due to low quality network</string>
<!-- Status for networked disabled from a wifi association failure -->
<string name="wifi_disabled_wifi_failure">WiFi Connection Failure</string>
<!-- Status for networks disabled from authentication failure (wrong password
@@ -584,6 +586,9 @@
<!-- UI debug setting: show the amount of overdraw in apps using the GPU [CHAR LIMIT=25] -->
<string name="debug_hw_overdraw">Debug GPU overdraw</string>
+ <!-- UI debug setting: select the renderer to use by RenderThread [CHAR LIMIT=25] -->
+ <string name="debug_hw_renderer">Set GPU Renderer</string>
+
<!-- UI debug setting: disable use of overlays? [CHAR LIMIT=25] -->
<string name="disable_overlays">Disable HW overlays</string>
<!-- UI debug setting: disable use of overlays summary [CHAR LIMIT=50] -->
@@ -903,4 +908,12 @@
<!-- Glyph to be overlaid atop the battery when the level is extremely low. Do not translate. -->
<string name="battery_meter_very_low_overlay_symbol">!</string>
+
+ <!-- Title for settings of active input methods in each IME [CHAR LIMIT=35] -->
+ <string name="active_input_method_subtypes">Active input methods</string>
+ <!-- Title for settings whether or not the framework will select input methods in an IME based
+ on the current system locales. (The user can select multiple system locales)
+ [CHAR LIMIT=35] -->
+ <string name="use_system_language_to_select_input_method_subtypes">Use system languages</string>
+
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/BatteryInfo.java b/packages/SettingsLib/src/com/android/settingslib/BatteryInfo.java
index fa1f91f..22f8856 100644
--- a/packages/SettingsLib/src/com/android/settingslib/BatteryInfo.java
+++ b/packages/SettingsLib/src/com/android/settingslib/BatteryInfo.java
@@ -38,6 +38,7 @@
public long remainingTimeUs = 0;
public String batteryPercentString;
public String remainingLabel;
+ public String statusLabel;
private BatteryStats mStats;
private boolean mCharging;
private long timePeriod;
@@ -135,6 +136,7 @@
info.batteryPercentString = Utils.formatPercentage(info.mBatteryLevel);
info.mCharging = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
final Resources resources = context.getResources();
+ info.statusLabel = Utils.getBatteryStatus(resources, batteryBroadcast, shortString);
if (!info.mCharging) {
final long drainTime = stats.computeBatteryTimeRemaining(elapsedRealtimeUs);
if (drainTime > 0) {
@@ -155,8 +157,6 @@
}
} else {
final long chargeTime = stats.computeChargeTimeRemaining(elapsedRealtimeUs);
- final String statusLabel = Utils.getBatteryStatus(
- resources, batteryBroadcast, shortString);
final int status = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_STATUS,
BatteryManager.BATTERY_STATUS_UNKNOWN);
if (chargeTime > 0 && status != BatteryManager.BATTERY_STATUS_FULL) {
@@ -184,9 +184,9 @@
info.mChargeLabelString = resources.getString(
resId, info.batteryPercentString, timeString);
} else {
- info.remainingLabel = statusLabel;
+ info.remainingLabel = null;
info.mChargeLabelString = resources.getString(
- R.string.power_charging, info.batteryPercentString, statusLabel);
+ R.string.power_charging, info.batteryPercentString, info.statusLabel);
}
}
return info;
diff --git a/packages/SettingsLib/src/com/android/settingslib/TronUtils.java b/packages/SettingsLib/src/com/android/settingslib/TronUtils.java
index 1d9d03a..bea6e8f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/TronUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/TronUtils.java
@@ -16,7 +16,7 @@
package com.android.settingslib;
import android.content.Context;
-import android.net.ScoredNetwork;
+import android.net.NetworkBadging;
import com.android.internal.logging.MetricsLogger;
@@ -34,7 +34,7 @@
*
* @param context Context
* @param histogram the Tron histogram name to write to
- * @param badgeEnum the {@link ScoredNetwork.Badging} badge value
+ * @param badgeEnum the {@link NetworkBadging.Badging} badge value
* @throws IllegalArgumentException if the given badge enum is not supported
*/
private static void logNetworkBadgeMetric(
@@ -42,16 +42,16 @@
throws IllegalArgumentException {
int bucket;
switch (badgeEnum) {
- case ScoredNetwork.BADGING_NONE:
+ case NetworkBadging.BADGING_NONE:
bucket = 0;
break;
- case ScoredNetwork.BADGING_SD:
+ case NetworkBadging.BADGING_SD:
bucket = 1;
break;
- case ScoredNetwork.BADGING_HD:
+ case NetworkBadging.BADGING_HD:
bucket = 2;
break;
- case ScoredNetwork.BADGING_4K:
+ case NetworkBadging.BADGING_4K:
bucket = 3;
break;
default:
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 8653523..ee7f927 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -17,7 +17,7 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.net.ConnectivityManager;
-import android.net.ScoredNetwork;
+import android.net.NetworkBadging;
import android.os.BatteryManager;
import android.os.UserManager;
import android.print.PrintManager;
@@ -304,13 +304,13 @@
*/
public static int getWifiBadgeResource(int badge) {
switch (badge) {
- case ScoredNetwork.BADGING_NONE:
+ case NetworkBadging.BADGING_NONE:
return View.NO_ID;
- case ScoredNetwork.BADGING_SD:
+ case NetworkBadging.BADGING_SD:
return com.android.internal.R.drawable.ic_signal_wifi_badged_sd;
- case ScoredNetwork.BADGING_HD:
+ case NetworkBadging.BADGING_HD:
return com.android.internal.R.drawable.ic_signal_wifi_badged_hd;
- case ScoredNetwork.BADGING_4K:
+ case NetworkBadging.BADGING_4K:
return com.android.internal.R.drawable.ic_signal_wifi_badged_4k;
default:
throw new IllegalArgumentException(
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeEnablerManager.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeEnablerManager.java
new file mode 100644
index 0000000..c9e3475
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeEnablerManager.java
@@ -0,0 +1,263 @@
+/*
+ * 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.inputmethod;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceCategory;
+import android.support.v7.preference.PreferenceScreen;
+import android.support.v7.preference.TwoStatePreference;
+import android.text.TextUtils;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.settingslib.R;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class InputMethodAndSubtypeEnablerManager implements Preference.OnPreferenceChangeListener {
+
+ private final PreferenceFragment mFragment;
+
+ private boolean mHaveHardKeyboard;
+ private final HashMap<String, List<Preference>> mInputMethodAndSubtypePrefsMap =
+ new HashMap<>();
+ private final HashMap<String, TwoStatePreference> mAutoSelectionPrefsMap = new HashMap<>();
+ private InputMethodManager mImm;
+ // TODO: Change mInputMethodInfoList to Map
+ private List<InputMethodInfo> mInputMethodInfoList;
+ private final Collator mCollator = Collator.getInstance();
+
+ public InputMethodAndSubtypeEnablerManager(PreferenceFragment fragment) {
+ mFragment = fragment;
+ mImm = fragment.getContext().getSystemService(InputMethodManager.class);
+
+ mInputMethodInfoList = mImm.getInputMethodList();
+ }
+
+ public void init(PreferenceFragment fragment, String targetImi, PreferenceScreen root) {
+ final Configuration config = fragment.getResources().getConfiguration();
+ mHaveHardKeyboard = (config.keyboard == Configuration.KEYBOARD_QWERTY);
+
+ for (final InputMethodInfo imi : mInputMethodInfoList) {
+ // Add subtype preferences of this IME when it is specified or no IME is specified.
+ if (imi.getId().equals(targetImi) || TextUtils.isEmpty(targetImi)) {
+ addInputMethodSubtypePreferences(fragment, imi, root);
+ }
+ }
+ }
+
+ public void refresh(Context context, PreferenceFragment fragment) {
+ // Refresh internal states in mInputMethodSettingValues to keep the latest
+ // "InputMethodInfo"s and "InputMethodSubtype"s
+ InputMethodSettingValuesWrapper
+ .getInstance(context).refreshAllInputMethodAndSubtypes();
+ InputMethodAndSubtypeUtil.loadInputMethodSubtypeList(fragment, context.getContentResolver(),
+ mInputMethodInfoList, mInputMethodAndSubtypePrefsMap);
+ updateAutoSelectionPreferences();
+ }
+
+ public void save(Context context, PreferenceFragment fragment) {
+ InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(fragment, context.getContentResolver(),
+ mInputMethodInfoList, mHaveHardKeyboard);
+ }
+
+ @Override
+ public boolean onPreferenceChange(final Preference pref, final Object newValue) {
+ if (!(newValue instanceof Boolean)) {
+ return true; // Invoke default behavior.
+ }
+ final boolean isChecking = (Boolean) newValue;
+ for (final String imiId : mAutoSelectionPrefsMap.keySet()) {
+ // An auto select subtype preference is changing.
+ if (mAutoSelectionPrefsMap.get(imiId) == pref) {
+ final TwoStatePreference autoSelectionPref = (TwoStatePreference) pref;
+ autoSelectionPref.setChecked(isChecking);
+ // Enable or disable subtypes depending on the auto selection preference.
+ setAutoSelectionSubtypesEnabled(imiId, autoSelectionPref.isChecked());
+ return false;
+ }
+ }
+ // A subtype preference is changing.
+ if (pref instanceof InputMethodSubtypePreference) {
+ final InputMethodSubtypePreference subtypePref = (InputMethodSubtypePreference) pref;
+ subtypePref.setChecked(isChecking);
+ if (!subtypePref.isChecked()) {
+ // It takes care of the case where no subtypes are explicitly enabled then the auto
+ // selection preference is going to be checked.
+ updateAutoSelectionPreferences();
+ }
+ return false;
+ }
+ return true; // Invoke default behavior.
+ }
+
+ private void addInputMethodSubtypePreferences(PreferenceFragment fragment, InputMethodInfo imi,
+ final PreferenceScreen root) {
+ Context prefContext = fragment.getPreferenceManager().getContext();
+
+ final int subtypeCount = imi.getSubtypeCount();
+ if (subtypeCount <= 1) {
+ return;
+ }
+ final String imiId = imi.getId();
+ final PreferenceCategory keyboardSettingsCategory =
+ new PreferenceCategory(prefContext);
+ root.addPreference(keyboardSettingsCategory);
+ final PackageManager pm = prefContext.getPackageManager();
+ final CharSequence label = imi.loadLabel(pm);
+
+ keyboardSettingsCategory.setTitle(label);
+ keyboardSettingsCategory.setKey(imiId);
+ // TODO: Use toggle Preference if images are ready.
+ final TwoStatePreference autoSelectionPref =
+ new SwitchWithNoTextPreference(prefContext);
+ mAutoSelectionPrefsMap.put(imiId, autoSelectionPref);
+ keyboardSettingsCategory.addPreference(autoSelectionPref);
+ autoSelectionPref.setOnPreferenceChangeListener(this);
+
+ final PreferenceCategory activeInputMethodsCategory =
+ new PreferenceCategory(prefContext);
+ activeInputMethodsCategory.setTitle(R.string.active_input_method_subtypes);
+ root.addPreference(activeInputMethodsCategory);
+
+ CharSequence autoSubtypeLabel = null;
+ final ArrayList<Preference> subtypePreferences = new ArrayList<>();
+ for (int index = 0; index < subtypeCount; ++index) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(index);
+ if (subtype.overridesImplicitlyEnabledSubtype()) {
+ if (autoSubtypeLabel == null) {
+ autoSubtypeLabel = InputMethodAndSubtypeUtil.getSubtypeLocaleNameAsSentence(
+ subtype, prefContext, imi);
+ }
+ } else {
+ final Preference subtypePref = new InputMethodSubtypePreference(
+ prefContext, subtype, imi);
+ subtypePreferences.add(subtypePref);
+ }
+ }
+ subtypePreferences.sort((lhs, rhs) -> {
+ if (lhs instanceof InputMethodSubtypePreference) {
+ return ((InputMethodSubtypePreference) lhs).compareTo(rhs, mCollator);
+ }
+ return lhs.compareTo(rhs);
+ });
+ for (final Preference pref : subtypePreferences) {
+ activeInputMethodsCategory.addPreference(pref);
+ pref.setOnPreferenceChangeListener(this);
+ InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(pref);
+ }
+ mInputMethodAndSubtypePrefsMap.put(imiId, subtypePreferences);
+ if (TextUtils.isEmpty(autoSubtypeLabel)) {
+ autoSelectionPref.setTitle(
+ R.string.use_system_language_to_select_input_method_subtypes);
+ } else {
+ autoSelectionPref.setTitle(autoSubtypeLabel);
+ }
+ }
+
+ private boolean isNoSubtypesExplicitlySelected(final String imiId) {
+ final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
+ for (final Preference pref : subtypePrefs) {
+ if (pref instanceof TwoStatePreference && ((TwoStatePreference) pref).isChecked()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void setAutoSelectionSubtypesEnabled(final String imiId,
+ final boolean autoSelectionEnabled) {
+ final TwoStatePreference autoSelectionPref = mAutoSelectionPrefsMap.get(imiId);
+ if (autoSelectionPref == null) {
+ return;
+ }
+ autoSelectionPref.setChecked(autoSelectionEnabled);
+ final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
+ for (final Preference pref : subtypePrefs) {
+ if (pref instanceof TwoStatePreference) {
+ // When autoSelectionEnabled is true, all subtype prefs need to be disabled with
+ // implicitly checked subtypes. In case of false, all subtype prefs need to be
+ // enabled.
+ pref.setEnabled(!autoSelectionEnabled);
+ if (autoSelectionEnabled) {
+ ((TwoStatePreference) pref).setChecked(false);
+ }
+ }
+ }
+ if (autoSelectionEnabled) {
+ InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(
+ mFragment, mFragment.getContext().getContentResolver(),
+ mInputMethodInfoList, mHaveHardKeyboard);
+ updateImplicitlyEnabledSubtypes(imiId);
+ }
+ }
+
+ private void updateImplicitlyEnabledSubtypes(final String targetImiId) {
+ // When targetImiId is null, apply to all subtypes of all IMEs
+ for (final InputMethodInfo imi : mInputMethodInfoList) {
+ final String imiId = imi.getId();
+ final TwoStatePreference autoSelectionPref = mAutoSelectionPrefsMap.get(imiId);
+ // No need to update implicitly enabled subtypes when the user has unchecked the
+ // "subtype auto selection".
+ if (autoSelectionPref == null || !autoSelectionPref.isChecked()) {
+ continue;
+ }
+ if (imiId.equals(targetImiId) || targetImiId == null) {
+ updateImplicitlyEnabledSubtypesOf(imi);
+ }
+ }
+ }
+
+ private void updateImplicitlyEnabledSubtypesOf(final InputMethodInfo imi) {
+ final String imiId = imi.getId();
+ final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
+ final List<InputMethodSubtype> implicitlyEnabledSubtypes =
+ mImm.getEnabledInputMethodSubtypeList(imi, true);
+ if (subtypePrefs == null || implicitlyEnabledSubtypes == null) {
+ return;
+ }
+ for (final Preference pref : subtypePrefs) {
+ if (!(pref instanceof TwoStatePreference)) {
+ continue;
+ }
+ final TwoStatePreference subtypePref = (TwoStatePreference) pref;
+ subtypePref.setChecked(false);
+ for (final InputMethodSubtype subtype : implicitlyEnabledSubtypes) {
+ final String implicitlyEnabledSubtypePrefKey = imiId + subtype.hashCode();
+ if (subtypePref.getKey().equals(implicitlyEnabledSubtypePrefKey)) {
+ subtypePref.setChecked(true);
+ break;
+ }
+ }
+ }
+ }
+
+ private void updateAutoSelectionPreferences() {
+ for (final String imiId : mInputMethodAndSubtypePrefsMap.keySet()) {
+ setAutoSelectionSubtypesEnabled(imiId, isNoSubtypesExplicitlySelected(imiId));
+ }
+ updateImplicitlyEnabledSubtypes(null /* targetImiId */ /* check */);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java
new file mode 100644
index 0000000..3f6f5b5
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java
@@ -0,0 +1,420 @@
+/*
+ * 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.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.icu.text.ListFormatter;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.support.v7.preference.TwoStatePreference;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.internal.app.LocaleHelper;
+import com.android.internal.inputmethod.InputMethodUtils;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+// TODO: Consolidate this with {@link InputMethodSettingValuesWrapper}.
+public class InputMethodAndSubtypeUtil {
+
+ private static final boolean DEBUG = false;
+ private static final String TAG = "InputMethdAndSubtypeUtl";
+
+ private static final char INPUT_METHOD_SEPARATER = ':';
+ private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
+ private static final int NOT_A_SUBTYPE_ID = -1;
+
+ private static final TextUtils.SimpleStringSplitter sStringInputMethodSplitter
+ = new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
+
+ private static final TextUtils.SimpleStringSplitter sStringInputMethodSubtypeSplitter
+ = new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
+
+ // InputMethods and subtypes are saved in the settings as follows:
+ // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
+ private static String buildInputMethodsAndSubtypesString(
+ final HashMap<String, HashSet<String>> imeToSubtypesMap) {
+ final StringBuilder builder = new StringBuilder();
+ for (final String imi : imeToSubtypesMap.keySet()) {
+ if (builder.length() > 0) {
+ builder.append(INPUT_METHOD_SEPARATER);
+ }
+ final HashSet<String> subtypeIdSet = imeToSubtypesMap.get(imi);
+ builder.append(imi);
+ for (final String subtypeId : subtypeIdSet) {
+ builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
+ }
+ }
+ return builder.toString();
+ }
+
+ private static String buildInputMethodsString(final HashSet<String> imiList) {
+ final StringBuilder builder = new StringBuilder();
+ for (final String imi : imiList) {
+ if (builder.length() > 0) {
+ builder.append(INPUT_METHOD_SEPARATER);
+ }
+ builder.append(imi);
+ }
+ return builder.toString();
+ }
+
+ private static int getInputMethodSubtypeSelected(ContentResolver resolver) {
+ try {
+ return Settings.Secure.getInt(resolver,
+ Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE);
+ } catch (SettingNotFoundException e) {
+ return NOT_A_SUBTYPE_ID;
+ }
+ }
+
+ private static boolean isInputMethodSubtypeSelected(ContentResolver resolver) {
+ return getInputMethodSubtypeSelected(resolver) != NOT_A_SUBTYPE_ID;
+ }
+
+ private static void putSelectedInputMethodSubtype(ContentResolver resolver, int hashCode) {
+ Settings.Secure.putInt(resolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, hashCode);
+ }
+
+ // Needs to modify InputMethodManageService if you want to change the format of saved string.
+ private static HashMap<String, HashSet<String>> getEnabledInputMethodsAndSubtypeList(
+ ContentResolver resolver) {
+ final String enabledInputMethodsStr = Settings.Secure.getString(
+ resolver, Settings.Secure.ENABLED_INPUT_METHODS);
+ if (DEBUG) {
+ Log.d(TAG, "--- Load enabled input methods: " + enabledInputMethodsStr);
+ }
+ return parseInputMethodsAndSubtypesString(enabledInputMethodsStr);
+ }
+
+ private static HashMap<String, HashSet<String>> parseInputMethodsAndSubtypesString(
+ final String inputMethodsAndSubtypesString) {
+ final HashMap<String, HashSet<String>> subtypesMap = new HashMap<>();
+ if (TextUtils.isEmpty(inputMethodsAndSubtypesString)) {
+ return subtypesMap;
+ }
+ sStringInputMethodSplitter.setString(inputMethodsAndSubtypesString);
+ while (sStringInputMethodSplitter.hasNext()) {
+ final String nextImsStr = sStringInputMethodSplitter.next();
+ sStringInputMethodSubtypeSplitter.setString(nextImsStr);
+ if (sStringInputMethodSubtypeSplitter.hasNext()) {
+ final HashSet<String> subtypeIdSet = new HashSet<>();
+ // The first element is {@link InputMethodInfoId}.
+ final String imiId = sStringInputMethodSubtypeSplitter.next();
+ while (sStringInputMethodSubtypeSplitter.hasNext()) {
+ subtypeIdSet.add(sStringInputMethodSubtypeSplitter.next());
+ }
+ subtypesMap.put(imiId, subtypeIdSet);
+ }
+ }
+ return subtypesMap;
+ }
+
+ private static HashSet<String> getDisabledSystemIMEs(ContentResolver resolver) {
+ HashSet<String> set = new HashSet<>();
+ String disabledIMEsStr = Settings.Secure.getString(
+ resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS);
+ if (TextUtils.isEmpty(disabledIMEsStr)) {
+ return set;
+ }
+ sStringInputMethodSplitter.setString(disabledIMEsStr);
+ while(sStringInputMethodSplitter.hasNext()) {
+ set.add(sStringInputMethodSplitter.next());
+ }
+ return set;
+ }
+
+ public static void saveInputMethodSubtypeList(PreferenceFragment context,
+ ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
+ boolean hasHardKeyboard) {
+ String currentInputMethodId = Settings.Secure.getString(resolver,
+ Settings.Secure.DEFAULT_INPUT_METHOD);
+ final int selectedInputMethodSubtype = getInputMethodSubtypeSelected(resolver);
+ final HashMap<String, HashSet<String>> enabledIMEsAndSubtypesMap =
+ getEnabledInputMethodsAndSubtypeList(resolver);
+ final HashSet<String> disabledSystemIMEs = getDisabledSystemIMEs(resolver);
+
+ boolean needsToResetSelectedSubtype = false;
+ for (final InputMethodInfo imi : inputMethodInfos) {
+ final String imiId = imi.getId();
+ final Preference pref = context.findPreference(imiId);
+ if (pref == null) {
+ continue;
+ }
+ // In the choose input method screen or in the subtype enabler screen,
+ // <code>pref</code> is an instance of TwoStatePreference.
+ final boolean isImeChecked = (pref instanceof TwoStatePreference) ?
+ ((TwoStatePreference) pref).isChecked()
+ : enabledIMEsAndSubtypesMap.containsKey(imiId);
+ final boolean isCurrentInputMethod = imiId.equals(currentInputMethodId);
+ final boolean systemIme = InputMethodUtils.isSystemIme(imi);
+ if ((!hasHardKeyboard && InputMethodSettingValuesWrapper.getInstance(
+ context.getActivity()).isAlwaysCheckedIme(imi, context.getActivity()))
+ || isImeChecked) {
+ if (!enabledIMEsAndSubtypesMap.containsKey(imiId)) {
+ // imiId has just been enabled
+ enabledIMEsAndSubtypesMap.put(imiId, new HashSet<>());
+ }
+ final HashSet<String> subtypesSet = enabledIMEsAndSubtypesMap.get(imiId);
+
+ boolean subtypePrefFound = false;
+ final int subtypeCount = imi.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(i);
+ final String subtypeHashCodeStr = String.valueOf(subtype.hashCode());
+ final TwoStatePreference subtypePref = (TwoStatePreference) context
+ .findPreference(imiId + subtypeHashCodeStr);
+ // In the Configure input method screen which does not have subtype preferences.
+ if (subtypePref == null) {
+ continue;
+ }
+ if (!subtypePrefFound) {
+ // Once subtype preference is found, subtypeSet needs to be cleared.
+ // Because of system change, hashCode value could have been changed.
+ subtypesSet.clear();
+ // If selected subtype preference is disabled, needs to reset.
+ needsToResetSelectedSubtype = true;
+ subtypePrefFound = true;
+ }
+ // Checking <code>subtypePref.isEnabled()</code> is insufficient to determine
+ // whether the user manually enabled this subtype or not. Implicitly-enabled
+ // subtypes are also checked just as an indicator to users. We also need to
+ // check <code>subtypePref.isEnabled()</code> so that only manually enabled
+ // subtypes can be saved here.
+ if (subtypePref.isEnabled() && subtypePref.isChecked()) {
+ subtypesSet.add(subtypeHashCodeStr);
+ if (isCurrentInputMethod) {
+ if (selectedInputMethodSubtype == subtype.hashCode()) {
+ // Selected subtype is still enabled, there is no need to reset
+ // selected subtype.
+ needsToResetSelectedSubtype = false;
+ }
+ }
+ } else {
+ subtypesSet.remove(subtypeHashCodeStr);
+ }
+ }
+ } else {
+ enabledIMEsAndSubtypesMap.remove(imiId);
+ if (isCurrentInputMethod) {
+ // We are processing the current input method, but found that it's not enabled.
+ // This means that the current input method has been uninstalled.
+ // If currentInputMethod is already uninstalled, InputMethodManagerService will
+ // find the applicable IME from the history and the system locale.
+ if (DEBUG) {
+ Log.d(TAG, "Current IME was uninstalled or disabled.");
+ }
+ currentInputMethodId = null;
+ }
+ }
+ // If it's a disabled system ime, add it to the disabled list so that it
+ // doesn't get enabled automatically on any changes to the package list
+ if (systemIme && hasHardKeyboard) {
+ if (disabledSystemIMEs.contains(imiId)) {
+ if (isImeChecked) {
+ disabledSystemIMEs.remove(imiId);
+ }
+ } else {
+ if (!isImeChecked) {
+ disabledSystemIMEs.add(imiId);
+ }
+ }
+ }
+ }
+
+ final String enabledIMEsAndSubtypesString = buildInputMethodsAndSubtypesString(
+ enabledIMEsAndSubtypesMap);
+ final String disabledSystemIMEsString = buildInputMethodsString(disabledSystemIMEs);
+ if (DEBUG) {
+ Log.d(TAG, "--- Save enabled inputmethod settings. :" + enabledIMEsAndSubtypesString);
+ Log.d(TAG, "--- Save disabled system inputmethod settings. :"
+ + disabledSystemIMEsString);
+ Log.d(TAG, "--- Save default inputmethod settings. :" + currentInputMethodId);
+ Log.d(TAG, "--- Needs to reset the selected subtype :" + needsToResetSelectedSubtype);
+ Log.d(TAG, "--- Subtype is selected :" + isInputMethodSubtypeSelected(resolver));
+ }
+
+ // Redefines SelectedSubtype when all subtypes are unchecked or there is no subtype
+ // selected. And if the selected subtype of the current input method was disabled,
+ // We should reset the selected input method's subtype.
+ if (needsToResetSelectedSubtype || !isInputMethodSubtypeSelected(resolver)) {
+ if (DEBUG) {
+ Log.d(TAG, "--- Reset inputmethod subtype because it's not defined.");
+ }
+ putSelectedInputMethodSubtype(resolver, NOT_A_SUBTYPE_ID);
+ }
+
+ Settings.Secure.putString(resolver,
+ Settings.Secure.ENABLED_INPUT_METHODS, enabledIMEsAndSubtypesString);
+ if (disabledSystemIMEsString.length() > 0) {
+ Settings.Secure.putString(resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS,
+ disabledSystemIMEsString);
+ }
+ // If the current input method is unset, InputMethodManagerService will find the applicable
+ // IME from the history and the system locale.
+ Settings.Secure.putString(resolver, Settings.Secure.DEFAULT_INPUT_METHOD,
+ currentInputMethodId != null ? currentInputMethodId : "");
+ }
+
+ public static void loadInputMethodSubtypeList(final PreferenceFragment context,
+ final ContentResolver resolver, final List<InputMethodInfo> inputMethodInfos,
+ final Map<String, List<Preference>> inputMethodPrefsMap) {
+ final HashMap<String, HashSet<String>> enabledSubtypes =
+ getEnabledInputMethodsAndSubtypeList(resolver);
+
+ for (final InputMethodInfo imi : inputMethodInfos) {
+ final String imiId = imi.getId();
+ final Preference pref = context.findPreference(imiId);
+ if (pref instanceof TwoStatePreference) {
+ final TwoStatePreference subtypePref = (TwoStatePreference) pref;
+ final boolean isEnabled = enabledSubtypes.containsKey(imiId);
+ subtypePref.setChecked(isEnabled);
+ if (inputMethodPrefsMap != null) {
+ for (final Preference childPref: inputMethodPrefsMap.get(imiId)) {
+ childPref.setEnabled(isEnabled);
+ }
+ }
+ setSubtypesPreferenceEnabled(context, inputMethodInfos, imiId, isEnabled);
+ }
+ }
+ updateSubtypesPreferenceChecked(context, inputMethodInfos, enabledSubtypes);
+ }
+
+ private static void setSubtypesPreferenceEnabled(final PreferenceFragment context,
+ final List<InputMethodInfo> inputMethodProperties, final String id,
+ final boolean enabled) {
+ final PreferenceScreen preferenceScreen = context.getPreferenceScreen();
+ for (final InputMethodInfo imi : inputMethodProperties) {
+ if (id.equals(imi.getId())) {
+ final int subtypeCount = imi.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(i);
+ final TwoStatePreference pref = (TwoStatePreference) preferenceScreen
+ .findPreference(id + subtype.hashCode());
+ if (pref != null) {
+ pref.setEnabled(enabled);
+ }
+ }
+ }
+ }
+ }
+
+ private static void updateSubtypesPreferenceChecked(final PreferenceFragment context,
+ final List<InputMethodInfo> inputMethodProperties,
+ final HashMap<String, HashSet<String>> enabledSubtypes) {
+ final PreferenceScreen preferenceScreen = context.getPreferenceScreen();
+ for (final InputMethodInfo imi : inputMethodProperties) {
+ final String id = imi.getId();
+ if (!enabledSubtypes.containsKey(id)) {
+ // There is no need to enable/disable subtypes of disabled IMEs.
+ continue;
+ }
+ final HashSet<String> enabledSubtypesSet = enabledSubtypes.get(id);
+ final int subtypeCount = imi.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(i);
+ final String hashCode = String.valueOf(subtype.hashCode());
+ if (DEBUG) {
+ Log.d(TAG, "--- Set checked state: " + "id" + ", " + hashCode + ", "
+ + enabledSubtypesSet.contains(hashCode));
+ }
+ final TwoStatePreference pref = (TwoStatePreference) preferenceScreen
+ .findPreference(id + hashCode);
+ if (pref != null) {
+ pref.setChecked(enabledSubtypesSet.contains(hashCode));
+ }
+ }
+ }
+ }
+
+ public static void removeUnnecessaryNonPersistentPreference(final Preference pref) {
+ final String key = pref.getKey();
+ if (pref.isPersistent() || key == null) {
+ return;
+ }
+ final SharedPreferences prefs = pref.getSharedPreferences();
+ if (prefs != null && prefs.contains(key)) {
+ prefs.edit().remove(key).apply();
+ }
+ }
+
+ @NonNull
+ public static String getSubtypeLocaleNameAsSentence(@Nullable InputMethodSubtype subtype,
+ @NonNull final Context context, @NonNull final InputMethodInfo inputMethodInfo) {
+ if (subtype == null) {
+ return "";
+ }
+ final Locale locale = getDisplayLocale(context);
+ final CharSequence subtypeName = subtype.getDisplayName(context,
+ inputMethodInfo.getPackageName(), inputMethodInfo.getServiceInfo()
+ .applicationInfo);
+ return LocaleHelper.toSentenceCase(subtypeName.toString(), locale);
+ }
+
+ @NonNull
+ public static String getSubtypeLocaleNameListAsSentence(
+ @NonNull final List<InputMethodSubtype> subtypes, @NonNull final Context context,
+ @NonNull final InputMethodInfo inputMethodInfo) {
+ if (subtypes.isEmpty()) {
+ return "";
+ }
+ final Locale locale = getDisplayLocale(context);
+ final int subtypeCount = subtypes.size();
+ final CharSequence[] subtypeNames = new CharSequence[subtypeCount];
+ for (int i = 0; i < subtypeCount; i++) {
+ subtypeNames[i] = subtypes.get(i).getDisplayName(context,
+ inputMethodInfo.getPackageName(), inputMethodInfo.getServiceInfo()
+ .applicationInfo);
+ }
+ return LocaleHelper.toSentenceCase(
+ ListFormatter.getInstance(locale).format(subtypeNames), locale);
+ }
+
+ @NonNull
+ private static Locale getDisplayLocale(@Nullable final Context context) {
+ if (context == null) {
+ return Locale.getDefault();
+ }
+ if (context.getResources() == null) {
+ return Locale.getDefault();
+ }
+ final Configuration configuration = context.getResources().getConfiguration();
+ if (configuration == null) {
+ return Locale.getDefault();
+ }
+ final Locale configurationLocale = configuration.getLocales().get(0);
+ if (configurationLocale == null) {
+ return Locale.getDefault();
+ }
+ return configurationLocale;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java
new file mode 100644
index 0000000..fac50bd
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java
@@ -0,0 +1,185 @@
+/*
+ * 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.inputmethod;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.internal.inputmethod.InputMethodUtils;
+import com.android.internal.inputmethod.InputMethodUtils.InputMethodSettings;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * This class is a wrapper for InputMethodSettings. You need to refresh internal states
+ * manually on some events when "InputMethodInfo"s and "InputMethodSubtype"s can be
+ * changed.
+ */
+// TODO: Consolidate this with {@link InputMethodAndSubtypeUtil}.
+public class InputMethodSettingValuesWrapper {
+ private static final String TAG = InputMethodSettingValuesWrapper.class.getSimpleName();
+
+ private static volatile InputMethodSettingValuesWrapper sInstance;
+ private final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
+ private final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<>();
+ private final InputMethodSettings mSettings;
+ private final InputMethodManager mImm;
+ private final HashSet<InputMethodInfo> mAsciiCapableEnabledImis = new HashSet<>();
+
+ public static InputMethodSettingValuesWrapper getInstance(Context context) {
+ if (sInstance == null) {
+ synchronized (TAG) {
+ if (sInstance == null) {
+ sInstance = new InputMethodSettingValuesWrapper(context);
+ }
+ }
+ }
+ return sInstance;
+ }
+
+ private static int getDefaultCurrentUserId() {
+ try {
+ return ActivityManager.getService().getCurrentUser().id;
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e);
+ }
+ return 0;
+ }
+
+ // Ensure singleton
+ private InputMethodSettingValuesWrapper(Context context) {
+ mSettings = new InputMethodSettings(context.getResources(), context.getContentResolver(),
+ mMethodMap, mMethodList, getDefaultCurrentUserId(), false /* copyOnWrite */);
+ mImm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
+ refreshAllInputMethodAndSubtypes();
+ }
+
+ public void refreshAllInputMethodAndSubtypes() {
+ synchronized (mMethodMap) {
+ mMethodList.clear();
+ mMethodMap.clear();
+ final List<InputMethodInfo> imms = mImm.getInputMethodList();
+ mMethodList.addAll(imms);
+ for (InputMethodInfo imi : imms) {
+ mMethodMap.put(imi.getId(), imi);
+ }
+ updateAsciiCapableEnabledImis();
+ }
+ }
+
+ // TODO: Add a cts to ensure at least one AsciiCapableSubtypeEnabledImis exist
+ private void updateAsciiCapableEnabledImis() {
+ synchronized (mMethodMap) {
+ mAsciiCapableEnabledImis.clear();
+ final List<InputMethodInfo> enabledImis = mSettings.getEnabledInputMethodListLocked();
+ for (final InputMethodInfo imi : enabledImis) {
+ final int subtypeCount = imi.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(i);
+ if (InputMethodUtils.SUBTYPE_MODE_KEYBOARD.equalsIgnoreCase(subtype.getMode())
+ && subtype.isAsciiCapable()) {
+ mAsciiCapableEnabledImis.add(imi);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ public List<InputMethodInfo> getInputMethodList() {
+ synchronized (mMethodMap) {
+ return mMethodList;
+ }
+ }
+
+ public boolean isAlwaysCheckedIme(InputMethodInfo imi, Context context) {
+ final boolean isEnabled = isEnabledImi(imi);
+ synchronized (mMethodMap) {
+ if (mSettings.getEnabledInputMethodListLocked().size() <= 1 && isEnabled) {
+ return true;
+ }
+ }
+
+ final int enabledValidSystemNonAuxAsciiCapableImeCount =
+ getEnabledValidSystemNonAuxAsciiCapableImeCount(context);
+
+ return enabledValidSystemNonAuxAsciiCapableImeCount <= 1
+ && !(enabledValidSystemNonAuxAsciiCapableImeCount == 1 && !isEnabled)
+ && InputMethodUtils.isSystemIme(imi)
+ && isValidSystemNonAuxAsciiCapableIme(imi, context);
+
+ }
+
+ private int getEnabledValidSystemNonAuxAsciiCapableImeCount(Context context) {
+ int count = 0;
+ final List<InputMethodInfo> enabledImis;
+ synchronized (mMethodMap) {
+ enabledImis = mSettings.getEnabledInputMethodListLocked();
+ }
+ for (final InputMethodInfo imi : enabledImis) {
+ if (isValidSystemNonAuxAsciiCapableIme(imi, context)) {
+ ++count;
+ }
+ }
+ if (count == 0) {
+ Log.w(TAG, "No \"enabledValidSystemNonAuxAsciiCapableIme\"s found.");
+ }
+ return count;
+ }
+
+ public boolean isEnabledImi(InputMethodInfo imi) {
+ final List<InputMethodInfo> enabledImis;
+ synchronized (mMethodMap) {
+ enabledImis = mSettings.getEnabledInputMethodListLocked();
+ }
+ for (final InputMethodInfo tempImi : enabledImis) {
+ if (tempImi.getId().equals(imi.getId())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean isValidSystemNonAuxAsciiCapableIme(InputMethodInfo imi, Context context) {
+ if (imi.isAuxiliaryIme()) {
+ return false;
+ }
+ final Locale systemLocale = context.getResources().getConfiguration().locale;
+ if (InputMethodUtils.isSystemImeThatHasSubtypeOf(imi, context,
+ true /* checkDefaultAttribute */, systemLocale, false /* checkCountry */,
+ InputMethodUtils.SUBTYPE_MODE_ANY)) {
+ return true;
+ }
+ if (mAsciiCapableEnabledImis.isEmpty()) {
+ Log.w(TAG, "ascii capable subtype enabled imi not found. Fall back to English"
+ + " Keyboard subtype.");
+ return InputMethodUtils.containsSubtypeOf(imi, Locale.ENGLISH, false /* checkCountry */,
+ InputMethodUtils.SUBTYPE_MODE_KEYBOARD);
+ }
+ return mAsciiCapableEnabledImis.contains(imi);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSubtypePreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSubtypePreference.java
new file mode 100644
index 0000000..5fdab29
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSubtypePreference.java
@@ -0,0 +1,91 @@
+/*
+ * 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.inputmethod;
+
+import android.content.Context;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+import android.text.TextUtils;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.internal.inputmethod.InputMethodUtils;
+
+import java.text.Collator;
+import java.util.Locale;
+
+/**
+ * Input method subtype preference.
+ *
+ * This preference represents a subtype of an IME. It is used to enable or disable the subtype.
+ */
+public class InputMethodSubtypePreference extends SwitchWithNoTextPreference {
+ private final boolean mIsSystemLocale;
+ private final boolean mIsSystemLanguage;
+
+ public InputMethodSubtypePreference(final Context context, final InputMethodSubtype subtype,
+ final InputMethodInfo imi) {
+ super(context);
+ setPersistent(false);
+ setKey(imi.getId() + subtype.hashCode());
+ final CharSequence subtypeLabel =
+ InputMethodAndSubtypeUtil.getSubtypeLocaleNameAsSentence(subtype, context, imi);
+ setTitle(subtypeLabel);
+ final String subtypeLocaleString = subtype.getLocale();
+ if (TextUtils.isEmpty(subtypeLocaleString)) {
+ mIsSystemLocale = false;
+ mIsSystemLanguage = false;
+ } else {
+ final Locale systemLocale = context.getResources().getConfiguration().locale;
+ mIsSystemLocale = subtypeLocaleString.equals(systemLocale.toString());
+ mIsSystemLanguage = mIsSystemLocale
+ || InputMethodUtils.getLanguageFromLocaleString(subtypeLocaleString)
+ .equals(systemLocale.getLanguage());
+ }
+ }
+
+ public int compareTo(final Preference rhs, final Collator collator) {
+ if (this == rhs) {
+ return 0;
+ }
+ if (rhs instanceof InputMethodSubtypePreference) {
+ final InputMethodSubtypePreference rhsPref = (InputMethodSubtypePreference) rhs;
+ if (mIsSystemLocale && !rhsPref.mIsSystemLocale) {
+ return -1;
+ }
+ if (!mIsSystemLocale && rhsPref.mIsSystemLocale) {
+ return 1;
+ }
+ if (mIsSystemLanguage && !rhsPref.mIsSystemLanguage) {
+ return -1;
+ }
+ if (!mIsSystemLanguage && rhsPref.mIsSystemLanguage) {
+ return 1;
+ }
+ final CharSequence t0 = getTitle();
+ final CharSequence t1 = rhs.getTitle();
+ if (t0 == null && t1 == null) {
+ return Integer.compare(hashCode(), rhs.hashCode());
+ }
+ if (t0 != null && t1 != null) {
+ return collator.compare(t0.toString(), t1.toString());
+ }
+ return t0 == null ? -1 : 1;
+ }
+ return super.compareTo(rhs);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/SwitchWithNoTextPreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/SwitchWithNoTextPreference.java
new file mode 100644
index 0000000..798f8fe
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/SwitchWithNoTextPreference.java
@@ -0,0 +1,30 @@
+/*
+ * 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.inputmethod;
+
+import android.content.Context;
+import android.support.v14.preference.SwitchPreference;
+
+public class SwitchWithNoTextPreference extends SwitchPreference {
+ private static final String EMPTY_TEXT = "";
+
+ public SwitchWithNoTextPreference(final Context context) {
+ super(context);
+ setSwitchTextOn(EMPTY_TEXT);
+ setSwitchTextOff(EMPTY_TEXT);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AppRestrictionsHelper.java b/packages/SettingsLib/src/com/android/settingslib/users/AppRestrictionsHelper.java
index 4c11197..0fc9a4d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/AppRestrictionsHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AppRestrictionsHelper.java
@@ -116,7 +116,7 @@
if (info == null || !info.enabled
|| (info.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
mIPm.installExistingPackageAsUser(packageName, mUser.getIdentifier(),
- PackageManager.INSTALL_REASON_UNKNOWN);
+ 0 /*installFlags*/, PackageManager.INSTALL_REASON_UNKNOWN);
if (DEBUG) {
Log.d(TAG, "Installing " + packageName);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 82e69d8..2dcbf90 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -22,12 +22,11 @@
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
-import android.net.Network;
+import android.net.NetworkBadging;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkInfo.State;
-import android.net.NetworkKey;
import android.net.ScoredNetwork;
import android.net.wifi.IWifiManager;
import android.net.wifi.ScanResult;
@@ -51,7 +50,6 @@
import com.android.settingslib.R;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
@@ -137,7 +135,7 @@
private Object mTag;
private int mRankingScore = Integer.MIN_VALUE;
- private int mBadge = ScoredNetwork.BADGING_NONE;
+ private int mBadge = NetworkBadging.BADGING_NONE;
// used to co-relate internal vs returned accesspoint.
int mId;
@@ -296,7 +294,7 @@
boolean updateScores(WifiNetworkScoreCache scoreCache) {
int oldBadge = mBadge;
int oldRankingScore = mRankingScore;
- mBadge = ScoredNetwork.BADGING_NONE;
+ mBadge = NetworkBadging.BADGING_NONE;
mRankingScore = Integer.MIN_VALUE;
for (ScanResult result : mScanResultCache.values()) {
@@ -503,7 +501,8 @@
// This is the active connection on non-passpoint network
summary.append(getSummary(mContext, getDetailedState(),
mInfo != null && mInfo.isEphemeral()));
- } else if (config != null && config.isPasspoint()) {
+ } else if (config != null && config.isPasspoint()
+ && config.getNetworkSelectionStatus().isNetworkEnabled()) {
String format = mContext.getString(R.string.available_via_passpoint);
summary.append(String.format(format, config.providerFriendlyName));
} else if (config != null && config.hasNoInternetAccess()) {
@@ -526,6 +525,8 @@
summary.append(mContext.getString(R.string.wifi_disabled_generic));
break;
}
+ } else if (config != null && config.getNetworkSelectionStatus().isNotRecommended()) {
+ summary.append(mContext.getString(R.string.wifi_disabled_by_recommendation_provider));
} else if (mRssi == Integer.MAX_VALUE) { // Wifi out of range
summary.append(mContext.getString(R.string.wifi_not_in_range));
} else { // In range, not disabled.
@@ -765,11 +766,7 @@
}
void loadConfig(WifiConfiguration config) {
- if (config.isPasspoint())
- ssid = config.providerFriendlyName;
- else
- ssid = (config.SSID == null ? "" : removeDoubleQuotes(config.SSID));
-
+ ssid = (config.SSID == null ? "" : removeDoubleQuotes(config.SSID));
bssid = config.BSSID;
security = getSecurity(config);
networkId = config.networkId;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index a77c310..69da548 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -22,7 +22,6 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.net.NetworkBadging;
-import android.net.ScoredNetwork;
import android.net.wifi.WifiConfiguration;
import android.os.Looper;
import android.os.UserHandle;
@@ -61,7 +60,7 @@
private int mLevel;
private CharSequence mContentDescription;
private int mDefaultIconResId;
- private int mWifiBadge = ScoredNetwork.BADGING_NONE;
+ private int mWifiBadge = NetworkBadging.BADGING_NONE;
static final int[] WIFI_CONNECTION_STRENGTH = {
R.string.accessibility_wifi_one_bar,
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 9ac4d2d..11bcdca 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -469,29 +469,22 @@
}
AccessPoint accessPoint = getCachedOrCreate(config, cachedAccessPoints);
if (mLastInfo != null && mLastNetworkInfo != null) {
- if (config.isPasspoint() == false) {
- accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
- }
+ accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
}
if (mIncludeSaved) {
- if (!config.isPasspoint() || mIncludePasspoints) {
- // If saved network not present in scan result then set its Rssi to MAX_VALUE
- boolean apFound = false;
- for (ScanResult result : results) {
- if (result.SSID.equals(accessPoint.getSsidStr())) {
- apFound = true;
- break;
- }
+ // If saved network not present in scan result then set its Rssi to MAX_VALUE
+ boolean apFound = false;
+ for (ScanResult result : results) {
+ if (result.SSID.equals(accessPoint.getSsidStr())) {
+ apFound = true;
+ break;
}
- if (!apFound) {
- accessPoint.setRssi(Integer.MAX_VALUE);
- }
- accessPoints.add(accessPoint);
}
-
- if (config.isPasspoint() == false) {
- apMap.put(accessPoint.getSsidStr(), accessPoint);
+ if (!apFound) {
+ accessPoint.setRssi(Integer.MAX_VALUE);
}
+ accessPoints.add(accessPoint);
+ apMap.put(accessPoint.getSsidStr(), accessPoint);
} else {
// If we aren't using saved networks, drop them into the cache so that
// we have access to their saved info.
@@ -528,20 +521,16 @@
}
if (result.isPasspointNetwork()) {
+ // Retrieve a WifiConfiguration for a Passpoint provider that matches
+ // the given ScanResult. This is used for showing that a given AP
+ // (ScanResult) is available via a Passpoint provider (provider friendly
+ // name).
WifiConfiguration config = mWifiManager.getMatchingWifiConfig(result);
if (config != null) {
accessPoint.update(config);
}
}
- if (mLastInfo != null && mLastInfo.getBSSID() != null
- && mLastInfo.getBSSID().equals(result.BSSID)
- && connectionConfig != null && connectionConfig.isPasspoint()) {
- /* This network is connected via this passpoint config */
- /* SSID match is not going to work for it; so update explicitly */
- accessPoint.update(connectionConfig);
- }
-
accessPoints.add(accessPoint);
apMap.put(accessPoint.getSsidStr(), accessPoint);
}
diff --git a/packages/SettingsLib/tests/integ/Android.mk b/packages/SettingsLib/tests/integ/Android.mk
index bd910dd..091f965 100644
--- a/packages/SettingsLib/tests/integ/Android.mk
+++ b/packages/SettingsLib/tests/integ/Android.mk
@@ -31,6 +31,12 @@
legacy-android-test \
truth-prebuilt
+# Code coverage puts us over the dex limit, so enable multi-dex for coverage-enabled builds
+ifeq (true,$(EMMA_INSTRUMENT))
+LOCAL_JACK_FLAGS := --multi-dex native
+LOCAL_DX_FLAGS := --multi-dex
+endif # EMMA_INSTRUMENT
+
include frameworks/base/packages/SettingsLib/common.mk
include $(BUILD_PACKAGE)
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java
index 4df199c..8cfec7a 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java
@@ -146,7 +146,7 @@
mHelper.applyUserAppsStates(mockListener);
verify(mIpm, times(1)).installExistingPackageAsUser("app1", testUserId,
- PackageManager.INSTALL_REASON_UNKNOWN);
+ 0 /*installFlags*/, PackageManager.INSTALL_REASON_UNKNOWN);
verify(mIpm, times(1)).setApplicationHiddenSettingAsUser("app2", false, testUserId);
verify(mockListener).onDisableUiForPackage("app2");
verify(mPm, times(1)).deletePackageAsUser(eq("app3"), any(IPackageDeleteObserver.class),
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
index 0f220aa..2018c13 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
@@ -27,6 +27,7 @@
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
+import android.net.NetworkBadging;
import android.net.NetworkKey;
import android.net.NetworkScoreManager;
import android.net.ScoredNetwork;
@@ -80,7 +81,7 @@
new NetworkKey(new WifiKey('"' + SSID_1 + '"', BSSID_1));
private static final int RSSI_1 = -30;
private static final byte SCORE_1 = 10;
- private static final int BADGE_1 = ScoredNetwork.BADGING_SD;
+ private static final int BADGE_1 = NetworkBadging.BADGING_SD;
private static final String SSID_2 = "ssid2";
private static final String BSSID_2 = "AA:AA:AA:AA:AA:AA";
@@ -88,7 +89,7 @@
new NetworkKey(new WifiKey('"' + SSID_2 + '"', BSSID_2));
private static final int RSSI_2 = -30;
private static final byte SCORE_2 = 15;
- private static final int BADGE_2 = ScoredNetwork.BADGING_HD;
+ private static final int BADGE_2 = NetworkBadging.BADGING_HD;
@Captor ArgumentCaptor<WifiNetworkScoreCache> mScoreCacheCaptor;
@Mock private ConnectivityManager mockConnectivityManager;
@@ -460,9 +461,9 @@
for (AccessPoint ap : aps) {
if (ap.getSsidStr().equals(SSID_1)) {
- assertEquals(ScoredNetwork.BADGING_NONE, ap.getBadge());
+ assertEquals(NetworkBadging.BADGING_NONE, ap.getBadge());
} else if (ap.getSsidStr().equals(SSID_2)) {
- assertEquals(ScoredNetwork.BADGING_NONE, ap.getBadge());
+ assertEquals(NetworkBadging.BADGING_NONE, ap.getBadge());
}
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/BatteryInfoTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/BatteryInfoTest.java
new file mode 100644
index 0000000..1364958
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/BatteryInfoTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.BatteryManager;
+import android.os.BatteryStats;
+import android.os.SystemClock;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.when;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class BatteryInfoTest {
+ private static final String STATUS_FULL = "Full";
+ private Intent mBatteryBroadcast;
+ @Mock
+ private BatteryStats mBatteryStats;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mBatteryBroadcast = new Intent();
+ mBatteryBroadcast.putExtra(BatteryManager.EXTRA_PLUGGED, 0);
+ mBatteryBroadcast.putExtra(BatteryManager.EXTRA_LEVEL, 0);
+ mBatteryBroadcast.putExtra(BatteryManager.EXTRA_SCALE, 100);
+ mBatteryBroadcast.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_FULL);
+
+ when(mContext.getResources().getString(R.string.battery_info_status_full))
+ .thenReturn(STATUS_FULL);
+ }
+
+ @Test
+ public void testGetBatteryInfo_HasStatusLabel() {
+ BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mBatteryBroadcast, mBatteryStats,
+ SystemClock.elapsedRealtime() * 1000, true);
+
+ assertThat(info.statusLabel).isEqualTo(STATUS_FULL);
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingLibRobolectricTestRunner.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingLibRobolectricTestRunner.java
index 11c925e..fd3b1dc6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingLibRobolectricTestRunner.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingLibRobolectricTestRunner.java
@@ -20,9 +20,6 @@
import org.robolectric.annotation.Config;
import org.robolectric.manifest.AndroidManifest;
import org.robolectric.res.Fs;
-import org.robolectric.res.ResourcePath;
-
-import java.util.List;
public class SettingLibRobolectricTestRunner extends RobolectricTestRunner {
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 136f17e..f660e1e 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -98,7 +98,7 @@
<bool name="def_accessibility_script_injection">false</bool>
<!-- Default for Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD -->
- <bool name="def_accessibility_speak_password">false</bool>
+ <bool name="def_accessibility_speak_password">true</bool>
<!-- Default for Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS -->
<string name="def_accessibility_web_content_key_bindings" translatable="false">
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index d5787e6..a71db85 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1037,7 +1037,7 @@
final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);
// Ensure the caller can access the setting.
- enforceSettingReadable(name, SETTINGS_TYPE_SECURE, callingUserId);
+ enforceSettingReadable(name, SETTINGS_TYPE_SECURE, UserHandle.getCallingUserId());
// Determine the owning user as some profile settings are cloned from the parent.
final int owningUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId, name);
@@ -1233,7 +1233,7 @@
final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);
// Ensure the caller can access the setting.
- enforceSettingReadable(name, SETTINGS_TYPE_SYSTEM, callingUserId);
+ enforceSettingReadable(name, SETTINGS_TYPE_SYSTEM, UserHandle.getCallingUserId());
// Determine the owning user as some profile settings are cloned from the parent.
final int owningUserId = resolveOwningUserIdForSystemSettingLocked(callingUserId, name);
@@ -1531,14 +1531,14 @@
}
}
- private Set<String> getEphemeralAccessibleSettings(int settingsType) {
+ private Set<String> getInstantAppAccessibleSettings(int settingsType) {
switch (settingsType) {
case SETTINGS_TYPE_GLOBAL:
- return Settings.Global.EPHEMERAL_SETTINGS;
+ return Settings.Global.INSTANT_APP_SETTINGS;
case SETTINGS_TYPE_SECURE:
- return Settings.Secure.EPHEMERAL_SETTINGS;
+ return Settings.Secure.INSTANT_APP_SETTINGS;
case SETTINGS_TYPE_SYSTEM:
- return Settings.System.EPHEMERAL_SETTINGS;
+ return Settings.System.INSTANT_APP_SETTINGS;
default:
throw new IllegalArgumentException("Invalid settings type: " + settingsType);
}
@@ -1547,7 +1547,7 @@
private List<String> getSettingsNamesLocked(int settingsType, int userId) {
ApplicationInfo ai = getCallingApplicationInfoOrThrow(userId);
if (ai.isInstantApp()) {
- return new ArrayList<String>(getEphemeralAccessibleSettings(settingsType));
+ return new ArrayList<String>(getInstantAppAccessibleSettings(settingsType));
} else {
return mSettingsRegistry.getSettingsNamesLocked(settingsType, userId);
}
@@ -1561,7 +1561,7 @@
if (!ai.isInstantApp()) {
return;
}
- if (!getEphemeralAccessibleSettings(settingsType).contains(settingName)) {
+ if (!getInstantAppAccessibleSettings(settingsType).contains(settingName)) {
throw new SecurityException("Setting " + settingName + " is not accessible from"
+ " ephemeral package " + getCallingPackage());
}
@@ -1654,7 +1654,7 @@
return false;
}
- String oldProviders = (settingValue != null) ? settingValue.getValue() : "";
+ String oldProviders = !settingValue.isNull() ? settingValue.getValue() : "";
int index = oldProviders.indexOf(value);
int end = index + value.length();
@@ -2735,7 +2735,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 139;
+ private static final int SETTINGS_VERSION = 140;
private final int mUserId;
@@ -3186,6 +3186,16 @@
currentVersion = 139;
}
+ if (currentVersion == 139) {
+ // Version 140: Settings.Secure#ACCESSIBILITY_SPEAK_PASSWORD is deprecated and
+ // the user can no longer change the value of this setting through the UI.
+ // Force to true.
+ final SettingsState secureSettings = getSecureSettingsLocked(userId);
+ secureSettings.updateSettingLocked(Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD,
+ "1", null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ currentVersion = 140;
+ }
+
if (currentVersion != newVersion) {
Slog.wtf("SettingsProvider", "warning: upgrading settings database to version "
+ newVersion + " left it at "
diff --git a/packages/SystemUI/res/drawable/instant_icon.xml b/packages/SystemUI/res/drawable/instant_icon.xml
new file mode 100644
index 0000000..0039c81
--- /dev/null
+++ b/packages/SystemUI/res/drawable/instant_icon.xml
@@ -0,0 +1,30 @@
+<!--
+ Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40dp"
+ android:height="40dp"
+ android:viewportWidth="2.2"
+ android:viewportHeight="2.2">
+
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M.1,1.1
+ c0,.55 .45,1 1,1
+ c.55,0 1,-.45 1,-1
+ c0,-.55 -.45,-1 -1,-1
+ c-.55,0 -1,.45 -1,1z
+ M1.15,.95 l.5,0 l-.7,1 l0.1,-.7 l-.5,0 l.7,-1 z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index b3ff5d6..18ffd0f 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -20,6 +20,7 @@
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/volume_dialog_margin_bottom"
android:background="@drawable/volume_dialog_background"
+ android:paddingTop="@dimen/volume_dialog_padding_top"
android:translationZ="4dp" >
<LinearLayout
@@ -29,16 +30,11 @@
android:orientation="vertical" >
<LinearLayout
- android:id="@+id/volume_dialog_rows"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingEnd="@dimen/volume_button_size"
- android:paddingTop="@dimen/volume_dialog_collapsed_padding_top"
- android:orientation="vertical" >
- <View android:id="@+id/spacer"
- android:layout_width="match_parent"
- android:layout_height="@dimen/volume_dialog_expanded_spacer"
- android:visibility="gone"/>
+ android:id="@+id/volume_dialog_rows"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingEnd="@dimen/volume_button_size"
+ android:orientation="vertical" >
<!-- volume rows added and removed here! :-) -->
</LinearLayout>
@@ -48,7 +44,21 @@
<include layout="@layout/tuner_zen_mode_panel" />
</LinearLayout>
- <com.android.keyguard.AlphaOptimizedImageButton
+ <LinearLayout
+ android:id="@+id/volume_dialog_content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_alignParentEnd="true"
+ android:layout_alignParentTop="true"
+ android:layout_marginEnd="@dimen/volume_expander_margin_end" >
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:textAppearance="@style/TextAppearance.Volume.Header" />
+ <com.android.keyguard.AlphaOptimizedImageButton
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/volume_expand_button"
@@ -60,9 +70,7 @@
android:src="@drawable/ic_volume_collapse_animation"
android:background="@drawable/ripple_drawable"
tools:ignore="RtlHardcoded"
- android:layout_alignParentEnd="true"
- android:layout_alignParentTop="true"
- android:layout_marginTop="@dimen/volume_expander_margin_top"
- android:layout_marginEnd="@dimen/volume_expander_margin_end"/>
+ />
+ </LinearLayout>
</RelativeLayout>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index c40797c..ca05240 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -19,9 +19,6 @@
<!-- thickness (width) of the navigation bar on phones that require it -->
<dimen name="navigation_bar_size">@*android:dimen/navigation_bar_width</dimen>
- <!-- Standard notification gravity -->
- <integer name="notification_panel_layout_gravity">@integer/standard_notification_panel_layout_gravity</integer>
-
<dimen name="docked_divider_handle_width">2dp</dimen>
<dimen name="docked_divider_handle_height">16dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 7e63cbf..c8ffe8f 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -18,7 +18,6 @@
<resources>
<!-- Standard notification width + gravity -->
<dimen name="notification_panel_width">@dimen/standard_notification_panel_width</dimen>
- <integer name="notification_panel_layout_gravity">@integer/standard_notification_panel_layout_gravity</integer>
<!-- Diameter of outer shape drawable shown in navbar search-->
<dimen name="navbar_search_outerring_diameter">430dip</dimen>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 7f30c83..407cddf 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -147,4 +147,6 @@
<color name="ksh_key_item_color">@color/material_grey_600</color>
<color name="ksh_key_item_background">@color/material_grey_100</color>
+ <color name="instant_apps_color">#ff4d5a64</color>
+
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 40d4d6f..728dde0 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -201,8 +201,7 @@
<dimen name="volume_dialog_panel_width">@dimen/standard_notification_panel_width</dimen>
<!-- Gravity for the notification panel -->
- <integer name="standard_notification_panel_layout_gravity">0x31</integer><!-- top|center_horizontal -->
- <integer name="notification_panel_layout_gravity">0x37</integer><!-- fill_horizontal|top -->
+ <integer name="notification_panel_layout_gravity">0x31</integer><!-- center_horizontal|top -->
<!-- Height of the carrier/wifi name label -->
<dimen name="carrier_label_height">24dp</dimen>
@@ -600,8 +599,7 @@
<!-- Volume dialog root view bottom margin, at rest -->
<dimen name="volume_dialog_margin_bottom">4dp</dimen>
- <dimen name="volume_dialog_collapsed_padding_top">8dp</dimen>
- <dimen name="volume_dialog_expanded_spacer">14dp</dimen>
+ <dimen name="volume_dialog_padding_top">8dp</dimen>
<dimen name="volume_dialog_padding_end">40dp</dimen>
<dimen name="volume_row_padding_bottom">9.4dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b825cfb..815c41f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1810,4 +1810,13 @@
<!-- Title for the notification channel for problems with storage (i.e. low disk). [CHAR LIMIT=NONE] -->
<string name="notification_channel_storage">Storage</string>
+ <!-- App label of the instant apps notification [CHAR LIMIT=60] -->
+ <string name="instant_apps">Instant Apps</string>
+
+ <!-- Message of the instant apps notification indicating they don't need install [CHAR LIMIT=NONE] -->
+ <string name="instant_apps_message">Instant apps don\'t require installation.</string>
+
+ <!-- Action label for launching app info on the specified app [CHAR LIMIT=20] -->
+ <string name="app_info">App info</string>
+
</resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index 1518bdc..45f1686 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -89,7 +89,6 @@
setOnClickListener(mListener);
setOnHoverListener(new LiftToActivateListener(context));
- setAccessibilityDelegate(new ObscureSpeechDelegate(context));
mEnableHaptics = new LockPatternUtils(context).isTactileFeedbackEnabled();
@@ -134,14 +133,6 @@
}
@Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- // Reset the "announced headset" flag when detached.
- ObscureSpeechDelegate.sAnnouncedHeadset = false;
- }
-
- @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
diff --git a/packages/SystemUI/src/com/android/keyguard/ObscureSpeechDelegate.java b/packages/SystemUI/src/com/android/keyguard/ObscureSpeechDelegate.java
deleted file mode 100644
index 410a43a..0000000
--- a/packages/SystemUI/src/com/android/keyguard/ObscureSpeechDelegate.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2013 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.keyguard;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.media.AudioManager;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.view.View;
-import android.view.View.AccessibilityDelegate;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
-
-import com.android.internal.R;
-
-/**
- * Accessibility delegate that obscures speech for a view when the user has
- * not turned on the "speak passwords" preference and is not listening
- * through headphones.
- */
-class ObscureSpeechDelegate extends AccessibilityDelegate {
- /** Whether any client has announced the "headset" notification. */
- static boolean sAnnouncedHeadset = false;
-
- private final ContentResolver mContentResolver;
- private final AudioManager mAudioManager;
-
- public ObscureSpeechDelegate(Context context) {
- mContentResolver = context.getContentResolver();
- mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- }
-
- @Override
- public void sendAccessibilityEvent(View host, int eventType) {
- super.sendAccessibilityEvent(host, eventType);
-
- // Play the "headset required" announcement the first time the user
- // places accessibility focus on a key.
- if ((eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED)
- && !sAnnouncedHeadset && shouldObscureSpeech()) {
- sAnnouncedHeadset = true;
- host.announceForAccessibility(host.getContext().getString(
- R.string.keyboard_headset_required_to_hear_password));
- }
- }
-
- @Override
- public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
- super.onPopulateAccessibilityEvent(host, event);
-
- if ((event.getEventType() != AccessibilityEvent.TYPE_ANNOUNCEMENT)
- && shouldObscureSpeech()) {
- event.getText().clear();
- event.setContentDescription(host.getContext().getString(
- R.string.keyboard_password_character_no_headset));
- }
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
-
- if (shouldObscureSpeech()) {
- final Context ctx = host.getContext();
- info.setText(null);
- info.setContentDescription(
- ctx.getString(R.string.keyboard_password_character_no_headset));
- }
- }
-
- @SuppressWarnings("deprecation")
- private boolean shouldObscureSpeech() {
- // The user can optionally force speaking passwords.
- if (Settings.Secure.getIntForUser(mContentResolver,
- Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0, UserHandle.USER_CURRENT) != 0) {
- return false;
- }
-
- // Always speak if the user is listening through headphones.
- if (mAudioManager.isWiredHeadsetOn() || mAudioManager.isBluetoothA2dpOn()) {
- return false;
- }
-
- // Don't speak since this key is used to type a password.
- return true;
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
index 48737f9..c43820d 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -40,6 +40,7 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
+import android.widget.EditText;
import java.util.ArrayList;
import java.util.Stack;
@@ -306,9 +307,6 @@
int removedCount, int addedCount) {
if (AccessibilityManager.getInstance(mContext).isEnabled() &&
(isFocused() || isSelected() && isShown())) {
- if (!shouldSpeakPasswordsForAccessibility()) {
- beforeText = null;
- }
AccessibilityEvent event =
AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
event.setFromIndex(fromIndex);
@@ -324,48 +322,22 @@
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
- event.setClassName(PasswordTextView.class.getName());
+ event.setClassName(EditText.class.getName());
event.setPassword(true);
}
@Override
- public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
- super.onPopulateAccessibilityEvent(event);
-
- if (shouldSpeakPasswordsForAccessibility()) {
- final CharSequence text = mText;
- if (!TextUtils.isEmpty(text)) {
- event.getText().add(text);
- }
- }
- }
-
- @Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName(PasswordTextView.class.getName());
info.setPassword(true);
- if (shouldSpeakPasswordsForAccessibility()) {
- info.setText(mText);
- }
-
info.setEditable(true);
info.setInputType(InputType.TYPE_NUMBER_VARIATION_PASSWORD);
}
- /**
- * @return true if the user has explicitly allowed accessibility services
- * to speak passwords.
- */
- private boolean shouldSpeakPasswordsForAccessibility() {
- return (Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0,
- UserHandle.USER_CURRENT_OR_SELF) == 1);
- }
-
private class CharState {
char whichChar;
ValueAnimator textAnimator;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index b3a0eb7..5d57daa 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -23,7 +23,6 @@
import android.hardware.SensorManager;
import android.os.Handler;
import android.os.PowerManager;
-import android.os.SystemClock;
import com.android.internal.hardware.AmbientDisplayConfiguration;
import com.android.systemui.SystemUIApplication;
@@ -52,7 +51,10 @@
DozeFactory.WakeLock wakeLock = new DozeFactory.WakeLock(powerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, "Doze"));
- DozeMachine machine = new DozeMachine(dozeService, params, wakeLock);
+ DozeMachine machine = new DozeMachine(
+ DozeScreenStatePreventingAdapter.wrapIfNeeded(dozeService, params),
+ params,
+ wakeLock);
machine.setParts(new DozeMachine.Part[]{
createDozeTriggers(context, sensorManager, host, config, params, handler, wakeLock,
machine),
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
new file mode 100644
index 0000000..ad5897a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
@@ -0,0 +1,66 @@
+/*
+ * 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.doze;
+
+import android.support.annotation.VisibleForTesting;
+import android.view.Display;
+
+import com.android.systemui.statusbar.phone.DozeParameters;
+
+/**
+ * Prevents usage of doze screen states on devices that don't support them.
+ */
+public class DozeScreenStatePreventingAdapter implements DozeMachine.Service {
+
+ private final DozeMachine.Service mInner;
+
+ @VisibleForTesting
+ DozeScreenStatePreventingAdapter(DozeMachine.Service inner) {
+ mInner = inner;
+ }
+
+ @Override
+ public void finish() {
+ mInner.finish();
+ }
+
+ @Override
+ public void setDozeScreenState(int state) {
+ if (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND) {
+ state = Display.STATE_ON;
+ }
+ mInner.setDozeScreenState(state);
+ }
+
+ @Override
+ public void requestWakeUp() {
+ mInner.requestWakeUp();
+ }
+
+ /**
+ * If the device supports the doze display state, return {@code inner}. Otherwise
+ * return a new instance of {@link DozeScreenStatePreventingAdapter} wrapping {@code inner}.
+ */
+ public static DozeMachine.Service wrapIfNeeded(DozeMachine.Service inner,
+ DozeParameters params) {
+ return isNeeded(params) ? new DozeScreenStatePreventingAdapter(inner) : inner;
+ }
+
+ private static boolean isNeeded(DozeParameters params) {
+ return !params.getDisplayStateSupported();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
index a49c482..5eb483d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
@@ -17,27 +17,37 @@
package com.android.systemui.keyguard;
import android.app.Activity;
+import android.app.ActivityManager;
import android.app.ActivityOptions;
+import android.app.IActivityManager;
import android.app.KeyguardManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
+import android.os.RemoteException;
import android.os.UserHandle;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
public class WorkLockActivityController {
private final Context mContext;
- final SystemServicesProxy mSsp;
+ private final SystemServicesProxy mSsp;
+ private final IActivityManager mIam;
public WorkLockActivityController(Context context) {
- mContext = context;
- mSsp = SystemServicesProxy.getInstance(context);
+ this(context, SystemServicesProxy.getInstance(context), ActivityManager.getService());
+ }
- EventBus.getDefault().register(this);
+ @VisibleForTesting
+ WorkLockActivityController(Context context, SystemServicesProxy ssp, IActivityManager am) {
+ mContext = context;
+ mSsp = ssp;
+ mIam = am;
+
mSsp.registerTaskStackListener(mLockListener);
}
@@ -52,7 +62,40 @@
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchTaskId(taskId);
options.setTaskOverlay(true, false /* canResume */);
- mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT);
+
+ final int result = startActivityAsUser(intent, options.toBundle(), UserHandle.USER_CURRENT);
+ if (result >= ActivityManager.START_SUCCESS) {
+ // OK
+ } else {
+ // Starting the activity inside the task failed. We can't be sure why, so to be
+ // safe just remove the whole task if it still exists.
+ mSsp.removeTask(taskId);
+ }
+ }
+
+ /**
+ * Version of {@link Context#startActivityAsUser} which keeps the success code from
+ * IActivityManager, so we can read back whether ActivityManager thinks it started properly.
+ */
+ private int startActivityAsUser(Intent intent, Bundle options, int userId) {
+ try {
+ return mIam.startActivityAsUser(
+ mContext.getIApplicationThread() /*caller*/,
+ mContext.getBasePackageName() /*callingPackage*/,
+ intent /*intent*/,
+ intent.resolveTypeIfNeeded(mContext.getContentResolver()) /*resolvedType*/,
+ null /*resultTo*/,
+ null /*resultWho*/,
+ 0 /*requestCode*/,
+ Intent.FLAG_ACTIVITY_NEW_TASK /*flags*/,
+ null /*profilerInfo*/,
+ options /*options*/,
+ userId /*user*/);
+ } catch (RemoteException e) {
+ return ActivityManager.START_CANCELED;
+ } catch (Exception e) {
+ return ActivityManager.START_CANCELED;
+ }
}
private final TaskStackListener mLockListener = new TaskStackListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 42f1b14..ae402ef 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -76,7 +76,7 @@
}
@Override
- public void onPinnedActivityRestartAttempt(ComponentName sourceComponent) {
+ public void onPinnedActivityRestartAttempt(String launchedFromPackage) {
if (!checkCurrentUserId(false /* debug */)) {
return;
}
@@ -84,11 +84,11 @@
// Expand the activity back to fullscreen only if it was attempted to be restarted from
// another package than the top activity in the stack
boolean expandPipToFullscreen = true;
- if (sourceComponent != null) {
+ if (launchedFromPackage != null) {
ComponentName topActivity = PipUtils.getTopPinnedActivity(mContext,
mActivityManager);
- if (topActivity != null && topActivity.getPackageName().equals(
- sourceComponent.getPackageName())) {
+ if (topActivity != null
+ && topActivity.getPackageName().equals(launchedFromPackage)) {
expandPipToFullscreen = false;
}
}
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 9066977..8a60cd1 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -138,6 +138,24 @@
}
@Override
+ protected void onUserLeaveHint() {
+ super.onUserLeaveHint();
+
+ // If another task is starting on top of the menu, then finish it so that it can be
+ // recreated on the top next time it starts
+ finish();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ // Fallback, if we are destroyed for any other reason (like when the task is being reset),
+ // also reset the callback.
+ notifyActivityCallback(null);
+ }
+
+ @Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
if (!isInPictureInPictureMode) {
finish();
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 112fedb..376a0b6 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -695,7 +695,7 @@
}
@Override
- public void onPinnedActivityRestartAttempt(ComponentName sourceComponent) {
+ public void onPinnedActivityRestartAttempt(String launchedFromPackage) {
if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
if (!checkCurrentUserId(DEBUG)) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index d789b44..c2c6f7a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -49,6 +49,9 @@
public QuickQSPanel(Context context, AttributeSet attrs) {
super(context, attrs);
+ if (mFooter != null) {
+ removeView((View) mFooter.getView());
+ }
if (mTileLayout != null) {
for (int i = 0; i < mRecords.size(); i++) {
mTileLayout.removeTile(mRecords.get(i));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 8de4e58..33ad7fb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -375,6 +375,10 @@
MutableBoolean isHomeStackVisible = new MutableBoolean(true);
if (!ssp.isRecentsActivityVisible(isHomeStackVisible)) {
ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
+ if (runningTask == null) {
+ return;
+ }
+
RecentsTaskLoader loader = Recents.getTaskLoader();
sInstanceLoadPlan = loader.createLoadPlan(mContext);
sInstanceLoadPlan.preloadRawTasks(!isHomeStackVisible.value);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index eae1b81..cda902b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -153,7 +153,7 @@
public void onTaskStackChanged() { }
public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { }
public void onActivityPinned() { }
- public void onPinnedActivityRestartAttempt(ComponentName sourceComponent) { }
+ public void onPinnedActivityRestartAttempt(String launchedFromPackage) { }
public void onPinnedStackAnimationEnded() { }
public void onActivityForcedResizable(String packageName, int taskId) { }
public void onActivityDismissingDockedStack() { }
@@ -198,10 +198,10 @@
}
@Override
- public void onPinnedActivityRestartAttempt(ComponentName sourceComponent)
+ public void onPinnedActivityRestartAttempt(String launchedFromPackage)
throws RemoteException{
mHandler.removeMessages(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT);
- mHandler.obtainMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT, sourceComponent)
+ mHandler.obtainMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT, launchedFromPackage)
.sendToTarget();
}
@@ -1244,8 +1244,7 @@
}
case ON_PINNED_ACTIVITY_RESTART_ATTEMPT: {
for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onPinnedActivityRestartAttempt(
- (ComponentName) msg.obj);
+ mTaskStackListeners.get(i).onPinnedActivityRestartAttempt((String) msg.obj);
}
break;
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 9a4b45a..2d47c7b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -21,6 +21,7 @@
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.app.admin.DevicePolicyManager;
import android.app.Notification;
import android.app.Notification.BigPictureStyle;
import android.app.NotificationManager;
@@ -46,6 +47,7 @@
import android.os.Bundle;
import android.os.Environment;
import android.os.Process;
+import android.os.UserHandle;
import android.provider.MediaStore;
import android.util.DisplayMetrics;
import android.view.Display;
@@ -866,6 +868,16 @@
.setAutoCancel(true)
.setColor(context.getColor(
com.android.internal.R.color.system_notification_accent_color));
+ final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
+ Context.DEVICE_POLICY_SERVICE);
+ final Intent intent = dpm.createAdminSupportIntent(
+ DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
+ if (intent != null) {
+ final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
+ context, 0, intent, 0, null, UserHandle.CURRENT);
+ b.setContentIntent(pendingIntent);
+ }
+
SystemUI.overrideNotificationAppName(context, b);
Notification n = new Notification.BigTextStyle(b)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 6da9e90..f55699b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -292,7 +292,7 @@
int panelGravity = getResources().getInteger(R.integer.notification_panel_layout_gravity);
FrameLayout.LayoutParams lp =
(FrameLayout.LayoutParams) mQsFrame.getLayoutParams();
- if (lp.width != panelWidth) {
+ if (lp.width != panelWidth || lp.gravity != panelGravity) {
lp.width = panelWidth;
lp.gravity = panelGravity;
mQsFrame.setLayoutParams(lp);
@@ -300,7 +300,7 @@
}
lp = (FrameLayout.LayoutParams) mNotificationStackScroller.getLayoutParams();
- if (lp.width != panelWidth) {
+ if (lp.width != panelWidth || lp.gravity != panelGravity) {
lp.width = panelWidth;
lp.gravity = panelGravity;
mNotificationStackScroller.setLayoutParams(lp);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 93f874d..9e93ed3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -17,30 +17,57 @@
package com.android.systemui.statusbar.phone;
import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
+import android.app.ActivityManager.StackId;
+import android.app.ActivityManager.StackInfo;
import android.app.AlarmManager;
import android.app.AlarmManager.AlarmClockInfo;
+import android.app.AppGlobals;
+import android.app.Notification;
+import android.app.Notification.Action;
+import android.app.Notification.BigTextStyle;
+import android.app.Notification.Style;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.app.SynchronousUserSwitchObserver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
+import android.graphics.drawable.Icon;
import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.provider.Settings.Global;
+import android.service.notification.StatusBarNotification;
import android.telecom.TelecomManager;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
+import android.util.Pair;
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.R.string;
import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.RotationLockTile;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
import com.android.systemui.statusbar.policy.BluetoothController;
@@ -58,6 +85,10 @@
import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.util.NotificationChannels;
+
+import java.util.ArrayList;
+import java.util.List;
/**
* This class contains all of the policy about which icons are installed in the status
@@ -96,6 +127,7 @@
private final ZenModeController mZenController;
private final DeviceProvisionedController mProvisionedController;
private final KeyguardMonitor mKeyguardMonitor;
+ private final ArraySet<Pair<String, Integer>> mCurrentNotifs = new ArraySet<>();
// Assume it's all good unless we hear otherwise. We don't always seem
// to get broadcasts that it *is* there.
@@ -163,7 +195,7 @@
}
// TTY status
- mIconController.setIcon(mSlotTty, R.drawable.stat_sys_tty_mode, null);
+ mIconController.setIcon(mSlotTty, R.drawable.stat_sys_tty_mode, null);
mIconController.setIconVisibility(mSlotTty, false);
// bluetooth status
@@ -212,6 +244,15 @@
mKeyguardMonitor.addCallback(this);
SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallbacks(this);
+ SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskListener);
+
+ // Clear out all old notifications on startup (only present in the case where sysui dies)
+ NotificationManager noMan = mContext.getSystemService(NotificationManager.class);
+ for (StatusBarNotification notification : noMan.getActiveNotifications()) {
+ if (notification.getId() == SystemMessage.NOTE_INSTANT_APPS) {
+ noMan.cancel(notification.getTag(), notification.getId());
+ }
+ }
}
public void destroy() {
@@ -226,6 +267,10 @@
mKeyguardMonitor.removeCallback(this);
SysUiServiceProvider.getComponent(mContext, CommandQueue.class).removeCallbacks(this);
mContext.unregisterReceiver(mIntentReceiver);
+
+ NotificationManager noMan = mContext.getSystemService(NotificationManager.class);
+ mCurrentNotifs.forEach(v -> noMan.cancelAsUser(v.first, SystemMessage.NOTE_INSTANT_APPS,
+ new UserHandle(v.second)));
}
@Override
@@ -423,8 +468,10 @@
}
private void updateManagedProfile() {
- if (DEBUG) Log.v(TAG, "updateManagedProfile: mManagedProfileFocused: "
- + mManagedProfileFocused);
+ if (DEBUG) {
+ Log.v(TAG, "updateManagedProfile: mManagedProfileFocused: "
+ + mManagedProfileFocused);
+ }
final boolean showIcon;
if (mManagedProfileFocused && !mKeyguardMonitor.isShowing()) {
showIcon = true;
@@ -445,6 +492,77 @@
}
}
+ private void updateForegroundInstantApps() {
+ NotificationManager noMan = mContext.getSystemService(NotificationManager.class);
+ ArraySet<Pair<String, Integer>> notifs = new ArraySet<>(mCurrentNotifs);
+ IPackageManager pm = AppGlobals.getPackageManager();
+ mCurrentNotifs.clear();
+ try {
+ int[] STACKS_TO_CHECK = new int[]{
+ StackId.FULLSCREEN_WORKSPACE_STACK_ID,
+ StackId.DOCKED_STACK_ID,
+ };
+ for (int i = 0; i < STACKS_TO_CHECK.length; i++) {
+ StackInfo info = ActivityManager.getService().getStackInfo(STACKS_TO_CHECK[i]);
+ if (info == null || info.topActivity == null) continue;
+ String pkg = info.topActivity.getPackageName();
+ if (!hasNotif(notifs, pkg, info.userId)) {
+ // TODO: Optimize by not always needing to get application info.
+ // Maybe cache non-ephemeral packages?
+ ApplicationInfo appInfo = pm.getApplicationInfo(pkg,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES, info.userId);
+ if (appInfo.isInstantApp()) {
+ postEphemeralNotif(pkg, info.userId, appInfo, noMan);
+ }
+ }
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ // Cancel all the leftover notifications that don't have a foreground process anymore.
+ notifs.forEach(v -> noMan.cancelAsUser(v.first, SystemMessage.NOTE_INSTANT_APPS,
+ new UserHandle(v.second)));
+ }
+
+ private void postEphemeralNotif(String pkg, int userId, ApplicationInfo appInfo,
+ NotificationManager noMan) {
+ final Bundle extras = new Bundle();
+ extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
+ mContext.getString(R.string.instant_apps));
+ mCurrentNotifs.add(new Pair<>(pkg, userId));
+ String message = mContext.getString(R.string.instant_apps_message);
+ PendingIntent appInfoAction = PendingIntent.getActivity(mContext, 0,
+ new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+ .setData(Uri.fromParts("package", pkg, null)), 0);
+ // TODO: Add action for go to web as well.
+ Action action = new Notification.Action.Builder(null, mContext.getString(R.string.app_info),
+ appInfoAction).build();
+
+ noMan.notifyAsUser(pkg, SystemMessage.NOTE_INSTANT_APPS,
+ new Notification.Builder(mContext, NotificationChannels.GENERAL)
+ .addExtras(extras)
+ .addAction(action)
+ .setContentIntent(appInfoAction)
+ .setColor(mContext.getColor(R.color.instant_apps_color))
+ .setContentTitle(appInfo.loadLabel(mContext.getPackageManager()))
+ .setLargeIcon(Icon.createWithResource(pkg, appInfo.icon))
+ .setSmallIcon(Icon.createWithResource(mContext.getPackageName(),
+ R.drawable.instant_icon))
+ .setContentText(message)
+ .setOngoing(true)
+ .build(),
+ new UserHandle(userId));
+ }
+
+ private boolean hasNotif(ArraySet<Pair<String, Integer>> notifs, String pkg, int userId) {
+ Pair<String, Integer> key = new Pair<>(pkg, userId);
+ if (notifs.remove(key)) {
+ mCurrentNotifs.add(key);
+ return true;
+ }
+ return false;
+ }
+
private final SynchronousUserSwitchObserver mUserSwitchListener =
new SynchronousUserSwitchObserver() {
@Override
@@ -466,6 +584,7 @@
profileChanged(newUserId);
updateQuietState();
updateManagedProfile();
+ updateForegroundInstantApps();
}
});
}
@@ -497,20 +616,22 @@
private final NextAlarmController.NextAlarmChangeCallback mNextAlarmCallback =
new NextAlarmController.NextAlarmChangeCallback() {
- @Override
- public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
- updateAlarm();
- }
- };
+ @Override
+ public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
+ updateAlarm();
+ }
+ };
@Override
public void appTransitionStarting(long startTime, long duration, boolean forced) {
updateManagedProfile();
+ updateForegroundInstantApps();
}
@Override
public void onKeyguardShowingChanged() {
updateManagedProfile();
+ updateForegroundInstantApps();
}
@Override
@@ -524,6 +645,11 @@
}
@Override
+ public void preloadRecentApps() {
+ updateForegroundInstantApps();
+ }
+
+ @Override
public void onRotationLockStateChanged(boolean rotationLocked, boolean affordanceVisible) {
boolean portrait = RotationLockTile.isCurrentOrientationLockPortrait(
mRotationLockController, mContext);
@@ -561,6 +687,14 @@
mIconController.setIconVisibility(mSlotDataSaver, isDataSaving);
}
+ private final TaskStackListener mTaskListener = new TaskStackListener() {
+ @Override
+ public void onTaskStackChanged() {
+ // Listen for changes to stacks and then check which instant apps are foreground.
+ updateForegroundInstantApps();
+ }
+ };
+
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index dd04741..eca8939 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1684,6 +1684,7 @@
if (entry != null && entry.row != null) {
entry.row.setRemoved();
+ mStackScroller.cleanUpViewState(entry.row);
}
// Let's remove the children if this was a summary
handleGroupSummaryRemoved(key, ranking);
@@ -1740,12 +1741,6 @@
// animations
toRemove.get(i).setRemoved();
}
- for (int i = 0; i < toRemove.size(); i++) {
- removeNotification(toRemove.get(i).getStatusBarNotification().getKey(), ranking);
- // we need to ensure that the view is actually properly removed from the viewstate
- // as this won't happen anymore when kept in the parent.
- mStackScroller.removeViewStateForView(toRemove.get(i));
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index 886b8be..12b7098 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -15,10 +15,10 @@
*/
package com.android.systemui.statusbar.policy;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
+import android.net.NetworkBadging;
import android.net.NetworkCapabilities;
import android.net.NetworkKey;
import android.net.NetworkScoreManager;
@@ -155,7 +155,7 @@
@Override
public int getCurrentIconId() {
- if (mCurrentState.badgeEnum != ScoredNetwork.BADGING_NONE) {
+ if (mCurrentState.badgeEnum != NetworkBadging.BADGING_NONE) {
return Utils.WIFI_PIE_FOR_BADGING[mCurrentState.level];
}
return super.getCurrentIconId();
@@ -201,14 +201,14 @@
*/
private int getWifiBadgeEnum() {
if (!mScoringUiEnabled || mWifiTracker.networkKey == null) {
- return ScoredNetwork.BADGING_NONE;
+ return NetworkBadging.BADGING_NONE;
}
ScoredNetwork score = mScoreCache.getScoredNetwork(mWifiTracker.networkKey);
if (score != null) {
return score.calculateBadge(mWifiTracker.rssi);
}
- return ScoredNetwork.BADGING_NONE;
+ return NetworkBadging.BADGING_NONE;
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 67cc5e3..fd40c68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -2473,6 +2473,17 @@
}
}
+ /**
+ * Called when a notification is removed from the shade. This cleans up the state for a given
+ * view.
+ */
+ public void cleanUpViewState(View child) {
+ if (child == mTranslatingParentView) {
+ mTranslatingParentView = null;
+ }
+ mCurrentStackScrollState.removeViewStateForView(child);
+ }
+
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
super.requestDisallowInterceptTouchEvent(disallowIntercept);
@@ -4070,15 +4081,6 @@
setFadingOut(alpha != 1.0f);
}
- /**
- * Remove the a given view from the viewstate. This is currently used when the children are
- * kept in the parent artificially to have a nicer animation.
- * @param view the view to remove
- */
- public void removeViewStateForView(View view) {
- mCurrentStackScrollState.removeViewStateForView(view);
- }
-
public void setQsExpanded(boolean qsExpanded) {
mQsExpanded = qsExpanded;
updateAlgorithmLayoutMinHeight();
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
index b9cb575..b320d60 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
@@ -622,7 +622,6 @@
if (!mShowing) {
trimObsoleteH();
}
- Util.setVisOrGone(mDialogRowsView.findViewById(R.id.spacer), mExpanded);
// apply changes to all rows
for (final VolumeRow row : mRows) {
final boolean isActive = row == activeRow;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java
index 8144ea4..d6d0f75 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java
@@ -140,7 +140,17 @@
final float v = (Float) mChevronPositionAnimator.getAnimatedValue();
final int posY = chevronPosY();
mChevron.setTranslationY(posY + v + -mDialogView.getTranslationY());
- }})
+ }
+ })
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ if (mChevronPositionAnimator == null) return;
+ // reposition chevron
+ final int posY = chevronPosY();
+ mChevron.setTranslationY(posY + -mDialogView.getTranslationY());
+ }
+ })
.start();
mContentsPositionAnimator = ValueAnimator.ofFloat(-chevronDistance(), 0)
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 8f5df7b..760d875 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -19,6 +19,7 @@
LOCAL_MODULE_TAGS := tests
LOCAL_JACK_FLAGS := --multi-dex native
+LOCAL_DX_FLAGS := --multi-dex
LOCAL_PROTOC_OPTIMIZE_TYPE := nano
LOCAL_PROTOC_FLAGS := -I$(LOCAL_PATH)/..
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java
new file mode 100644
index 0000000..ada8ac0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.doze;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.support.test.filters.SmallTest;
+import android.view.Display;
+
+import com.android.systemui.statusbar.phone.DozeParameters;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@SmallTest
+public class DozeScreenStatePreventingAdapterTest {
+
+ private DozeMachine.Service mInner;
+ private DozeScreenStatePreventingAdapter mWrapper;
+
+ @Before
+ public void setup() throws Exception {
+ mInner = mock(DozeMachine.Service.class);
+ mWrapper = new DozeScreenStatePreventingAdapter(mInner);
+ }
+
+ @Test
+ public void forwards_finish() throws Exception {
+ mWrapper.finish();
+ verify(mInner).finish();
+ }
+
+ @Test
+ public void forwards_setDozeScreenState_on() throws Exception {
+ mWrapper.setDozeScreenState(Display.STATE_ON);
+ verify(mInner).setDozeScreenState(Display.STATE_ON);
+ }
+
+ @Test
+ public void forwards_setDozeScreenState_off() throws Exception {
+ mWrapper.setDozeScreenState(Display.STATE_OFF);
+ verify(mInner).setDozeScreenState(Display.STATE_OFF);
+ }
+
+ @Test
+ public void forwards_setDozeScreenState_doze() throws Exception {
+ mWrapper.setDozeScreenState(Display.STATE_DOZE);
+ verify(mInner).setDozeScreenState(Display.STATE_ON);
+ }
+
+ @Test
+ public void forwards_setDozeScreenState_doze_suspend() throws Exception {
+ mWrapper.setDozeScreenState(Display.STATE_DOZE_SUSPEND);
+ verify(mInner).setDozeScreenState(Display.STATE_ON);
+ }
+
+ @Test
+ public void forwards_requestWakeUp() throws Exception {
+ mWrapper.requestWakeUp();
+ verify(mInner).requestWakeUp();
+ }
+
+ @Test
+ public void wrapIfNeeded_needed() throws Exception {
+ DozeParameters params = mock(DozeParameters.class);
+ when(params.getDisplayStateSupported()).thenReturn(false);
+
+ assertEquals(DozeScreenStatePreventingAdapter.class,
+ DozeScreenStatePreventingAdapter.wrapIfNeeded(mInner, params).getClass());
+ }
+
+ @Test
+ public void wrapIfNeeded_not_needed() throws Exception {
+ DozeParameters params = mock(DozeParameters.class);
+ when(params.getDisplayStateSupported()).thenReturn(true);
+
+ assertSame(mInner, DozeScreenStatePreventingAdapter.wrapIfNeeded(mInner, params));
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
new file mode 100644
index 0000000..0f2878b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
@@ -0,0 +1,169 @@
+/*
+ * 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.keyguard;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.argThat;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.IActivityManager;
+import android.app.IApplicationThread;
+import android.app.ProfilerInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.systemui.keyguard.WorkLockActivity;
+import com.android.systemui.keyguard.WorkLockActivityController;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * runtest systemui -c com.android.systemui.keyguard.WorkLockActivityControllerTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class WorkLockActivityControllerTest {
+ private static final int USER_ID = 333;
+ private static final int TASK_ID = 444;
+
+ private @Mock Context mContext;
+ private @Mock SystemServicesProxy mSystemServicesProxy;
+ private @Mock IActivityManager mIActivityManager;
+
+ private WorkLockActivityController mController;
+ private TaskStackListener mTaskStackListener;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ // Set a package name to use for checking ComponentName well-formedness in tests.
+ doReturn("com.example.test").when(mContext).getPackageName();
+
+ // Construct controller. Save the TaskStackListener for injecting events.
+ final ArgumentCaptor<TaskStackListener> listenerCaptor =
+ ArgumentCaptor.forClass(TaskStackListener.class);
+ mController =
+ new WorkLockActivityController(mContext, mSystemServicesProxy, mIActivityManager);
+
+ verify(mSystemServicesProxy).registerTaskStackListener(listenerCaptor.capture());
+ mTaskStackListener = listenerCaptor.getValue();
+ }
+
+ @Test
+ public void testOverlayStartedWhenLocked() throws Exception {
+ // When starting an activity succeeds,
+ setActivityStartCode(TASK_ID, true /*taskOverlay*/, ActivityManager.START_SUCCESS);
+
+ // And the controller receives a message saying the profile is locked,
+ mTaskStackListener.onTaskProfileLocked(TASK_ID, USER_ID);
+
+ // The overlay should start and the task the activity started in should not be removed.
+ verifyStartActivity(TASK_ID, true /*taskOverlay*/);
+ verify(mSystemServicesProxy, never()).removeTask(anyInt() /*taskId*/);
+ }
+
+ @Test
+ public void testRemoveTaskOnFailureToStartOverlay() throws Exception {
+ // When starting an activity fails,
+ setActivityStartCode(TASK_ID, true /*taskOverlay*/, ActivityManager.START_CLASS_NOT_FOUND);
+
+ // And the controller receives a message saying the profile is locked,
+ mTaskStackListener.onTaskProfileLocked(TASK_ID, USER_ID);
+
+ // The task the activity started in should be removed to prevent the locked task from
+ // being shown.
+ verifyStartActivity(TASK_ID, true /*taskOverlay*/);
+ verify(mSystemServicesProxy).removeTask(TASK_ID);
+ }
+
+ // End of tests, start of helpers
+ // ------------------------------
+
+ private void setActivityStartCode(int taskId, boolean taskOverlay, int code) throws Exception {
+ doReturn(code).when(mIActivityManager).startActivityAsUser(
+ eq((IApplicationThread) null),
+ eq((String) null),
+ any(Intent.class),
+ eq((String) null),
+ eq((IBinder) null),
+ eq((String) null),
+ anyInt(),
+ anyInt(),
+ eq((ProfilerInfo) null),
+ argThat(hasOptions(taskId, taskOverlay)),
+ eq(UserHandle.USER_CURRENT));
+ }
+
+ private void verifyStartActivity(int taskId, boolean taskOverlay) throws Exception {
+ verify(mIActivityManager).startActivityAsUser(
+ eq((IApplicationThread) null),
+ eq((String) null),
+ any(Intent.class),
+ eq((String) null),
+ eq((IBinder) null),
+ eq((String) null),
+ anyInt(),
+ anyInt(),
+ eq((ProfilerInfo) null),
+ argThat(hasOptions(taskId, taskOverlay)),
+ eq(UserHandle.USER_CURRENT));
+ }
+
+ private static ArgumentMatcher<Intent> hasComponent(final Context context,
+ final Class<? extends Activity> activityClass) {
+ return new ArgumentMatcher<Intent>() {
+ @Override
+ public boolean matches(Intent intent) {
+ return new ComponentName(context, activityClass).equals(intent.getComponent());
+ }
+ };
+ }
+
+ private static ArgumentMatcher<Bundle> hasOptions(final int taskId, final boolean overlay) {
+ return new ArgumentMatcher<Bundle>() {
+ @Override
+ public boolean matches(Bundle item) {
+ final ActivityOptions options = ActivityOptions.fromBundle(item);
+ return (options.getLaunchTaskId() == taskId)
+ && (options.getTaskOverlay() == overlay);
+ }
+ };
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index 9b382f6..8cbf95b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -1,6 +1,7 @@
package com.android.systemui.statusbar.policy;
import android.content.Intent;
+import android.net.NetworkBadging;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkKey;
@@ -12,7 +13,6 @@
import android.net.wifi.WifiNetworkScoreCache;
import android.os.Bundle;
import android.provider.Settings;
-import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
@@ -106,7 +106,7 @@
setWifiState(true, TEST_SSID, TEST_BSSID);
mRequestScoresLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS);
- when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) ScoredNetwork.BADGING_SD);
+ when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) NetworkBadging.BADGING_SD);
ArgumentCaptor<WifiNetworkScoreCache> scoreCacheCaptor =
ArgumentCaptor.forClass(WifiNetworkScoreCache.class);
@@ -129,7 +129,7 @@
Utils.WIFI_PIE_FOR_BADGING[testLevel],
iconState.icon);
assertEquals("SD Badge is set",
- Utils.getWifiBadgeResource(ScoredNetwork.BADGING_SD),
+ Utils.getWifiBadgeResource(NetworkBadging.BADGING_SD),
iconState.iconOverlay);
settingsOverrider.release();
diff --git a/packages/WallpaperCropper/Android.mk b/packages/WallpaperCropper/Android.mk
index d8fb7a4..09b41fd 100644
--- a/packages/WallpaperCropper/Android.mk
+++ b/packages/WallpaperCropper/Android.mk
@@ -6,7 +6,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_JAVA_LIBRARIES := telephony-common
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 junit
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
LOCAL_PACKAGE_NAME := WallpaperCropper
LOCAL_CERTIFICATE := platform
diff --git a/packages/WallpaperCropper/src/com/android/gallery3d/glrenderer/BitmapTexture.java b/packages/WallpaperCropper/src/com/android/gallery3d/glrenderer/BitmapTexture.java
index 100b0b3b..f8b01cb 100644
--- a/packages/WallpaperCropper/src/com/android/gallery3d/glrenderer/BitmapTexture.java
+++ b/packages/WallpaperCropper/src/com/android/gallery3d/glrenderer/BitmapTexture.java
@@ -18,7 +18,7 @@
import android.graphics.Bitmap;
-import junit.framework.Assert;
+import com.android.gallery3d.common.Utils;
// BitmapTexture is a texture whose content is specified by a fixed Bitmap.
//
@@ -34,7 +34,7 @@
public BitmapTexture(Bitmap bitmap, boolean hasBorder) {
super(hasBorder);
- Assert.assertTrue(bitmap != null && !bitmap.isRecycled());
+ Utils.assertTrue(bitmap != null && !bitmap.isRecycled());
mContentBitmap = bitmap;
}
diff --git a/packages/WallpaperCropper/src/com/android/gallery3d/glrenderer/GLPaint.java b/packages/WallpaperCropper/src/com/android/gallery3d/glrenderer/GLPaint.java
index 16b2206..b26e9ab 100644
--- a/packages/WallpaperCropper/src/com/android/gallery3d/glrenderer/GLPaint.java
+++ b/packages/WallpaperCropper/src/com/android/gallery3d/glrenderer/GLPaint.java
@@ -16,7 +16,7 @@
package com.android.gallery3d.glrenderer;
-import junit.framework.Assert;
+import com.android.gallery3d.common.Utils;
public class GLPaint {
private float mLineWidth = 1f;
@@ -31,7 +31,7 @@
}
public void setLineWidth(float width) {
- Assert.assertTrue(width >= 0);
+ Utils.assertTrue(width >= 0);
mLineWidth = width;
}
diff --git a/packages/WallpaperCropper/src/com/android/gallery3d/glrenderer/UploadedTexture.java b/packages/WallpaperCropper/src/com/android/gallery3d/glrenderer/UploadedTexture.java
index f41a979..417102a 100644
--- a/packages/WallpaperCropper/src/com/android/gallery3d/glrenderer/UploadedTexture.java
+++ b/packages/WallpaperCropper/src/com/android/gallery3d/glrenderer/UploadedTexture.java
@@ -20,7 +20,7 @@
import android.graphics.Bitmap.Config;
import android.opengl.GLUtils;
-import junit.framework.Assert;
+import com.android.gallery3d.common.Utils;
import java.util.HashMap;
@@ -144,7 +144,7 @@
}
private void freeBitmap() {
- Assert.assertTrue(mBitmap != null);
+ Utils.assertTrue(mBitmap != null);
onFreeBitmap(mBitmap);
mBitmap = null;
}
@@ -219,7 +219,7 @@
int texWidth = getTextureWidth();
int texHeight = getTextureHeight();
- Assert.assertTrue(bWidth <= texWidth && bHeight <= texHeight);
+ Utils.assertTrue(bWidth <= texWidth && bHeight <= texHeight);
// Upload the bitmap to a new texture.
mId = canvas.getGLId().generateTexture();
diff --git a/preloaded-classes b/preloaded-classes
index 7dc5a25..a72a042 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -3919,6 +3919,18 @@
org.apache.http.message.BasicHttpResponse
org.apache.http.message.BasicStatusLine
org.apache.http.message.HeaderGroup
+org.ccil.cowan.tagsoup.AttributesImpl
+org.ccil.cowan.tagsoup.AutoDetector
+org.ccil.cowan.tagsoup.Element
+org.ccil.cowan.tagsoup.ElementType
+org.ccil.cowan.tagsoup.HTMLModels
+org.ccil.cowan.tagsoup.HTMLScanner
+org.ccil.cowan.tagsoup.HTMLSchema
+org.ccil.cowan.tagsoup.Parser
+org.ccil.cowan.tagsoup.Parser$1
+org.ccil.cowan.tagsoup.ScanHandler
+org.ccil.cowan.tagsoup.Scanner
+org.ccil.cowan.tagsoup.Schema
org.json.JSON
org.json.JSONArray
org.json.JSONException
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 8d2f0c3..c81bd1b 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3429,6 +3429,35 @@
// VALUE: true if the connection was successful, false if the connection failed
WIFI_NETWORK_RECOMMENDATION_CONNECTION_SUCCESS = 837;
+ // OPEN: Settings > Storage > Games
+ // CATEGORY: SETTINGS
+ // OS: O
+ APPLICATIONS_STORAGE_GAMES = 838;
+
+ // OPEN: Settings > Storage > Audio and Music
+ // CATEGORY: SETTINGS
+ // OS: O
+ APPLICATIONS_STORAGE_MUSIC = 839;
+
+ // ACTION: Settings > Storage > Free Up Space to launch Deletion Helper
+ // CATEGORY: SETTINGS
+ // OS: O
+ STORAGE_FREE_UP_SPACE_NOW = 840;
+
+ // ACTION: Settings > Storage > Files to open the File Manager
+ // CATEGORY: SETTINGS
+ // OS: O
+ STORAGE_FILES = 841;
+
+ // FIELD - Rank of the clicked Settings search result
+ FIELD_SETTINGS_SERACH_RESULT_RANK = 842;
+
+ // OPEN: Settings > Apps > Default Apps > Assist > Default assist
+ DEFAULT_ASSIST_PICKER = 843;
+
+ // OPEN: Settings > Apps > Default Apps > Assist > Default voice input
+ DEFAULT_VOICE_INPUT_PICKER = 844;
+
// ---- End O Constants, all O constants go above this line ----
// Add new aosp constants above this line.
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 5b91776..74f5cf5 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -52,6 +52,10 @@
// Package: com.android.systemui
NOTE_PLUGIN = 6;
+ // Notify the user that instant app is running.
+ // Package: com.android.systemui
+ NOTE_INSTANT_APPS = 7;
+
// Confirm that the user wants to remove the guest account.
// Package: com.android.systemui
NOTE_REMOVE_GUEST = 1010;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index aae5dd8..44afe1d 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -912,8 +912,21 @@
*/
// TODO: (multi-display) Make sure this works for multiple displays.
boolean getAccessibilityFocusClickPointInScreen(Point outPoint) {
- return getInteractionBridgeLocked()
- .getAccessibilityFocusClickPointInScreenNotLocked(outPoint);
+ return getInteractionBridge().getAccessibilityFocusClickPointInScreenNotLocked(outPoint);
+ }
+
+ /**
+ * Perform an accessibility action on the view that currently has accessibility focus.
+ * Has no effect if no item has accessibility focus, if the item with accessibility
+ * focus does not expose the specified action, or if the action fails.
+ *
+ * @param actionId The id of the action to perform.
+ *
+ * @return {@code true} if the action was performed. {@code false} if it was not.
+ */
+ public boolean performActionOnAccessibilityFocusedItem(
+ AccessibilityNodeInfo.AccessibilityAction action) {
+ return getInteractionBridge().performActionOnAccessibilityFocusedItemNotLocked(action);
}
/**
@@ -1029,11 +1042,13 @@
onUserStateChangedLocked(userState);
}
- private InteractionBridge getInteractionBridgeLocked() {
- if (mInteractionBridge == null) {
- mInteractionBridge = new InteractionBridge();
+ private InteractionBridge getInteractionBridge() {
+ synchronized (mLock) {
+ if (mInteractionBridge == null) {
+ mInteractionBridge = new InteractionBridge();
+ }
+ return mInteractionBridge;
}
- return mInteractionBridge;
}
private boolean notifyGestureLocked(int gestureId, boolean isDefault) {
@@ -1991,23 +2006,26 @@
private void updateFingerprintGestureHandling(UserState userState) {
final List<Service> services;
synchronized (mLock) {
- // Only create the controller when a service wants to use the feature
services = userState.mBoundServices;
- int numServices = services.size();
- for (int i = 0; i < numServices; i++) {
- if (services.get(i).isCapturingFingerprintGestures()) {
- final long identity = Binder.clearCallingIdentity();
- IFingerprintService service = null;
- try {
- service = IFingerprintService.Stub.asInterface(
- ServiceManager.getService(Context.FINGERPRINT_SERVICE));
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- if (service != null) {
- mFingerprintGestureDispatcher = new FingerprintGestureDispatcher(
- service, mLock);
- break;
+ if ((mFingerprintGestureDispatcher == null)
+ && mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+ // Only create the controller when a service wants to use the feature
+ int numServices = services.size();
+ for (int i = 0; i < numServices; i++) {
+ if (services.get(i).isCapturingFingerprintGestures()) {
+ final long identity = Binder.clearCallingIdentity();
+ IFingerprintService service = null;
+ try {
+ service = IFingerprintService.Stub.asInterface(
+ ServiceManager.getService(Context.FINGERPRINT_SERVICE));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ if (service != null) {
+ mFingerprintGestureDispatcher = new FingerprintGestureDispatcher(
+ service, mLock);
+ break;
+ }
}
}
}
@@ -2274,11 +2292,7 @@
case MSG_CLEAR_ACCESSIBILITY_FOCUS: {
final int windowId = msg.arg1;
- InteractionBridge bridge;
- synchronized (mLock) {
- bridge = getInteractionBridgeLocked();
- }
- bridge.clearAccessibilityFocusNotLocked(windowId);
+ getInteractionBridge().clearAccessibilityFocusNotLocked(windowId);
} break;
case MSG_SEND_SERVICES_STATE_CHANGED_TO_CLIENTS: {
@@ -4051,6 +4065,24 @@
}
}
+ /**
+ * Perform an accessibility action on the view that currently has accessibility focus.
+ * Has no effect if no item has accessibility focus, if the item with accessibility
+ * focus does not expose the specified action, or if the action fails.
+ *
+ * @param actionId The id of the action to perform.
+ *
+ * @return {@code true} if the action was performed. {@code false} if it was not.
+ */
+ public boolean performActionOnAccessibilityFocusedItemNotLocked(
+ AccessibilityNodeInfo.AccessibilityAction action) {
+ AccessibilityNodeInfo focus = getAccessibilityFocusNotLocked();
+ if ((focus == null) || !focus.getActionList().contains(action)) {
+ return false;
+ }
+ return focus.performAction(action.getId());
+ }
+
public boolean getAccessibilityFocusClickPointInScreenNotLocked(Point outPoint) {
AccessibilityNodeInfo focus = getAccessibilityFocusNotLocked();
if (focus == null) {
diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
index ecba245..6e87f88 100644
--- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
@@ -29,6 +29,7 @@
import android.view.WindowManagerPolicy;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
import java.util.ArrayList;
import java.util.Arrays;
@@ -407,6 +408,13 @@
mSendTouchInteractionEndDelayed.forceSendAndRemove();
}
+ // Try to use the standard accessibility API to click
+ if (mAms.performActionOnAccessibilityFocusedItem(
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK)) {
+ return true;
+ }
+ Slog.e(LOG_TAG, "ACTION_CLICK failed. Dispatching motion events to simulate click.");
+
final int pointerIndex = event.getActionIndex();
final int pointerId = event.getPointerId(pointerIndex);
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
index a697a8e..081acf5 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
@@ -451,7 +451,7 @@
* Called when the fill UI is ready to be shown for this view.
*/
void onFillReady(ViewState viewState, FillResponse fillResponse, Rect bounds,
- @Nullable AutoFillValue value);
+ AutoFillId focusedId, @Nullable AutoFillValue value);
}
final AutoFillId mId;
@@ -509,12 +509,14 @@
}
/**
- * Calls {@link Listener#onFillReady(ViewState, FillResponse, Rect, AutoFillValue)} if the
+ * Calls {@link
+ * Listener#onFillReady(ViewState, FillResponse, Rect, AutoFillId, AutoFillValue)} if the
* fill UI is ready to be displayed (i.e. when response and bounds are set).
*/
void maybeCallOnFillReady() {
- if (mResponse != null && mBounds != null) {
- mListener.onFillReady(this, mResponse, mBounds, mAutoFillValue);
+ if (mResponse != null && (mResponse.getAuthentication() != null
+ || mResponse.getDatasets() != null) && mBounds != null) {
+ mListener.onFillReady(this, mResponse, mBounds, mId, mAutoFillValue);
}
}
@@ -641,8 +643,14 @@
// FillServiceCallbacks
@Override
- public void authenticate(IntentSender intent, Intent fillInIntent) {
- startAuthentication(intent, fillInIntent);
+ public void authenticate(IntentSender intent) {
+ final Intent fillInIntent;
+ synchronized (mLock) {
+ fillInIntent = createAuthFillInIntent(mStructure);
+ }
+ mHandlerCaller.getHandler().post(() -> {
+ startAuthentication(intent, fillInIntent);
+ });
}
// FillServiceCallbacks
@@ -675,15 +683,10 @@
processResponseLocked(mCurrentResponse);
} else if (result instanceof Dataset) {
Dataset dataset = (Dataset) result;
- final int datasetIndex = Helper.indexOfDataset(
- dataset.getName(), mCurrentResponse);
- if (datasetIndex <= 0) {
- Slog.e(TAG, "Response for a dataset auth has"
- + " an invalid dataset result: " + dataset.getName());
- }
- mCurrentResponse.getDatasets().removeAt(datasetIndex);
+ mCurrentResponse.getDatasets().remove(mAutoFilledDataset);
mCurrentResponse.getDatasets().add(dataset);
- autoFill(dataset);
+ mAutoFilledDataset = dataset;
+ processResponseLocked(mCurrentResponse);
}
}
}
@@ -727,6 +730,7 @@
return;
}
}
+
// Nothing changed...
if (DEBUG) Slog.d(TAG, "showSaveLocked(): with no changes, comes no responsibilities");
}
@@ -802,8 +806,11 @@
}
}
- // Just change value, don't update the UI
+ // Change value
viewState.mAutoFillValue = value;
+
+ // Update the chooser UI
+ mUi.updateFillUi(value.coerceToString());
return;
}
@@ -838,7 +845,7 @@
@Override
public void onFillReady(ViewState viewState, FillResponse response, Rect bounds,
- @Nullable AutoFillValue value) {
+ AutoFillId filledId, @Nullable AutoFillValue value) {
String filterText = "";
if (value != null) {
// TODO(b/33197203): Handle other AutoFillValue types
@@ -848,8 +855,7 @@
}
}
- getUiForShowing().showFillUi(mActivityToken, viewState, response.getDatasets(),
- bounds, filterText);
+ getUiForShowing().showFillUi(filledId, response, bounds, filterText);
}
private void processResponseLocked(FillResponse response) {
@@ -869,26 +875,17 @@
if (mCurrentResponse.getAuthentication() != null) {
// Handle authentication.
final Intent fillInIntent = createAuthFillInIntent(mStructure);
-
mCurrentViewState.setResponse(mCurrentResponse, fillInIntent);
return;
}
- final ArraySet<AutoFillId> savableIds = mCurrentResponse.getSavableIds();
- if (savableIds == null || savableIds.isEmpty()) {
- // NOTE: it's assuming the response has no datasets, since when a dataset is added
- // it's view id is automatically added to savable_ids
- if (DEBUG) Slog.d(TAG, "processResponseLocked(): nothing to do");
-
- removeSelf();
- return;
- }
-
mCurrentViewState.setResponse(mCurrentResponse);
}
void autoFill(Dataset dataset) {
synchronized (mLock) {
+ mAutoFilledDataset = dataset;
+
// Autofill it directly...
if (dataset.getAuthentication() == null) {
autoFillApp(dataset);
@@ -948,9 +945,7 @@
synchronized (mLock) {
try {
if (DEBUG) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
-
mClient.autoFill(dataset.getFieldIds(), dataset.getFieldValues());
- mAutoFilledDataset = dataset;
} catch (RemoteException e) {
Slog.w(TAG, "Error auto-filling activity: " + e);
}
@@ -999,7 +994,6 @@
private void destroyLocked() {
mRemoteFillService.destroy();
- mUi.hideAll();
mUi.setCallbackLocked(null, null);
}
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/AutoFillUI.java
index e83dc1e..78dd247 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillUI.java
@@ -18,32 +18,23 @@
import static com.android.server.autofill.Helper.DEBUG;
import android.annotation.Nullable;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.StatusBarManager;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.content.IntentSender;
import android.graphics.Rect;
-import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
import android.service.autofill.Dataset;
-import android.util.ArraySet;
-import android.os.Looper;
+import android.service.autofill.FillResponse;
import android.text.format.DateUtils;
import android.util.Slog;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
+import android.view.autofill.AutoFillId;
import android.widget.Toast;
-import com.android.internal.os.HandlerCaller;
import com.android.server.UiThread;
-import com.android.server.autofill.AutoFillManagerServiceImpl.ViewState;
import java.io.PrintWriter;
@@ -53,36 +44,24 @@
// TODO(b/33197203): document exactly what once the auto-fill bar is implemented
final class AutoFillUI {
private static final String TAG = "AutoFillUI";
- private static final long SNACK_BAR_LIFETIME_MS = 30 * DateUtils.SECOND_IN_MILLIS;
+
+ private static final long SNACK_BAR_LIFETIME_MS = 5 * DateUtils.SECOND_IN_MILLIS;
+
private static final int MSG_HIDE_SNACK_BAR = 1;
+ private final Handler mHandler = UiThread.getHandler();
+
private final Context mContext;
+
private final WindowManager mWm;
- // TODO(b/33197203) Fix locking - some state requires lock and some not - requires refactoring
- private final Object mLock = new Object();
-
- // Fill UI variables
private AnchoredWindow mFillWindow;
- private View mFillView;
- private ViewState mViewState;
+
+ private DatasetPicker mDatasetPicker;
private AutoFillUiCallback mCallback;
- private IBinder mActivityToken;
- private final HandlerCaller.Callback mHandlerCallback = (msg) -> {
- switch (msg.what) {
- case MSG_HIDE_SNACK_BAR: {
- hideSnackbarUiThread();
- return;
- }
- default: {
- Slog.w(TAG, "Invalid message: " + msg);
- }
- }
- };
- private final HandlerCaller mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(),
- mHandlerCallback, true);
+ private IBinder mActivityToken;
/**
* Custom snackbar UI used for saving autofill or other informational messages.
@@ -95,32 +74,32 @@
}
void setCallbackLocked(AutoFillUiCallback callback, IBinder activityToken) {
- hideAll();
- mCallback = callback;
- mActivityToken = activityToken;
+ mHandler.post(() -> {
+ hideAllUiThread();
+ mCallback = callback;
+ mActivityToken = activityToken;
+ });
}
/**
* Displays an error message to the user.
*/
void showError(CharSequence message) {
- if (!hasCallback()) {
- return;
- }
- hideAll();
// TODO(b/33197203): proper implementation
- UiThread.getHandler().runWithScissors(() -> {
+ UiThread.getHandler().post(() -> {
+ if (!hasCallback()) {
+ return;
+ }
+ hideAllUiThread();
Toast.makeText(mContext, "AutoFill error: " + message, Toast.LENGTH_LONG).show();
- }, 0);
+ });
}
/**
* Hides the fill UI.
*/
void hideFillUi() {
- UiThread.getHandler().runWithScissors(() -> {
- hideFillUiUiThread();
- }, 0);
+ mHandler.post(() -> hideFillUiUiThread());
}
@android.annotation.UiThread
@@ -129,111 +108,80 @@
if (DEBUG) Slog.d(TAG, "hideFillUiUiThread(): hide" + mFillWindow);
mFillWindow.hide();
}
-
- mViewState = null;
- mFillView = null;
mFillWindow = null;
+ mDatasetPicker = null;
+ }
+
+ void updateFillUi(@Nullable String filterText) {
+ mHandler.post(() -> {
+ if (!hasCallback()) {
+ return;
+ }
+ hideSnackbarUiThread();
+ if (mDatasetPicker != null) {
+ mDatasetPicker.update(filterText);
+ }
+ });
}
/**
* Shows the fill UI, removing the previous fill UI if the has changed.
*
- * @param appToken the token of the app to be autofilled
- * @param viewState the view state, compared by reference to know if new UI should be shown
- * @param datasets the datasets to show, not used if viewState is the same
+ * @param focusedId the currently focused field
+ * @param response the current fill response
* @param bounds bounds of the view to be filled, used if changed
* @param filterText text of the view to be filled, used if changed
*/
- void showFillUi(IBinder appToken, ViewState viewState, @Nullable ArraySet<Dataset> datasets,
- Rect bounds, String filterText) {
- if (!hasCallback()) {
- return;
- }
-
- UiThread.getHandler().runWithScissors(() -> {
+ void showFillUi(AutoFillId focusedId, @Nullable FillResponse response, Rect bounds,
+ String filterText) {
+ mHandler.post(() -> {
+ if (!hasCallback()) {
+ return;
+ }
hideSnackbarUiThread();
- }, 0);
-
- if (datasets == null && viewState.mAuthIntent == null) {
- // TODO(b/33197203): shouldn't be called, but keeping the WTF for a while just to be
- // safe, otherwise it would crash system server...
- Slog.wtf(TAG, "showFillUI(): no dataset");
- return;
- }
-
- // TODO(b/33197203): should not display UI after we launched an authentication intent, since
- // we have no warranty the provider will call onFailure() if the authentication failed or
- // user dismissed the auth window
- // because if the service does not handle calling the callback,
-
-
- UiThread.getHandler().runWithScissors(() -> {
- // The dataset picker is only shown when authentication is not required...
- DatasetPicker datasetPicker = null;
-
- if (mViewState == null || !mViewState.mId.equals(viewState.mId)) {
- hideFillUiUiThread();
-
- mViewState = viewState;
- if (viewState.mAuthIntent != null) {
- final CharSequence serviceName = viewState.getServiceName();
-
- mFillView = new SignInPrompt(mContext, serviceName, (e) -> {
- final IntentSender intentSender = viewState.mResponse.getAuthentication();
- final AutoFillUiCallback callback;
- final Intent authIntent;
- synchronized (mLock) {
- callback = mCallback;
- authIntent = viewState.mAuthIntent;
- // Must reset the authentication intent so UI display the datasets after
- // the user authenticated.
- viewState.mAuthIntent = null;
+ final View content;
+ if (response.getPresentation() != null) {
+ content = response.getPresentation().apply(mContext, null);
+ content.setOnClickListener((view) -> {
+ if (mCallback != null) {
+ mCallback.authenticate(response.getAuthentication());
+ }
+ hideFillUiUiThread();
+ });
+ } else {
+ mDatasetPicker = new DatasetPicker(mContext, response.getDatasets(),
+ focusedId, new DatasetPicker.Listener() {
+ @Override
+ public void onDatasetPicked(Dataset dataset) {
+ if (mCallback != null) {
+ mCallback.fill(dataset);
}
- if (callback != null) {
- callback.authenticate(intentSender, authIntent);
- } else {
- // TODO(b/33197203): need to figure out why it's null sometimes
- Slog.w(TAG, "no callback on showFillUi().auth for " + viewState.mId);
- }
- });
+ hideFillUiUiThread();
+ }
- } else {
- mFillView = datasetPicker = new DatasetPicker(mContext, datasets,
- (dataset) -> {
- final AutoFillUiCallback callback;
- synchronized (mLock) {
- callback = mCallback;
- }
- if (callback != null) {
- callback.fill(dataset);
- } else {
- // TODO(b/33197203): need to figure out why it's null sometimes
- Slog.w(TAG, "no callback on showFillUi() for " + viewState.mId);
- }
- hideFillUiUiThread();
- });
- }
- mFillWindow = new AnchoredWindow(mWm, appToken, mFillView);
+ @Override
+ public void onCanceled() {
+ hideFillUiUiThread();
+ }
+ });
+ mDatasetPicker.update(filterText);
+ content = mDatasetPicker;
+ }
- if (DEBUG) Slog.d(TAG, "showFillUi(): view changed for: " + viewState.mId);
- }
- if (datasetPicker != null) {
- datasetPicker.update(filterText);
- }
+ mFillWindow = new AnchoredWindow(mWm, mActivityToken, content);
mFillWindow.show(bounds);
-
- }, 0);
+ });
}
/**
* Shows the UI asking the user to save for auto-fill.
*/
void showSaveUi() {
- if (!hasCallback()) {
- return;
- }
- hideAll();
- UiThread.getHandler().runWithScissors(() -> {
+ mHandler.post(() -> {
+ if (!hasCallback()) {
+ return;
+ }
+ hideAllUiThread();
showSnackbarUiThread(new SavePrompt(mContext,
new SavePrompt.OnSaveListener() {
@Override
@@ -249,17 +197,20 @@
hideSnackbarUiThread();
}
}));
- }, 0);
+ });
}
/**
* Hides all UI affordances.
*/
void hideAll() {
- UiThread.getHandler().runWithScissors(() -> {
- hideSnackbarUiThread();
- hideFillUiUiThread();
- }, 0);
+ mHandler.post(() -> hideAllUiThread());
+ }
+
+ @android.annotation.UiThread
+ private void hideAllUiThread() {
+ hideSnackbarUiThread();
+ hideFillUiUiThread();
}
void dump(PrintWriter pw) {
@@ -267,14 +218,13 @@
final String prefix = " ";
pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
pw.print(prefix); pw.print("mSnackBar: "); pw.println(mSnackbar);
- pw.print(prefix); pw.print("mViewState: "); pw.println(mViewState);
}
//similar to a snackbar, but can be a bit custom since it is more than just text. This will
//allow two buttons for saving or not saving the autofill for instance as well.
+ @android.annotation.UiThread
private void showSnackbarUiThread(View snackBar) {
final LayoutParams params = new LayoutParams();
- params.setTitle("AutoFill Save");
params.type = LayoutParams.TYPE_PHONE; // TODO(b/33197203) use app window token
params.flags =
LayoutParams.FLAG_NOT_FOCUSABLE // don't receive input events,
@@ -286,20 +236,21 @@
params.width = LayoutParams.MATCH_PARENT;
params.height = LayoutParams.WRAP_CONTENT;
- UiThread.getHandler().runWithScissors(() -> {
+ mHandler.post(() -> {
mSnackbar = snackBar;
mWm.addView(mSnackbar, params);
- }, 0);
+ });
if (DEBUG) {
Slog.d(TAG, "showSnackbar(): auto dismissing it in " + SNACK_BAR_LIFETIME_MS + " ms");
}
- mHandlerCaller.sendMessageDelayed(mHandlerCaller.obtainMessage(MSG_HIDE_SNACK_BAR),
- SNACK_BAR_LIFETIME_MS);
+ mHandler.sendMessageDelayed(mHandler
+ .obtainMessage(MSG_HIDE_SNACK_BAR), SNACK_BAR_LIFETIME_MS);
}
+ @android.annotation.UiThread
private void hideSnackbarUiThread() {
- mHandlerCaller.getHandler().removeMessages(MSG_HIDE_SNACK_BAR);
+ mHandler.removeMessages(MSG_HIDE_SNACK_BAR);
if (mSnackbar != null) {
mWm.removeView(mSnackbar);
mSnackbar = null;
@@ -307,13 +258,11 @@
}
private boolean hasCallback() {
- synchronized (mLock) {
- return mCallback != null;
- }
+ return mCallback != null;
}
interface AutoFillUiCallback {
- void authenticate(IntentSender intent, Intent fillInIntent);
+ void authenticate(IntentSender intent);
void fill(Dataset dataset);
void save();
}
diff --git a/services/autofill/java/com/android/server/autofill/DatasetPicker.java b/services/autofill/java/com/android/server/autofill/DatasetPicker.java
index a54cab9..e25f2ce 100644
--- a/services/autofill/java/com/android/server/autofill/DatasetPicker.java
+++ b/services/autofill/java/com/android/server/autofill/DatasetPicker.java
@@ -16,73 +16,71 @@
package com.android.server.autofill;
import android.content.Context;
-import android.graphics.Color;
import android.service.autofill.Dataset;
-import android.text.TextUtils;
import android.util.ArraySet;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.autofill.AutoFillId;
+import android.view.autofill.AutoFillValue;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
-import android.widget.Filter.FilterListener;
+import android.widget.FrameLayout;
import android.widget.ListView;
-import android.widget.TextView;
+import android.widget.RemoteViews;
+import com.android.internal.R;
import java.util.ArrayList;
import java.util.List;
/**
- * View for dataset picker.
- *
- * <p>A fill session starts when a View is clicked and FillResponse is supplied.
- * <p>A fill session ends when 1) the user takes action in the UI, 2) another View is clicked, or
- * 3) the View is detached.
+ * This class manages the dataset selection UI.
*/
-final class DatasetPicker extends ListView implements OnItemClickListener {
+final class DatasetPicker extends FrameLayout implements OnItemClickListener {
interface Listener {
void onDatasetPicked(Dataset dataset);
+ void onCanceled();
}
private final Listener mListener;
- DatasetPicker(Context context, ArraySet<Dataset> datasets, Listener listener) {
+ private final ArrayAdapter<ViewItem> mAdapter;
+
+ DatasetPicker(Context context, ArrayList<Dataset> datasets, AutoFillId filteredViewId,
+ Listener listener) {
super(context);
mListener = listener;
final List<ViewItem> items = new ArrayList<>(datasets.size());
for (Dataset dataset : datasets) {
- items.add(new ViewItem(dataset));
+ final int index = dataset.getFieldIds().indexOf(filteredViewId);
+ if (index >= 0) {
+ AutoFillValue value = dataset.getFieldValues().get(index);
+ items.add(new ViewItem(dataset, value.coerceToString()));
+ }
}
- final ArrayAdapter<ViewItem> adapter = new ArrayAdapter<ViewItem>(
- context,
- android.R.layout.simple_list_item_1,
- android.R.id.text1,
- items) {
+ mAdapter = new ArrayAdapter<ViewItem>(context, 0, items) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
- final TextView textView = (TextView) super.getView(position, convertView, parent);
- textView.setSingleLine();
- textView.setEllipsize(TextUtils.TruncateAt.END);
- textView.setMinHeight(
- getDimen(com.android.internal.R.dimen.autofill_fill_item_height));
- return textView;
+ RemoteViews presentation = getItem(position).getDataset().getPresentation();
+ return presentation.apply(context, parent);
}
};
- setAdapter(adapter);
- setBackgroundColor(Color.WHITE);
- setDivider(null);
- setElevation(getDimen(com.android.internal.R.dimen.autofill_fill_elevation));
- setOnItemClickListener(this);
+
+ LayoutInflater inflater = LayoutInflater.from(context);
+ ListView content = (ListView) inflater.inflate(
+ com.android.internal.R.layout.autofill_dataset_picker, this, true)
+ .findViewById(com.android.internal.R.id.list);
+ content.setAdapter(mAdapter);
+ content.setOnItemClickListener(this);
}
public void update(String prefix) {
- final ArrayAdapter<ViewItem> adapter = (ArrayAdapter) getAdapter();
- adapter.getFilter().filter(prefix, new FilterListener() {
- @Override
- public void onFilterComplete(int count) {
- setVisibility(count > 0 ? View.VISIBLE : View.GONE);
+ mAdapter.getFilter().filter(prefix, (count) -> {
+ if (count <= 0 && mListener != null) {
+ mListener.onCanceled();
}
});
}
@@ -91,29 +89,27 @@
public void onItemClick(AdapterView<?> adapterView, View view, int pos, long id) {
if (mListener != null) {
final ViewItem vi = (ViewItem) adapterView.getItemAtPosition(pos);
- mListener.onDatasetPicked(vi.getData());
+ mListener.onDatasetPicked(vi.getDataset());
}
}
- private int getDimen(int resId) {
- return getContext().getResources().getDimensionPixelSize(resId);
- }
-
private static class ViewItem {
- private final Dataset mData;
+ private final String mValue;
+ private final Dataset mDataset;
- ViewItem(Dataset data) {
- mData = data;
+ ViewItem(Dataset dataset, String value) {
+ mDataset = dataset;
+ mValue = value;
}
- public Dataset getData() {
- return mData;
+ public Dataset getDataset() {
+ return mDataset;
}
@Override
public String toString() {
// used by ArrayAdapter
- return mData.getName().toString();
+ return mValue;
}
}
}
diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
index 48ae635..2f600c2 100644
--- a/services/autofill/java/com/android/server/autofill/Helper.java
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -19,8 +19,6 @@
import android.annotation.Nullable;
import android.os.Bundle;
import android.service.autofill.Dataset;
-import android.service.autofill.FillResponse;
-import android.util.ArraySet;
import android.view.autofill.AutoFillId;
import android.view.autofill.AutoFillValue;
@@ -77,28 +75,6 @@
return null;
}
- /**
- * Finds the index of a data set given its name.
- *
- * @param name The dataset name.
- * @param response The response to search.
- * @return The index of dataset if found or -1.
- */
- static int indexOfDataset(CharSequence name, FillResponse response) {
- ArraySet<Dataset> datasets = response.getDatasets();
- if (datasets == null || datasets.isEmpty()) {
- return -1;
- }
- final int datasetCount = datasets.size();
- for (int i = 0; i < datasetCount; i++) {
- Dataset dataset = datasets.valueAt(i);
- if (dataset.getName().toString().equals(name.toString())) {
- return i;
- }
- }
- return -1;
- }
-
private Helper() {
throw new UnsupportedOperationException("contains static members only");
}
diff --git a/services/autofill/java/com/android/server/autofill/SavePrompt.java b/services/autofill/java/com/android/server/autofill/SavePrompt.java
index f0b51e2..85c6d7d 100644
--- a/services/autofill/java/com/android/server/autofill/SavePrompt.java
+++ b/services/autofill/java/com/android/server/autofill/SavePrompt.java
@@ -17,9 +17,7 @@
package com.android.server.autofill;
import android.content.Context;
-import android.graphics.Color;
import android.widget.RelativeLayout;
-import android.widget.RelativeLayout.LayoutParams;
import android.widget.TextView;
import android.view.LayoutInflater;
import android.view.View;
@@ -55,6 +53,5 @@
mListener.onSaveClick();
});
- //addView(view);
}
}
diff --git a/services/autofill/java/com/android/server/autofill/SignInPrompt.java b/services/autofill/java/com/android/server/autofill/SignInPrompt.java
deleted file mode 100644
index 6d17acd..0000000
--- a/services/autofill/java/com/android/server/autofill/SignInPrompt.java
+++ /dev/null
@@ -1,38 +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.autofill;
-
-import android.content.Context;
-import android.view.View;
-import android.widget.Button;
-
-/**
- * A view displaying the sign-in prompt for an auto-fill service.
- */
-final class SignInPrompt extends Button {
-
- SignInPrompt(Context context, CharSequence serviceName, View.OnClickListener listener) {
- super(context);
- // TODO(b/33197203): use strings.xml
- final String text = serviceName != null
- ? "Sign in to " + serviceName + " to autofill"
- : "Sign in to autofill";
-
- // TODO(b/33197203): polish UI / use better altenative than a button...
- setText(text);
- setOnClickListener(listener);
- }
-}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 9b55c7a..b459f74 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -16,6 +16,9 @@
package com.android.server.backup;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY;
+import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_OLD_VERSION;
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_BACKUP_IN_FOREGROUND;
import android.app.ActivityManager;
@@ -2340,7 +2343,7 @@
Slog.e(TAG, "No packages named for backup request");
sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
monitor = monitorEvent(monitor, BackupManagerMonitor.LOG_EVENT_ID_NO_PACKAGES,
- null, BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT);
+ null, BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, null);
throw new IllegalArgumentException("No packages are provided for backup");
}
@@ -3459,6 +3462,9 @@
// fail repeatedly (i.e. have proved themselves to be buggy).
Slog.e(TAG, "Cancel backing up " + mCurrentPackage.packageName);
EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, mCurrentPackage.packageName);
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_BACKUP_TIMEOUT,
+ mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null);
addBackupTrace(
"cancel of " + mCurrentPackage.packageName + ", cancelAll=" + cancelAll);
errorCleanup();
@@ -4555,6 +4561,11 @@
mPackages.add(info);
} catch (NameNotFoundException e) {
Slog.i(TAG, "Requested package " + pkg + " not found; ignoring");
+ monitor = monitorEvent(monitor,
+ BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_NOT_FOUND,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ null);
}
}
}
@@ -4638,6 +4649,10 @@
if (mTransport == null) {
Slog.w(TAG, "Transport not present; full data backup not performed");
backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_TRANSPORT_NOT_PRESENT,
+ mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT,
+ null);
return;
}
@@ -5148,7 +5163,7 @@
mMonitor = monitorEvent(mMonitor,
BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_TIMEOUT,
- mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT);
+ mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null);
mIsCancelled = true;
// Cancel tasks spun off by this task.
BackupManagerService.this.handleCancel(mEphemeralToken, cancelAll);
@@ -5525,6 +5540,8 @@
// Dedicated observer, if any
IFullBackupRestoreObserver mObserver;
+ IBackupManagerMonitor mMonitor;
+
// Where we're delivering the file data as we go
IBackupAgent mAgent;
@@ -5606,11 +5623,12 @@
}
public FullRestoreEngine(BackupRestoreTask monitorTask, IFullBackupRestoreObserver observer,
- PackageInfo onlyPackage, boolean allowApks, boolean allowObbs,
- int ephemeralOpToken) {
+ IBackupManagerMonitor monitor, PackageInfo onlyPackage, boolean allowApks,
+ boolean allowObbs, int ephemeralOpToken) {
mEphemeralOpToken = ephemeralOpToken;
mMonitorTask = monitorTask;
mObserver = observer;
+ mMonitor = monitor;
mOnlyPackage = onlyPackage;
mAllowApks = allowApks;
mAllowObbs = allowObbs;
@@ -6326,6 +6344,16 @@
} else {
Slog.i(TAG, "Data requires newer version "
+ version + "; ignoring");
+ ArrayList<Pair<String, String>> list =
+ new ArrayList<>();
+ list.add(new Pair<String, String>(
+ EXTRA_LOG_OLD_VERSION,
+ Integer.toString(version)));
+ mMonitor = monitorEvent(mMonitor,
+ LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER,
+ pkgInfo,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ list);
policy = RestorePolicy.IGNORE;
}
}
@@ -8461,6 +8489,10 @@
RestoreDescription desc = mTransport.nextRestorePackage();
if (desc == null) {
Slog.e(TAG, "No restore metadata available; halting");
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_NO_RESTORE_METADATA_AVAILABLE,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null);
mStatus = BackupTransport.TRANSPORT_ERROR;
executeNextState(UnifiedRestoreState.FINAL);
return;
@@ -8562,6 +8594,11 @@
// Whoops, we thought we could restore this package but it
// turns out not to be present. Skip it.
Slog.e(TAG, "Package not present: " + pkgName);
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_NOT_PRESENT,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ null);
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, pkgName,
"Package missing on device");
nextState = UnifiedRestoreState.RUNNING_QUEUE;
@@ -8631,6 +8668,9 @@
Slog.i(TAG, "Data exists for package " + packageName
+ " but app has no agent; skipping");
}
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_APP_HAS_NO_AGENT, mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null);
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
"Package has no agent");
executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
@@ -8652,6 +8692,9 @@
ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL);
if (mAgent == null) {
Slog.w(TAG, "Can't find backup agent for " + packageName);
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_CANT_FIND_AGENT, mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null);
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
"Restore agent missing");
executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
@@ -8857,7 +8900,7 @@
EventLog.writeEvent(EventLogTags.FULL_RESTORE_PACKAGE,
mCurrentPackage.packageName);
- mEngine = new FullRestoreEngine(this, null, mCurrentPackage, false, false, mEphemeralOpToken);
+ mEngine = new FullRestoreEngine(this, null, mMonitor, mCurrentPackage, false, false, mEphemeralOpToken);
mEngineThread = new EngineThread(mEngine, mEnginePipes[0]);
ParcelFileDescriptor eWriteEnd = mEnginePipes[1];
@@ -9001,8 +9044,10 @@
if (DEBUG) {
Slog.w(TAG, "Full-data restore target timed out; shutting down");
}
- mMonitor = monitorEvent(mMonitor, BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_TIMEOUT,
- mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT);
+
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_TIMEOUT,
+ mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null);
mEngineThread.handleTimeout();
IoUtils.closeQuietly(mEnginePipes[1]);
@@ -9245,8 +9290,9 @@
public void handleCancel(boolean cancelAll) {
removeOperation(mEphemeralOpToken);
Slog.e(TAG, "Timeout restoring application " + mCurrentPackage.packageName);
- mMonitor = monitorEvent(mMonitor, BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_RESTORE_TIMEOUT,
- mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT);
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_RESTORE_TIMEOUT,
+ mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null);
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
mCurrentPackage.packageName, "restore timeout");
// Handle like an agent that threw on invocation: wipe it and go on to the next
@@ -10913,7 +10959,7 @@
}
private static IBackupManagerMonitor monitorEvent(IBackupManagerMonitor monitor, int id,
- PackageInfo pkg, int category) {
+ PackageInfo pkg, int category, ArrayList<Pair<String, String>> extras) {
if (monitor != null) {
try {
Bundle bundle = new Bundle();
@@ -10925,6 +10971,11 @@
bundle.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_VERSION,
pkg.versionCode);
}
+ if (extras != null) {
+ for (Pair<String,String> pair : extras) {
+ bundle.putString(pair.first, pair.second);
+ }
+ }
monitor.onEvent(bundle);
return monitor;
} catch(RemoteException e) {
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index b1560e6..02223c1 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -2458,9 +2458,9 @@
expectedClockTime = lastTimeChangeClockTime
+ (nowELAPSED - mLastTimeChangeRealtime);
}
- if (lastTimeChangeClockTime == 0 || nowRTC < (expectedClockTime-500)
- || nowRTC > (expectedClockTime+500)) {
- // The change is by at least +/- 500 ms (or this is the first change),
+ if (lastTimeChangeClockTime == 0 || nowRTC < (expectedClockTime-1000)
+ || nowRTC > (expectedClockTime+1000)) {
+ // The change is by at least +/- 1000 ms (or this is the first change),
// let's do it!
if (DEBUG_BATCH) {
Slog.v(TAG, "Time changed notification from kernel; rebatching");
@@ -2477,7 +2477,8 @@
}
Intent intent = new Intent(Intent.ACTION_TIME_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
- | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
// The world has changed on us, so we need to re-evaluate alarms
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 4bc9bb1..0bc9d19 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -815,6 +815,8 @@
intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
mContext.registerReceiverAsUser(
mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
+ mContext.registerReceiverAsUser(mUserPresentReceiver, UserHandle.SYSTEM,
+ new IntentFilter(Intent.ACTION_USER_PRESENT), null, null);
try {
mNetd.registerObserver(mTethering);
@@ -4008,6 +4010,16 @@
}
};
+ private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Try creating lockdown tracker, since user present usually means
+ // unlocked keystore.
+ updateLockdownVpn();
+ mContext.unregisterReceiver(this);
+ }
+ };
+
private final HashMap<Messenger, NetworkFactoryInfo> mNetworkFactoryInfos =
new HashMap<Messenger, NetworkFactoryInfo>();
private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests =
diff --git a/services/core/java/com/android/server/GraphicsStatsService.java b/services/core/java/com/android/server/GraphicsStatsService.java
index ecbe1ca..bdd80e38 100644
--- a/services/core/java/com/android/server/GraphicsStatsService.java
+++ b/services/core/java/com/android/server/GraphicsStatsService.java
@@ -16,67 +16,145 @@
package com.android.server;
+import android.app.AlarmManager;
import android.app.AppOpsManager;
import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.os.Binder;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.os.MemoryFile;
+import android.os.Message;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
import android.os.RemoteException;
+import android.os.Trace;
import android.util.Log;
import android.view.IGraphicsStats;
-import android.view.ThreadedRenderer;
+import android.view.IGraphicsStatsCallback;
+import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.HashSet;
+import java.util.TimeZone;
/**
* This service's job is to collect aggregate rendering profile data. It
* does this by allowing rendering processes to request an ashmem buffer
- * to place their stats into. This buffer will be pre-initialized with historical
- * data for that process if it exists (if the userId & packageName match a buffer
- * in the historical log)
+ * to place their stats into.
*
- * This service does not itself attempt to understand the data in the buffer,
- * its primary job is merely to manage distributing these buffers. However,
- * it is assumed that this buffer is for ThreadedRenderer and delegates
- * directly to ThreadedRenderer for dumping buffers.
+ * Buffers are rotated on a daily (in UTC) basis and only the 3 most-recent days
+ * are kept.
*
- * MEMORY USAGE:
+ * The primary consumer of this is incident reports and automated metric checking. It is not
+ * intended for end-developer consumption, for that we have gfxinfo.
*
- * This class consumes UP TO:
- * 1) [active rendering processes] * (ASHMEM_SIZE * 2)
- * 2) ASHMEM_SIZE (for scratch space used during dumping)
- * 3) ASHMEM_SIZE * HISTORY_SIZE
- *
- * This is currently under 20KiB total memory in the worst case of
- * 20 processes in history + 10 unique active processes.
+ * Buffer rotation process:
+ * 1) Alarm fires
+ * 2) onRotateGraphicsStatsBuffer() is sent to all active processes
+ * 3) Upon receiving the callback, the process will stop using the previous ashmem buffer and
+ * request a new one.
+ * 4) When that request is received we now know that the ashmem region is no longer in use so
+ * it gets queued up for saving to disk and a new ashmem region is created and returned
+ * for the process to use.
*
* @hide */
public class GraphicsStatsService extends IGraphicsStats.Stub {
public static final String GRAPHICS_STATS_SERVICE = "graphicsstats";
private static final String TAG = "GraphicsStatsService";
- private static final int ASHMEM_SIZE = 464;
- private static final int HISTORY_SIZE = 20;
+
+ private static final int SAVE_BUFFER = 1;
+ private static final int DELETE_OLD = 2;
+
+ // This isn't static because we need this to happen after registerNativeMethods, however
+ // the class is loaded (and thus static ctor happens) before that occurs.
+ private final int ASHMEM_SIZE = nGetAshmemSize();
+ private final byte[] ZERO_DATA = new byte[ASHMEM_SIZE];
private final Context mContext;
private final AppOpsManager mAppOps;
+ private final AlarmManager mAlarmManager;
private final Object mLock = new Object();
private ArrayList<ActiveBuffer> mActive = new ArrayList<>();
- private HistoricalData[] mHistoricalLog = new HistoricalData[HISTORY_SIZE];
- private int mNextHistoricalSlot = 0;
- private byte[] mTempBuffer = new byte[ASHMEM_SIZE];
+ private File mGraphicsStatsDir;
+ private final Object mFileAccessLock = new Object();
+ private Handler mWriteOutHandler;
+ private boolean mRotateIsScheduled = false;
public GraphicsStatsService(Context context) {
mContext = context;
mAppOps = context.getSystemService(AppOpsManager.class);
+ mAlarmManager = context.getSystemService(AlarmManager.class);
+ File systemDataDir = new File(Environment.getDataDirectory(), "system");
+ mGraphicsStatsDir = new File(systemDataDir, "graphicsstats");
+ mGraphicsStatsDir.mkdirs();
+ if (!mGraphicsStatsDir.exists()) {
+ throw new IllegalStateException("Graphics stats directory does not exist: "
+ + mGraphicsStatsDir.getAbsolutePath());
+ }
+ HandlerThread bgthread = new HandlerThread("GraphicsStats-disk", Process.THREAD_PRIORITY_BACKGROUND);
+ bgthread.start();
+
+ mWriteOutHandler = new Handler(bgthread.getLooper(), new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case SAVE_BUFFER:
+ saveBuffer((HistoricalBuffer) msg.obj);
+ break;
+ case DELETE_OLD:
+ deleteOldBuffers();
+ break;
+ }
+ return true;
+ }
+ });
+ }
+
+ /**
+ * Current rotation policy is to rotate at midnight UTC. We don't specify RTC_WAKEUP because
+ * rotation can be delayed if there's otherwise no activity. However exact is used because
+ * we don't want the system to delay it by TOO much.
+ */
+ private void scheduleRotateLocked() {
+ if (mRotateIsScheduled) {
+ return;
+ }
+ mRotateIsScheduled = true;
+ Calendar calendar = normalizeDate(System.currentTimeMillis());
+ calendar.add(Calendar.DATE, 1);
+ mAlarmManager.setExact(AlarmManager.RTC, calendar.getTimeInMillis(), TAG, this::onAlarm,
+ mWriteOutHandler);
+ }
+
+ private void onAlarm() {
+ synchronized (mLock) {
+ mRotateIsScheduled = false;
+ scheduleRotateLocked();
+ for (ActiveBuffer active : mActive) {
+ try {
+ active.mCallback.onRotateGraphicsStatsBuffer();
+ } catch (RemoteException e) {
+ Log.w(TAG, String.format("Failed to notify '%s' (pid=%d) to rotate buffers",
+ active.mInfo.packageName, active.mPid), e);
+ }
+ }
+ }
+ // Give a few seconds for everyone to rotate before doing the cleanup
+ mWriteOutHandler.sendEmptyMessageDelayed(DELETE_OLD, 10000);
}
@Override
- public ParcelFileDescriptor requestBufferForProcess(String packageName, IBinder token)
+ public ParcelFileDescriptor requestBufferForProcess(String packageName, IGraphicsStatsCallback token)
throws RemoteException {
int uid = Binder.getCallingUid();
int pid = Binder.getCallingPid();
@@ -84,9 +162,12 @@
long callingIdentity = Binder.clearCallingIdentity();
try {
mAppOps.checkPackage(uid, packageName);
+ PackageInfo info = mContext.getPackageManager().getPackageInfo(packageName, 0);
synchronized (mLock) {
- pfd = requestBufferForProcessLocked(token, uid, pid, packageName);
+ pfd = requestBufferForProcessLocked(token, uid, pid, packageName, info.versionCode);
}
+ } catch (PackageManager.NameNotFoundException ex) {
+ throw new RemoteException("Unable to find package: '" + packageName + "'");
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
@@ -95,51 +176,130 @@
private ParcelFileDescriptor getPfd(MemoryFile file) {
try {
+ if (!file.getFileDescriptor().valid()) {
+ throw new IllegalStateException("Invalid file descriptor");
+ }
return new ParcelFileDescriptor(file.getFileDescriptor());
} catch (IOException ex) {
throw new IllegalStateException("Failed to get PFD from memory file", ex);
}
}
- private ParcelFileDescriptor requestBufferForProcessLocked(IBinder token,
- int uid, int pid, String packageName) throws RemoteException {
- ActiveBuffer buffer = fetchActiveBuffersLocked(token, uid, pid, packageName);
+ private ParcelFileDescriptor requestBufferForProcessLocked(IGraphicsStatsCallback token,
+ int uid, int pid, String packageName, int versionCode) throws RemoteException {
+ ActiveBuffer buffer = fetchActiveBuffersLocked(token, uid, pid, packageName, versionCode);
+ scheduleRotateLocked();
return getPfd(buffer.mProcessBuffer);
}
+ private Calendar normalizeDate(long timestamp) {
+ Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+ calendar.setTimeInMillis(timestamp);
+ calendar.set(Calendar.HOUR_OF_DAY, 0);
+ calendar.set(Calendar.MINUTE, 0);
+ calendar.set(Calendar.SECOND, 0);
+ calendar.set(Calendar.MILLISECOND, 0);
+ return calendar;
+ }
+
+ private File pathForApp(BufferInfo info) {
+ String subPath = String.format("%d/%s/%d/total",
+ normalizeDate(info.startTime).getTimeInMillis(), info.packageName, info.versionCode);
+ return new File(mGraphicsStatsDir, subPath);
+ }
+
+ private void saveBuffer(HistoricalBuffer buffer) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "saving graphicsstats for " + buffer.mInfo.packageName);
+ }
+ synchronized (mFileAccessLock) {
+ File path = pathForApp(buffer.mInfo);
+ File parent = path.getParentFile();
+ parent.mkdirs();
+ if (!parent.exists()) {
+ Log.w(TAG, "Unable to create path: '" + parent.getAbsolutePath() + "'");
+ return;
+ }
+ nSaveBuffer(path.getAbsolutePath(), buffer.mInfo.packageName, buffer.mInfo.versionCode,
+ buffer.mInfo.startTime, buffer.mInfo.endTime, buffer.mData);
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ }
+
+ private void deleteRecursiveLocked(File file) {
+ if (file.isDirectory()) {
+ for (File child : file.listFiles()) {
+ deleteRecursiveLocked(child);
+ }
+ }
+ if (!file.delete()) {
+ Log.w(TAG, "Failed to delete '" + file.getAbsolutePath() + "'!");
+ }
+ }
+
+ private void deleteOldBuffers() {
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "deleting old graphicsstats buffers");
+ synchronized (mFileAccessLock) {
+ File[] files = mGraphicsStatsDir.listFiles();
+ if (files == null || files.length <= 3) {
+ return;
+ }
+ long[] sortedDates = new long[files.length];
+ for (int i = 0; i < files.length; i++) {
+ try {
+ sortedDates[i] = Long.parseLong(files[i].getName());
+ } catch (NumberFormatException ex) {
+ // Skip unrecognized folders
+ }
+ }
+ if (sortedDates.length <= 3) {
+ return;
+ }
+ Arrays.sort(sortedDates);
+ for (int i = 0; i < sortedDates.length - 3; i++) {
+ deleteRecursiveLocked(new File(mGraphicsStatsDir, Long.toString(sortedDates[i])));
+ }
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ }
+
+ private void addToSaveQueue(ActiveBuffer buffer) {
+ try {
+ HistoricalBuffer data = new HistoricalBuffer(buffer);
+ Message.obtain(mWriteOutHandler, SAVE_BUFFER, data).sendToTarget();
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to copy graphicsstats from " + buffer.mInfo.packageName, e);
+ }
+ buffer.closeAllBuffers();
+ }
+
private void processDied(ActiveBuffer buffer) {
synchronized (mLock) {
mActive.remove(buffer);
- Log.d("GraphicsStats", "Buffer count: " + mActive.size());
}
- HistoricalData data = buffer.mPreviousData;
- buffer.mPreviousData = null;
- if (data == null) {
- data = mHistoricalLog[mNextHistoricalSlot];
- if (data == null) {
- data = new HistoricalData();
- }
- }
- data.update(buffer.mPackageName, buffer.mUid, buffer.mProcessBuffer);
- buffer.closeAllBuffers();
-
- mHistoricalLog[mNextHistoricalSlot] = data;
- mNextHistoricalSlot = (mNextHistoricalSlot + 1) % mHistoricalLog.length;
+ addToSaveQueue(buffer);
}
- private ActiveBuffer fetchActiveBuffersLocked(IBinder token, int uid, int pid,
- String packageName) throws RemoteException {
+ private ActiveBuffer fetchActiveBuffersLocked(IGraphicsStatsCallback token, int uid, int pid,
+ String packageName, int versionCode) throws RemoteException {
int size = mActive.size();
+ long today = normalizeDate(System.currentTimeMillis()).getTimeInMillis();
for (int i = 0; i < size; i++) {
- ActiveBuffer buffers = mActive.get(i);
- if (buffers.mPid == pid
- && buffers.mUid == uid) {
- return buffers;
+ ActiveBuffer buffer = mActive.get(i);
+ if (buffer.mPid == pid
+ && buffer.mUid == uid) {
+ // If the buffer is too old we remove it and return a new one
+ if (buffer.mInfo.startTime < today) {
+ buffer.binderDied();
+ break;
+ } else {
+ return buffer;
+ }
}
}
// Didn't find one, need to create it
try {
- ActiveBuffer buffers = new ActiveBuffer(token, uid, pid, packageName);
+ ActiveBuffer buffers = new ActiveBuffer(token, uid, pid, packageName, versionCode);
mActive.add(buffers);
return buffers;
} catch (IOException ex) {
@@ -147,71 +307,106 @@
}
}
- private HistoricalData removeHistoricalDataLocked(int uid, String packageName) {
- for (int i = 0; i < mHistoricalLog.length; i++) {
- final HistoricalData data = mHistoricalLog[i];
- if (data != null && data.mUid == uid
- && data.mPackageName.equals(packageName)) {
- if (i == mNextHistoricalSlot) {
- mHistoricalLog[i] = null;
- } else {
- mHistoricalLog[i] = mHistoricalLog[mNextHistoricalSlot];
- mHistoricalLog[mNextHistoricalSlot] = null;
+ private HashSet<File> dumpActiveLocked(long dump, ArrayList<HistoricalBuffer> buffers) {
+ HashSet<File> skipFiles = new HashSet<>(buffers.size());
+ for (int i = 0; i < buffers.size(); i++) {
+ HistoricalBuffer buffer = buffers.get(i);
+ File path = pathForApp(buffer.mInfo);
+ skipFiles.add(path);
+ nAddToDump(dump, path.getAbsolutePath(), buffer.mInfo.packageName,
+ buffer.mInfo.versionCode, buffer.mInfo.startTime, buffer.mInfo.endTime,
+ buffer.mData);
+ }
+ return skipFiles;
+ }
+
+ private void dumpHistoricalLocked(long dump, HashSet<File> skipFiles) {
+ for (File date : mGraphicsStatsDir.listFiles()) {
+ for (File pkg : date.listFiles()) {
+ for (File version : pkg.listFiles()) {
+ File data = new File(version, "total");
+ if (skipFiles.contains(data)) {
+ continue;
+ }
+ nAddToDump(dump, data.getAbsolutePath());
}
- return data;
}
}
- return null;
}
@Override
protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+ boolean dumpProto = false;
+ for (String str : args) {
+ if ("--proto".equals(str)) {
+ dumpProto = true;
+ break;
+ }
+ }
+ ArrayList<HistoricalBuffer> buffers;
synchronized (mLock) {
+ buffers = new ArrayList<>(mActive.size());
for (int i = 0; i < mActive.size(); i++) {
- final ActiveBuffer buffer = mActive.get(i);
- fout.print("Package: ");
- fout.print(buffer.mPackageName);
- fout.flush();
try {
- buffer.mProcessBuffer.readBytes(mTempBuffer, 0, 0, ASHMEM_SIZE);
- ThreadedRenderer.dumpProfileData(mTempBuffer, fd);
- } catch (IOException e) {
- fout.println("Failed to dump");
+ buffers.add(new HistoricalBuffer(mActive.get(i)));
+ } catch (IOException ex) {
+ // Ignore
}
- fout.println();
}
- for (HistoricalData buffer : mHistoricalLog) {
- if (buffer == null) continue;
- fout.print("Package: ");
- fout.print(buffer.mPackageName);
- fout.flush();
- ThreadedRenderer.dumpProfileData(buffer.mBuffer, fd);
- fout.println();
+ }
+ long dump = nCreateDump(fd.getInt$(), dumpProto);
+ try {
+ synchronized (mFileAccessLock) {
+ HashSet<File> skipList = dumpActiveLocked(dump, buffers);
+ buffers.clear();
+ dumpHistoricalLocked(dump, skipList);
}
+ } finally {
+ nFinishDump(dump);
+ }
+ }
+
+ private static native int nGetAshmemSize();
+ private static native long nCreateDump(int outFd, boolean isProto);
+ private static native void nAddToDump(long dump, String path, String packageName,
+ int versionCode, long startTime, long endTime, byte[] data);
+ private static native void nAddToDump(long dump, String path);
+ private static native void nFinishDump(long dump);
+ private static native void nSaveBuffer(String path, String packageName, int versionCode,
+ long startTime, long endTime, byte[] data);
+
+ private final class BufferInfo {
+ final String packageName;
+ final int versionCode;
+ long startTime;
+ long endTime;
+
+ BufferInfo(String packageName, int versionCode, long startTime) {
+ this.packageName = packageName;
+ this.versionCode = versionCode;
+ this.startTime = startTime;
}
}
private final class ActiveBuffer implements DeathRecipient {
+ final BufferInfo mInfo;
final int mUid;
final int mPid;
- final String mPackageName;
+ final IGraphicsStatsCallback mCallback;
final IBinder mToken;
MemoryFile mProcessBuffer;
- HistoricalData mPreviousData;
- ActiveBuffer(IBinder token, int uid, int pid, String packageName)
+ ActiveBuffer(IGraphicsStatsCallback token, int uid, int pid, String packageName, int versionCode)
throws RemoteException, IOException {
+ mInfo = new BufferInfo(packageName, versionCode, System.currentTimeMillis());
mUid = uid;
mPid = pid;
- mPackageName = packageName;
- mToken = token;
+ mCallback = token;
+ mToken = mCallback.asBinder();
mToken.linkToDeath(this, 0);
- mProcessBuffer = new MemoryFile("GFXStats-" + uid, ASHMEM_SIZE);
- mPreviousData = removeHistoricalDataLocked(mUid, mPackageName);
- if (mPreviousData != null) {
- mProcessBuffer.writeBytes(mPreviousData.mBuffer, 0, 0, ASHMEM_SIZE);
- }
+ mProcessBuffer = new MemoryFile("GFXStats-" + pid, ASHMEM_SIZE);
+ mProcessBuffer.writeBytes(ZERO_DATA, 0, 0, ASHMEM_SIZE);
}
@Override
@@ -228,17 +423,13 @@
}
}
- private final static class HistoricalData {
- final byte[] mBuffer = new byte[ASHMEM_SIZE];
- int mUid;
- String mPackageName;
-
- void update(String packageName, int uid, MemoryFile file) {
- mUid = uid;
- mPackageName = packageName;
- try {
- file.readBytes(mBuffer, 0, 0, ASHMEM_SIZE);
- } catch (IOException e) {}
+ private final class HistoricalBuffer {
+ final BufferInfo mInfo;
+ final byte[] mData = new byte[ASHMEM_SIZE];
+ HistoricalBuffer(ActiveBuffer active) throws IOException {
+ mInfo = active.mInfo;
+ mInfo.endTime = System.currentTimeMillis();
+ active.mProcessBuffer.readBytes(mData, 0, 0, ASHMEM_SIZE);
}
}
}
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 8442c11..fe5b3a2 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -2046,7 +2046,7 @@
}
mWindowManagerInternal.updateInputMethodWindowStatus(token,
(vis & InputMethodService.IME_VISIBLE) != 0,
- token != null ? info.mTargetWindow : null);
+ info != null ? info.mTargetWindow : null);
}
private void updateSystemUi(IBinder token, int vis, int backDisposition) {
diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java
index 14abb53..40499c9 100644
--- a/services/core/java/com/android/server/IntentResolver.java
+++ b/services/core/java/com/android/server/IntentResolver.java
@@ -351,7 +351,7 @@
}
public List<R> queryIntentFromList(Intent intent, String resolvedType, boolean defaultOnly,
- boolean visibleToEphemeral, boolean isEphemeral, ArrayList<F[]> listCut, int userId) {
+ ArrayList<F[]> listCut, int userId) {
ArrayList<R> resultList = new ArrayList<R>();
final boolean debug = localLOGV ||
@@ -361,8 +361,8 @@
final String scheme = intent.getScheme();
int N = listCut.size();
for (int i = 0; i < N; ++i) {
- buildResolveList(intent, categories, debug, defaultOnly, visibleToEphemeral,
- isEphemeral, resolvedType, scheme, listCut.get(i), resultList, userId);
+ buildResolveList(intent, categories, debug, defaultOnly, resolvedType, scheme,
+ listCut.get(i), resultList, userId);
}
filterResults(resultList);
sortResults(resultList);
@@ -370,7 +370,7 @@
}
public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly,
- boolean visibleToEphemeral, boolean isEphemeral, int userId) {
+ int userId) {
String scheme = intent.getScheme();
ArrayList<R> finalList = new ArrayList<R>();
@@ -443,20 +443,20 @@
FastImmutableArraySet<String> categories = getFastIntentCategories(intent);
if (firstTypeCut != null) {
- buildResolveList(intent, categories, debug, defaultOnly, visibleToEphemeral,
- isEphemeral, resolvedType, scheme, firstTypeCut, finalList, userId);
+ buildResolveList(intent, categories, debug, defaultOnly, resolvedType,
+ scheme, firstTypeCut, finalList, userId);
}
if (secondTypeCut != null) {
- buildResolveList(intent, categories, debug, defaultOnly, visibleToEphemeral,
- isEphemeral, resolvedType, scheme, secondTypeCut, finalList, userId);
+ buildResolveList(intent, categories, debug, defaultOnly, resolvedType,
+ scheme, secondTypeCut, finalList, userId);
}
if (thirdTypeCut != null) {
- buildResolveList(intent, categories, debug, defaultOnly, visibleToEphemeral,
- isEphemeral, resolvedType, scheme, thirdTypeCut, finalList, userId);
+ buildResolveList(intent, categories, debug, defaultOnly, resolvedType,
+ scheme, thirdTypeCut, finalList, userId);
}
if (schemeCut != null) {
- buildResolveList(intent, categories, debug, defaultOnly, visibleToEphemeral,
- isEphemeral, resolvedType, scheme, schemeCut, finalList, userId);
+ buildResolveList(intent, categories, debug, defaultOnly, resolvedType,
+ scheme, schemeCut, finalList, userId);
}
filterResults(finalList);
sortResults(finalList);
@@ -694,8 +694,8 @@
}
private void buildResolveList(Intent intent, FastImmutableArraySet<String> categories,
- boolean debug, boolean defaultOnly, boolean visibleToEphemeral, boolean isEphemeral,
- String resolvedType, String scheme, F[] src, List<R> dest, int userId) {
+ boolean debug, boolean defaultOnly, String resolvedType, String scheme,
+ F[] src, List<R> dest, int userId) {
final String action = intent.getAction();
final Uri data = intent.getData();
final String packageName = intent.getPackage();
@@ -735,15 +735,6 @@
continue;
}
- // throw out filters that aren't visible to ephemeral apps
- if (visibleToEphemeral && !filter.isVisibleToEphemeral()) {
- continue;
- }
- // throw out ephemeral filters if we're not explicitly requesting them
- if (!isEphemeral && filter.isEphemeral()) {
- continue;
- }
-
// Are we verified ?
if (filter.getAutoVerify()) {
if (localVerificationLOGV || debug) {
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index 4a44530..f76ddc7 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -1064,6 +1064,14 @@
private void setLockCredentialInternal(String credential, int credentialType,
String savedCredential, int userId) throws RemoteException {
+ // Normalize savedCredential and credential such that empty string is always represented
+ // as null.
+ if (TextUtils.isEmpty(savedCredential)) {
+ savedCredential = null;
+ }
+ if (TextUtils.isEmpty(credential)) {
+ credential = null;
+ }
synchronized (mSpManager) {
if (isSyntheticPasswordBasedCredentialLocked(userId)) {
spBasedSetLockCredentialInternalLocked(credential, credentialType, savedCredential,
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index c77a407..33351ff 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -20,9 +20,12 @@
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.BatteryManager;
-import android.os.BatteryManagerInternal;
+import android.os.BatteryProperties;
import android.os.Build;
+import android.os.IBatteryPropertiesListener;
+import android.os.IBatteryPropertiesRegistrar;
import android.os.RecoverySystem;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -31,11 +34,15 @@
import android.text.format.DateUtils;
import android.util.ExceptionUtils;
import android.util.MathUtils;
+import android.util.MutableBoolean;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.util.ArrayUtils;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
/**
* Utilities to help rescue the system from crash loops. Callers are expected to
* report boot events and persistent app crashes, and if they happen frequently
@@ -66,24 +73,26 @@
private static boolean isDisabled() {
// We're disabled on all engineering devices
- if (Build.IS_ENG) return true;
+ if (Build.IS_ENG) {
+ Slog.v(TAG, "Disabled because of eng build");
+ return true;
+ }
// We're disabled on userdebug devices connected over USB, since that's
// a decent signal that someone is actively trying to debug the device,
// or that it's in a lab environment.
- if (Build.IS_USERDEBUG) {
- try {
- if (LocalServices.getService(BatteryManagerInternal.class)
- .getPlugType() == BatteryManager.BATTERY_PLUGGED_USB) {
- return true;
- } else {
- }
- } catch (Throwable ignored) {
- }
+ if (Build.IS_USERDEBUG && isUsbActive()) {
+ Slog.v(TAG, "Disabled because of active USB connection");
+ return true;
}
// One last-ditch check
- return SystemProperties.getBoolean(PROP_DISABLE_RESCUE, false);
+ if (SystemProperties.getBoolean(PROP_DISABLE_RESCUE, false)) {
+ Slog.v(TAG, "Disabled because of manual property");
+ return true;
+ }
+
+ return false;
}
/**
@@ -185,14 +194,14 @@
final ContentResolver resolver = context.getContentResolver();
try {
Settings.Global.resetToDefaultsAsUser(resolver, null, mode, UserHandle.USER_SYSTEM);
- } catch (Exception e) {
- res = new RuntimeException("Failed to reset global settings", e);
+ } catch (Throwable t) {
+ res = new RuntimeException("Failed to reset global settings", t);
}
for (int userId : getAllUserIds(context)) {
try {
Settings.Secure.resetToDefaultsAsUser(resolver, null, mode, userId);
- } catch (Exception e) {
- res = new RuntimeException("Failed to reset secure settings for " + userId, e);
+ } catch (Throwable t) {
+ res = new RuntimeException("Failed to reset secure settings for " + userId, t);
}
}
if (res != null) {
@@ -314,6 +323,38 @@
return userIds;
}
+ /**
+ * Hacky test to check if the device has an active USB connection, which is
+ * a good proxy for someone doing local development work. It uses a low
+ * level call since we may not have started {@link BatteryManager} yet.
+ */
+ private static boolean isUsbActive() {
+ final MutableBoolean res = new MutableBoolean(false);
+ final CountDownLatch latch = new CountDownLatch(1);
+ final IBatteryPropertiesListener listener = new IBatteryPropertiesListener.Stub() {
+ @Override
+ public void batteryPropertiesChanged(BatteryProperties props) {
+ res.value = props.chargerUsbOnline;
+ latch.countDown();
+ }
+ };
+
+ try {
+ final IBatteryPropertiesRegistrar bpr = IBatteryPropertiesRegistrar.Stub
+ .asInterface(ServiceManager.getService("batteryproperties"));
+ bpr.registerListener(listener);
+ try {
+ latch.await(5, TimeUnit.SECONDS);
+ } finally {
+ bpr.unregisterListener(listener);
+ }
+ return res.value;
+ } catch (Throwable t) {
+ Slog.w(TAG, "Failed to determine if device was on USB", t);
+ return false;
+ }
+ }
+
private static String levelToString(int level) {
switch (level) {
case LEVEL_NONE: return "NONE";
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 0415971..32136bb 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -70,13 +70,13 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.DiskInfo;
-import android.os.storage.IStorageEventListener;
-import android.os.storage.IStorageShutdownObserver;
import android.os.storage.IObbActionListener;
+import android.os.storage.IStorageEventListener;
import android.os.storage.IStorageManager;
-import android.os.storage.StorageManagerInternal;
+import android.os.storage.IStorageShutdownObserver;
import android.os.storage.OnObbStateChangeListener;
import android.os.storage.StorageManager;
+import android.os.storage.StorageManagerInternal;
import android.os.storage.StorageResultCode;
import android.os.storage.StorageVolume;
import android.os.storage.VolumeInfo;
@@ -109,6 +109,7 @@
import com.android.server.NativeDaemonConnector.SensitiveArg;
import com.android.server.pm.PackageManagerService;
import com.android.server.storage.AppFuseBridge;
+
import libcore.io.IoUtils;
import libcore.util.EmptyArray;
@@ -3293,14 +3294,69 @@
}
@Override
- public long getAllocatableBytes(String path, int flags) {
- return new File(path).getUsableSpace();
+ public long getAllocatableBytes(String volumeUuid, int flags) {
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ final StorageStatsManager stats = mContext.getSystemService(StorageStatsManager.class);
+
+ final boolean aggressive = (flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0;
+ if (aggressive) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ALLOCATE_AGGRESSIVE, TAG);
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // In general, apps can allocate as much space as they want, except
+ // we never let them eat into either the minimum cache space or into
+ // the low disk warning space.
+ final File path = storage.findPathForUuid(volumeUuid);
+ if (stats.isQuotaSupported(volumeUuid)) {
+ if (aggressive) {
+ return Math.max(0,
+ stats.getFreeBytes(volumeUuid) - storage.getStorageFullBytes(path));
+ } else {
+ return Math.max(0,
+ stats.getFreeBytes(volumeUuid) - storage.getStorageLowBytes(path)
+ - storage.getStorageCacheBytes(path));
+ }
+ } else {
+ // When we don't have fast quota information, we ignore cached
+ // data and only consider unused bytes.
+ if (aggressive) {
+ return Math.max(0, path.getUsableSpace() - storage.getStorageFullBytes(path));
+ } else {
+ return Math.max(0, path.getUsableSpace() - storage.getStorageLowBytes(path));
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
- public void allocateBytes(String path, long bytes, int flags) {
- if (bytes > new File(path).getUsableSpace()) {
- throw new ParcelableException(new IOException("Not enough usable space"));
+ public void allocateBytes(String volumeUuid, long bytes, int flags) {
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+
+ // This method call will enforce FLAG_ALLOCATE_AGGRESSIVE permissions so
+ // we don't have to enforce them locally
+ final long allocatableBytes = getAllocatableBytes(volumeUuid, flags);
+ if (bytes > allocatableBytes) {
+ throw new ParcelableException(new IOException("Failed to allocate " + bytes
+ + " because only " + allocatableBytes + " allocatable"));
+ }
+
+ // Free up enough disk space to satisfy both the requested allocation
+ // and our low disk warning space.
+ final File path = storage.findPathForUuid(volumeUuid);
+ bytes += storage.getStorageLowBytes(path);
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mPms.freeStorage(volumeUuid, bytes, flags);
+ } catch (IOException e) {
+ throw new ParcelableException(e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index be5cbfc..1b2c75d 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -182,7 +182,8 @@
static {
ACCOUNTS_CHANGED_INTENT = new Intent(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION);
- ACCOUNTS_CHANGED_INTENT.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ ACCOUNTS_CHANGED_INTENT.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
}
private final LinkedHashMap<String, Session> mSessions = new LinkedHashMap<String, Session>();
@@ -481,8 +482,13 @@
managedTypes.add(accountType);
}
- return getAccountsAndVisibilityForPackage(packageName, managedTypes, callingUid,
- getUserAccounts(UserHandle.getUserId(callingUid)));
+ long identityToken = clearCallingIdentity();
+ try {
+ return getAccountsAndVisibilityForPackage(packageName, managedTypes, callingUid,
+ getUserAccounts(UserHandle.getUserId(callingUid)));
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
}
/*
@@ -560,7 +566,7 @@
a.type);
throw new SecurityException(msg);
}
- return getAccountVisibility(a, packageName,
+ return resolveAccountVisibility(a, packageName,
getUserAccounts(UserHandle.getUserId(callingUid)));
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 35c2462..8656047 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -231,6 +231,7 @@
import android.content.pm.PermissionInfo;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
+import android.content.pm.SELinuxUtil;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.content.res.CompatibilityInfo;
@@ -294,6 +295,7 @@
import android.service.voice.VoiceInteractionManagerInternal;
import android.service.voice.VoiceInteractionSession;
import android.telecom.TelecomManager;
+import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.format.Time;
import android.text.style.SuggestionSpan;
@@ -3506,6 +3508,7 @@
info.processName = processName;
info.className = entryPoint;
info.packageName = "android";
+ info.seInfoUser = SELinuxUtil.COMPLETE_STR;
ProcessRecord proc = startProcessLocked(processName, info /* info */,
false /* knownToBeDead */, 0 /* intentFlags */, "" /* hostingType */,
null /* hostingName */, true /* allowWhileBooting */, true /* isolated */,
@@ -3777,6 +3780,13 @@
app.requiredAbi = requiredAbi;
app.instructionSet = instructionSet;
+ // the per-user SELinux context must be set
+ if (TextUtils.isEmpty(app.info.seInfoUser)) {
+ Slog.wtf(TAG, "SELinux tag not defined",
+ new IllegalStateException("SELinux tag not defined"));
+ }
+ final String seInfo = app.info.seInfo
+ + (TextUtils.isEmpty(app.info.seInfoUser) ? "" : app.info.seInfoUser);
// Start the process. It will either succeed and return a result containing
// the PID of the new process, or else throw a RuntimeException.
boolean isActivityProcess = (entryPoint == null);
@@ -3788,12 +3798,12 @@
if (hostingType.equals("webview_service")) {
startResult = Process.startWebView(entryPoint,
app.processName, uid, uid, gids, debugFlags, mountExternal,
- app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
+ app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, null, entryPointArgs);
} else {
startResult = Process.start(entryPoint,
app.processName, uid, uid, gids, debugFlags, mountExternal,
- app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
+ app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, invokeWith, entryPointArgs);
}
checkTime(startTime, "startProcess: returned from zygote!");
@@ -3809,7 +3819,7 @@
try {
AppGlobals.getPackageManager().logAppProcessStartIfNeeded(app.processName, app.uid,
- app.info.seinfo, app.info.sourceDir, startResult.pid);
+ seInfo, app.info.sourceDir, startResult.pid);
} catch (RemoteException ex) {
// Ignore
}
@@ -4225,7 +4235,8 @@
final int registeredCallbackCount = mUidObservers.getRegisteredCallbackCount();
for (int i = 0; i < N; ++i) {
final UidRecord.ChangeItem item = mActiveUidChanges[i];
- if (item.change == UidRecord.CHANGE_PROCSTATE) {
+ if (item.change != UidRecord.CHANGE_GONE
+ && item.change != UidRecord.CHANGE_GONE_IDLE) {
mUidStateWithSeqObserver.onUidStateChangedWithSeq(
item.uid, item.processState, item.procStateSeq);
if (VALIDATE_UID_STATES && registeredCallbackCount == 0) {
@@ -18777,8 +18788,7 @@
}
List<BroadcastFilter> registeredReceiversForUser =
mReceiverResolver.queryIntent(intent,
- resolvedType, false, false /*visibleToEphemeral*/,
- false /*isInstant*/, users[i]);
+ resolvedType, false /*defaultOnly*/, users[i]);
if (registeredReceivers == null) {
registeredReceivers = registeredReceiversForUser;
} else if (registeredReceiversForUser != null) {
@@ -18787,8 +18797,7 @@
}
} else {
registeredReceivers = mReceiverResolver.queryIntent(intent,
- resolvedType, false, false /*visibleToEphemeral*/,
- false /*isInstant*/, userId);
+ resolvedType, false /*defaultOnly*/, userId);
}
}
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 082b6b5..6f89cf2 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -201,6 +201,7 @@
// WARNING: Reference points to {@link TaskRecord#getMergedOverrideConfig}, so its internal
// state should never be altered directly.
private Configuration mLastReportedOverrideConfiguration;
+ private int mLastReportedDisplayId;
CompatibilityInfo compat;// last used compatibility mode
ActivityRecord resultTo; // who started this entry, so will get our reply
final String resultWho; // additional identifier for use by resultTo.
@@ -513,16 +514,37 @@
mSmallestSizeConfigurations = smallestSizeConfigurations;
}
- private void scheduleConfigurationChanged(Configuration config, boolean reportToActivity) {
+ private void scheduleActivityMovedToDisplay(int displayId, Configuration config) {
if (app == null || app.thread == null) {
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.w(TAG,
+ "Can't report activity moved to display - client not running, activityRecord="
+ + this + ", displayId=" + displayId);
return;
}
try {
- if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending new config to " + this + " " +
- "reportToActivity=" + reportToActivity + " and config: " + config);
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
+ "Reporting activity moved to display" + ", activityRecord=" + this
+ + ", displayId=" + displayId + ", config=" + config);
- app.thread.scheduleActivityConfigurationChanged(appToken, new Configuration(config),
- reportToActivity);
+ app.thread.scheduleActivityMovedToDisplay(appToken, displayId,
+ new Configuration(config));
+ } catch (RemoteException e) {
+ // If process died, whatever.
+ }
+ }
+
+ private void scheduleConfigurationChanged(Configuration config) {
+ if (app == null || app.thread == null) {
+ if (DEBUG_CONFIGURATION) Slog.w(TAG,
+ "Can't report activity configuration update - client not running"
+ + ", activityRecord=" + this);
+ return;
+ }
+ try {
+ if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending new config to " + this + ", config: "
+ + config);
+
+ app.thread.scheduleActivityConfigurationChanged(appToken, new Configuration(config));
} catch (RemoteException e) {
// If process died, whatever.
}
@@ -977,25 +999,26 @@
boolean hasPinnedStack = mStackSupervisor.getStack(PINNED_STACK_ID) != null;
// Don't return early if !isNotLocked, since we want to throw an exception if the activity
// is in an incorrect state
- boolean isNotLocked = !isKeyguardLocked && !isCurrentAppLocked;
+ boolean isNotLockedOrOnKeyguard = !isKeyguardLocked && !isCurrentAppLocked;
switch (state) {
case RESUMED:
- // When visible, allow entering PiP if not on the lockscreen and if the task is not
- // locked
- return isNotLocked;
+ // When visible, allow entering PiP if the app is not locked. If it is over the
+ // keyguard, then we will prompt to unlock in the caller before entering PiP.
+ return !isCurrentAppLocked;
case PAUSING:
case PAUSED:
// When pausing, then only allow enter PiP as in the resume state, and in addition,
// require that there is not an existing PiP activity and that the current system
// state supports entering PiP
- return isNotLocked && !hasPinnedStack && supportsPictureInPictureWhilePausing
+ return isNotLockedOrOnKeyguard && !hasPinnedStack
+ && supportsPictureInPictureWhilePausing
&& checkEnterPictureInPictureOnHideAppOpsState();
case STOPPING:
// When stopping in a valid state, then only allow enter PiP as in the pause state.
// Otherwise, fall through to throw an exception if the caller is trying to enter
// PiP in an invalid stopping state.
if (supportsPictureInPictureWhilePausing) {
- return isNotLocked && !hasPinnedStack
+ return isNotLockedOrOnKeyguard && !hasPinnedStack
&& checkEnterPictureInPictureOnHideAppOpsState();
}
default:
@@ -1969,26 +1992,31 @@
return true;
}
+ // We don't worry about activities that are finishing.
+ if (finishing) {
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
+ "Configuration doesn't matter in finishing " + this);
+ stopFreezingScreenLocked(false);
+ return true;
+ }
+
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Ensuring correct configuration: " + this);
+ final int newDisplayId = getDisplayId();
+ final boolean displayChanged = mLastReportedDisplayId != newDisplayId;
+ if (displayChanged) {
+ mLastReportedDisplayId = newDisplayId;
+ }
// Short circuit: if the two full configurations are equal (the common case), then there is
// nothing to do. We test the full configuration instead of the global and merged override
// configurations because there are cases (like moving a task to the pinned stack) where
// the combine configurations are equal, but would otherwise differ in the override config
mTmpConfig1.setTo(mLastReportedConfiguration);
mTmpConfig1.updateFrom(mLastReportedOverrideConfiguration);
- if (task.getConfiguration().equals(mTmpConfig1) && !forceNewConfig) {
+ if (task.getConfiguration().equals(mTmpConfig1) && !forceNewConfig && !displayChanged) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
- "Configuration unchanged in " + this);
- return true;
- }
-
- // We don't worry about activities that are finishing.
- if (finishing) {
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
- "Configuration doesn't matter in finishing " + this);
- stopFreezingScreenLocked(false);
+ "Configuration & display unchanged in " + this);
return true;
}
@@ -2009,7 +2037,11 @@
"Configuration no differences in " + this);
// There are no significant differences, so we won't relaunch but should still deliver
// the new configuration to the client process.
- scheduleConfigurationChanged(newTaskMergedOverrideConfig, true);
+ if (displayChanged) {
+ scheduleActivityMovedToDisplay(newDisplayId, newTaskMergedOverrideConfig);
+ } else {
+ scheduleConfigurationChanged(newTaskMergedOverrideConfig);
+ }
return true;
}
@@ -2082,7 +2114,11 @@
// NOTE: We only forward the task override configuration as the system level configuration
// changes is always sent to all processes when they happen so it can just use whatever
// system level configuration it last got.
- scheduleConfigurationChanged(newTaskMergedOverrideConfig, true);
+ if (displayChanged) {
+ scheduleActivityMovedToDisplay(newDisplayId, newTaskMergedOverrideConfig);
+ } else {
+ scheduleConfigurationChanged(newTaskMergedOverrideConfig);
+ }
stopFreezingScreenLocked(false);
return true;
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index f75ce25..6087fb3 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1559,12 +1559,13 @@
return STACK_INVISIBLE;
}
- final ActivityStack focusedStack = mStackSupervisor.getFocusedStack();
- final int focusedStackId = focusedStack.mStackId;
+ // Check position and visibility of this stack relative to the front stack on its display.
+ final ActivityStack topStack = getTopStackOnDisplay();
+ final int topStackId = topStack.mStackId;
if (StackId.isBackdropToTranslucentActivity(mStackId)
- && hasVisibleBehindActivity() && StackId.isHomeOrRecentsStack(focusedStackId)
- && !focusedStack.topActivity().fullscreen) {
+ && hasVisibleBehindActivity() && StackId.isHomeOrRecentsStack(topStackId)
+ && !topStack.topActivity().fullscreen) {
// The fullscreen or assistant stack should be visible if it has a visible behind
// activity behind the home or recents stack that is translucent.
return STACK_VISIBLE_ACTIVITY_BEHIND;
@@ -1574,39 +1575,39 @@
// Docked stack is always visible, except in the case where the top running activity
// task in the focus stack doesn't support any form of resizing but we show it for the
// home task even though it's not resizable.
- final ActivityRecord r = focusedStack.topRunningActivityLocked();
+ final ActivityRecord r = topStack.topRunningActivityLocked();
final TaskRecord task = r != null ? r.task : null;
return task == null || task.supportsSplitScreen() || task.isHomeTask() ? STACK_VISIBLE
: STACK_INVISIBLE;
}
- // Find the first stack behind focused stack that actually got something visible.
- int stackBehindFocusedIndex = mStacks.indexOf(focusedStack) - 1;
- while (stackBehindFocusedIndex >= 0 &&
- mStacks.get(stackBehindFocusedIndex).topRunningActivityLocked() == null) {
- stackBehindFocusedIndex--;
+ // Find the first stack behind front stack that actually got something visible.
+ int stackBehindTopIndex = mStacks.indexOf(topStack) - 1;
+ while (stackBehindTopIndex >= 0 &&
+ mStacks.get(stackBehindTopIndex).topRunningActivityLocked() == null) {
+ stackBehindTopIndex--;
}
- if ((focusedStackId == DOCKED_STACK_ID || focusedStackId == PINNED_STACK_ID)
- && stackIndex == stackBehindFocusedIndex) {
+ if ((topStackId == DOCKED_STACK_ID || topStackId == PINNED_STACK_ID)
+ && stackIndex == stackBehindTopIndex) {
// Stacks directly behind the docked or pinned stack are always visible.
return STACK_VISIBLE;
}
- final int stackBehindFocusedId = (stackBehindFocusedIndex >= 0)
- ? mStacks.get(stackBehindFocusedIndex).mStackId : INVALID_STACK_ID;
+ final int stackBehindTopId = (stackBehindTopIndex >= 0)
+ ? mStacks.get(stackBehindTopIndex).mStackId : INVALID_STACK_ID;
- if (StackId.isBackdropToTranslucentActivity(focusedStackId)
- && focusedStack.isStackTranslucent(starting, stackBehindFocusedId)) {
+ if (StackId.isBackdropToTranslucentActivity(topStackId)
+ && topStack.isStackTranslucent(starting, stackBehindTopId)) {
// Stacks behind the fullscreen or assistant stack with a translucent activity are
// always visible so they can act as a backdrop to the translucent activity.
// For example, dialog activities
- if (stackIndex == stackBehindFocusedIndex) {
+ if (stackIndex == stackBehindTopIndex) {
return STACK_VISIBLE;
}
- if (stackBehindFocusedIndex >= 0) {
- if ((stackBehindFocusedId == DOCKED_STACK_ID
- || stackBehindFocusedId == PINNED_STACK_ID)
- && stackIndex == (stackBehindFocusedIndex - 1)) {
+ if (stackBehindTopIndex >= 0) {
+ if ((stackBehindTopId == DOCKED_STACK_ID
+ || stackBehindTopId == PINNED_STACK_ID)
+ && stackIndex == (stackBehindTopIndex - 1)) {
// The stack behind the docked or pinned stack is also visible so we can have a
// complete backdrop to the translucent activity when the docked stack is up.
return STACK_VISIBLE;
@@ -4271,8 +4272,8 @@
AppTimeTracker timeTracker, String reason) {
if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "moveTaskToFront: " + tr);
- final ActivityRecord focusedTopActivity = mStackSupervisor.getFocusedStack() != null
- ? mStackSupervisor.getFocusedStack().topActivity() : null;
+ final ActivityStack topStack = getTopStackOnDisplay();
+ final ActivityRecord topActivity = topStack != null ? topStack.topActivity() : null;
final int numTasks = mTaskHistory.size();
final int index = mTaskHistory.indexOf(tr);
if (numTasks == 0 || index < 0) {
@@ -4297,7 +4298,7 @@
insertTaskAtTop(tr, null);
// Don't refocus if invisible to current user
- ActivityRecord top = tr.getTopActivity();
+ final ActivityRecord top = tr.getTopActivity();
if (top == null || !top.okToShowLocked()) {
addRecentActivityLocked(top);
ActivityOptions.abort(options);
@@ -4305,7 +4306,7 @@
}
// Set focus to the top running activity of this stack.
- ActivityRecord r = topRunningActivityLocked();
+ final ActivityRecord r = topRunningActivityLocked();
mStackSupervisor.moveFocusableActivityStackToFrontLocked(r, reason);
if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare to front transition: task=" + tr);
@@ -4320,8 +4321,8 @@
}
// If a new task is moved to the front, then mark the existing top activity as supporting
// picture-in-picture while paused
- if (focusedTopActivity != null) {
- focusedTopActivity.supportsPictureInPictureWhilePausing = true;
+ if (topActivity != null) {
+ topActivity.supportsPictureInPictureWhilePausing = true;
}
mStackSupervisor.resumeFocusedStackTopActivityLocked();
@@ -4447,6 +4448,15 @@
return true;
}
+ /**
+ * Get the topmost stack on the current display. It may be different from focused stack, because
+ * focus may be on another display.
+ */
+ private ActivityStack getTopStackOnDisplay() {
+ final ArrayList<ActivityStack> stacks = mActivityContainer.mActivityDisplay.mStacks;
+ return stacks.isEmpty() ? null : stacks.get(stacks.size() - 1);
+ }
+
static void logStartActivity(int tag, ActivityRecord r, TaskRecord task) {
final Uri data = r.intent.getData();
final String strData = data != null ? data.toSafeString() : null;
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 65c768f..1c5233e 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -181,6 +181,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
public class ActivityStackSupervisor extends ConfigurationContainer implements DisplayListener {
@@ -1199,7 +1200,7 @@
ResolveInfo resolveIntent(Intent intent, String resolvedType, int userId, int flags) {
try {
return AppGlobals.getPackageManager().resolveIntent(intent, resolvedType,
- PackageManager.MATCH_DEFAULT_ONLY | flags
+ PackageManager.MATCH_INSTANT | PackageManager.MATCH_DEFAULT_ONLY | flags
| ActivityManagerService.STOCK_PM_FLAGS, userId);
} catch (RemoteException e) {
}
@@ -2392,7 +2393,18 @@
mWindowManager.deferSurfaceLayout();
try {
ActivityRecord r = stack.topRunningActivityLocked();
- stack.resize(pinnedBounds, tempPinnedTaskBounds, null);
+ Rect insetBounds = null;
+ if (tempPinnedTaskBounds != null) {
+ // We always use 0,0 as the position for the inset rect because
+ // if we are getting insets at all in the pinned stack it must mean
+ // we are headed for fullscreen.
+ insetBounds = tempRect;
+ insetBounds.top = 0;
+ insetBounds.left = 0;
+ insetBounds.right = tempPinnedTaskBounds.width();
+ insetBounds.bottom = tempPinnedTaskBounds.height();
+ }
+ stack.resize(pinnedBounds, tempPinnedTaskBounds, insetBounds);
stack.ensureVisibleActivitiesConfigurationLocked(r, false);
} finally {
mWindowManager.continueSurfaceLayout();
@@ -2772,7 +2784,8 @@
stack.prepareFreezingTaskBounds();
// Make sure the task has the appropriate bounds/size for the stack it is in.
- if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) {
+ if (stackId == FULLSCREEN_WORKSPACE_STACK_ID
+ && !Objects.equals(task.mBounds, stack.mBounds)) {
kept = task.resize(stack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow,
deferResume);
} else if (stackId == FREEFORM_WORKSPACE_STACK_ID) {
@@ -2895,6 +2908,7 @@
mService.mTaskChangeNotificationController.notifyActivityPinned();
}
+ /** Move activity with its stack to front and make the stack focused. */
boolean moveFocusableActivityStackToFrontLocked(ActivityRecord r, String reason) {
if (r == null || !r.isFocusable()) {
if (DEBUG_FOCUS) Slog.d(TAG_FOCUS,
@@ -3774,18 +3788,27 @@
}
private void handleDisplayRemoved(int displayId) {
+ if (displayId == DEFAULT_DISPLAY) {
+ throw new IllegalArgumentException("Can't remove the primary display.");
+ }
+
synchronized (mService) {
ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
if (activityDisplay != null) {
final boolean destroyContentOnRemoval
= activityDisplay.shouldDestroyContentOnRemove();
- ArrayList<ActivityStack> stacks = activityDisplay.mStacks;
- for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = stacks.get(stackNdx);
- moveStackToDisplayLocked(stack.mStackId, DEFAULT_DISPLAY,
- !destroyContentOnRemoval /* onTop */);
+ final ArrayList<ActivityStack> stacks = activityDisplay.mStacks;
+ while (!stacks.isEmpty()) {
+ final ActivityStack stack = stacks.get(0);
if (destroyContentOnRemoval) {
+ moveStackToDisplayLocked(stack.mStackId, DEFAULT_DISPLAY,
+ false /* onTop */);
stack.finishAllActivitiesLocked(true /* immediately */);
+ } else {
+ // Moving all tasks to fullscreen stack, because it's guaranteed to be
+ // a valid launch stack for all activities. This way the task history from
+ // external display will be preserved on primary after move.
+ moveTasksToFullscreenStackLocked(stack.getStackId(), true /* onTop */);
}
}
mActivityDisplays.remove(displayId);
@@ -4896,24 +4919,24 @@
* @return a list of activities which are the top ones in each visible stack. The first
* entry will be the focused activity.
*/
- public List<IBinder> getTopVisibleActivities() {
- // TODO(multi-display): Get rid of DEFAULT_DISPLAY here. Used in
- // VoiceInteractionManagerServiceImpl#showSessionLocked.
- final ActivityDisplay display = mActivityDisplays.get(DEFAULT_DISPLAY);
- if (display == null) {
- return Collections.EMPTY_LIST;
- }
- ArrayList<IBinder> topActivityTokens = new ArrayList<>();
- final ArrayList<ActivityStack> stacks = display.mStacks;
- for (int i = stacks.size() - 1; i >= 0; i--) {
- ActivityStack stack = stacks.get(i);
- if (stack.getStackVisibilityLocked(null) == ActivityStack.STACK_VISIBLE) {
- ActivityRecord top = stack.topActivity();
- if (top != null) {
- if (stack == mFocusedStack) {
- topActivityTokens.add(0, top.appToken);
- } else {
- topActivityTokens.add(top.appToken);
+ List<IBinder> getTopVisibleActivities() {
+ final ArrayList<IBinder> topActivityTokens = new ArrayList<>();
+ // Traverse all displays.
+ for (int i = mActivityDisplays.size() - 1; i >= 0; i--) {
+ final ActivityDisplay display = mActivityDisplays.valueAt(i);
+ // Traverse all stacks on a display.
+ for (int j = display.mStacks.size() - 1; j >= 0; j--) {
+ final ActivityStack stack = display.mStacks.get(j);
+ // Get top activity from a visible stack and add it to the list.
+ if (stack.getStackVisibilityLocked(null /* starting */)
+ == ActivityStack.STACK_VISIBLE) {
+ final ActivityRecord top = stack.topActivity();
+ if (top != null) {
+ if (stack == mFocusedStack) {
+ topActivityTokens.add(0, top.appToken);
+ } else {
+ topActivityTokens.add(top.appToken);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 4b07af0..7605a1e 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -585,10 +585,8 @@
// The activity was already running in the pinned stack so it wasn't started, but either
// brought to the front or the new intent was delivered to it since it was already in
// front. Notify anyone interested in this piece of information.
- final ComponentName sourceComponent = sourceRecord == null ? null :
- sourceRecord.realActivity;
mService.mTaskChangeNotificationController.notifyPinnedActivityRestartAttempt(
- sourceComponent);
+ sourceRecord.launchedFromPackage);
return;
}
}
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 9b459d1..62c3f4a 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -57,7 +57,7 @@
private final ActivityManagerService mActivityManagerService;
- private static final long WAIT_FOR_NETWORK_TIMEOUT_DEFAULT_MS = 2000; // 2 sec
+ private static final long WAIT_FOR_NETWORK_TIMEOUT_DEFAULT_MS = 0; // 0 sec
public CoreSettingsObserver(ActivityManagerService activityManagerService) {
super(activityManagerService.mHandler);
diff --git a/services/core/java/com/android/server/am/TaskChangeNotificationController.java b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
index 2990dff..9dfc7cd 100644
--- a/services/core/java/com/android/server/am/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
@@ -97,7 +97,7 @@
};
private final TaskStackConsumer mNotifyPinnedActivityRestartAttempt = (l, m) -> {
- l.onPinnedActivityRestartAttempt((ComponentName) m.obj);
+ l.onPinnedActivityRestartAttempt((String) m.obj);
};
private final TaskStackConsumer mNotifyPinnedStackAnimationEnded = (l, m) -> {
@@ -267,11 +267,11 @@
* running in the pinned stack and the activity was not actually started, but the task is
* either brought to the front or a new Intent is delivered to it.
*/
- void notifyPinnedActivityRestartAttempt(ComponentName sourceComponent) {
+ void notifyPinnedActivityRestartAttempt(String launchedFromPackage) {
mHandler.removeMessages(NOTIFY_PINNED_ACTIVITY_RESTART_ATTEMPT_LISTENERS_MSG);
final Message msg =
mHandler.obtainMessage(NOTIFY_PINNED_ACTIVITY_RESTART_ATTEMPT_LISTENERS_MSG,
- sourceComponent);
+ launchedFromPackage);
forAllLocalListeners(mNotifyPinnedActivityRestartAttempt, msg);
msg.sendToTarget();
}
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index b4feef3..abdcfe7 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -437,7 +437,7 @@
* @param attr attributes of the sound about to start playing
* @return time in ms
*/
- protected int getFocusRampTimeMs(int focusGain, AudioAttributes attr) {
+ protected static int getFocusRampTimeMs(int focusGain, AudioAttributes attr) {
switch (attr.getUsage()) {
case AudioAttributes.USAGE_MEDIA:
case AudioAttributes.USAGE_GAME:
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index a95a627..1f64b65 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -25,6 +25,7 @@
import android.media.IPlaybackConfigDispatcher;
import android.media.MediaRecorder;
import android.media.PlayerBase;
+import android.media.VolumeShaper;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -47,6 +48,7 @@
public final static String TAG = "AudioService.PlaybackActivityMonitor";
private final static boolean DEBUG = false;
+ private final static int VOLUME_SHAPER_SYSTEM_DUCK_ID = 1;
private ArrayList<PlayMonitorClient> mClients = new ArrayList<PlayMonitorClient>();
// a public client is one that needs an anonymized version of the playback configurations, we
@@ -104,7 +106,9 @@
synchronized(mPlayerLock) {
final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
// FIXME SoundPool not ready for state reporting
- if (apc.getPlayerType() == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
+ if (apc != null
+ && apc.getPlayerType() == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL)
+ {
return;
}
if (checkConfigurationCaller(piid, apc, binderUid)) {
@@ -126,6 +130,11 @@
final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
if (checkConfigurationCaller(piid, apc, binderUid)) {
mPlayers.remove(new Integer(piid));
+ final VolumeShaper vs = mDuckVolumeShapers.get(new Integer(piid));
+ if (vs != null) {
+ vs.release();
+ mDuckVolumeShapers.remove(new Integer(piid));
+ }
} else {
Log.e(TAG, "Error releasing player " + piid);
}
@@ -242,6 +251,20 @@
private final ArrayList<Integer> mDuckedPlayers = new ArrayList<Integer>();
private final ArrayList<Integer> mMutedPlayers = new ArrayList<Integer>();
+ private final VolumeShaper.Configuration DUCK_VSHAPE =
+ new VolumeShaper.Configuration.Builder()
+ .setId(VOLUME_SHAPER_SYSTEM_DUCK_ID)
+ .setCurve(new float[] { 0.f, 1.f } /* times */,
+ new float[] { 1.f, 0.2f } /* volumes */)
+ .setDurationMs(MediaFocusControl.getFocusRampTimeMs(
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
+ new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION)
+ .build()))
+ .build();
+
+ private final HashMap<Integer, VolumeShaper> mDuckVolumeShapers =
+ new HashMap<Integer, VolumeShaper>();
+
@Override
public boolean duckPlayers(FocusRequester winner, FocusRequester loser) {
if (DEBUG) {
@@ -257,6 +280,9 @@
while (piidIterator.hasNext()) {
final Integer piid = piidIterator.next();
final AudioPlaybackConfiguration apc = mPlayers.get(piid);
+ if (apc == null) {
+ continue;
+ }
if (!winner.hasSameUid(apc.getClientUid())
&& loser.hasSameUid(apc.getClientUid())
&& apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED)
@@ -268,11 +294,24 @@
// the player is speaking, ducking will make the speech unintelligible
// so let the app handle it instead
return false;
+ } else if (apc.getPlayerType()
+ == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
+ // TODO support ducking of SoundPool players
+ return false;
} else {
try {
if (DEBUG) { Log.v(TAG, "ducking player " + piid); }
- //FIXME just a test before we have VolumeShape
- apc.getPlayerProxy().setPan(-1.0f);
+ final VolumeShaper ducker;
+ if (mDuckVolumeShapers.containsKey(new Integer(piid))) {
+ ducker = mDuckVolumeShapers.get(new Integer(piid));
+ } else {
+ ducker = new VolumeShaper(
+ DUCK_VSHAPE,
+ apc.getPlayerProxy(),
+ true /* keepReference */);
+ mDuckVolumeShapers.put(new Integer(piid), ducker);
+ }
+ ducker.apply(VolumeShaper.Operation.PLAY); // duck
mDuckedPlayers.add(piid);
} catch (Exception e) {
Log.e(TAG, "Error ducking player " + piid, e);
@@ -301,8 +340,10 @@
try {
if (DEBUG) { Log.v(TAG, "unducking player" + piid); }
mDuckedPlayers.remove(new Integer(piid));
- //FIXME just a test before we have VolumeShape
- apc.getPlayerProxy().setPan(0.0f);
+ if (mDuckVolumeShapers.containsKey(new Integer(piid))) {
+ final VolumeShaper ducker = mDuckVolumeShapers.get(new Integer(piid));
+ ducker.apply(VolumeShaper.Operation.REVERSE); // unduck
+ }
} catch (Exception e) {
Log.e(TAG, "Error unducking player " + piid, e);
}
@@ -327,6 +368,9 @@
while (piidIterator.hasNext()) {
final Integer piid = piidIterator.next();
final AudioPlaybackConfiguration apc = mPlayers.get(piid);
+ if (apc == null) {
+ continue;
+ }
final int playerUsage = apc.getAudioAttributes().getUsage();
boolean mute = false;
for (int usageToMute : usagesToMute) {
diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index 017c5fb..63024db 100644
--- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -183,11 +183,31 @@
case CALLBACK_LISTEN_ALL:
break;
case CALLBACK_TRACK_DEFAULT:
+ if (mDefaultNetworkCallback == null) {
+ // The callback was unregistered in the interval between
+ // ConnectivityService calling onAvailable() and our
+ // handling of it here on the mTarget.getHandler() thread.
+ // Clean-up of this network entry is deferred to the
+ // handling of onLost() by other callbacks.
+ // TODO: change to Log.wtf() after oag/331764 is merged.
+ return;
+ }
+
cm().requestNetworkCapabilities(mDefaultNetworkCallback);
cm().requestLinkProperties(mDefaultNetworkCallback);
mCurrentDefault = network;
break;
case CALLBACK_MOBILE_REQUEST:
+ if (mMobileNetworkCallback == null) {
+ // The callback was unregistered in the interval between
+ // ConnectivityService calling onAvailable() and our
+ // handling of it here on the mTarget.getHandler() thread.
+ // Clean-up of this network entry is deferred to the
+ // handling of onLost() by other callbacks.
+ // TODO: change to Log.wtf() after oag/331764 is merged.
+ return;
+ }
+
cm().requestNetworkCapabilities(mMobileNetworkCallback);
cm().requestLinkProperties(mMobileNetworkCallback);
break;
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 5f348bf..a947b41 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -261,7 +261,6 @@
mPrimaryDisplayDeviceInfo = deviceInfo;
mInfo = null;
- mOverrideDisplayInfo = null;
}
}
diff --git a/services/core/java/com/android/server/firewall/IntentFirewall.java b/services/core/java/com/android/server/firewall/IntentFirewall.java
index 93c14b9..376a864 100644
--- a/services/core/java/com/android/server/firewall/IntentFirewall.java
+++ b/services/core/java/com/android/server/firewall/IntentFirewall.java
@@ -151,8 +151,7 @@
// For the first pass, find all the rules that have at least one intent-filter or
// component-filter that matches this intent
List<Rule> candidateRules;
- candidateRules = resolver.queryIntent(intent, resolvedType, false /*defaultOnly*/,
- false /*visibleToEphemeral*/, false /*isInstant*/, 0);
+ candidateRules = resolver.queryIntent(intent, resolvedType, false /*defaultOnly*/, 0);
if (candidateRules == null) {
candidateRules = new ArrayList<Rule>();
}
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 8bc72de..fdaba0b 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -263,8 +263,6 @@
private Object mLock = new Object();
- private int mLocationFlags = LOCATION_INVALID;
-
// current status
private int mStatus = LocationProvider.TEMPORARILY_UNAVAILABLE;
@@ -1451,7 +1449,6 @@
native_stop();
mTimeToFirstFix = 0;
mLastFixTime = 0;
- mLocationFlags = LOCATION_INVALID;
// reset SV count to zero
updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, 0);
@@ -1475,12 +1472,9 @@
/**
* called from native code to update our position.
*/
- private void reportLocation(int flags, double latitude, double longitude, double altitude,
- float speedMetersPerSecond, float bearing, float horizontalAccuracyMeters,
- float verticalAccuracyMeters, float speedAccuracyMetersPerSeconds,
- float bearingAccuracyDegrees, long timestamp) {
- if ((flags & LOCATION_HAS_SPEED) == LOCATION_HAS_SPEED) {
- mItarSpeedLimitExceeded = speedMetersPerSecond > ITAR_SPEED_LIMIT_METERS_PER_SECOND;
+ private void reportLocation(boolean hasLatLong, Location location) {
+ if (location.hasSpeed()) {
+ mItarSpeedLimitExceeded = location.getSpeed() > ITAR_SPEED_LIMIT_METERS_PER_SECOND;
}
if (mItarSpeedLimitExceeded) {
@@ -1489,54 +1483,13 @@
return; // No output of location allowed
}
- if (VERBOSE) Log.v(TAG, "reportLocation lat: " + latitude + " long: " + longitude +
- " timestamp: " + timestamp + " flags: " + flags);
+ if (VERBOSE) Log.v(TAG, "reportLocation " + location.toString());
synchronized (mLocation) {
- mLocationFlags = flags;
- if ((flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) {
- mLocation.setLatitude(latitude);
- mLocation.setLongitude(longitude);
- mLocation.setTime(timestamp);
- // It would be nice to push the elapsed real-time timestamp
- // further down the stack, but this is still useful
- mLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
- }
- if ((flags & LOCATION_HAS_ALTITUDE) == LOCATION_HAS_ALTITUDE) {
- mLocation.setAltitude(altitude);
- } else {
- mLocation.removeAltitude();
- }
- if ((flags & LOCATION_HAS_SPEED) == LOCATION_HAS_SPEED) {
- mLocation.setSpeed(speedMetersPerSecond);
- } else {
- mLocation.removeSpeed();
- }
- if ((flags & LOCATION_HAS_BEARING) == LOCATION_HAS_BEARING) {
- mLocation.setBearing(bearing);
- } else {
- mLocation.removeBearing();
- }
- if ((flags & LOCATION_HAS_HORIZONTAL_ACCURACY) == LOCATION_HAS_HORIZONTAL_ACCURACY) {
- mLocation.setAccuracy(horizontalAccuracyMeters);
- } else {
- mLocation.removeAccuracy();
- }
- if ((flags & LOCATION_HAS_VERTICAL_ACCURACY) == LOCATION_HAS_VERTICAL_ACCURACY) {
- mLocation.setVerticalAccuracyMeters(verticalAccuracyMeters);
- } else {
- mLocation.removeVerticalAccuracy();
- }
- if((flags & LOCATION_HAS_SPEED_ACCURACY) == LOCATION_HAS_SPEED_ACCURACY) {
- mLocation.setSpeedAccuracyMetersPerSecond(speedAccuracyMetersPerSeconds);
- } else {
- mLocation.removeSpeedAccuracy();
- }
- if((flags & LOCATION_HAS_BEARING_ACCURACY) == LOCATION_HAS_BEARING_ACCURACY) {
- mLocation.setBearingAccuracyDegrees(bearingAccuracyDegrees);
- } else {
- mLocation.removeBearingAccuracy();
- }
+ mLocation = location;
+ // It would be nice to push the elapsed real-time timestamp
+ // further down the stack, but this is still useful
+ mLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
mLocation.setExtras(mLocationExtras);
try {
@@ -1548,7 +1501,7 @@
mLastFixTime = System.currentTimeMillis();
// report time to first fix
- if (mTimeToFirstFix == 0 && (flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) {
+ if (mTimeToFirstFix == 0 && hasLatLong) {
mTimeToFirstFix = (int)(mLastFixTime - mFixRequestTime);
if (DEBUG) Log.d(TAG, "TTFF: " + mTimeToFirstFix);
@@ -1871,52 +1824,6 @@
}
/**
- * Helper method to construct a location object.
- */
- private Location buildLocation(
- int flags,
- double latitude,
- double longitude,
- double altitude,
- float speed,
- float bearing,
- float horizontalAccuracy,
- float verticalAccuracy,
- float speedAccuracy,
- float bearingAccuracy,
- long timestamp) {
- Location location = new Location(LocationManager.GPS_PROVIDER);
- if((flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) {
- location.setLatitude(latitude);
- location.setLongitude(longitude);
- location.setTime(timestamp);
- location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
- }
- if((flags & LOCATION_HAS_ALTITUDE) == LOCATION_HAS_ALTITUDE) {
- location.setAltitude(altitude);
- }
- if((flags & LOCATION_HAS_SPEED) == LOCATION_HAS_SPEED) {
- location.setSpeed(speed);
- }
- if((flags & LOCATION_HAS_BEARING) == LOCATION_HAS_BEARING) {
- location.setBearing(bearing);
- }
- if((flags & LOCATION_HAS_HORIZONTAL_ACCURACY) == LOCATION_HAS_HORIZONTAL_ACCURACY) {
- location.setAccuracy(horizontalAccuracy);
- }
- if((flags & LOCATION_HAS_VERTICAL_ACCURACY) == LOCATION_HAS_VERTICAL_ACCURACY) {
- location.setVerticalAccuracyMeters(verticalAccuracy);
- }
- if((flags & LOCATION_HAS_SPEED_ACCURACY) == LOCATION_HAS_SPEED_ACCURACY) {
- location.setSpeedAccuracyMetersPerSecond(speedAccuracy);
- }
- if((flags & LOCATION_HAS_BEARING_ACCURACY) == LOCATION_HAS_BEARING_ACCURACY) {
- location.setBearingAccuracyDegrees(bearingAccuracy);
- }
- return location;
- }
-
- /**
* Converts the GPS HAL status to the internal Geofence Hardware status.
*/
private int getGeofenceStatus(int status) {
@@ -1942,25 +1849,12 @@
* Called from native to report GPS Geofence transition
* All geofence callbacks are called on the same thread
*/
- private void reportGeofenceTransition(int geofenceId, int flags, double latitude,
- double longitude, double altitude, float speed, float bearing, float horizontalAccuracy,
- float verticalAccuracy, float speedAccuracy, float bearingAccuracy, long timestamp,
- int transition, long transitionTimestamp) {
+ private void reportGeofenceTransition(int geofenceId, Location location, int transition,
+ long transitionTimestamp) {
if (mGeofenceHardwareImpl == null) {
mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
}
- Location location = buildLocation(
- flags,
- latitude,
- longitude,
- altitude,
- speed,
- bearing,
- horizontalAccuracy,
- verticalAccuracy,
- speedAccuracy,
- bearingAccuracy,
- timestamp);
+
mGeofenceHardwareImpl.reportGeofenceTransition(
geofenceId,
location,
@@ -1973,24 +1867,10 @@
/**
* called from native code to report GPS status change.
*/
- private void reportGeofenceStatus(int status, int flags, double latitude,
- double longitude, double altitude, float speed, float bearing, float horizontalAccuracy,
- float verticalAccuracy, float speedAccuracy, float bearingAccuracy, long timestamp) {
+ private void reportGeofenceStatus(int status, Location location) {
if (mGeofenceHardwareImpl == null) {
mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
}
- Location location = buildLocation(
- flags,
- latitude,
- longitude,
- altitude,
- speed,
- bearing,
- horizontalAccuracy,
- verticalAccuracy,
- speedAccuracy,
- bearingAccuracy,
- timestamp);
int monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_UNAVAILABLE;
if(status == GPS_GEOFENCE_AVAILABLE) {
monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_AVAILABLE;
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 407262b..36f3287 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -64,6 +64,7 @@
import android.util.Slog;
import android.util.SparseArray;
import android.view.KeyEvent;
+import android.view.ViewConfiguration;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -99,6 +100,7 @@
private final Object mLock = new Object();
private final MessageHandler mHandler = new MessageHandler();
private final PowerManager.WakeLock mMediaEventWakeLock;
+ private final int mLongPressTimeout;
private KeyguardManager mKeyguardManager;
private IAudioService mAudioService;
@@ -120,6 +122,7 @@
mPriorityStack = new MediaSessionStack();
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
+ mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
}
@Override
@@ -536,6 +539,16 @@
return "";
}
+ private void dispatchVolumeKeyLongPressLocked(KeyEvent keyEvent) {
+ // Only consider full user.
+ UserRecord user = mUserRecords.get(mCurrentUserIdList.get(0));
+ try {
+ user.mOnVolumeKeyLongPressListener.onVolumeKeyLongPress(keyEvent);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to send " + keyEvent + " to volume key long-press listener");
+ }
+ }
+
/**
* Information about a particular user. The contents of this object is
* guarded by mLock.
@@ -944,7 +957,8 @@
try {
synchronized (mLock) {
// Only consider full user.
- UserRecord user = mUserRecords.get(mCurrentUserIdList.get(0));
+ int userId = mCurrentUserIdList.get(0);
+ UserRecord user = mUserRecords.get(userId);
if (mPriorityStack.isGlobalPriorityActive()
|| user.mOnVolumeKeyLongPressListener == null) {
@@ -954,11 +968,17 @@
// at the same time.
if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
if (keyEvent.getRepeatCount() == 0) {
- user.mInitialDownVolumeKeyEvent = keyEvent;
+ // Keeps the copy of the KeyEvent because it can be reused.
+ user.mInitialDownVolumeKeyEvent = KeyEvent.obtain(keyEvent);
user.mInitialDownVolumeStream = stream;
user.mInitialDownMusicOnly = musicOnly;
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(
+ MessageHandler.MSG_VOLUME_INITIAL_DOWN, userId, 0),
+ mLongPressTimeout);
}
if (keyEvent.getRepeatCount() > 0 || keyEvent.isLongPress()) {
+ mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
if (user.mInitialDownVolumeKeyEvent != null) {
dispatchVolumeKeyLongPressLocked(
user.mInitialDownVolumeKeyEvent);
@@ -968,6 +988,7 @@
dispatchVolumeKeyLongPressLocked(keyEvent);
}
} else { // if up
+ mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
if (user.mInitialDownVolumeKeyEvent != null
&& user.mInitialDownVolumeKeyEvent.getDownTime()
== keyEvent.getDownTime()) {
@@ -988,16 +1009,6 @@
}
}
- private void dispatchVolumeKeyLongPressLocked(KeyEvent keyEvent) {
- // Only consider full user.
- UserRecord user = mUserRecords.get(mCurrentUserIdList.get(0));
- try {
- user.mOnVolumeKeyLongPressListener.onVolumeKeyLongPress(keyEvent);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to send " + keyEvent + " to volume key long-press listener");
- }
- }
-
private void dispatchVolumeKeyEventLocked(
KeyEvent keyEvent, int stream, boolean musicOnly) {
boolean down = keyEvent.getAction() == KeyEvent.ACTION_DOWN;
@@ -1480,6 +1491,7 @@
final class MessageHandler extends Handler {
private static final int MSG_SESSIONS_CHANGED = 1;
+ private static final int MSG_VOLUME_INITIAL_DOWN = 2;
@Override
public void handleMessage(Message msg) {
@@ -1487,6 +1499,16 @@
case MSG_SESSIONS_CHANGED:
pushSessionsChanged(msg.arg1);
break;
+ case MSG_VOLUME_INITIAL_DOWN:
+ synchronized (mLock) {
+ UserRecord user = mUserRecords.get((int) msg.arg1);
+ if (user != null && user.mInitialDownVolumeKeyEvent != null) {
+ dispatchVolumeKeyLongPressLocked(user.mInitialDownVolumeKeyEvent);
+ // Mark that the key is already handled.
+ user.mInitialDownVolumeKeyEvent = null;
+ }
+ }
+ break;
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index f2b5564..ff42527 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4195,7 +4195,7 @@
.setCategory(MetricsEvent.NOTIFICATION_SNOOZED)
.setType(MetricsEvent.TYPE_CLOSE)
.addTaggedData(MetricsEvent.NOTIFICATION_SNOOZED_CRITERIA,
- snoozeCriterionId == null ? false : true));
+ snoozeCriterionId == null ? 0 : 1));
cancelNotificationLocked(r, false, REASON_SNOOZED);
updateLightsLocked();
if (snoozeCriterionId != null) {
diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
index 06b6f66..6365d15 100644
--- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
@@ -59,6 +59,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -1035,14 +1036,22 @@
}
}
+ private File[] getDefaultPermissionFiles() {
+ ArrayList<File> ret = new ArrayList<File>();
+ File dir = new File(Environment.getRootDirectory(), "etc/default-permissions");
+ if (dir.isDirectory() && dir.canRead()) {
+ Collections.addAll(ret, dir.listFiles());
+ }
+ dir = new File(Environment.getVendorDirectory(), "etc/default-permissions");
+ if (dir.isDirectory() && dir.canRead()) {
+ Collections.addAll(ret, dir.listFiles());
+ }
+ return ret.isEmpty() ? null : ret.toArray(new File[0]);
+ }
+
private @NonNull ArrayMap<String, List<DefaultPermissionGrant>>
readDefaultPermissionExceptionsLPw() {
- File dir = new File(Environment.getRootDirectory(), "etc/default-permissions");
- if (!dir.exists() || !dir.isDirectory() || !dir.canRead()) {
- return new ArrayMap<>(0);
- }
-
- File[] files = dir.listFiles();
+ File[] files = getDefaultPermissionFiles();
if (files == null) {
return new ArrayMap<>(0);
}
@@ -1052,7 +1061,7 @@
// Iterate over the files in the directory and scan .xml files
for (File file : files) {
if (!file.getPath().endsWith(".xml")) {
- Slog.i(TAG, "Non-xml file " + file + " in " + dir + " directory, ignoring");
+ Slog.i(TAG, "Non-xml file " + file + " in " + file.getParent() + " directory, ignoring");
continue;
}
if (!file.canRead()) {
diff --git a/services/core/java/com/android/server/pm/EphemeralResolver.java b/services/core/java/com/android/server/pm/EphemeralResolver.java
index 3c55422..d99a1b6 100644
--- a/services/core/java/com/android/server/pm/EphemeralResolver.java
+++ b/services/core/java/com/android/server/pm/EphemeralResolver.java
@@ -234,8 +234,7 @@
}
}
List<EphemeralResponse> matchedResolveInfoList = ephemeralResolver.queryIntent(
- intent, resolvedType, false /*defaultOnly*/, false /*visibleToEphemeral*/,
- false /*isInstant*/, userId);
+ intent, resolvedType, false /*defaultOnly*/, userId);
if (!matchedResolveInfoList.isEmpty()) {
return matchedResolveInfoList.get(0);
}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 449d808..db04515 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -465,6 +465,15 @@
}
}
+ public boolean isQuotaSupported(String volumeUuid) throws InstallerException {
+ if (!checkBeforeRemote()) return false;
+ try {
+ return mInstalld.isQuotaSupported(volumeUuid);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
private static void assertValidInstructionSet(String instructionSet)
throws InstallerException {
for (String abi : Build.SUPPORTED_ABIS) {
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index 42934a4..55a5f72 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -42,6 +42,7 @@
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
+import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.XmlUtils;
import libcore.io.IoUtils;
@@ -115,8 +116,14 @@
}
public byte[] getInstantAppCookieLPw(@NonNull String packageName,
- @UserIdInt int userId) {
- byte[] pendingCookie = mCookiePersistence.getPendingPersistCookie(userId, packageName);
+ @UserIdInt int userId) {
+ // Only installed packages can get their own cookie
+ PackageParser.Package pkg = mService.mPackages.get(packageName);
+ if (pkg == null) {
+ return null;
+ }
+
+ byte[] pendingCookie = mCookiePersistence.getPendingPersistCookieLPr(pkg, userId);
if (pendingCookie != null) {
return pendingCookie;
}
@@ -132,7 +139,7 @@
}
public boolean setInstantAppCookieLPw(@NonNull String packageName,
- @Nullable byte[] cookie, @UserIdInt int userId) {
+ @Nullable byte[] cookie, @UserIdInt int userId) {
if (cookie != null && cookie.length > 0) {
final int maxCookieSize = mService.mContext.getPackageManager()
.getInstantAppCookieMaxSize();
@@ -143,25 +150,25 @@
}
}
- mCookiePersistence.schedulePersist(userId, packageName, cookie);
+ // Only an installed package can set its own cookie
+ PackageParser.Package pkg = mService.mPackages.get(packageName);
+ if (pkg == null) {
+ return false;
+ }
+
+ mCookiePersistence.schedulePersistLPw(userId, pkg, cookie);
return true;
}
private void persistInstantApplicationCookie(@Nullable byte[] cookie,
- @NonNull String packageName, @UserIdInt int userId) {
+ @NonNull String packageName, @NonNull File cookieFile, @UserIdInt int userId) {
synchronized (mService.mPackages) {
- PackageParser.Package pkg = mService.mPackages.get(packageName);
- if (pkg == null) {
- return;
- }
-
File appDir = getInstantApplicationDir(packageName, userId);
if (!appDir.exists() && !appDir.mkdirs()) {
Slog.e(LOG_TAG, "Cannot create instant app cookie directory");
return;
}
- File cookieFile = computeInstantCookieFile(pkg, userId);
if (cookieFile.exists() && !cookieFile.delete()) {
Slog.e(LOG_TAG, "Cannot delete instant app cookie file");
}
@@ -170,12 +177,11 @@
if (cookie == null || cookie.length <= 0) {
return;
}
-
- try (FileOutputStream fos = new FileOutputStream(cookieFile)) {
- fos.write(cookie, 0, cookie.length);
- } catch (IOException e) {
- Slog.e(LOG_TAG, "Error writing instant app cookie file: " + cookieFile, e);
- }
+ }
+ try (FileOutputStream fos = new FileOutputStream(cookieFile)) {
+ fos.write(cookie, 0, cookie.length);
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "Error writing instant app cookie file: " + cookieFile, e);
}
}
@@ -217,7 +223,7 @@
propagateInstantAppPermissionsIfNeeded(pkg.packageName, userId);
// Track instant apps
- if (pkg.applicationInfo.isInstantApp()) {
+ if (ps.getInstantApp(userId)) {
addInstantAppLPw(userId, ps.appId);
}
@@ -240,6 +246,8 @@
if (!currentCookieFile.equals(expectedCookeFile)) {
Slog.i(LOG_TAG, "Signature for package " + pkg.packageName
+ " changed - dropping cookie");
+ // Make sure a pending write for the old signed app is cancelled
+ mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
currentCookieFile.delete();
}
}
@@ -257,7 +265,7 @@
continue;
}
- if (pkg.applicationInfo.isInstantApp()) {
+ if (ps.getInstantApp(userId)) {
// Add a record for an uninstalled instant app
addUninstalledInstantAppLPw(pkg, userId);
removeInstantAppLPw(userId, ps.appId);
@@ -533,11 +541,12 @@
final int packageCount = mService.mPackages.size();
for (int i = 0; i < packageCount; i++) {
- PackageParser.Package pkg = mService.mPackages.valueAt(i);
- if (!pkg.applicationInfo.isInstantApp()) {
+ final PackageParser.Package pkg = mService.mPackages.valueAt(i);
+ final PackageSetting ps = (PackageSetting) pkg.mExtras;
+ if (ps == null || !ps.getInstantApp(userId)) {
continue;
}
- InstantAppInfo info = createInstantAppInfoForPackage(
+ final InstantAppInfo info = createInstantAppInfoForPackage(
pkg, userId, true);
if (info == null) {
continue;
@@ -889,71 +898,90 @@
// In case you wonder why we stash the cookies aside, we use
// the user id for the message id and the package for the payload.
// Handler allows removing messages by id and tag where the
- // tag is is compared using ==. So to allow cancelling the
+ // tag is compared using ==. So to allow cancelling the
// pending persistence for an app under a given user we use
- // the fact that package names are interned in the system
- // process so the == comparison would match and we end up
- // with a way to cancel persisting the cookie for a user
- // and package.
- private final SparseArray<ArrayMap<String, byte[]>> mPendingPersistCookies =
- new SparseArray<>();
+ // the fact that package are cached by the system so the ==
+ // comparison would match and we end up with a way to cancel
+ // persisting the cookie for a user and package.
+ private final SparseArray<ArrayMap<PackageParser.Package, SomeArgs>> mPendingPersistCookies
+ = new SparseArray<>();
public CookiePersistence(Looper looper) {
super(looper);
}
- public void schedulePersist(@UserIdInt int userId,
- @NonNull String packageName, @NonNull byte[] cookie) {
- cancelPendingPersist(userId, packageName);
- addPendingPersistCookie(userId, packageName, cookie);
- sendMessageDelayed(obtainMessage(userId, packageName),
+ public void schedulePersistLPw(@UserIdInt int userId, @NonNull PackageParser.Package pkg,
+ @NonNull byte[] cookie) {
+ File cookieFile = computeInstantCookieFile(pkg, userId);
+ cancelPendingPersistLPw(pkg, userId);
+ addPendingPersistCookieLPw(userId, pkg, cookie, cookieFile);
+ sendMessageDelayed(obtainMessage(userId, pkg),
PERSIST_COOKIE_DELAY_MILLIS);
}
- public @Nullable byte[] getPendingPersistCookie(@UserIdInt int userId,
- @NonNull String packageName) {
- ArrayMap<String, byte[]> pendingWorkForUser = mPendingPersistCookies.get(userId);
+ public @Nullable byte[] getPendingPersistCookieLPr(@NonNull PackageParser.Package pkg,
+ @UserIdInt int userId) {
+ ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
+ mPendingPersistCookies.get(userId);
if (pendingWorkForUser != null) {
- return pendingWorkForUser.remove(packageName);
+ SomeArgs state = pendingWorkForUser.get(pkg);
+ if (state != null) {
+ return (byte[]) state.arg1;
+ }
}
return null;
}
- private void cancelPendingPersist(@UserIdInt int userId,
- @NonNull String packageName) {
- removePendingPersistCookie(userId, packageName);
- removeMessages(userId, packageName);
+ public void cancelPendingPersistLPw(@NonNull PackageParser.Package pkg,
+ @UserIdInt int userId) {
+ removeMessages(userId, pkg);
+ SomeArgs state = removePendingPersistCookieLPr(pkg, userId);
+ if (state != null) {
+ state.recycle();
+ }
}
- private void addPendingPersistCookie(@UserIdInt int userId,
- @NonNull String packageName, @NonNull byte[] cookie) {
- ArrayMap<String, byte[]> pendingWorkForUser = mPendingPersistCookies.get(userId);
+ private void addPendingPersistCookieLPw(@UserIdInt int userId,
+ @NonNull PackageParser.Package pkg, @NonNull byte[] cookie,
+ @NonNull File cookieFile) {
+ ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
+ mPendingPersistCookies.get(userId);
if (pendingWorkForUser == null) {
pendingWorkForUser = new ArrayMap<>();
mPendingPersistCookies.put(userId, pendingWorkForUser);
}
- pendingWorkForUser.put(packageName, cookie);
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = cookie;
+ args.arg2 = cookieFile;
+ pendingWorkForUser.put(pkg, args);
}
- private byte[] removePendingPersistCookie(@UserIdInt int userId,
- @NonNull String packageName) {
- ArrayMap<String, byte[]> pendingWorkForUser = mPendingPersistCookies.get(userId);
- byte[] cookie = null;
+ private SomeArgs removePendingPersistCookieLPr(@NonNull PackageParser.Package pkg,
+ @UserIdInt int userId) {
+ ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
+ mPendingPersistCookies.get(userId);
+ SomeArgs state = null;
if (pendingWorkForUser != null) {
- cookie = pendingWorkForUser.remove(packageName);
+ state = pendingWorkForUser.remove(pkg);
if (pendingWorkForUser.isEmpty()) {
mPendingPersistCookies.remove(userId);
}
}
- return cookie;
+ return state;
}
@Override
public void handleMessage(Message message) {
int userId = message.what;
- String packageName = (String) message.obj;
- byte[] cookie = removePendingPersistCookie(userId, packageName);
- persistInstantApplicationCookie(cookie, packageName, userId);
+ PackageParser.Package pkg = (PackageParser.Package) message.obj;
+ SomeArgs state = removePendingPersistCookieLPr(pkg, userId);
+ if (state == null) {
+ return;
+ }
+ byte[] cookie = (byte[]) state.arg1;
+ File cookieFile = (File) state.arg2;
+ state.recycle();
+ persistInstantApplicationCookie(cookie, pkg.packageName, cookieFile, userId);
}
}
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 8b4ef56..53765f2 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -683,9 +683,9 @@
File stageDir = null;
String stageCid = null;
if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
- final boolean isEphemeral =
- (params.installFlags & PackageManager.INSTALL_EPHEMERAL) != 0;
- stageDir = buildStageDir(params.volumeUuid, sessionId, isEphemeral);
+ final boolean isInstant =
+ (params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
+ stageDir = buildStageDir(params.volumeUuid, sessionId, isInstant);
} else {
stageCid = buildExternalStageCid(sessionId);
}
@@ -787,9 +787,6 @@
}
private File buildStagingDir(String volumeUuid, boolean isEphemeral) {
- if (isEphemeral) {
- return Environment.getDataAppEphemeralDirectory(volumeUuid);
- }
return Environment.getDataAppDirectory(volumeUuid);
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 067a136..1c5675a 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -24,6 +24,7 @@
import static android.system.OsConstants.O_CREAT;
import static android.system.OsConstants.O_RDONLY;
import static android.system.OsConstants.O_WRONLY;
+
import static com.android.server.pm.PackageInstallerService.prepareExternalStageCid;
import static com.android.server.pm.PackageInstallerService.prepareStageDir;
@@ -54,8 +55,8 @@
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
-import android.os.SELinux;
import android.os.UserHandle;
+import android.os.storage.StorageManager;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -66,9 +67,6 @@
import android.util.MathUtils;
import android.util.Slog;
-import libcore.io.IoUtils;
-import libcore.io.Libcore;
-
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.NativeLibraryHelper;
import com.android.internal.content.PackageHelper;
@@ -78,6 +76,9 @@
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.PackageInstallerService.PackageInstallObserverAdapter;
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileFilter;
@@ -455,14 +456,9 @@
// If caller specified a total length, allocate it for them. Free up
// cache space to grow, if needed.
- if (lengthBytes > 0) {
- final StructStat stat = Libcore.os.fstat(targetFd);
- final long deltaBytes = lengthBytes - stat.st_size;
- // Only need to free up space when writing to internal stage
- if (stageDir != null && deltaBytes > 0) {
- mPm.freeStorage(params.volumeUuid, deltaBytes);
- }
- Libcore.os.posix_fallocate(targetFd, 0, lengthBytes);
+ if (stageDir != null && lengthBytes > 0) {
+ mContext.getSystemService(StorageManager.class).allocateBytes(targetFd,
+ lengthBytes, 0);
}
if (offsetBytes > 0) {
@@ -704,7 +700,7 @@
final ApkLite apk;
try {
int flags = PackageParser.PARSE_COLLECT_CERTIFICATES;
- if ((params.installFlags & PackageManager.INSTALL_EPHEMERAL) != 0) {
+ if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
flags |= PackageParser.PARSE_IS_EPHEMERAL;
}
apk = PackageParser.parseApkLite(addedFile, flags);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f43e468..371a062 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -41,7 +41,7 @@
import static android.content.pm.PackageManager.INSTALL_FAILED_CONFLICTING_PROVIDER;
import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION;
-import static android.content.pm.PackageManager.INSTALL_FAILED_EPHEMERAL_INVALID;
+import static android.content.pm.PackageManager.INSTALL_FAILED_INSTANT_APP_INVALID;
import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
@@ -164,6 +164,7 @@
import android.content.pm.PermissionInfo;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
+import android.content.pm.SELinuxUtil;
import android.content.pm.ServiceInfo;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.Signature;
@@ -257,6 +258,7 @@
import com.android.internal.util.XmlUtils;
import com.android.server.AttributeCache;
import com.android.server.BackgroundDexOptJobService;
+import com.android.server.DeviceIdleController;
import com.android.server.EventLogTags;
import com.android.server.FgThread;
import com.android.server.IntentResolver;
@@ -421,8 +423,11 @@
static final int SCAN_CHECK_ONLY = 1<<13;
static final int SCAN_DONT_KILL_APP = 1<<14;
static final int SCAN_IGNORE_FROZEN = 1<<15;
- static final int REMOVE_CHATTY = 1<<16;
- static final int SCAN_FIRST_BOOT_OR_UPGRADE = 1<<17;
+ static final int SCAN_FIRST_BOOT_OR_UPGRADE = 1<<16;
+ static final int SCAN_AS_INSTANT_APP = 1<<17;
+ static final int SCAN_AS_FULL_APP = 1<<18;
+ /** Should not be with the scan flags */
+ static final int FLAGS_REMOVE_CHATTY = 1<<31;
private static final String STATIC_SHARED_LIB_DELIMITER = "_";
@@ -623,7 +628,6 @@
/** Directory where installed third-party apps stored */
final File mAppInstallDir;
- final File mEphemeralInstallDir;
/**
* Directory to which applications installed internally have their
@@ -847,6 +851,8 @@
private UserManagerInternal mUserManagerInternal;
+ private DeviceIdleController.LocalService mDeviceIdleController;
+
private File mCacheDir;
private ArraySet<String> mPrivappPermissionsViolations;
@@ -1776,28 +1782,32 @@
// the first time vs. those who are seeing an update.
int[] firstUsers = EMPTY_INT_ARRAY;
int[] updateUsers = EMPTY_INT_ARRAY;
- if (res.origUsers == null || res.origUsers.length == 0) {
- firstUsers = res.newUsers;
- } else {
- for (int newUser : res.newUsers) {
- boolean isNew = true;
- for (int origUser : res.origUsers) {
- if (origUser == newUser) {
- isNew = false;
- break;
- }
+ final boolean allNewUsers = res.origUsers == null || res.origUsers.length == 0;
+ final PackageSetting ps = (PackageSetting) res.pkg.mExtras;
+ for (int newUser : res.newUsers) {
+ if (ps.getInstantApp(newUser)) {
+ continue;
+ }
+ if (allNewUsers) {
+ firstUsers = ArrayUtils.appendInt(firstUsers, newUser);
+ continue;
+ }
+ boolean isNew = true;
+ for (int origUser : res.origUsers) {
+ if (origUser == newUser) {
+ isNew = false;
+ break;
}
- if (isNew) {
- firstUsers = ArrayUtils.appendInt(firstUsers, newUser);
- } else {
- updateUsers = ArrayUtils.appendInt(updateUsers, newUser);
- }
+ }
+ if (isNew) {
+ firstUsers = ArrayUtils.appendInt(firstUsers, newUser);
+ } else {
+ updateUsers = ArrayUtils.appendInt(updateUsers, newUser);
}
}
- // Send installed broadcasts if the install/update is not ephemeral
- // and the package is not a static shared lib.
- if (!isEphemeral(res.pkg) && res.pkg.staticSharedLibName == null) {
+ // Send installed broadcasts if the package is not a static shared lib.
+ if (res.pkg.staticSharedLibName == null) {
mProcessLoggingHandler.invalidateProcessLoggingBaseApkHash(res.pkg.baseCodePath);
// Send added for users that see the package for the first time
@@ -1884,16 +1894,14 @@
}
}
- if (!isEphemeral(res.pkg)) {
- // Notify DexManager that the package was installed for new users.
- // The updated users should already be indexed and the package code paths
- // should not change.
- // Don't notify the manager for ephemeral apps as they are not expected to
- // survive long enough to benefit of background optimizations.
- for (int userId : firstUsers) {
- PackageInfo info = getPackageInfo(packageName, /*flags*/ 0, userId);
- mDexManager.notifyPackageInstalled(info, userId);
- }
+ // Notify DexManager that the package was installed for new users.
+ // The updated users should already be indexed and the package code paths
+ // should not change.
+ // Don't notify the manager for ephemeral apps as they are not expected to
+ // survive long enough to benefit of background optimizations.
+ for (int userId : firstUsers) {
+ PackageInfo info = getPackageInfo(packageName, /*flags*/ 0, userId);
+ mDexManager.notifyPackageInstalled(info, userId);
}
}
@@ -2282,7 +2290,6 @@
File dataDir = Environment.getDataDirectory();
mAppInstallDir = new File(dataDir, "app");
mAppLib32InstallDir = new File(dataDir, "app-lib");
- mEphemeralInstallDir = new File(dataDir, "app-ephemeral");
mAsecInternalPath = new File(dataDir, "app-asec").getPath();
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
sUserManager = new UserManagerService(context, this,
@@ -2586,10 +2593,6 @@
| PackageParser.PARSE_FORWARD_LOCK,
scanFlags | SCAN_REQUIRE_KNOWN, 0);
- scanDirLI(mEphemeralInstallDir, mDefParseFlags
- | PackageParser.PARSE_IS_EPHEMERAL,
- scanFlags | SCAN_REQUIRE_KNOWN, 0);
-
/**
* Remove disable package settings for any updated system
* apps that were removed via an OTA. If they're not a
@@ -3332,14 +3335,14 @@
&& callingAppId != Process.ROOT_UID
&& checkUidPermission(Manifest.permission.ACCESS_INSTANT_APPS,
Binder.getCallingUid()) != PackageManager.PERMISSION_GRANTED) {
- final String ephemeralPackageName = getEphemeralPackageName(Binder.getCallingUid());
- if (ephemeralPackageName != null) {
+ final String instantAppPackageName = getInstantAppPackageName(Binder.getCallingUid());
+ if (instantAppPackageName != null) {
// ephemeral apps can only get information on themselves
- if (!ephemeralPackageName.equals(p.packageName)) {
+ if (!instantAppPackageName.equals(p.packageName)) {
return null;
}
} else {
- if (p.applicationInfo.isInstantApp()) {
+ if (ps.getInstantApp(userId)) {
// only get access to the ephemeral app if we've been granted access
if (!mInstantAppRegistry.isInstantAccessGranted(
userId, callingAppId, ps.appId)) {
@@ -3818,7 +3821,8 @@
});
}
- void freeStorage(String volumeUuid, long freeStorageSize) throws IOException {
+ public void freeStorage(String volumeUuid, long freeStorageSize, int storageFlags)
+ throws IOException {
synchronized (mInstallLock) {
try {
mInstaller.freeCache(volumeUuid, freeStorageSize, 0);
@@ -3855,6 +3859,14 @@
return mUserManagerInternal;
}
+ private DeviceIdleController.LocalService getDeviceIdleController() {
+ if (mDeviceIdleController == null) {
+ mDeviceIdleController =
+ LocalServices.getService(DeviceIdleController.LocalService.class);
+ }
+ return mDeviceIdleController;
+ }
+
/**
* Update given flags when being used to request {@link PackageInfo}.
*/
@@ -3950,17 +3962,17 @@
flags |= PackageManager.MATCH_SYSTEM_ONLY;
}
final int callingUid = Binder.getCallingUid();
- if (callingUid == Process.SYSTEM_UID || callingUid == 0) {
- // The system sees all components
- flags |= PackageManager.MATCH_EPHEMERAL;
- } else if (getEphemeralPackageName(callingUid) != null) {
+ if (getInstantAppPackageName(callingUid) != null) {
// But, ephemeral apps see both ephemeral and exposed, non-ephemeral components
- flags |= PackageManager.MATCH_VISIBLE_TO_EPHEMERAL_ONLY;
- flags |= PackageManager.MATCH_EPHEMERAL;
+ flags |= PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY;
+ flags |= PackageManager.MATCH_INSTANT;
} else {
// Otherwise, prevent leaking ephemeral components
- flags &= ~PackageManager.MATCH_VISIBLE_TO_EPHEMERAL_ONLY;
- flags &= ~PackageManager.MATCH_EPHEMERAL;
+ flags &= ~PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY;
+ if (callingUid != Process.SYSTEM_UID && callingUid != 0) {
+ // Unless called from the system process
+ flags &= ~PackageManager.MATCH_INSTANT;
+ }
}
return updateFlagsForComponent(flags, userId, cookie);
}
@@ -4689,7 +4701,8 @@
return;
}
- if (pkg.applicationInfo.isInstantApp() && !bp.isInstant()) {
+ final PackageSetting ps = mSettings.mPackages.get(packageName);
+ if (ps.getInstantApp(userId) && !bp.isInstant()) {
throw new SecurityException("Cannot grant non-ephemeral permission"
+ name + " for package " + packageName);
}
@@ -5738,8 +5751,7 @@
List<PersistentPreferredActivity> pprefs = ppir != null
? ppir.queryIntent(intent, resolvedType,
(flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
- (flags & PackageManager.MATCH_VISIBLE_TO_EPHEMERAL_ONLY) != 0,
- (flags & PackageManager.MATCH_EPHEMERAL) != 0, userId)
+ userId)
: null;
if (pprefs != null && pprefs.size() > 0) {
final int M = pprefs.size();
@@ -5811,8 +5823,7 @@
List<PreferredActivity> prefs = pir != null
? pir.queryIntent(intent, resolvedType,
(flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
- (flags & PackageManager.MATCH_VISIBLE_TO_EPHEMERAL_ONLY) != 0,
- (flags & PackageManager.MATCH_EPHEMERAL) != 0, userId)
+ userId)
: null;
if (prefs != null && prefs.size() > 0) {
boolean changed = false;
@@ -5983,8 +5994,7 @@
String resolvedType, int userId) {
CrossProfileIntentResolver resolver = mSettings.mCrossProfileIntentResolvers.get(userId);
if (resolver != null) {
- return resolver.queryIntent(intent, resolvedType, false /*defaultOnly*/,
- false /*visibleToEphemeral*/, false /*isInstant*/, userId);
+ return resolver.queryIntent(intent, resolvedType, false /*defaultOnly*/, userId);
}
return null;
}
@@ -6003,16 +6013,17 @@
}
/**
- * Returns the package name of the calling Uid if it's an ephemeral app. If it isn't
- * ephemeral, returns {@code null}.
+ * Returns the package name of the calling Uid if it's an instant app. If it isn't
+ * instant, returns {@code null}.
*/
- private String getEphemeralPackageName(int callingUid) {
+ private String getInstantAppPackageName(int callingUid) {
final int appId = UserHandle.getAppId(callingUid);
synchronized (mPackages) {
final Object obj = mSettings.getUserIdLPr(appId);
if (obj instanceof PackageSetting) {
final PackageSetting ps = (PackageSetting) obj;
- return ps.pkg.applicationInfo.isInstantApp() ? ps.pkg.packageName : null;
+ final boolean isInstantApp = ps.getInstantApp(UserHandle.getUserId(callingUid));
+ return isInstantApp ? ps.pkg.packageName : null;
}
}
return null;
@@ -6021,7 +6032,7 @@
private @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
String resolvedType, int flags, int userId) {
if (!sUserManager.exists(userId)) return Collections.emptyList();
- final String ephemeralPkgName = getEphemeralPackageName(Binder.getCallingUid());
+ final String instantAppPkgName = getInstantAppPackageName(Binder.getCallingUid());
flags = updateFlagsForResolve(flags, userId, intent);
enforceCrossUserPermission(Binder.getCallingUid(), userId,
false /* requireFullPermission */, false /* checkShell */,
@@ -6042,16 +6053,24 @@
// used when either 1) the calling package is normal and the activity is within
// an ephemeral application or 2) the calling package is ephemeral and the
// activity is not visible to ephemeral applications.
- boolean matchEphemeral =
- (flags & PackageManager.MATCH_EPHEMERAL) != 0;
- boolean ephemeralVisibleOnly =
- (flags & PackageManager.MATCH_VISIBLE_TO_EPHEMERAL_ONLY) != 0;
- boolean blockResolution =
- (!matchEphemeral && ephemeralPkgName == null
- && (ai.applicationInfo.privateFlags
- & ApplicationInfo.PRIVATE_FLAG_EPHEMERAL) != 0)
- || (ephemeralVisibleOnly && ephemeralPkgName != null
- && (ai.flags & ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL) == 0);
+ final boolean matchInstantApp =
+ (flags & PackageManager.MATCH_INSTANT) != 0;
+ final boolean matchVisibleToInstantAppOnly =
+ (flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
+ final boolean isCallerInstantApp =
+ instantAppPkgName != null;
+ final boolean isTargetSameInstantApp =
+ comp.getPackageName().equals(instantAppPkgName);
+ final boolean isTargetInstantApp =
+ (ai.applicationInfo.privateFlags
+ & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
+ final boolean isTargetHiddenFromInstantApp =
+ (ai.flags & ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL) == 0;
+ final boolean blockResolution =
+ !isTargetSameInstantApp
+ && ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp)
+ || (matchVisibleToInstantAppOnly && isCallerInstantApp
+ && isTargetHiddenFromInstantApp));
if (!blockResolution) {
final ResolveInfo ri = new ResolveInfo();
ri.activityInfo = ai;
@@ -6077,7 +6096,7 @@
List<ResolveInfo> xpResult = new ArrayList<ResolveInfo>(1);
xpResult.add(xpResolveInfo);
return filterForEphemeral(
- filterIfNotSystemUser(xpResult, userId), ephemeralPkgName);
+ filterIfNotSystemUser(xpResult, userId), instantAppPkgName);
}
// Check for results in the current profile.
@@ -6117,13 +6136,13 @@
// And we are not going to add emphemeral app, so we can return the
// result straight away.
result.add(xpDomainInfo.resolveInfo);
- return filterForEphemeral(result, ephemeralPkgName);
+ return filterForEphemeral(result, instantAppPkgName);
}
} else if (result.size() <= 1 && !addEphemeral) {
// No result in parent user and <= 1 result in current profile, and we
// are not going to add emphemeral app, so we can return the result without
// further processing.
- return filterForEphemeral(result, ephemeralPkgName);
+ return filterForEphemeral(result, instantAppPkgName);
}
// We have more than one candidate (combining results from current and parent
// profile), so we need filtering and sorting.
@@ -6137,7 +6156,7 @@
result = filterForEphemeral(filterIfNotSystemUser(
mActivities.queryIntentForPackage(
intent, resolvedType, flags, pkg.activities, userId),
- userId), ephemeralPkgName);
+ userId), instantAppPkgName);
} else {
// the caller wants to resolve for a particular package; however, there
// were no installed results, so, try to find an ephemeral result
@@ -6175,7 +6194,7 @@
if (sortResult) {
Collections.sort(result, mResolvePrioritySorter);
}
- return filterForEphemeral(result, ephemeralPkgName);
+ return filterForEphemeral(result, instantAppPkgName);
}
private static class CrossProfileDomainInfo {
@@ -7139,9 +7158,9 @@
return false;
}
synchronized (mPackages) {
- PackageParser.Package pkg = mPackages.get(packageName);
- if (pkg != null) {
- return pkg.applicationInfo.isInstantApp();
+ final PackageSetting ps = mSettings.mPackages.get(packageName);
+ if (ps != null) {
+ return ps.getInstantApp(userId);
}
}
return false;
@@ -7644,7 +7663,7 @@
* @throws PackageManagerException on a parse error.
*/
private PackageParser.Package scanPackageLI(PackageParser.Package pkg, File scanFile,
- final int policyFlags, int scanFlags, long currentTime, UserHandle user)
+ final int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user)
throws PackageManagerException {
// If the package has children and this is the first dive in the function
// we scan the package with the SCAN_CHECK_ONLY flag set to see whether all
@@ -7684,7 +7703,7 @@
* @throws PackageManagerException on a parse error.
*/
private PackageParser.Package scanPackageInternalLI(PackageParser.Package pkg, File scanFile,
- int policyFlags, int scanFlags, long currentTime, UserHandle user)
+ int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user)
throws PackageManagerException {
PackageSetting ps = null;
PackageSetting updatedPkg;
@@ -7920,6 +7939,11 @@
pkg.setApplicationInfoBaseResourcePath(baseResourcePath);
pkg.setApplicationInfoSplitResourcePaths(pkg.splitCodePaths);
+ final int userId = ((user == null) ? 0 : user.getIdentifier());
+ if (ps != null && ps.getInstantApp(userId)) {
+ scanFlags |= SCAN_AS_INSTANT_APP;
+ }
+
// Note that we invoke the following method only if we are about to unpack an application
PackageParser.Package scannedPkg = scanPackageLI(pkg, policyFlags, scanFlags
| SCAN_UPDATE_SIGNATURE, currentTime, user);
@@ -8857,7 +8881,7 @@
}
private PackageParser.Package scanPackageTracedLI(PackageParser.Package pkg,
- final int policyFlags, int scanFlags, long currentTime, UserHandle user)
+ final int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user)
throws PackageManagerException {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage");
// If the package has children and this is the first dive in the function
@@ -8896,7 +8920,8 @@
}
private PackageParser.Package scanPackageLI(PackageParser.Package pkg, final int policyFlags,
- int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
+ int scanFlags, long currentTime, @Nullable UserHandle user)
+ throws PackageManagerException {
boolean success = false;
try {
final PackageParser.Package res = scanPackageDirtyLI(pkg, policyFlags, scanFlags,
@@ -8962,7 +8987,7 @@
}
private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg,
- final int policyFlags, final int scanFlags, long currentTime, UserHandle user)
+ final int policyFlags, final int scanFlags, long currentTime, @Nullable UserHandle user)
throws PackageManagerException {
if (DEBUG_PACKAGE_SCANNING) {
if ((policyFlags & PackageParser.PARSE_CHATTY) != 0)
@@ -9100,16 +9125,16 @@
if (pkgSetting == null) {
final String parentPackageName = (pkg.parentPackage != null)
? pkg.parentPackage.packageName : null;
-
+ final boolean instantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0;
// REMOVE SharedUserSetting from method; update in a separate call
pkgSetting = Settings.createNewSetting(pkg.packageName, origPackage,
disabledPkgSetting, realName, suid, destCodeFile, destResourceFile,
pkg.applicationInfo.nativeLibraryRootDir, pkg.applicationInfo.primaryCpuAbi,
pkg.applicationInfo.secondaryCpuAbi, pkg.mVersionCode,
pkg.applicationInfo.flags, pkg.applicationInfo.privateFlags, user,
- true /*allowInstall*/, parentPackageName, pkg.getChildPackageNames(),
- UserManagerService.getInstance(), usesStaticLibraries,
- pkg.usesStaticLibrariesVersions);
+ true /*allowInstall*/, instantApp, parentPackageName,
+ pkg.getChildPackageNames(), UserManagerService.getInstance(),
+ usesStaticLibraries, pkg.usesStaticLibrariesVersions);
// SIDE EFFECTS; updates system state; move elsewhere
if (origPackage != null) {
mSettings.addRenamedPackageLPw(pkg.packageName, origPackage.name);
@@ -9176,9 +9201,8 @@
}
if (mFoundPolicyFile) {
- SELinuxMMAC.assignSeinfoValue(pkg);
+ SELinuxMMAC.assignSeInfoValue(pkg);
}
-
pkg.applicationInfo.uid = pkgSetting.appId;
pkg.mExtras = pkgSetting;
@@ -9413,11 +9437,11 @@
}
}
} else {
+ final int userId = user == null ? 0 : user.getIdentifier();
// Modify state for the given package setting
commitPackageSettings(pkg, pkgSetting, user, scanFlags,
(policyFlags & PackageParser.PARSE_CHATTY) != 0 /*chatty*/);
- if (isEphemeral(pkg)) {
- final int userId = user == null ? 0 : user.getIdentifier();
+ if (pkgSetting.getInstantApp(userId)) {
mInstantAppRegistry.addInstantAppLPw(userId, pkgSetting.appId);
}
}
@@ -9532,10 +9556,10 @@
"Packages declaring static-shared libs must target O SDK or higher");
}
- // Package declaring static a shared lib cannot be ephemeral
- if (pkg.applicationInfo.isInstantApp()) {
+ // Package declaring static a shared lib cannot be instant apps
+ if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
throw new PackageManagerException(
- "Packages declaring static-shared libs cannot be ephemeral");
+ "Packages declaring static-shared libs cannot be instant apps");
}
// Package declaring static a shared lib cannot be renamed since the package
@@ -9778,7 +9802,6 @@
mPlatformPackage = pkg;
pkg.mVersionCode = mSdkVersion;
mAndroidApplication = pkg.applicationInfo;
-
if (!mResolverReplaced) {
mResolveActivity.applicationInfo = mAndroidApplication;
mResolveActivity.name = ResolverActivity.class.getName();
@@ -10067,10 +10090,10 @@
PackageParser.PermissionGroup cur = mPermissionGroups.get(pg.info.name);
final String curPackageName = cur == null ? null : cur.info.packageName;
// Dont allow ephemeral apps to define new permission groups.
- if (pkg.applicationInfo.isInstantApp()) {
+ if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
Slog.w(TAG, "Permission group " + pg.info.name + " from package "
+ pg.info.packageName
- + " ignored: ephemeral apps cannot define new permission groups.");
+ + " ignored: instant apps cannot define new permission groups.");
continue;
}
final boolean isPackageUpdate = pg.info.packageName.equals(curPackageName);
@@ -10112,10 +10135,10 @@
PackageParser.Permission p = pkg.permissions.get(i);
// Dont allow ephemeral apps to define new permissions.
- if (pkg.applicationInfo.isInstantApp()) {
+ if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
Slog.w(TAG, "Permission " + p.info.name + " from package "
+ p.info.packageName
- + " ignored: ephemeral apps cannot define new permissions.");
+ + " ignored: instant apps cannot define new permissions.");
continue;
}
@@ -11737,13 +11760,10 @@
final class ActivityIntentResolver
extends IntentResolver<PackageParser.ActivityIntentInfo, ResolveInfo> {
public List<ResolveInfo> queryIntent(Intent intent, String resolvedType,
- boolean defaultOnly, boolean visibleToEphemeral, boolean isEphemeral, int userId) {
+ boolean defaultOnly, int userId) {
if (!sUserManager.exists(userId)) return null;
- mFlags = (defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0)
- | (visibleToEphemeral ? PackageManager.MATCH_VISIBLE_TO_EPHEMERAL_ONLY : 0)
- | (isEphemeral ? PackageManager.MATCH_EPHEMERAL : 0);
- return super.queryIntent(intent, resolvedType, defaultOnly, visibleToEphemeral,
- isEphemeral, userId);
+ mFlags = (defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0);
+ return super.queryIntent(intent, resolvedType, defaultOnly, userId);
}
public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags,
@@ -11752,8 +11772,7 @@
mFlags = flags;
return super.queryIntent(intent, resolvedType,
(flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
- (flags & PackageManager.MATCH_VISIBLE_TO_EPHEMERAL_ONLY) != 0,
- (flags & PackageManager.MATCH_EPHEMERAL) != 0, userId);
+ userId);
}
public List<ResolveInfo> queryIntentForPackage(Intent intent, String resolvedType,
@@ -11764,9 +11783,6 @@
}
mFlags = flags;
final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0;
- final boolean vislbleToEphemeral =
- (flags & PackageManager.MATCH_VISIBLE_TO_EPHEMERAL_ONLY) != 0;
- final boolean isEphemeral = (flags & PackageManager.MATCH_EPHEMERAL) != 0;
final int N = packageActivities.size();
ArrayList<PackageParser.ActivityIntentInfo[]> listCut =
new ArrayList<PackageParser.ActivityIntentInfo[]>(N);
@@ -11781,8 +11797,7 @@
listCut.add(array);
}
}
- return super.queryIntentFromList(intent, resolvedType, defaultOnly,
- vislbleToEphemeral, isEphemeral, listCut, userId);
+ return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut, userId);
}
/**
@@ -12194,11 +12209,24 @@
if (ps == null) {
return null;
}
+ final PackageUserState userState = ps.readUserState(userId);
ActivityInfo ai = PackageParser.generateActivityInfo(activity, mFlags,
- ps.readUserState(userId), userId);
+ userState, userId);
if (ai == null) {
return null;
}
+ final boolean matchVisibleToInstantApp =
+ (mFlags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
+ final boolean isInstantApp = (mFlags & PackageManager.MATCH_INSTANT) != 0;
+ // throw out filters that aren't visible to ephemeral apps
+ if (matchVisibleToInstantApp
+ && !(info.isVisibleToInstantApp() || userState.instantApp)) {
+ return null;
+ }
+ // throw out ephemeral filters if we're not explicitly requesting them
+ if (!isInstantApp && userState.instantApp) {
+ return null;
+ }
final ResolveInfo res = new ResolveInfo();
res.activityInfo = ai;
if ((mFlags&PackageManager.GET_RESOLVED_FILTER) != 0) {
@@ -12267,10 +12295,9 @@
private final class ServiceIntentResolver
extends IntentResolver<PackageParser.ServiceIntentInfo, ResolveInfo> {
public List<ResolveInfo> queryIntent(Intent intent, String resolvedType,
- boolean defaultOnly, boolean visibleToEphemeral, boolean isEphemeral, int userId) {
+ boolean defaultOnly, int userId) {
mFlags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0;
- return super.queryIntent(intent, resolvedType, defaultOnly, visibleToEphemeral,
- isEphemeral, userId);
+ return super.queryIntent(intent, resolvedType, defaultOnly, userId);
}
public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags,
@@ -12279,8 +12306,7 @@
mFlags = flags;
return super.queryIntent(intent, resolvedType,
(flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
- (flags & PackageManager.MATCH_VISIBLE_TO_EPHEMERAL_ONLY) != 0,
- (flags & PackageManager.MATCH_EPHEMERAL) != 0, userId);
+ userId);
}
public List<ResolveInfo> queryIntentForPackage(Intent intent, String resolvedType,
@@ -12291,9 +12317,6 @@
}
mFlags = flags;
final boolean defaultOnly = (flags&PackageManager.MATCH_DEFAULT_ONLY) != 0;
- final boolean vislbleToEphemeral =
- (flags&PackageManager.MATCH_VISIBLE_TO_EPHEMERAL_ONLY) != 0;
- final boolean isEphemeral = (flags&PackageManager.MATCH_EPHEMERAL) != 0;
final int N = packageServices.size();
ArrayList<PackageParser.ServiceIntentInfo[]> listCut =
new ArrayList<PackageParser.ServiceIntentInfo[]>(N);
@@ -12308,8 +12331,7 @@
listCut.add(array);
}
}
- return super.queryIntentFromList(intent, resolvedType, defaultOnly,
- vislbleToEphemeral, isEphemeral, listCut, userId);
+ return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut, userId);
}
public final void addService(PackageParser.Service s) {
@@ -12484,10 +12506,9 @@
private final class ProviderIntentResolver
extends IntentResolver<PackageParser.ProviderIntentInfo, ResolveInfo> {
public List<ResolveInfo> queryIntent(Intent intent, String resolvedType,
- boolean defaultOnly, boolean visibleToEphemeral, boolean isEphemeral, int userId) {
+ boolean defaultOnly, int userId) {
mFlags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0;
- return super.queryIntent(intent, resolvedType, defaultOnly, visibleToEphemeral,
- isEphemeral, userId);
+ return super.queryIntent(intent, resolvedType, defaultOnly, userId);
}
public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags,
@@ -12497,8 +12518,7 @@
mFlags = flags;
return super.queryIntent(intent, resolvedType,
(flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
- (flags & PackageManager.MATCH_VISIBLE_TO_EPHEMERAL_ONLY) != 0,
- (flags & PackageManager.MATCH_EPHEMERAL) != 0, userId);
+ userId);
}
public List<ResolveInfo> queryIntentForPackage(Intent intent, String resolvedType,
@@ -12510,9 +12530,6 @@
}
mFlags = flags;
final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0;
- final boolean isEphemeral = (flags&PackageManager.MATCH_EPHEMERAL) != 0;
- final boolean vislbleToEphemeral =
- (flags&PackageManager.MATCH_VISIBLE_TO_EPHEMERAL_ONLY) != 0;
final int N = packageProviders.size();
ArrayList<PackageParser.ProviderIntentInfo[]> listCut =
new ArrayList<PackageParser.ProviderIntentInfo[]>(N);
@@ -12527,8 +12544,7 @@
listCut.add(array);
}
}
- return super.queryIntentFromList(intent, resolvedType, defaultOnly,
- vislbleToEphemeral, isEphemeral, listCut, userId);
+ return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut, userId);
}
public final void addProvider(PackageParser.Provider p) {
@@ -13069,7 +13085,7 @@
String installerPackageName, int installerUid, UserHandle user,
Certificate[][] certificates) {
if (DEBUG_EPHEMERAL) {
- if ((sessionParams.installFlags & PackageManager.INSTALL_EPHEMERAL) != 0) {
+ if ((sessionParams.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
Slog.d(TAG, "Ephemeral install of " + packageName);
}
}
@@ -13284,7 +13300,8 @@
* @hide
*/
@Override
- public int installExistingPackageAsUser(String packageName, int userId, int installReason) {
+ public int installExistingPackageAsUser(String packageName, int userId, int installFlags,
+ int installReason) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES,
null);
PackageSetting pkgSetting;
@@ -13299,6 +13316,10 @@
long callingId = Binder.clearCallingIdentity();
try {
boolean installed = false;
+ final boolean instantApp =
+ (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
+ final boolean fullApp =
+ (installFlags & PackageManager.INSTALL_FULL_APP) != 0;
// writer
synchronized (mPackages) {
@@ -13313,7 +13334,11 @@
mSettings.writePackageRestrictionsLPr(userId);
mSettings.writeKernelMappingLPr(pkgSetting);
installed = true;
+ } else if (fullApp && pkgSetting.getInstantApp(userId)) {
+ // upgrade app from instant to full; we don't allow app downgrade
+ installed = true;
}
+ setInstantAppForUser(pkgSetting, userId, instantApp, fullApp);
}
if (installed) {
@@ -13335,6 +13360,29 @@
return PackageManager.INSTALL_SUCCEEDED;
}
+ void setInstantAppForUser(PackageSetting pkgSetting, int userId,
+ boolean instantApp, boolean fullApp) {
+ // no state specified; do nothing
+ if (!instantApp && !fullApp) {
+ return;
+ }
+ if (userId != UserHandle.USER_ALL) {
+ if (instantApp && !pkgSetting.getInstantApp(userId)) {
+ pkgSetting.setInstantApp(true /*instantApp*/, userId);
+ } else if (fullApp && pkgSetting.getInstantApp(userId)) {
+ pkgSetting.setInstantApp(false /*instantApp*/, userId);
+ }
+ } else {
+ for (int currentUserId : sUserManager.getUserIds()) {
+ if (instantApp && !pkgSetting.getInstantApp(currentUserId)) {
+ pkgSetting.setInstantApp(true /*instantApp*/, currentUserId);
+ } else if (fullApp && pkgSetting.getInstantApp(currentUserId)) {
+ pkgSetting.setInstantApp(false /*instantApp*/, currentUserId);
+ }
+ }
+ }
+ }
+
boolean isUserRestricted(int userId, String restrictionKey) {
Bundle restrictions = sUserManager.getUserRestrictions(userId);
if (restrictions.getBoolean(restrictionKey, false)) {
@@ -13693,7 +13741,7 @@
return false;
}
// Ephemeral apps don't get the full verification treatment
- if ((installFlags & PackageManager.INSTALL_EPHEMERAL) != 0) {
+ if ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
if (DEBUG_EPHEMERAL) {
Slog.d(TAG, "INSTALL_EPHEMERAL so skipping verification");
}
@@ -14485,7 +14533,7 @@
final boolean onSd = (installFlags & PackageManager.INSTALL_EXTERNAL) != 0;
final boolean onInt = (installFlags & PackageManager.INSTALL_INTERNAL) != 0;
- final boolean ephemeral = (installFlags & PackageManager.INSTALL_EPHEMERAL) != 0;
+ final boolean ephemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
PackageInfoLite pkgLite = null;
if (onInt && onSd) {
@@ -14569,7 +14617,7 @@
if (DEBUG_EPHEMERAL) {
Slog.v(TAG, "...setting INSTALL_EPHEMERAL install flag");
}
- installFlags |= PackageManager.INSTALL_EPHEMERAL;
+ installFlags |= PackageManager.INSTALL_INSTANT_APP;
installFlags &= ~(PackageManager.INSTALL_EXTERNAL
|PackageManager.INSTALL_INTERNAL);
} else {
@@ -14662,6 +14710,9 @@
final List<ComponentName> sufficientVerifiers = matchVerifiers(pkgLite,
receivers, verificationState);
+ DeviceIdleController.LocalService idleController = getDeviceIdleController();
+ final long idleDuration = getVerificationTimeout();
+
/*
* If any sufficient verifiers were listed in the package
* manifest, attempt to ask them.
@@ -14674,6 +14725,9 @@
} else {
for (int i = 0; i < N; i++) {
final ComponentName verifierComponent = sufficientVerifiers.get(i);
+ idleController.addPowerSaveTempWhitelistApp(Process.myUid(),
+ verifierComponent.getPackageName(), idleDuration,
+ verifierUser.getIdentifier(), false, "package verifier");
final Intent sufficientIntent = new Intent(verification);
sufficientIntent.setComponent(verifierComponent);
@@ -14694,6 +14748,9 @@
* target BroadcastReceivers have run.
*/
verification.setComponent(requiredVerifierComponent);
+ idleController.addPowerSaveTempWhitelistApp(Process.myUid(),
+ requiredVerifierComponent.getPackageName(), idleDuration,
+ verifierUser.getIdentifier(), false, "package verifier");
mContext.sendOrderedBroadcastAsUser(verification, verifierUser,
android.Manifest.permission.PACKAGE_VERIFICATION_AGENT,
new BroadcastReceiver() {
@@ -14903,7 +14960,7 @@
}
protected boolean isEphemeral() {
- return (installFlags & PackageManager.INSTALL_EPHEMERAL) != 0;
+ return (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
}
UserHandle getUser() {
@@ -14983,7 +15040,7 @@
}
try {
- final boolean isEphemeral = (installFlags & PackageManager.INSTALL_EPHEMERAL) != 0;
+ final boolean isEphemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
final File tempDir =
mInstallerService.allocateStageDirLegacy(volumeUuid, isEphemeral);
codeFile = tempDir;
@@ -15810,7 +15867,7 @@
private void replacePackageLIF(PackageParser.Package pkg, final int policyFlags, int scanFlags,
UserHandle user, String installerPackageName, PackageInstalledInfo res,
int installReason) {
- final boolean isEphemeral = (policyFlags & PackageParser.PARSE_IS_EPHEMERAL) != 0;
+ final boolean isInstantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0;
final PackageParser.Package oldPackage;
final String pkgName = pkg.packageName;
@@ -15834,17 +15891,17 @@
return;
}
+ final PackageSetting ps = mSettings.mPackages.get(pkgName);
+
// don't allow an upgrade from full to ephemeral
- final boolean oldIsEphemeral = oldPackage.applicationInfo.isInstantApp();
- if (isEphemeral && !oldIsEphemeral) {
- // can't downgrade from full to ephemeral
- Slog.w(TAG, "Can't replace app with ephemeral: " + pkgName);
- res.setReturnCode(PackageManager.INSTALL_FAILED_EPHEMERAL_INVALID);
+ if (isInstantApp && !ps.getInstantApp(user.getIdentifier())) {
+ // can't downgrade from full to instant
+ Slog.w(TAG, "Can't replace app with instant app: " + pkgName);
+ res.setReturnCode(PackageManager.INSTALL_FAILED_INSTANT_APP_INVALID);
return;
}
// verify signatures are valid
- final PackageSetting ps = mSettings.mPackages.get(pkgName);
if (shouldCheckUpgradeKeySetLP(ps, scanFlags)) {
if (!checkUpgradeKeySetLP(ps, pkg)) {
res.setError(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
@@ -16046,6 +16103,10 @@
childPs.oldCodePaths = ps.oldCodePaths;
}
}
+ // set instant app status, but, only if it's explicitly specified
+ final boolean instantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0;
+ final boolean fullApp = (scanFlags & SCAN_AS_FULL_APP) != 0;
+ setInstantAppForUser(ps, user.getIdentifier(), instantApp, fullApp);
prepareAppDataAfterInstallLIF(newPackage);
addedPkg = true;
} catch (PackageManagerException e) {
@@ -16517,7 +16578,8 @@
final boolean forwardLocked = ((installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0);
final boolean onExternal = (((installFlags & PackageManager.INSTALL_EXTERNAL) != 0)
|| (args.volumeUuid != null));
- final boolean ephemeral = ((installFlags & PackageManager.INSTALL_EPHEMERAL) != 0);
+ final boolean instantApp = ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0);
+ final boolean fullApp = ((installFlags & PackageManager.INSTALL_FULL_APP) != 0);
final boolean forceSdk = ((installFlags & PackageManager.INSTALL_FORCE_SDK) != 0);
boolean replace = false;
int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
@@ -16528,6 +16590,12 @@
if ((installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0) {
scanFlags |= SCAN_DONT_KILL_APP;
}
+ if (instantApp) {
+ scanFlags |= SCAN_AS_INSTANT_APP;
+ }
+ if (fullApp) {
+ scanFlags |= SCAN_AS_FULL_APP;
+ }
// Result object to be returned
res.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
@@ -16535,10 +16603,10 @@
if (DEBUG_INSTALL) Slog.d(TAG, "installPackageLI: path=" + tmpPackageFile);
// Sanity check
- if (ephemeral && (forwardLocked || onExternal)) {
+ if (instantApp && (forwardLocked || onExternal)) {
Slog.i(TAG, "Incompatible ephemeral install; fwdLocked=" + forwardLocked
+ " external=" + onExternal);
- res.setReturnCode(PackageManager.INSTALL_FAILED_EPHEMERAL_INVALID);
+ res.setReturnCode(PackageManager.INSTALL_FAILED_INSTANT_APP_INVALID);
return;
}
@@ -16547,7 +16615,7 @@
| PackageParser.PARSE_ENFORCE_CODE
| (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
| (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0)
- | (ephemeral ? PackageParser.PARSE_IS_EPHEMERAL : 0)
+ | (instantApp ? PackageParser.PARSE_IS_EPHEMERAL : 0)
| (forceSdk ? PackageParser.PARSE_FORCE_SDK : 0);
PackageParser pp = new PackageParser();
pp.setSeparateProcesses(mSeparateProcesses);
@@ -16566,7 +16634,7 @@
// // Ephemeral apps must have target SDK >= O.
// // TODO: Update conditional and error message when O gets locked down
-// if (ephemeral && pkg.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.N_MR1) {
+// if (instantApp && pkg.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.N_MR1) {
// res.setError(PackageManager.INSTALL_FAILED_EPHEMERAL_INVALID,
// "Ephemeral apps must have target SDK version of at least O");
// return;
@@ -16809,10 +16877,10 @@
res.setError(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
"Cannot install updates to system apps on sdcard");
return;
- } else if (ephemeral) {
- // Abort update; system app can't be replaced with an ephemeral app
- res.setError(INSTALL_FAILED_EPHEMERAL_INVALID,
- "Cannot update a system app with an ephemeral app");
+ } else if (instantApp) {
+ // Abort update; system app can't be replaced with an instant app
+ res.setError(INSTALL_FAILED_INSTANT_APP_INVALID,
+ "Cannot update a system app with an instant app");
return;
}
}
@@ -17059,14 +17127,6 @@
return (ps.pkgFlags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
}
- private static boolean isEphemeral(PackageParser.Package pkg) {
- return pkg.applicationInfo.isInstantApp();
- }
-
- private static boolean isEphemeral(PackageSetting ps) {
- return ps.pkg != null && isEphemeral(ps.pkg);
- }
-
private static boolean isSystemApp(PackageParser.Package pkg) {
return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
}
@@ -17089,9 +17149,6 @@
private int packageFlagsToInstallFlags(PackageSetting ps) {
int installFlags = 0;
- if (isEphemeral(ps)) {
- installFlags |= PackageManager.INSTALL_EPHEMERAL;
- }
if (isExternal(ps) && TextUtils.isEmpty(ps.volumeUuid)) {
// This existing package was an external ASEC install when we have
// the external flag without a UUID
@@ -17493,7 +17550,7 @@
try (PackageFreezer freezer = freezePackageForDelete(packageName, freezeUser,
deleteFlags, "deletePackageX")) {
res = deletePackageLIF(packageName, UserHandle.of(removeUser), true, allUsers,
- deleteFlags | REMOVE_CHATTY, info, true, null);
+ deleteFlags | FLAGS_REMOVE_CHATTY, info, true, null);
}
synchronized (mPackages) {
if (res) {
@@ -17643,7 +17700,7 @@
}
}
- removePackageLI(ps, (flags & REMOVE_CHATTY) != 0);
+ removePackageLI(ps, (flags & FLAGS_REMOVE_CHATTY) != 0);
if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
final PackageParser.Package resolvedPkg;
@@ -18202,11 +18259,18 @@
Slog.d(TAG, "Marking package:" + ps.name + " uninstalled for user:" + nextUserId);
}
ps.setUserState(nextUserId, 0, COMPONENT_ENABLED_STATE_DEFAULT,
- false /*installed*/, true /*stopped*/, true /*notLaunched*/,
- false /*hidden*/, false /*suspended*/, null, null, null,
+ false /*installed*/,
+ true /*stopped*/,
+ true /*notLaunched*/,
+ false /*hidden*/,
+ false /*suspended*/,
+ false /*instantApp*/,
+ null /*lastDisableAppCaller*/,
+ null /*enabledComponents*/,
+ null /*disabledComponents*/,
false /*blockUninstall*/,
- ps.readUserState(nextUserId).domainVerificationStatus, 0,
- PackageManager.INSTALL_REASON_UNKNOWN);
+ ps.readUserState(nextUserId).domainVerificationStatus,
+ 0, PackageManager.INSTALL_REASON_UNKNOWN);
}
mSettings.writeKernelMappingLPr(ps);
}
@@ -21688,12 +21752,12 @@
final ApplicationInfo app = pkg.applicationInfo;
final int appId = UserHandle.getAppId(app.uid);
- Preconditions.checkNotNull(app.seinfo);
+ Preconditions.checkNotNull(app.seInfo);
long ceDataInode = -1;
try {
ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId, flags,
- appId, app.seinfo, app.targetSdkVersion);
+ appId, app.seInfo, app.targetSdkVersion);
} catch (InstallerException e) {
if (app.isSystemApp()) {
logCriticalInfo(Log.ERROR, "Failed to create app data for " + packageName
@@ -21701,7 +21765,7 @@
destroyAppDataLeafLIF(pkg, userId, flags);
try {
ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId, flags,
- appId, app.seinfo, app.targetSdkVersion);
+ appId, app.seInfo, app.targetSdkVersion);
logCriticalInfo(Log.DEBUG, "Recovery succeeded!");
} catch (InstallerException e2) {
logCriticalInfo(Log.DEBUG, "Recovery failed!");
@@ -22003,7 +22067,7 @@
installerPackageName = ps.installerPackageName;
packageAbiOverride = ps.cpuAbiOverrideString;
appId = UserHandle.getAppId(pkg.applicationInfo.uid);
- seinfo = pkg.applicationInfo.seinfo;
+ seinfo = pkg.applicationInfo.seInfo;
label = String.valueOf(pm.getApplicationLabel(pkg.applicationInfo));
targetSdkVersion = pkg.applicationInfo.targetSdkVersion;
freezer = freezePackage(packageName, "movePackageInternal");
@@ -22812,8 +22876,8 @@
@Override
public boolean isPackageEphemeral(int userId, String packageName) {
synchronized (mPackages) {
- PackageParser.Package p = mPackages.get(packageName);
- return p != null ? p.applicationInfo.isInstantApp() : false;
+ final PackageSetting ps = mSettings.mPackages.get(packageName);
+ return ps != null ? ps.getInstantApp(userId) : false;
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 1203e4d..a7349fc 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -39,6 +39,7 @@
import android.content.pm.PermissionInfo;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.VersionedPackage;
import android.content.res.AssetManager;
@@ -116,6 +117,8 @@
return runInstallRemove();
case "install-write":
return runInstallWrite();
+ case "install-existing":
+ return runInstallExisting();
case "compile":
return runCompile();
case "reconcile-secondary-dex-files":
@@ -301,6 +304,51 @@
return doRemoveSplit(sessionId, splitName, true /*logSuccess*/);
}
+ private int runInstallExisting() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ int userId = UserHandle.USER_SYSTEM;
+ int installFlags = 0;
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "--user":
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ break;
+ case "--ephemeral":
+ case "--instant":
+ installFlags |= PackageManager.INSTALL_INSTANT_APP;
+ installFlags &= ~PackageManager.INSTALL_FULL_APP;
+ break;
+ case "--full":
+ installFlags &= ~PackageManager.INSTALL_INSTANT_APP;
+ installFlags |= PackageManager.INSTALL_FULL_APP;
+ break;
+ default:
+ pw.println("Error: Unknown option: " + opt);
+ return 1;
+ }
+ }
+
+ final String packageName = getNextArg();
+ if (packageName == null) {
+ pw.println("Error: package name not specified");
+ return 1;
+ }
+
+ try {
+ final int res = mInterface.installExistingPackageAsUser(packageName, userId,
+ installFlags, PackageManager.INSTALL_REASON_UNKNOWN);
+ if (res == PackageManager.INSTALL_FAILED_INVALID_URI) {
+ throw new NameNotFoundException("Package " + packageName + " doesn't exist");
+ }
+ pw.println("Package " + packageName + " installed for user: " + userId);
+ return 0;
+ } catch (RemoteException | NameNotFoundException e) {
+ pw.println(e.toString());
+ return 1;
+ }
+ }
+
private int runCompile() throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
boolean checkProfiles = SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
@@ -1145,8 +1193,12 @@
sessionParams.abiOverride = checkAbiArgument(getNextArg());
break;
case "--ephemeral":
+ case "--instantapp":
sessionParams.setInstallAsInstantApp(true /*isInstantApp*/);
break;
+ case "--full":
+ sessionParams.setInstallAsInstantApp(false /*isInstantApp*/);
+ break;
case "--user":
params.userId = UserHandle.parseUserArg(getNextArgRequired());
break;
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 0e11b0c..601377d6 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -397,11 +397,19 @@
modifyUserState(userId).blockUninstall = blockUninstall;
}
+ boolean getInstantApp(int userId) {
+ return readUserState(userId).instantApp;
+ }
+
+ void setInstantApp(boolean instantApp, int userId) {
+ modifyUserState(userId).instantApp = instantApp;
+ }
+
void setUserState(int userId, long ceDataInode, int enabled, boolean installed, boolean stopped,
- boolean notLaunched, boolean hidden, boolean suspended,
+ boolean notLaunched, boolean hidden, boolean suspended, boolean instantApp,
String lastDisableAppCaller, ArraySet<String> enabledComponents,
- ArraySet<String> disabledComponents, boolean blockUninstall, int domainVerifState,
- int linkGeneration, int installReason) {
+ ArraySet<String> disabledComponents, boolean blockUninstall,
+ int domainVerifState, int linkGeneration, int installReason) {
PackageUserState state = modifyUserState(userId);
state.ceDataInode = ceDataInode;
state.enabled = enabled;
@@ -417,6 +425,7 @@
state.domainVerificationStatus = domainVerifState;
state.appLinkGeneration = linkGeneration;
state.installReason = installReason;
+ state.instantApp = instantApp;
}
ArraySet<String> getEnabledComponents(int userId) {
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index 7e7de21..188e66f 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -17,6 +17,8 @@
package com.android.server.pm;
import android.content.pm.PackageParser;
+import android.content.pm.PackageUserState;
+import android.content.pm.SELinuxUtil;
import android.content.pm.Signature;
import android.os.Environment;
import android.util.Slog;
@@ -69,9 +71,6 @@
// Append v2 to existing seinfo label
private static final String SANDBOX_V2_STR = ":v2";
- // Append ephemeral to existing seinfo label
- private static final String EPHEMERAL_APP_STR = ":ephemeralapp";
-
// Append targetSdkVersion=n to existing seinfo label where n is the app's targetSdkVersion
private static final String TARGETSDKVERSION_STR = ":targetSdkVersion=";
@@ -279,31 +278,28 @@
*
* @param pkg object representing the package to be labeled.
*/
- public static void assignSeinfoValue(PackageParser.Package pkg) {
+ public static void assignSeInfoValue(PackageParser.Package pkg) {
synchronized (sPolicies) {
for (Policy policy : sPolicies) {
- String seinfo = policy.getMatchedSeinfo(pkg);
- if (seinfo != null) {
- pkg.applicationInfo.seinfo = seinfo;
+ String seInfo = policy.getMatchedSeInfo(pkg);
+ if (seInfo != null) {
+ pkg.applicationInfo.seInfo = seInfo;
break;
}
}
}
- if (pkg.applicationInfo.isInstantApp())
- pkg.applicationInfo.seinfo += EPHEMERAL_APP_STR;
-
if (pkg.applicationInfo.targetSandboxVersion == 2)
- pkg.applicationInfo.seinfo += SANDBOX_V2_STR;
+ pkg.applicationInfo.seInfo += SANDBOX_V2_STR;
if (pkg.applicationInfo.isPrivilegedApp())
- pkg.applicationInfo.seinfo += PRIVILEGED_APP_STR;
+ pkg.applicationInfo.seInfo += PRIVILEGED_APP_STR;
- pkg.applicationInfo.seinfo += TARGETSDKVERSION_STR + pkg.applicationInfo.targetSdkVersion;
+ pkg.applicationInfo.seInfo += TARGETSDKVERSION_STR + pkg.applicationInfo.targetSdkVersion;
if (DEBUG_POLICY_INSTALL) {
Slog.i(TAG, "package (" + pkg.packageName + ") labeled with " +
- "seinfo=" + pkg.applicationInfo.seinfo);
+ "seinfo=" + pkg.applicationInfo.seInfo);
}
}
}
@@ -438,7 +434,7 @@
* @return A string representing the seinfo matched during policy lookup.
* A value of null can also be returned if no match occured.
*/
- public String getMatchedSeinfo(PackageParser.Package pkg) {
+ public String getMatchedSeInfo(PackageParser.Package pkg) {
// Check for exact signature matches across all certs.
Signature[] certs = mCerts.toArray(new Signature[0]);
if (!Signature.areExactMatch(certs, pkg.mSignatures)) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 6156802..a8a5ff0 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -219,6 +219,7 @@
private static final String ATTR_DOMAIN_VERIFICATON_STATE = "domainVerificationStatus";
private static final String ATTR_APP_LINK_GENERATION = "app-link-generation";
private static final String ATTR_INSTALL_REASON = "install-reason";
+ private static final String ATTR_INSTANT_APP = "instant-app";
private static final String ATTR_PACKAGE_NAME = "packageName";
private static final String ATTR_FINGERPRINT = "fingerprint";
@@ -687,7 +688,7 @@
PackageSetting disabledPkg, String realPkgName, SharedUserSetting sharedUser,
File codePath, File resourcePath, String legacyNativeLibraryPath, String primaryCpuAbi,
String secondaryCpuAbi, int versionCode, int pkgFlags, int pkgPrivateFlags,
- UserHandle installUser, boolean allowInstall, String parentPkgName,
+ UserHandle installUser, boolean allowInstall, boolean instantApp, String parentPkgName,
List<String> childPkgNames, UserManagerService userManager,
String[] usesStaticLibraries, int[] usesStaticLibrariesVersions) {
final PackageSetting pkgSetting;
@@ -745,14 +746,17 @@
|| installUserId == user.id;
pkgSetting.setUserState(user.id, 0, COMPONENT_ENABLED_STATE_DEFAULT,
installed,
- true, // stopped,
- true, // notLaunched
- false, // hidden
- false, // suspended
- null, null, null,
- false, // blockUninstall
- INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED, 0,
- PackageManager.INSTALL_REASON_UNKNOWN);
+ true /*stopped*/,
+ true /*notLaunched*/,
+ false /*hidden*/,
+ false /*suspended*/,
+ instantApp,
+ null /*lastDisableAppCaller*/,
+ null /*enabledComponents*/,
+ null /*disabledComponents*/,
+ false /*blockUninstall*/,
+ INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED,
+ 0, PackageManager.INSTALL_REASON_UNKNOWN);
}
}
}
@@ -1643,15 +1647,18 @@
// consider all applications to be installed.
for (PackageSetting pkg : mPackages.values()) {
pkg.setUserState(userId, 0, COMPONENT_ENABLED_STATE_DEFAULT,
- true, // installed
- false, // stopped
- false, // notLaunched
- false, // hidden
- false, // suspended
- null, null, null,
- false, // blockUninstall
- INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED, 0,
- PackageManager.INSTALL_REASON_UNKNOWN);
+ true /*installed*/,
+ false /*stopped*/,
+ false /*notLaunched*/,
+ false /*hidden*/,
+ false /*suspended*/,
+ false /*instantApp*/,
+ null /*lastDisableAppCaller*/,
+ null /*enabledComponents*/,
+ null /*disabledComponents*/,
+ false /*blockUninstall*/,
+ INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED,
+ 0, PackageManager.INSTALL_REASON_UNKNOWN);
}
return;
}
@@ -1718,6 +1725,8 @@
false);
final boolean blockUninstall = XmlUtils.readBooleanAttribute(parser,
ATTR_BLOCK_UNINSTALL, false);
+ final boolean instantApp = XmlUtils.readBooleanAttribute(parser,
+ ATTR_INSTANT_APP, false);
final int enabled = XmlUtils.readIntAttribute(parser, ATTR_ENABLED,
COMPONENT_ENABLED_STATE_DEFAULT);
final String enabledCaller = parser.getAttributeValue(null,
@@ -1754,8 +1763,9 @@
}
ps.setUserState(userId, ceDataInode, enabled, installed, stopped, notLaunched,
- hidden, suspended, enabledCaller, enabledComponents, disabledComponents,
- blockUninstall, verifState, linkGeneration, installReason);
+ hidden, suspended, instantApp, enabledCaller, enabledComponents,
+ disabledComponents, blockUninstall, verifState, linkGeneration,
+ installReason);
} else if (tagName.equals("preferred-activities")) {
readPreferredActivitiesLPw(parser, userId);
} else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) {
@@ -2025,6 +2035,9 @@
if (ustate.blockUninstall) {
serializer.attribute(null, ATTR_BLOCK_UNINSTALL, "true");
}
+ if (ustate.instantApp) {
+ serializer.attribute(null, ATTR_INSTANT_APP, "true");
+ }
if (ustate.enabled != COMPONENT_ENABLED_STATE_DEFAULT) {
serializer.attribute(null, ATTR_ENABLED,
Integer.toString(ustate.enabled));
@@ -2682,7 +2695,7 @@
sb.append(isDebug ? " 1 " : " 0 ");
sb.append(dataPath);
sb.append(" ");
- sb.append(ai.seinfo);
+ sb.append(ai.seInfo);
sb.append(" ");
if (gids != null && gids.length > 0) {
sb.append(gids[0]);
@@ -4140,7 +4153,7 @@
volumeUuids[i] = ps.volumeUuid;
names[i] = ps.name;
appIds[i] = ps.appId;
- seinfos[i] = ps.pkg.applicationInfo.seinfo;
+ seinfos[i] = ps.pkg.applicationInfo.seInfo;
targetSdkVersions[i] = ps.pkg.applicationInfo.targetSdkVersion;
}
}
@@ -4429,7 +4442,7 @@
ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE, "DEFAULT_TO_DEVICE_PROTECTED_STORAGE",
ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE, "DIRECT_BOOT_AWARE",
ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE, "PARTIALLY_DIRECT_BOOT_AWARE",
- ApplicationInfo.PRIVATE_FLAG_EPHEMERAL, "EPHEMERAL",
+ ApplicationInfo.PRIVATE_FLAG_INSTANT, "EPHEMERAL",
ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER, "REQUIRED_FOR_SYSTEM_USER",
ApplicationInfo.PRIVATE_FLAG_RESIZEABLE_ACTIVITIES_EXPLICITLY_SET, "RESIZEABLE_ACTIVITIES_EXPLICITLY_SET",
ApplicationInfo.PRIVATE_FLAG_RESIZEABLE_ACTIVITIES_VIA_SDK_VERSION, "RESIZEABLE_ACTIVITIES_VIA_SDK_VERSION",
@@ -4502,6 +4515,7 @@
pw.print(ps.getSuspended(user.id) ? "SU" : "su");
pw.print(ps.getStopped(user.id) ? "S" : "s");
pw.print(ps.getNotLaunched(user.id) ? "l" : "L");
+ pw.print(ps.getInstantApp(user.id) ? "IA" : "ia");
pw.print(",");
pw.print(ps.getEnabled(user.id));
String lastDisabledAppCaller = ps.getLastDisabledAppCaller(user.id);
@@ -4755,6 +4769,8 @@
pw.print(ps.getNotLaunched(user.id));
pw.print(" enabled=");
pw.println(ps.getEnabled(user.id));
+ pw.print(" instant=");
+ pw.println(ps.getInstantApp(user.id));
String lastDisabledAppCaller = ps.getLastDisabledAppCaller(user.id);
if (lastDisabledAppCaller != null) {
pw.print(prefix); pw.print(" lastDisabledCaller: ");
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 477bb7f..0d6cd80 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -236,6 +236,7 @@
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;
@@ -416,6 +417,9 @@
AppOpsManager mAppOpsManager;
private boolean mHasFeatureWatch;
+ // Assigned on main thread, accessed on UI thread
+ volatile VrManagerInternal mVrManagerInternal;
+
// Vibrator pattern for haptic feedback of a long press.
long[] mLongPressVibePattern;
@@ -503,6 +507,8 @@
volatile boolean mGoingToSleep;
volatile boolean mRecentsVisible;
volatile boolean mTvPictureInPictureVisible;
+ // Written by vr manager thread, only read in this class
+ volatile boolean mPersistentVrModeEnabled;
// Used to hold the last user key used to wake the device. This helps us prevent up events
// from being passed to the foregrounded app without a corresponding down event
@@ -982,6 +988,14 @@
}
MyOrientationListener mOrientationListener;
+ final PersistentVrStateListener mPersistentVrModeListener =
+ new PersistentVrStateListener() {
+ @Override
+ public void onPersistentVrStateChanged(boolean enabled) {
+ mPersistentVrModeEnabled = enabled;
+ }
+ };
+
private final StatusBarController mStatusBarController = new StatusBarController();
private final BarController mNavigationBarController = new BarController("NavigationBar",
@@ -1914,24 +1928,36 @@
if (mStatusBar != null) {
requestTransientBars(mStatusBar);
}
+ if (mPersistentVrModeEnabled) {
+ exitPersistentVrMode();
+ }
}
@Override
public void onSwipeFromBottom() {
if (mNavigationBar != null && mNavigationBarPosition == NAV_BAR_BOTTOM) {
requestTransientBars(mNavigationBar);
}
+ if (mPersistentVrModeEnabled) {
+ exitPersistentVrMode();
+ }
}
@Override
public void onSwipeFromRight() {
if (mNavigationBar != null && mNavigationBarPosition == NAV_BAR_RIGHT) {
requestTransientBars(mNavigationBar);
}
+ if (mPersistentVrModeEnabled) {
+ exitPersistentVrMode();
+ }
}
@Override
public void onSwipeFromLeft() {
if (mNavigationBar != null && mNavigationBarPosition == NAV_BAR_LEFT) {
requestTransientBars(mNavigationBar);
}
+ if (mPersistentVrModeEnabled) {
+ exitPersistentVrMode();
+ }
}
@Override
public void onFling(int duration) {
@@ -2730,6 +2756,15 @@
if (DEBUG_SPLASH_SCREEN) Slog.d(TAG, "addSplashScreen " + packageName
+ ": nonLocalizedLabel=" + nonLocalizedLabel + " theme="
+ Integer.toHexString(theme));
+
+ // Obtain proper context to launch on the right display.
+ final Context displayContext = getDisplayContext(context, displayId);
+ if (displayContext == null) {
+ // Can't show splash screen on requested display, so skip showing at all.
+ return null;
+ }
+ context = displayContext;
+
if (theme != context.getThemeResId() || labelRes != 0) {
try {
context = context.createPackageContext(packageName, 0);
@@ -2814,14 +2849,7 @@
}
params.setTitle("Splash Screen " + packageName);
-
- // Obtain proper context to launch on the right display.
- final Context displayContext = getDisplayContext(context, displayId);
- if (displayContext == null) {
- // Can't show splash screen on requested display, so skip showing at all.
- return null;
- }
- wm = (WindowManager) displayContext.getSystemService(WINDOW_SERVICE);
+ wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);
view = win.getDecorView();
if (DEBUG_SPLASH_SCREEN) Slog.d(TAG, "Adding splash screen window for "
@@ -6487,11 +6515,17 @@
}
private void reportScreenStateToVrManager(boolean isScreenOn) {
- VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class);
- if (vrService == null) {
+ if (mVrManagerInternal == null) {
return;
}
- vrService.onScreenStateChanged(isScreenOn);
+ mVrManagerInternal.onScreenStateChanged(isScreenOn);
+ }
+
+ private void exitPersistentVrMode() {
+ if (mVrManagerInternal == null) {
+ return;
+ }
+ mVrManagerInternal.setPersistentVrModeEnabled(false);
}
private void finishWindowsDrawn() {
@@ -6980,6 +7014,11 @@
});
mKeyguardDelegate.onSystemReady();
+ mVrManagerInternal = LocalServices.getService(VrManagerInternal.class);
+ if (mVrManagerInternal != null) {
+ mVrManagerInternal.addPersistentVrModeStateListener(mPersistentVrModeListener);
+ }
+
readCameraLensCoverState();
updateUiMode();
boolean bindKeyguardNow;
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index ffbb428..b76a249 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -674,9 +674,6 @@
mAppOps, createSuspendBlockerLocked("PowerManagerService.Broadcasts"),
mPolicy);
- final ContentResolver resolver = mContext.getContentResolver();
- mConstants.start(resolver);
-
mWirelessChargerDetector = new WirelessChargerDetector(sensorManager,
createSuspendBlockerLocked("PowerManagerService.WirelessChargerDetector"),
mHandler);
@@ -689,57 +686,6 @@
mDisplayManagerInternal.initPowerManagement(
mDisplayPowerCallbacks, mHandler, sensorManager);
- // Register for settings changes.
- resolver.registerContentObserver(Settings.Secure.getUriFor(
- Settings.Secure.SCREENSAVER_ENABLED),
- false, mSettingsObserver, UserHandle.USER_ALL);
- resolver.registerContentObserver(Settings.Secure.getUriFor(
- Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP),
- false, mSettingsObserver, UserHandle.USER_ALL);
- resolver.registerContentObserver(Settings.Secure.getUriFor(
- Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK),
- false, mSettingsObserver, UserHandle.USER_ALL);
- resolver.registerContentObserver(Settings.System.getUriFor(
- Settings.System.SCREEN_OFF_TIMEOUT),
- false, mSettingsObserver, UserHandle.USER_ALL);
- resolver.registerContentObserver(Settings.Secure.getUriFor(
- Settings.Secure.SLEEP_TIMEOUT),
- false, mSettingsObserver, UserHandle.USER_ALL);
- resolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.STAY_ON_WHILE_PLUGGED_IN),
- false, mSettingsObserver, UserHandle.USER_ALL);
- resolver.registerContentObserver(Settings.System.getUriFor(
- Settings.System.SCREEN_BRIGHTNESS),
- false, mSettingsObserver, UserHandle.USER_ALL);
- resolver.registerContentObserver(Settings.System.getUriFor(
- Settings.System.SCREEN_BRIGHTNESS_FOR_VR),
- false, mSettingsObserver, UserHandle.USER_ALL);
- resolver.registerContentObserver(Settings.System.getUriFor(
- Settings.System.SCREEN_BRIGHTNESS_MODE),
- false, mSettingsObserver, UserHandle.USER_ALL);
- resolver.registerContentObserver(Settings.System.getUriFor(
- Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ),
- false, mSettingsObserver, UserHandle.USER_ALL);
- resolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.LOW_POWER_MODE),
- false, mSettingsObserver, UserHandle.USER_ALL);
- resolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL),
- false, mSettingsObserver, UserHandle.USER_ALL);
- resolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.THEATER_MODE_ON),
- false, mSettingsObserver, UserHandle.USER_ALL);
- resolver.registerContentObserver(Settings.Secure.getUriFor(
- Settings.Secure.DOUBLE_TAP_TO_WAKE),
- false, mSettingsObserver, UserHandle.USER_ALL);
- IVrManager vrManager = (IVrManager) getBinderService(Context.VR_SERVICE);
- if (vrManager != null) {
- try {
- vrManager.registerListener(mVrStateCallbacks);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to register VR mode state listener: " + e);
- }
- }
// Go.
readConfigurationLocked();
updateSettingsLocked();
@@ -747,6 +693,61 @@
updatePowerStateLocked();
}
+ final ContentResolver resolver = mContext.getContentResolver();
+ mConstants.start(resolver);
+
+ // Register for settings changes.
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.SCREENSAVER_ENABLED),
+ false, mSettingsObserver, UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP),
+ false, mSettingsObserver, UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK),
+ false, mSettingsObserver, UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.SCREEN_OFF_TIMEOUT),
+ false, mSettingsObserver, UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.SLEEP_TIMEOUT),
+ false, mSettingsObserver, UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.STAY_ON_WHILE_PLUGGED_IN),
+ false, mSettingsObserver, UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.SCREEN_BRIGHTNESS),
+ false, mSettingsObserver, UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.SCREEN_BRIGHTNESS_FOR_VR),
+ false, mSettingsObserver, UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.SCREEN_BRIGHTNESS_MODE),
+ false, mSettingsObserver, UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ),
+ false, mSettingsObserver, UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.LOW_POWER_MODE),
+ false, mSettingsObserver, UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL),
+ false, mSettingsObserver, UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.THEATER_MODE_ON),
+ false, mSettingsObserver, UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.DOUBLE_TAP_TO_WAKE),
+ false, mSettingsObserver, UserHandle.USER_ALL);
+ IVrManager vrManager = (IVrManager) getBinderService(Context.VR_SERVICE);
+ if (vrManager != null) {
+ try {
+ vrManager.registerListener(mVrStateCallbacks);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to register VR mode state listener: " + e);
+ }
+ }
+
// Register for broadcasts from other components of the system.
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
diff --git a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
new file mode 100644
index 0000000..10d30aa
--- /dev/null
+++ b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
@@ -0,0 +1,226 @@
+/*
+ * 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.storage;
+
+import android.annotation.MainThread;
+import android.app.usage.CacheQuotaHint;
+import android.app.usage.CacheQuotaService;
+import android.app.usage.ICacheQuotaService;
+import android.app.usage.UsageStats;
+import android.app.usage.UsageStatsManager;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.format.DateUtils;
+import android.util.Slog;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.pm.Installer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * CacheQuotaStrategy is a strategy for determining cache quotas using usage stats and foreground
+ * time using the calculation as defined in the refuel rocket.
+ */
+public class CacheQuotaStrategy implements RemoteCallback.OnResultListener {
+ private static final String TAG = "CacheQuotaStrategy";
+
+ private final Object mLock = new Object();
+
+ private final Context mContext;
+ private final UsageStatsManagerInternal mUsageStats;
+ private final Installer mInstaller;
+ private ServiceConnection mServiceConnection;
+ private ICacheQuotaService mRemoteService;
+
+ public CacheQuotaStrategy(
+ Context context, UsageStatsManagerInternal usageStatsManager, Installer installer) {
+ mContext = Preconditions.checkNotNull(context);
+ mUsageStats = Preconditions.checkNotNull(usageStatsManager);
+ mInstaller = Preconditions.checkNotNull(installer);
+ }
+
+ /**
+ * Recalculates the quotas and stores them to installd.
+ */
+ public void recalculateQuotas() {
+ createServiceConnection();
+
+ ComponentName component = getServiceComponentName();
+ if (component != null) {
+ Intent intent = new Intent();
+ intent.setComponent(component);
+ mContext.bindServiceAsUser(
+ intent, mServiceConnection, Context.BIND_AUTO_CREATE, UserHandle.CURRENT);
+ }
+ }
+
+ private void createServiceConnection() {
+ // If we're already connected, don't create a new connection.
+ if (mServiceConnection != null) {
+ return;
+ }
+
+ mServiceConnection = new ServiceConnection() {
+ @Override
+ @MainThread
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ mRemoteService = ICacheQuotaService.Stub.asInterface(service);
+ List<CacheQuotaHint> requests = getUnfulfilledRequests();
+ final RemoteCallback remoteCallback =
+ new RemoteCallback(CacheQuotaStrategy.this);
+ try {
+ mRemoteService.computeCacheQuotaHints(remoteCallback, requests);
+ } catch (RemoteException ex) {
+ Slog.w(TAG,
+ "Remote exception occurred while trying to get cache quota",
+ ex);
+ }
+ }
+ }
+ };
+ AsyncTask.execute(runnable);
+ }
+
+ @Override
+ @MainThread
+ public void onServiceDisconnected(ComponentName name) {
+ synchronized (mLock) {
+ mRemoteService = null;
+ }
+ }
+ };
+ }
+
+ /**
+ * Returns a list of CacheQuotaRequests which do not have their quotas filled out for apps
+ * which have been used in the last year.
+ */
+ private List<CacheQuotaHint> getUnfulfilledRequests() {
+ long timeNow = System.currentTimeMillis();
+ long oneYearAgo = timeNow - DateUtils.YEAR_IN_MILLIS;
+
+ List<CacheQuotaHint> requests = new ArrayList<>();
+ UserManager um = mContext.getSystemService(UserManager.class);
+ final List<UserInfo> users = um.getUsers();
+ final int userCount = users.size();
+ final PackageManager packageManager = mContext.getPackageManager();
+ for (int i = 0; i < userCount; i++) {
+ UserInfo info = users.get(i);
+ List<UsageStats> stats =
+ mUsageStats.queryUsageStatsForUser(info.id, UsageStatsManager.INTERVAL_BEST,
+ oneYearAgo, timeNow);
+ if (stats == null) {
+ continue;
+ }
+
+ for (UsageStats stat : stats) {
+ String packageName = stat.getPackageName();
+ try {
+ // We need the app info to determine the uid and the uuid of the volume
+ // where the app is installed.
+ ApplicationInfo appInfo = packageManager.getApplicationInfo(packageName, 0);
+ requests.add(
+ new CacheQuotaHint.Builder()
+ .setVolumeUuid(appInfo.volumeUuid)
+ .setUid(appInfo.uid)
+ .setUsageStats(stat)
+ .setQuota(CacheQuotaHint.QUOTA_NOT_SET)
+ .build());
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Unable to find package for quota calculation", e);
+ continue;
+ }
+ }
+ }
+ return requests;
+ }
+
+ @Override
+ public void onResult(Bundle data) {
+ final List<CacheQuotaHint> processedRequests =
+ data.getParcelableArrayList(
+ CacheQuotaService.REQUEST_LIST_KEY);
+ final int requestSize = processedRequests.size();
+ for (int i = 0; i < requestSize; i++) {
+ CacheQuotaHint request = processedRequests.get(i);
+ long proposedQuota = request.getQuota();
+ if (proposedQuota == CacheQuotaHint.QUOTA_NOT_SET) {
+ continue;
+ }
+
+ try {
+ int uid = request.getUid();
+ mInstaller.setAppQuota(request.getVolumeUuid(),
+ UserHandle.getUserId(uid),
+ UserHandle.getAppId(uid), proposedQuota);
+ } catch (Installer.InstallerException ex) {
+ Slog.w(TAG,
+ "Failed to set cache quota for " + request.getUid(),
+ ex);
+ }
+ }
+
+ disconnectService();
+ }
+
+ private void disconnectService() {
+ mContext.unbindService(mServiceConnection);
+ mServiceConnection = null;
+ }
+
+ private ComponentName getServiceComponentName() {
+ String packageName =
+ mContext.getPackageManager().getServicesSystemSharedLibraryPackageName();
+ if (packageName == null) {
+ Slog.w(TAG, "could not access the cache quota service: no package!");
+ return null;
+ }
+
+ Intent intent = new Intent(CacheQuotaService.SERVICE_INTERFACE);
+ intent.setPackage(packageName);
+ ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+ if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+ Slog.w(TAG, "No valid components found.");
+ return null;
+ }
+ ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+ return new ComponentName(serviceInfo.packageName, serviceInfo.name);
+ }
+}
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index 6c1648c..996a3d2 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -16,6 +16,7 @@
package com.android.server.trust;
+import android.annotation.TargetApi;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
@@ -27,6 +28,7 @@
import android.content.ServiceConnection;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -35,11 +37,11 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.util.Log;
-import android.util.Slog;
import android.service.trust.ITrustAgentService;
import android.service.trust.ITrustAgentServiceCallback;
-
+import android.service.trust.TrustAgentService;
+import android.util.Log;
+import android.util.Slog;
import java.util.Collections;
import java.util.List;
@@ -47,6 +49,7 @@
* A wrapper around a TrustAgentService interface. Coordinates communication between
* TrustManager and the actual TrustAgent.
*/
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class TrustAgentWrapper {
private static final String EXTRA_COMPONENT_NAME = "componentName";
private static final String TRUST_EXPIRED_ACTION = "android.server.trust.TRUST_EXPIRED_ACTION";
@@ -60,6 +63,10 @@
private static final int MSG_RESTART_TIMEOUT = 4;
private static final int MSG_SET_TRUST_AGENT_FEATURES_COMPLETED = 5;
private static final int MSG_MANAGING_TRUST = 6;
+ private static final int MSG_ADD_ESCROW_TOKEN = 7;
+ private static final int MSG_REMOVE_ESCROW_TOKEN = 8;
+ private static final int MSG_ESCROW_TOKEN_STATE = 9;
+ private static final int MSG_UNLOCK_USER = 10;
/**
* Time in uptime millis that we wait for the service connection, both when starting
@@ -71,6 +78,9 @@
* Long extra for {@link #MSG_GRANT_TRUST}
*/
private static final String DATA_DURATION = "duration";
+ private static final String DATA_ESCROW_TOKEN = "escrow_token";
+ private static final String DATA_HANDLE = "handle";
+ private static final String DATA_USER_ID = "user_id";
private final TrustManagerService mTrustManagerService;
private final int mUserId;
@@ -190,6 +200,49 @@
mTrustManagerService.mArchive.logManagingTrust(mUserId, mName, mManagingTrust);
mTrustManagerService.updateTrust(mUserId, 0);
break;
+ case MSG_ADD_ESCROW_TOKEN: {
+ byte[] eToken = msg.getData().getByteArray(DATA_ESCROW_TOKEN);
+ int userId = msg.getData().getInt(DATA_USER_ID);
+ long handle = mTrustManagerService.addEscrowToken(eToken, userId);
+ try {
+ mTrustAgentService.onEscrowTokenAdded(
+ eToken, handle, UserHandle.of(userId));
+ } catch (RemoteException e) {
+ onError(e);
+ }
+ break;
+ }
+ case MSG_ESCROW_TOKEN_STATE: {
+ long handle = msg.getData().getLong(DATA_HANDLE);
+ int userId = msg.getData().getInt(DATA_USER_ID);
+ boolean active = mTrustManagerService.isEscrowTokenActive(handle, userId);
+ try {
+ mTrustAgentService.onTokenStateReceived(handle,
+ active ? TrustAgentService.TOKEN_STATE_ACTIVE
+ : TrustAgentService.TOKEN_STATE_INACTIVE);
+ } catch (RemoteException e) {
+ onError(e);
+ }
+ break;
+ }
+ case MSG_REMOVE_ESCROW_TOKEN: {
+ long handle = msg.getData().getLong(DATA_HANDLE);
+ int userId = msg.getData().getInt(DATA_USER_ID);
+ boolean success = mTrustManagerService.removeEscrowToken(handle, userId);
+ try {
+ mTrustAgentService.onEscrowTokenRemoved(handle, success);
+ } catch (RemoteException e) {
+ onError(e);
+ }
+ break;
+ }
+ case MSG_UNLOCK_USER: {
+ long handle = msg.getData().getLong(DATA_HANDLE);
+ int userId = msg.getData().getInt(DATA_USER_ID);
+ byte[] eToken = msg.getData().getByteArray(DATA_ESCROW_TOKEN);
+ mTrustManagerService.unlockUserWithToken(handle, eToken, userId);
+ break;
+ }
}
}
};
@@ -225,6 +278,67 @@
mHandler.obtainMessage(MSG_SET_TRUST_AGENT_FEATURES_COMPLETED,
result ? 1 : 0, 0, token).sendToTarget();
}
+
+ @Override
+ public void addEscrowToken(byte[] token, int userId) {
+ if (mContext.getResources()
+ .getBoolean(com.android.internal.R.bool.config_allowEscrowTokenForTrustAgent)) {
+ Slog.e(TAG, "Escrow token API is not allowed.");
+ return;
+ }
+
+ if (DEBUG) Slog.d(TAG, "adding escrow token for user " + userId);
+ Message msg = mHandler.obtainMessage(MSG_ADD_ESCROW_TOKEN);
+ msg.getData().putByteArray(DATA_ESCROW_TOKEN, token);
+ msg.getData().putInt(DATA_USER_ID, userId);
+ msg.sendToTarget();
+ }
+
+ @Override
+ public void isEscrowTokenActive(long handle, int userId) {
+ if (mContext.getResources()
+ .getBoolean(com.android.internal.R.bool.config_allowEscrowTokenForTrustAgent)) {
+ Slog.e(TAG, "Escrow token API is not allowed.");
+ return;
+ }
+
+ if (DEBUG) Slog.d(TAG, "checking the state of escrow token on user " + userId);
+ Message msg = mHandler.obtainMessage(MSG_ESCROW_TOKEN_STATE);
+ msg.getData().putLong(DATA_HANDLE, handle);
+ msg.getData().putInt(DATA_USER_ID, userId);
+ msg.sendToTarget();
+ }
+
+ @Override
+ public void removeEscrowToken(long handle, int userId) {
+ if (mContext.getResources()
+ .getBoolean(com.android.internal.R.bool.config_allowEscrowTokenForTrustAgent)) {
+ Slog.e(TAG, "Escrow token API is not allowed.");
+ return;
+ }
+
+ if (DEBUG) Slog.d(TAG, "removing escrow token on user " + userId);
+ Message msg = mHandler.obtainMessage(MSG_REMOVE_ESCROW_TOKEN);
+ msg.getData().putLong(DATA_HANDLE, handle);
+ msg.getData().putInt(DATA_USER_ID, userId);
+ msg.sendToTarget();
+ }
+
+ @Override
+ public void unlockUserWithToken(long handle, byte[] token, int userId) {
+ if (mContext.getResources()
+ .getBoolean(com.android.internal.R.bool.config_allowEscrowTokenForTrustAgent)) {
+ Slog.e(TAG, "Escrow token API is not allowed.");
+ return;
+ }
+
+ if (DEBUG) Slog.d(TAG, "unlocking user " + userId);
+ Message msg = mHandler.obtainMessage(MSG_UNLOCK_USER);
+ msg.getData().putInt(DATA_USER_ID, userId);
+ msg.getData().putLong(DATA_HANDLE, handle);
+ msg.getData().putByteArray(DATA_ESCROW_TOKEN, token);
+ msg.sendToTarget();
+ }
};
private final ServiceConnection mConnection = new ServiceConnection() {
@@ -294,7 +408,7 @@
}
private void onError(Exception e) {
- Slog.w(TAG , "Remote Exception", e);
+ Slog.w(TAG , "Exception ", e);
}
private void onTrustTimeout() {
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 71b725e..4570b0d 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -16,14 +16,6 @@
package com.android.server.trust;
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.content.PackageMonitor;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.server.SystemService;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import android.Manifest;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -65,12 +57,17 @@
import android.util.Xml;
import android.view.IWindowManager;
import android.view.WindowManagerGlobal;
-
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.server.SystemService;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
/**
* Manages trust agents and trust listeners.
@@ -229,6 +226,22 @@
TRUST_USUALLY_MANAGED_FLUSH_DELAY);
}
+ public long addEscrowToken(byte[] token, int userId) {
+ return mLockPatternUtils.addEscrowToken(token, userId);
+ }
+
+ public boolean removeEscrowToken(long handle, int userId) {
+ return mLockPatternUtils.removeEscrowToken(handle, userId);
+ }
+
+ public boolean isEscrowTokenActive(long handle, int userId) {
+ return mLockPatternUtils.isEscrowTokenActive(handle, userId);
+ }
+
+ public void unlockUserWithToken(long handle, byte[] token, int userId) {
+ mLockPatternUtils.unlockUserWithToken(handle, token, userId);
+ }
+
void refreshAgentList(int userIdOrAll) {
if (DEBUG) Slog.d(TAG, "refreshAgentList(" + userIdOrAll + ")");
if (!mTrustAgentsCanRun) {
@@ -329,7 +342,7 @@
if (!StorageManager.isUserKeyUnlocked(userInfo.id)
&& !directUnlock) {
if (DEBUG) Slog.d(TAG, "refreshAgentList: skipping user " + userInfo.id
- + "'s trust agent " + name + ": FDE still locked and "
+ + "'s trust agent " + name + ": FBE still locked and "
+ " the agent cannot unlock user profile.");
continue;
}
diff --git a/services/core/java/com/android/server/vr/PersistentVrStateListener.java b/services/core/java/com/android/server/vr/PersistentVrStateListener.java
new file mode 100644
index 0000000..bccd5f1
--- /dev/null
+++ b/services/core/java/com/android/server/vr/PersistentVrStateListener.java
@@ -0,0 +1,31 @@
+/**
+ * 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 45b7baf..58e4bdc 100644
--- a/services/core/java/com/android/server/vr/VrManagerInternal.java
+++ b/services/core/java/com/android/server/vr/VrManagerInternal.java
@@ -88,4 +88,9 @@
* @param enabled true if the device should be placed in persistent VR mode.
*/
public abstract void setPersistentVrModeEnabled(boolean enabled);
+
+ /**
+ * Adds listener that reports state changes to persistent VR mode.
+ */
+ public abstract void addPersistentVrModeStateListener(PersistentVrStateListener listener);
}
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index f0ea527..21a4f74 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -122,6 +122,8 @@
private boolean mGuard;
private final RemoteCallbackList<IVrStateCallbacks> mRemoteCallbacks =
new RemoteCallbackList<>();
+ private final ArrayList<PersistentVrStateListener> mPersistentVrStateListeners =
+ new ArrayList<>();
private int mPreviousCoarseLocationMode = INVALID_APPOPS_MODE;
private int mPreviousManageOverlayMode = INVALID_APPOPS_MODE;
private VrState mPendingState;
@@ -132,6 +134,7 @@
private static final int MSG_VR_STATE_CHANGE = 0;
private static final int MSG_PENDING_VR_STATE_CHANGE = 1;
+ private static final int MSG_PERSISTENT_VR_MODE_STATE_CHANGE = 2;
/**
* Set whether VR mode may be enabled.
@@ -151,7 +154,7 @@
} else {
// Disable persistent mode when VR mode isn't allowed, allows an escape hatch to
// exit persistent VR mode when screen is turned off.
- mPersistentVrModeEnabled = false;
+ setPersistentModeAndNotifyListenersLocked(false);
// Set pending state to current state.
mPendingState = (mVrModeEnabled && mCurrentVrService != null)
@@ -213,6 +216,13 @@
}
}
} 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);
+ }
+ } break;
default :
throw new IllegalStateException("Unknown message type: " + msg.what);
}
@@ -424,6 +434,16 @@
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 *********");
}
@@ -471,6 +491,11 @@
public void setPersistentVrModeEnabled(boolean enabled) {
VrManagerService.this.setPersistentVrModeEnabled(enabled);
}
+
+ @Override
+ public void addPersistentVrModeStateListener(PersistentVrStateListener listener) {
+ VrManagerService.this.addPersistentVrModeStateListener(listener);
+ }
}
public VrManagerService(Context context) {
@@ -1013,9 +1038,8 @@
}
private void setPersistentVrModeEnabled(boolean enabled) {
- synchronized (mLock) {
- mPersistentVrModeEnabled = enabled;
-
+ synchronized(mLock) {
+ setPersistentModeAndNotifyListenersLocked(enabled);
// Disabling persistent mode when not showing a VR should disable the overall vr mode.
if (!enabled && mCurrentVrModeComponent == null) {
setVrMode(false, null, 0, null);
@@ -1023,6 +1047,22 @@
}
}
+ private void setPersistentModeAndNotifyListenersLocked(boolean enabled) {
+ if (mPersistentVrModeEnabled == enabled) {
+ return;
+ }
+ mPersistentVrModeEnabled = enabled;
+
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_PERSISTENT_VR_MODE_STATE_CHANGE,
+ (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);
diff --git a/services/core/java/com/android/server/wm/AlertWindowNotification.java b/services/core/java/com/android/server/wm/AlertWindowNotification.java
new file mode 100644
index 0000000..0d282ef
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AlertWindowNotification.java
@@ -0,0 +1,144 @@
+/*
+ * 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.wm;
+
+import static android.app.Notification.VISIBILITY_PRIVATE;
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
+import static android.content.Context.NOTIFICATION_SERVICE;
+import static android.content.Intent.EXTRA_PACKAGE_NAME;
+import static android.content.Intent.EXTRA_UID;
+import static com.android.server.wm.WindowManagerService.ACTION_REVOKE_SYSTEM_ALERT_WINDOW_PERMISSION;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import com.android.internal.R;
+
+/** Displays an ongoing notification for a process displaying an alert window */
+class AlertWindowNotification {
+ private static final String CHANNEL_PREFIX = "com.android.server.wm.AlertWindowNotification - ";
+ private static final int NOTIFICATION_ID = 0;
+
+ private static int sNextRequestCode = 0;
+ private final int mRequestCode;
+ private final WindowManagerService mService;
+ private String mNotificationTag;
+ private final NotificationManager mNotificationManager;
+ private final String mPackageName;
+ private final int mUid;
+ private boolean mCancelled;
+
+ AlertWindowNotification(WindowManagerService service, String packageName, int uid) {
+ mService = service;
+ mPackageName = packageName;
+ mUid = uid;
+ mNotificationManager =
+ (NotificationManager) mService.mContext.getSystemService(NOTIFICATION_SERVICE);
+ mNotificationTag = CHANNEL_PREFIX + mPackageName;
+ mRequestCode = sNextRequestCode++;
+
+ // We can't create/post the notification while the window manager lock is held since it will
+ // end up calling into activity manager. So, we post a message to do it later.
+ mService.mH.post(this::postNotification);
+ }
+
+ /** Cancels the notification */
+ void cancel() {
+ mNotificationManager.cancel(mNotificationTag, NOTIFICATION_ID);
+ mCancelled = true;
+ }
+
+ /** Don't call with the window manager lock held! */
+ private void postNotification() {
+ final Context context = mService.mContext;
+ final PackageManager pm = context.getPackageManager();
+ final ApplicationInfo aInfo = getApplicationInfo(pm, mPackageName);
+ final String appName = (aInfo != null)
+ ? pm.getApplicationLabel(aInfo).toString() : mPackageName;
+
+ createNotificationChannelIfNeeded(context, appName);
+
+ final String message = context.getString(R.string.alert_windows_notification_message);
+ final Notification.Builder builder = new Notification.Builder(context, mNotificationTag)
+ .setOngoing(true)
+ .setContentTitle(
+ context.getString(R.string.alert_windows_notification_title, appName))
+ .setContentText(message)
+ .setSmallIcon(R.drawable.alert_window_layer)
+ .setColor(context.getColor(R.color.system_notification_accent_color))
+ .setStyle(new Notification.BigTextStyle().bigText(message))
+ .setLocalOnly(true)
+ .addAction(getTurnOffAction(context, mPackageName, mUid));
+
+ if (aInfo != null) {
+ final Bitmap bitmap = ((BitmapDrawable) pm.getApplicationIcon(aInfo)).getBitmap();
+ builder.setLargeIcon(bitmap);
+ }
+
+ synchronized (mService.mWindowMap) {
+ if (mCancelled) {
+ // Notification was cancelled, so nothing more to do...
+ return;
+ }
+ mNotificationManager.notify(mNotificationTag, NOTIFICATION_ID, builder.build());
+ }
+ }
+
+ private Notification.Action getTurnOffAction(Context context, String packageName, int uid) {
+ final Intent intent = new Intent(ACTION_REVOKE_SYSTEM_ALERT_WINDOW_PERMISSION);
+ intent.putExtra(EXTRA_PACKAGE_NAME, packageName);
+ intent.putExtra(EXTRA_UID, uid);
+ // Calls into activity manager...
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, mRequestCode,
+ intent, FLAG_CANCEL_CURRENT);
+ return new Notification.Action.Builder(R.drawable.alert_window_layer,
+ context.getString(R.string.alert_windows_notification_turn_off_action),
+ pendingIntent).build();
+
+ }
+
+ private void createNotificationChannelIfNeeded(Context context, String appName) {
+ if (mNotificationManager.getNotificationChannel(mNotificationTag) != null) {
+ return;
+ }
+ final String nameChannel =
+ context.getString(R.string.alert_windows_notification_channel_name, appName);
+ final NotificationChannel channel =
+ new NotificationChannel(mNotificationTag, nameChannel, IMPORTANCE_MIN);
+ channel.enableLights(false);
+ channel.enableVibration(false);
+ mNotificationManager.createNotificationChannel(channel);
+ }
+
+
+ private ApplicationInfo getApplicationInfo(PackageManager pm, String packageName) {
+ try {
+ return pm.getApplicationInfo(packageName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java
index cf5cecda..01bd86d 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationController.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java
@@ -176,7 +176,7 @@
// we trigger any size changes, so it can swap surfaces
// in to appropriate modes, or do as it wishes otherwise.
if (!mReplacement) {
- mTarget.onAnimationStart();
+ mTarget.onAnimationStart(mMoveToFullScreen);
}
// Immediately update the task bounds if they have to become larger, but preserve
@@ -263,7 +263,7 @@
*/
boolean setPinnedStackSize(Rect bounds, Rect taskBounds);
- void onAnimationStart();
+ void onAnimationStart(boolean toFullscreen);
/**
* Callback for the target to inform it that the animation has ended, so it can do some
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 8f38be8..2c315445 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -919,10 +919,6 @@
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);
}
@@ -938,8 +934,10 @@
}
}
- updateBaseDisplayMetrics(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight,
- mDisplayInfo.logicalDensityDpi);
+ mBaseDisplayWidth = mInitialDisplayWidth = mDisplayInfo.logicalWidth;
+ mBaseDisplayHeight = mInitialDisplayHeight = mDisplayInfo.logicalHeight;
+ mBaseDisplayDensity = mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi;
+ mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight);
}
void getLogicalDisplayRect(Rect out) {
@@ -969,30 +967,6 @@
}
}
- /** If display metrics changed 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;
-
- boolean displayMetricsChanged
- = mBaseDisplayWidth != newWidth || mBaseDisplayHeight != newHeight;
- displayMetricsChanged |= mBaseDisplayDensity != mDisplayInfo.logicalDensityDpi;
-
- if (displayMetricsChanged) {
- updateBaseDisplayMetrics(newWidth, newHeight, mDisplayInfo.logicalDensityDpi);
- mService.reconfigureDisplayLocked(this);
- }
- }
-
- void updateBaseDisplayMetrics(int baseWidth, int baseHeight, int baseDensity) {
- mBaseDisplayWidth = mInitialDisplayWidth = baseWidth;
- mBaseDisplayHeight = mInitialDisplayHeight = baseHeight;
- mBaseDisplayDensity = mInitialDisplayDensity = baseDensity;
- mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight);
- }
-
void getContentRect(Rect out) {
out.set(mContentRect);
}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 37b8deb..dc4806a 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -631,14 +631,26 @@
return;
}
- if (mAddPipInputConsumerHandle
- && w.getStackId() == PINNED_STACK_ID
- && inputWindowHandle.layer <= pipInputConsumer.mWindowHandle.layer) {
- // Update the bounds of the Pip input consumer to match the Pinned stack
- w.getStack().getBounds(pipTouchableBounds);
- pipInputConsumer.mWindowHandle.touchableRegion.set(pipTouchableBounds);
- addInputWindowHandle(pipInputConsumer.mWindowHandle);
- mAddPipInputConsumerHandle = false;
+ final int flags = w.mAttrs.flags;
+ final int privateFlags = w.mAttrs.privateFlags;
+ final int type = w.mAttrs.type;
+ final boolean hasFocus = w == mInputFocus;
+ final boolean isVisible = w.isVisibleLw();
+
+ if (w.getStackId() == PINNED_STACK_ID) {
+ if (mAddPipInputConsumerHandle
+ && (inputWindowHandle.layer <= pipInputConsumer.mWindowHandle.layer)) {
+ // Update the bounds of the Pip input consumer to match the Pinned stack
+ w.getStack().getBounds(pipTouchableBounds);
+ pipInputConsumer.mWindowHandle.touchableRegion.set(pipTouchableBounds);
+ addInputWindowHandle(pipInputConsumer.mWindowHandle);
+ mAddPipInputConsumerHandle = false;
+ }
+ // TODO: Fix w.canReceiveTouchInput() to handle this case
+ if (!hasFocus) {
+ // Skip this pinned stack window if it does not have focus
+ return;
+ }
}
if (mAddInputConsumerHandle
@@ -655,12 +667,6 @@
}
}
- final int flags = w.mAttrs.flags;
- final int privateFlags = w.mAttrs.privateFlags;
- final int type = w.mAttrs.type;
-
- final boolean hasFocus = w == mInputFocus;
- final boolean isVisible = w.isVisibleLw();
if ((privateFlags & PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS) != 0) {
mDisableWallpaperTouchEvents = true;
}
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index 6a8417dc..cfeb198 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -233,7 +233,7 @@
* @return the movement bounds for the given {@param stackBounds} and the current state of the
* controller.
*/
- Rect getMovementBounds(Rect stackBounds) {
+ private Rect getMovementBounds(Rect stackBounds) {
return getMovementBounds(stackBounds, true /* adjustForIme */);
}
@@ -241,7 +241,7 @@
* @return the movement bounds for the given {@param stackBounds} and the current state of the
* controller.
*/
- Rect getMovementBounds(Rect stackBounds, boolean adjustForIme) {
+ private Rect getMovementBounds(Rect stackBounds, boolean adjustForIme) {
final Rect movementBounds = new Rect();
getInsetBounds(movementBounds);
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 782f9f2..5355f31 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -16,7 +16,10 @@
package com.android.server.wm;
+import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
@@ -54,6 +57,8 @@
import com.android.server.wm.WindowManagerService.H;
import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.Set;
/**
* This class represents an active client session. There is generally one
@@ -70,9 +75,15 @@
private final String mStringName;
SurfaceSession mSurfaceSession;
private int mNumWindow = 0;
- private int mNumOverlayWindow = 0;
+ // Set of visible application overlay window surfaces connected to this session.
+ private final Set<WindowSurfaceController> mAppOverlaySurfaces = new HashSet<>();
+ // Set of visible alert window surfaces connected to this session.
+ private final Set<WindowSurfaceController> mAlertWindowSurfaces = new HashSet<>();
+ final boolean mCanAddInternalSystemWindow;
+ private AlertWindowNotification mAlertWindowNotification;
private boolean mClientDead = false;
private float mLastReportedAnimatorScale;
+ private String mPackageName;
public Session(WindowManagerService service, IWindowSessionCallback callback,
IInputMethodClient client, IInputContext inputContext) {
@@ -82,6 +93,8 @@
mUid = Binder.getCallingUid();
mPid = Binder.getCallingPid();
mLastReportedAnimatorScale = service.getCurrentAnimatorScale();
+ mCanAddInternalSystemWindow = service.mContext.checkCallingPermission(
+ INTERNAL_SYSTEM_WINDOW) == PERMISSION_GRANTED;
StringBuilder sb = new StringBuilder();
sb.append("Session{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
@@ -544,7 +557,8 @@
}
}
- void windowAddedLocked(int type) {
+ void windowAddedLocked(String packageName) {
+ mPackageName = packageName;
if (mSurfaceSession == null) {
if (WindowManagerService.localLOGV) Slog.v(
TAG_WM, "First window added to " + this + ", creating SurfaceSession");
@@ -557,24 +571,57 @@
}
}
mNumWindow++;
- if (type == TYPE_APPLICATION_OVERLAY) {
- mNumOverlayWindow++;
- setHasOverlayUi(true);
- }
}
- void windowRemovedLocked(int type) {
+ void windowRemovedLocked() {
mNumWindow--;
- if (type == TYPE_APPLICATION_OVERLAY) {
- mNumOverlayWindow--;
- if (mNumOverlayWindow == 0) {
- setHasOverlayUi(false);
- } else if (mNumOverlayWindow < 0) {
- throw new IllegalStateException("mNumOverlayWindow=" + mNumOverlayWindow
- + " less than 0 for session=" + this);
+ killSessionLocked();
+ }
+
+
+ void onWindowSurfaceVisibilityChanged(WindowSurfaceController surfaceController,
+ boolean visible, int type) {
+
+ if (!isSystemAlertWindowType(type)) {
+ return;
+ }
+
+ boolean changed;
+
+ if (!mCanAddInternalSystemWindow) {
+ // We want to track non-system signature apps adding alert windows so we can post an
+ // on-going notification for the user to control their visibility.
+ if (visible) {
+ changed = mAlertWindowSurfaces.add(surfaceController);
+ } else {
+ changed = mAlertWindowSurfaces.remove(surfaceController);
+ }
+
+ if (changed) {
+ if (mAlertWindowSurfaces.isEmpty()) {
+ cancelAlertWindowNotification();
+ } else if (mAlertWindowNotification == null){
+ mAlertWindowNotification = new AlertWindowNotification(
+ mService, mPackageName, mUid);
+ }
}
}
- killSessionLocked();
+
+ if (type != TYPE_APPLICATION_OVERLAY) {
+ return;
+ }
+
+ if (visible) {
+ changed = mAppOverlaySurfaces.add(surfaceController);
+ } else {
+ changed = mAppOverlaySurfaces.remove(surfaceController);
+ }
+
+ if (changed) {
+ // Notify activity manager of changes to app overlay windows so it can adjust the
+ // importance score for the process.
+ setHasOverlayUi(!mAppOverlaySurfaces.isEmpty());
+ }
}
private void killSessionLocked() {
@@ -597,16 +644,28 @@
+ " in session " + this + ": " + e.toString());
}
mSurfaceSession = null;
+ mAlertWindowSurfaces.clear();
+ mAppOverlaySurfaces.clear();
setHasOverlayUi(false);
+ cancelAlertWindowNotification();
}
private void setHasOverlayUi(boolean hasOverlayUi) {
mService.mH.obtainMessage(H.SET_HAS_OVERLAY_UI, mPid, hasOverlayUi ? 1 : 0).sendToTarget();
}
+ private void cancelAlertWindowNotification() {
+ if (mAlertWindowNotification == null) {
+ return;
+ }
+ mAlertWindowNotification.cancel();
+ mAlertWindowNotification = null;
+ }
+
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("mNumWindow="); pw.print(mNumWindow);
- pw.print(" mNumOverlayWindow="); pw.print(mNumOverlayWindow);
+ pw.print(" mAppOverlaySurfaces="); pw.print(mAppOverlaySurfaces);
+ pw.print(" mAlertWindowSurfaces="); pw.print(mAlertWindowSurfaces);
pw.print(" mClientDead="); pw.print(mClientDead);
pw.print(" mSurfaceSession="); pw.println(mSurfaceSession);
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index ab9a378..da5fcf3 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -592,8 +592,15 @@
return mStack != null && mStack.mStackId == PINNED_STACK_ID;
}
+ /**
+ * When we are in a floating stack (Freeform, Pinned, ...) we calculate
+ * insets differently. However if we are animating to the fullscreen stack
+ * we need to begin calculating insets as if we were fullscreen, otherwise
+ * we will have a jump at the end.
+ */
boolean isFloating() {
- return StackId.tasksAreFloating(mStack.mStackId);
+ return StackId.tasksAreFloating(mStack.mStackId)
+ && !mStack.isBoundsAnimatingToFullscreen();
}
WindowState getTopVisibleAppMainWindow() {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index cfcbbd0..9f52412 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -226,7 +226,8 @@
@Override
public void resized(Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets,
Rect stableInsets, Rect outsets, boolean reportDraw, Configuration newConfig,
- Rect backDropFrame, boolean forceLayout, boolean alwaysConsumeNavBar) {
+ Rect backDropFrame, boolean forceLayout, boolean alwaysConsumeNavBar,
+ int displayId) {
if (reportDraw) {
sHandler.obtainMessage(MSG_REPORT_DRAW, mOuter).sendToTarget();
}
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index b9429f4..5f8b694 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -125,6 +125,7 @@
// perfectly fit the region it would have been cropped to. We may also avoid certain logic we
// would otherwise apply while resizing, while resizing in the bounds animating mode.
private boolean mBoundsAnimating = false;
+ private boolean mBoundsAnimatingToFullscreen = false;
private Rect mBoundsAnimationTarget = new Rect();
// Temporary storage for the new bounds that should be used after the configuration change.
@@ -1443,9 +1444,10 @@
}
@Override // AnimatesBounds
- public void onAnimationStart() {
+ public void onAnimationStart(boolean toFullscreen) {
synchronized (mService.mWindowMap) {
mBoundsAnimating = true;
+ mBoundsAnimatingToFullscreen = toFullscreen;
}
}
@@ -1486,7 +1488,7 @@
return StackId.hasMovementAnimations(mStackId);
}
- public boolean getForceScaleToStack() {
+ public boolean isForceScaled() {
return mBoundsAnimating;
}
@@ -1494,6 +1496,10 @@
return mBoundsAnimating;
}
+ public boolean isBoundsAnimatingToFullscreen() {
+ return mBoundsAnimating && mBoundsAnimatingToFullscreen;
+ }
+
/** Returns true if a removal action is still being deferred. */
boolean checkCompleteDeferredRemoval() {
if (isAnimating()) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5653113..9397c9e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -21,8 +21,13 @@
import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS;
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
import static android.app.StatusBarManager.DISABLE_MASK;
+import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
import static android.content.Intent.ACTION_USER_REMOVED;
+import static android.content.Intent.EXTRA_PACKAGE_NAME;
+import static android.content.Intent.EXTRA_UID;
import static android.content.Intent.EXTRA_USER_HANDLE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -66,6 +71,7 @@
import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START;
import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
+import static com.android.server.wm.KeyguardDisableHandler.KEYGUARD_POLICY_CHANGED;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
@@ -343,20 +349,34 @@
final private KeyguardDisableHandler mKeyguardDisableHandler;
- final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ static final String ACTION_REVOKE_SYSTEM_ALERT_WINDOW_PERMISSION =
+ "com.android.server.wm.ACTION_REVOKE_SYSTEM_ALERT_WINDOW_PERMISSION";
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action)) {
- mKeyguardDisableHandler.sendEmptyMessage(
- KeyguardDisableHandler.KEYGUARD_POLICY_CHANGED);
- } else if (ACTION_USER_REMOVED.equals(action)) {
- final int userId = intent.getIntExtra(EXTRA_USER_HANDLE, USER_NULL);
- if (userId != USER_NULL) {
- synchronized (mWindowMap) {
- mScreenCaptureDisabled.remove(userId);
+ switch (intent.getAction()) {
+ case ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED:
+ mKeyguardDisableHandler.sendEmptyMessage(KEYGUARD_POLICY_CHANGED);
+ break;
+ case ACTION_USER_REMOVED:
+ final int userId = intent.getIntExtra(EXTRA_USER_HANDLE, USER_NULL);
+ if (userId != USER_NULL) {
+ synchronized (mWindowMap) {
+ mScreenCaptureDisabled.remove(userId);
+ }
}
- }
+ break;
+ case ACTION_REVOKE_SYSTEM_ALERT_WINDOW_PERMISSION:
+ final String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
+ final int uid = intent.getIntExtra(EXTRA_UID, -1);
+ if (packageName != null && uid != -1) {
+ synchronized (mWindowMap) {
+ // Revoke permission.
+ mAppOps.setMode(OP_SYSTEM_ALERT_WINDOW, uid, packageName, MODE_IGNORED);
+ }
+ }
+ break;
}
}
};
@@ -1018,7 +1038,7 @@
updateAppOpsState();
}
};
- mAppOps.startWatchingMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, null, opListener);
+ mAppOps.startWatchingMode(OP_SYSTEM_ALERT_WINDOW, null, opListener);
mAppOps.startWatchingMode(AppOpsManager.OP_TOAST_WINDOW, null, opListener);
// Get persisted window scale setting
@@ -1034,9 +1054,10 @@
IntentFilter filter = new IntentFilter();
// Track changes to DevicePolicyManager state so we can enable/disable keyguard.
- filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
+ filter.addAction(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
// Listen to user removal broadcasts so that we can remove the user-specific data.
filter.addAction(Intent.ACTION_USER_REMOVED);
+ filter.addAction(ACTION_REVOKE_SYSTEM_ALERT_WINDOW_PERMISSION);
mContext.registerReceiver(mBroadcastReceiver, filter);
mSettingsObserver = new SettingsObserver();
@@ -1111,8 +1132,6 @@
long origId;
final int callingUid = Binder.getCallingUid();
final int type = attrs.type;
- final boolean ownerCanAddInternalSystemWindow =
- mContext.checkCallingPermission(INTERNAL_SYSTEM_WINDOW) == PERMISSION_GRANTED;
synchronized(mWindowMap) {
if (!mDisplayReady) {
@@ -1215,7 +1234,7 @@
}
}
token = new WindowToken(this, attrs.token, type, false, displayContent,
- ownerCanAddInternalSystemWindow);
+ session.mCanAddInternalSystemWindow);
} else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
atoken = token.asAppWindowToken();
if (atoken == null) {
@@ -1286,12 +1305,12 @@
// instead make a new token for it (as if null had been passed in for the token).
attrs.token = null;
token = new WindowToken(this, null, type, false, displayContent,
- ownerCanAddInternalSystemWindow);
+ session.mCanAddInternalSystemWindow);
}
final WindowState win = new WindowState(this, session, client, token, parentWindow,
appOp[0], seq, attrs, viewVisibility, session.mUid,
- ownerCanAddInternalSystemWindow);
+ session.mCanAddInternalSystemWindow);
if (win.mDeathRecipient == null) {
// Client has apparently died, so there is no reason to
// continue.
@@ -1984,7 +2003,7 @@
if (viewVisibility == View.VISIBLE &&
(win.mAppToken == null || win.mAttrs.type == TYPE_APPLICATION_STARTING
|| !win.mAppToken.clientHidden)) {
- result = relayoutVisibleWindow(outConfig, result, win, winAnimator, attrChanges,
+ result = win.relayoutVisibleWindow(outConfig, result, attrChanges,
oldVisibility);
try {
result = createSurfaceControl(outSurface, result, win, winAnimator);
@@ -2202,69 +2221,6 @@
return result;
}
- private int relayoutVisibleWindow(Configuration outConfig, int result, WindowState win,
- WindowStateAnimator winAnimator, int attrChanges, int oldVisibility) {
- result |= !win.isVisibleLw() ? WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME : 0;
- if (win.mAnimatingExit) {
- Slog.d(TAG, "relayoutVisibleWindow: " + win + " mAnimatingExit=true, mRemoveOnExit="
- + win.mRemoveOnExit + ", mDestroying=" + win.mDestroying);
-
- winAnimator.cancelExitAnimationForNextAnimationLocked();
- win.mAnimatingExit = false;
- }
- if (win.mDestroying) {
- win.mDestroying = false;
- mDestroySurface.remove(win);
- }
- if (oldVisibility == View.GONE) {
- winAnimator.mEnterAnimationPending = true;
- }
-
- win.mLastVisibleLayoutRotation = mRotation;
-
- winAnimator.mEnteringAnimation = true;
- if ((result & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
- win.prepareWindowToDisplayDuringRelayout(outConfig);
- }
- if ((attrChanges & LayoutParams.FORMAT_CHANGED) != 0) {
- // If the format can't be changed in place, preserve the old surface until the app draws
- // on the new one. This prevents blinking when we change elevation of freeform and
- // pinned windows.
- if (!winAnimator.tryChangeFormatInPlaceLocked()) {
- winAnimator.preserveSurfaceLocked();
- result |= RELAYOUT_RES_SURFACE_CHANGED
- | WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
- }
- }
-
- // If we're starting a drag-resize, we'll be changing the surface size as well as
- // notifying the client to render to with an offset from the surface's top-left.
- if (win.isDragResizeChanged() || win.isResizedWhileNotDragResizing()) {
- win.setDragResizing();
- win.setResizedWhileNotDragResizing(false);
- // We can only change top level windows to the full-screen surface when
- // resizing (as we only have one full-screen surface). So there is no need
- // to preserve and destroy windows which are attached to another, they
- // will keep their surface and its size may change over time.
- if (win.mHasSurface && !win.isChildWindow()) {
- winAnimator.preserveSurfaceLocked();
- result |= WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
- }
- }
- final boolean freeformResizing = win.isDragResizing()
- && win.getResizeMode() == DRAG_RESIZE_MODE_FREEFORM;
- final boolean dockedResizing = win.isDragResizing()
- && win.getResizeMode() == DRAG_RESIZE_MODE_DOCKED_DIVIDER;
- result |= freeformResizing ? WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM : 0;
- result |= dockedResizing ? WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED : 0;
- if (win.isAnimatingWithSavedSurface()) {
- // If we're animating with a saved surface now, request client to report draw.
- // We still need to know when the real thing is drawn.
- result |= WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
- }
- return result;
- }
-
public void performDeferredDestroyWindow(Session session, IWindow client) {
long origId = Binder.clearCallingIdentity();
@@ -5932,8 +5888,8 @@
if (displayContent.mBaseDisplayWidth != width
|| displayContent.mBaseDisplayHeight != height) {
Slog.i(TAG_WM, "FORCED DISPLAY SIZE: " + width + "x" + height);
- displayContent.updateBaseDisplayMetrics(width, height,
- displayContent.mBaseDisplayDensity);
+ displayContent.mBaseDisplayWidth = width;
+ displayContent.mBaseDisplayHeight = height;
}
} catch (NumberFormatException ex) {
}
@@ -5958,7 +5914,8 @@
// 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.updateBaseDisplayMetrics(width, height, displayContent.mBaseDisplayDensity);
+ displayContent.mBaseDisplayWidth = width;
+ displayContent.mBaseDisplayHeight = height;
reconfigureDisplayLocked(displayContent);
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 65e1f84..945a349 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -40,6 +40,7 @@
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
import static android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
+import static android.view.WindowManager.LayoutParams.FORMAT_CHANGED;
import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
@@ -56,6 +57,10 @@
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static android.view.WindowManagerPolicy.TRANSIT_ENTER;
import static android.view.WindowManagerPolicy.TRANSIT_EXIT;
@@ -540,6 +545,12 @@
private static final Region sEmptyRegion = new Region();
/**
+ * Surface insets from the previous call to relayout(), used to track
+ * if we are changing the Surface insets.
+ */
+ final Rect mLastSurfaceInsets = new Rect();
+
+ /**
* Compares two window sub-layers and returns -1 if the first is lesser than the second in terms
* of z-order and 1 otherwise.
*/
@@ -665,7 +676,7 @@
void attach() {
if (localLOGV) Slog.v(TAG, "Attaching " + this + " token=" + mToken);
- mSession.windowAddedLocked(mAttrs.type);
+ mSession.windowAddedLocked(mAttrs.packageName);
}
@Override
@@ -1581,7 +1592,7 @@
// Anyway we don't need to synchronize position and content updates for these
// windows since they aren't at the base layer and could be moved around anyway.
if (!computeDragResizing() && mAttrs.type == TYPE_BASE_APPLICATION &&
- !getTask().mStack.getBoundsAnimating() && !isGoneForLayoutLw() &&
+ !mWinAnimator.isForceScaled() && !isGoneForLayoutLw() &&
!getTask().inPinnedWorkspace()) {
setResizedWhileNotDragResizing(true);
}
@@ -1738,7 +1749,7 @@
mWinAnimator.destroyDeferredSurfaceLocked();
mWinAnimator.destroySurfaceLocked();
- mSession.windowRemovedLocked(mAttrs.type);
+ mSession.windowRemovedLocked();
try {
mClient.asBinder().unlinkToDeath(mDeathRecipient, 0);
} catch (RuntimeException e) {
@@ -3027,6 +3038,7 @@
final Rect outsets = mLastOutsets;
final boolean reportDraw = mWinAnimator.mDrawState == DRAW_PENDING;
final boolean reportOrientation = mReportOrientationChanged;
+ final int displayId = getDisplayId();
if (mAttrs.type != WindowManager.LayoutParams.TYPE_APPLICATION_STARTING
&& mClient instanceof IWindow.Stub) {
// To prevent deadlock simulate one-way call if win.mClient is a local object.
@@ -3036,7 +3048,7 @@
try {
dispatchResized(frame, overscanInsets, contentInsets, visibleInsets,
stableInsets, outsets, reportDraw, newConfig,
- reportOrientation);
+ reportOrientation, displayId);
} catch (RemoteException e) {
// Not a remote call, RemoteException won't be raised.
}
@@ -3044,7 +3056,7 @@
});
} else {
dispatchResized(frame, overscanInsets, contentInsets, visibleInsets, stableInsets,
- outsets, reportDraw, newConfig, reportOrientation);
+ outsets, reportDraw, newConfig, reportOrientation, displayId);
}
//TODO (multidisplay): Accessibility supported only for the default display.
@@ -3101,13 +3113,14 @@
private void dispatchResized(Rect frame, Rect overscanInsets, Rect contentInsets,
Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw,
- Configuration newConfig, boolean reportOrientation) throws RemoteException {
+ Configuration newConfig, boolean reportOrientation, int displayId)
+ throws RemoteException {
final boolean forceRelayout = isDragResizeChanged() || mResizedWhileNotDragResizing
|| reportOrientation;
mClient.resized(frame, overscanInsets, contentInsets, visibleInsets, stableInsets, outsets,
reportDraw, newConfig, getBackdropFrame(frame),
- forceRelayout, mPolicy.isNavBarForcedShownLw(this));
+ forceRelayout, mPolicy.isNavBarForcedShownLw(this), displayId);
mDragResizingChangeReported = true;
}
@@ -3876,7 +3889,7 @@
}
private boolean forAllWindowBottomToTop(ToBooleanFunction<WindowState> callback) {
- // We want to consumer the negative sublayer children first because they need to appear
+ // We want to consume the negative sublayer children first because they need to appear
// below the parent, then this window (the parent), and then the positive sublayer children
// because they need to appear above the parent.
int i = 0;
@@ -3884,7 +3897,7 @@
WindowState child = mChildren.get(i);
while (i < count && child.mSubLayer < 0) {
- if (callback.apply(child)) {
+ if (child.applyInOrderWithImeWindows(callback, false /* traverseTopToBottom */)) {
return true;
}
i++;
@@ -3899,7 +3912,7 @@
}
while (i < count) {
- if (callback.apply(child)) {
+ if (child.applyInOrderWithImeWindows(callback, false /* traverseTopToBottom */)) {
return true;
}
i++;
@@ -3913,14 +3926,14 @@
}
private boolean forAllWindowTopToBottom(ToBooleanFunction<WindowState> callback) {
- // We want to consumer the positive sublayer children first because they need to appear
+ // We want to consume the positive sublayer children first because they need to appear
// above the parent, then this window (the parent), and then the negative sublayer children
// because they need to appear above the parent.
int i = mChildren.size() - 1;
WindowState child = mChildren.get(i);
while (i >= 0 && child.mSubLayer >= 0) {
- if (callback.apply(child)) {
+ if (child.applyInOrderWithImeWindows(callback, true /* traverseTopToBottom */)) {
return true;
}
--i;
@@ -3935,7 +3948,7 @@
}
while (i >= 0) {
- if (callback.apply(child)) {
+ if (child.applyInOrderWithImeWindows(callback, true /* traverseTopToBottom */)) {
return true;
}
--i;
@@ -3978,10 +3991,43 @@
}
WindowState getWindow(Predicate<WindowState> callback) {
+ if (mChildren.isEmpty()) {
+ return callback.test(this) ? this : null;
+ }
+
+ // We want to consume the positive sublayer children first because they need to appear
+ // above the parent, then this window (the parent), and then the negative sublayer children
+ // because they need to appear above the parent.
+ int i = mChildren.size() - 1;
+ WindowState child = mChildren.get(i);
+
+ while (i >= 0 && child.mSubLayer >= 0) {
+ if (callback.test(child)) {
+ return child;
+ }
+ --i;
+ if (i < 0) {
+ break;
+ }
+ child = mChildren.get(i);
+ }
+
if (callback.test(this)) {
return this;
}
- return super.getWindow(callback);
+
+ while (i >= 0) {
+ if (callback.test(child)) {
+ return child;
+ }
+ --i;
+ if (i < 0) {
+ break;
+ }
+ child = mChildren.get(i);
+ }
+
+ return null;
}
boolean isWindowAnimationSet() {
@@ -4296,6 +4342,78 @@
-mAttrs.surfaceInsets.bottom);
}
+ boolean surfaceInsetsChanging() {
+ return !mLastSurfaceInsets.equals(mAttrs.surfaceInsets);
+ }
+
+ int relayoutVisibleWindow(Configuration outConfig, int result,
+ int attrChanges, int oldVisibility) {
+ result |= !isVisibleLw() ? RELAYOUT_RES_FIRST_TIME : 0;
+ if (mAnimatingExit) {
+ Slog.d(TAG, "relayoutVisibleWindow: " + this + " mAnimatingExit=true, mRemoveOnExit="
+ + mRemoveOnExit + ", mDestroying=" + mDestroying);
+
+ mWinAnimator.cancelExitAnimationForNextAnimationLocked();
+ mAnimatingExit = false;
+ }
+ if (mDestroying) {
+ mDestroying = false;
+ mService.mDestroySurface.remove(this);
+ }
+ if (oldVisibility == View.GONE) {
+ mWinAnimator.mEnterAnimationPending = true;
+ }
+
+ mLastVisibleLayoutRotation = mService.mRotation;
+
+ mWinAnimator.mEnteringAnimation = true;
+ if ((result & RELAYOUT_RES_FIRST_TIME) != 0) {
+ prepareWindowToDisplayDuringRelayout(outConfig);
+ }
+ if ((attrChanges & FORMAT_CHANGED) != 0) {
+ // If the format can't be changed in place, preserve the old surface until the app draws
+ // on the new one. This prevents blinking when we change elevation of freeform and
+ // pinned windows.
+ if (!mWinAnimator.tryChangeFormatInPlaceLocked()) {
+ mWinAnimator.preserveSurfaceLocked();
+ result |= RELAYOUT_RES_SURFACE_CHANGED
+ | RELAYOUT_RES_FIRST_TIME;
+ }
+ }
+
+ // When we change the Surface size, in scenarios which may require changing
+ // the surface position in sync with the resize, we use a preserved surface
+ // so we can freeze it while waiting for the client to report draw on the newly
+ // sized surface.
+ if (isDragResizeChanged() || isResizedWhileNotDragResizing()
+ || surfaceInsetsChanging()) {
+ mLastSurfaceInsets.set(mAttrs.surfaceInsets);
+
+ setDragResizing();
+ setResizedWhileNotDragResizing(false);
+ // We can only change top level windows to the full-screen surface when
+ // resizing (as we only have one full-screen surface). So there is no need
+ // to preserve and destroy windows which are attached to another, they
+ // will keep their surface and its size may change over time.
+ if (mHasSurface && !isChildWindow()) {
+ mWinAnimator.preserveSurfaceLocked();
+ result |= RELAYOUT_RES_FIRST_TIME;
+ }
+ }
+ final boolean freeformResizing = isDragResizing()
+ && getResizeMode() == DRAG_RESIZE_MODE_FREEFORM;
+ final boolean dockedResizing = isDragResizing()
+ && getResizeMode() == DRAG_RESIZE_MODE_DOCKED_DIVIDER;
+ result |= freeformResizing ? RELAYOUT_RES_DRAG_RESIZING_FREEFORM : 0;
+ result |= dockedResizing ? RELAYOUT_RES_DRAG_RESIZING_DOCKED : 0;
+ if (isAnimatingWithSavedSurface()) {
+ // If we're animating with a saved surface now, request client to report draw.
+ // We still need to know when the real thing is drawn.
+ result |= RELAYOUT_RES_FIRST_TIME;
+ }
+ return result;
+ }
+
// TODO: Hack to work around the number of states AppWindowToken needs to access without having
// access to its windows children. Need to investigate re-writing
// {@link AppWindowToken#updateReportedVisibilityLocked} so this can be removed.
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index d2ea64c8..c0929cb 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1318,7 +1318,7 @@
float surfaceWidth = mSurfaceController.getWidth();
float surfaceHeight = mSurfaceController.getHeight();
- if ((task != null && task.mStack.getForceScaleToStack()) || mForceScaleUntilResize) {
+ if (isForceScaled()) {
int hInsets = w.getAttrs().surfaceInsets.left + w.getAttrs().surfaceInsets.right;
int vInsets = w.getAttrs().surfaceInsets.top + w.getAttrs().surfaceInsets.bottom;
if (!mForceScaleUntilResize) {
@@ -1328,8 +1328,8 @@
task.mStack.getDimBounds(mTmpStackBounds);
// We want to calculate the scaling based on the content area, not based on
// the entire surface, so that we scale in sync with windows that don't have insets.
- mExtraHScale = (mTmpStackBounds.width() - hInsets) / (float)(surfaceWidth - hInsets);
- mExtraVScale = (mTmpStackBounds.height() - vInsets) / (float)(surfaceHeight - vInsets);
+ mExtraHScale = mTmpStackBounds.width() / (float)(surfaceWidth - hInsets);
+ mExtraVScale = mTmpStackBounds.height() / (float)(surfaceHeight - vInsets);
// In the case of ForceScaleToStack we scale entire tasks together,
// and so we need to scale our offsets relative to the task bounds
@@ -1353,6 +1353,7 @@
// expose the whole window in buffer space, and not risk extending
// past where the system would have cropped us
clipRect = null;
+ finalClipRect = null;
// Various surfaces in the scaled stack may resize at different times.
// We need to ensure for each surface, that we disable transformation matrix
@@ -1952,4 +1953,16 @@
}
}
}
+
+ /** The force-scaled state for a given window can persist past
+ * the state for it's stack as the windows complete resizing
+ * independently of one another.
+ */
+ boolean isForceScaled() {
+ final Task task = mWin.getTask();
+ if (task != null && task.mStack.isForceScaled()) {
+ return true;
+ }
+ return mForceScaleUntilResize;
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index 1096ede..f8e7428 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -52,6 +52,7 @@
private SurfaceControl mSurfaceControl;
+ // Should only be set from within setShown().
private boolean mSurfaceShown = false;
private float mSurfaceX = 0;
private float mSurfaceY = 0;
@@ -79,6 +80,9 @@
private final WindowManagerService mService;
+ private final int mWindowType;
+ private final Session mWindowSession;
+
public WindowSurfaceController(SurfaceSession s, String name, int w, int h, int format,
int flags, WindowStateAnimator animator, int windowType, int ownerUid) {
mAnimator = animator;
@@ -89,16 +93,16 @@
title = name;
mService = animator.mService;
+ final WindowState win = animator.mWin;
+ mWindowType = windowType;
+ mWindowSession = win.mSession;
- // For opaque child windows placed under parent windows,
- // we use a special SurfaceControl which mirrors commands
- // to a black-out layer placed one Z-layer below the surface.
+ // For opaque child windows placed under parent windows, we use a special SurfaceControl
+ // which mirrors commands to a black-out layer placed one Z-layer below the surface.
// This prevents holes to whatever app/wallpaper is underneath.
- if (animator.mWin.isChildWindow() &&
- animator.mWin.mSubLayer < 0 &&
- animator.mWin.mAppToken != null) {
- mSurfaceControl = new SurfaceControlWithBackground(s,
- name, w, h, format, flags, animator.mWin.mAppToken, windowType, ownerUid);
+ if (win.isChildWindow() && win.mSubLayer < 0 && win.mAppToken != null) {
+ mSurfaceControl = new SurfaceControlWithBackground(
+ s, name, w, h, format, flags, win.mAppToken, windowType, ownerUid);
} else if (DEBUG_SURFACE_TRACE) {
mSurfaceControl = new SurfaceTrace(
s, name, w, h, format, flags, windowType, ownerUid);
@@ -109,8 +113,7 @@
if (mService.mRoot.mSurfaceTraceEnabled) {
mSurfaceControl = new RemoteSurfaceTrace(
- mService.mRoot.mSurfaceTraceFd.getFileDescriptor(),
- mSurfaceControl, animator.mWin);
+ mService.mRoot.mSurfaceTraceFd.getFileDescriptor(), mSurfaceControl, win);
}
}
@@ -141,13 +144,14 @@
}
private void hideSurface() {
- if (mSurfaceControl != null) {
- mSurfaceShown = false;
- try {
- mSurfaceControl.hide();
- } catch (RuntimeException e) {
- Slog.w(TAG, "Exception hiding surface in " + this);
- }
+ if (mSurfaceControl == null) {
+ return;
+ }
+ setShown(false);
+ try {
+ mSurfaceControl.hide();
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "Exception hiding surface in " + this);
}
}
@@ -165,7 +169,7 @@
mSurfaceControl.setLayer(layer);
mSurfaceControl.setAlpha(0);
- mSurfaceShown = false;
+ setShown(false);
} catch (RuntimeException e) {
Slog.w(TAG, "Error creating surface in " + this, e);
mAnimator.reclaimSomeSurfaceMemory("create-init", true);
@@ -188,7 +192,7 @@
} catch (RuntimeException e) {
Slog.w(TAG, "Error destroying surface in: " + this, e);
} finally {
- mSurfaceShown = false;
+ setShown(false);
mSurfaceControl = null;
}
}
@@ -447,7 +451,7 @@
private boolean showSurface() {
try {
- mSurfaceShown = true;
+ setShown(true);
mSurfaceControl.show();
return true;
} catch (RuntimeException e) {
@@ -515,6 +519,10 @@
void setShown(boolean surfaceShown) {
mSurfaceShown = surfaceShown;
+
+ if (mWindowSession != null) {
+ mWindowSession.onWindowSurfaceVisibilityChanged(this, mSurfaceShown, mWindowType);
+ }
}
float getX() {
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index 2c3cda5..ac95db5 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -30,6 +30,7 @@
$(LOCAL_REL_DIR)/com_android_server_UsbHostManager.cpp \
$(LOCAL_REL_DIR)/com_android_server_VibratorService.cpp \
$(LOCAL_REL_DIR)/com_android_server_PersistentDataBlockService.cpp \
+ $(LOCAL_REL_DIR)/com_android_server_GraphicsStatsService.cpp \
$(LOCAL_REL_DIR)/onload.cpp
LOCAL_C_INCLUDES += \
@@ -37,7 +38,6 @@
external/scrypt/lib/crypto \
frameworks/base/services \
frameworks/base/libs \
- frameworks/base/libs/hwui \
frameworks/base/core/jni \
frameworks/native/services \
system/core/libappfuse/include \
@@ -76,6 +76,7 @@
libhidltransport \
libhwbinder \
libutils \
+ libhwui \
android.hardware.audio.common@2.0 \
android.hardware.contexthub@1.0 \
android.hardware.gnss@1.0 \
diff --git a/services/core/jni/com_android_server_GraphicsStatsService.cpp b/services/core/jni/com_android_server_GraphicsStatsService.cpp
new file mode 100644
index 0000000..5d5728d
--- /dev/null
+++ b/services/core/jni/com_android_server_GraphicsStatsService.cpp
@@ -0,0 +1,110 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "GraphicsStatsService"
+
+#include <jni.h>
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+#include <ScopedPrimitiveArray.h>
+#include <ScopedUtfChars.h>
+#include <JankTracker.h>
+#include <service/GraphicsStatsService.h>
+
+namespace android {
+
+using namespace android::uirenderer;
+
+static jint getAshmemSize(JNIEnv*, jobject) {
+ return sizeof(ProfileData);
+}
+
+static jlong createDump(JNIEnv*, jobject, jint fd, jboolean isProto) {
+ GraphicsStatsService::Dump* dump = GraphicsStatsService::createDump(fd, isProto
+ ? GraphicsStatsService::DumpType::Protobuf : GraphicsStatsService::DumpType::Text);
+ return reinterpret_cast<jlong>(dump);
+}
+
+static void addToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath, jstring jpackage,
+ jint versionCode, jlong startTime, jlong endTime, jbyteArray jdata) {
+ std::string path;
+ const ProfileData* data = nullptr;
+ LOG_ALWAYS_FATAL_IF(jdata == nullptr && jpath == nullptr, "Path and data can't both be null");
+ if (jdata != nullptr) {
+ ScopedByteArrayRO buffer(env, jdata);
+ LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData),
+ "Buffer size %zu doesn't match expected %zu!", buffer.size(), sizeof(ProfileData));
+ data = reinterpret_cast<const ProfileData*>(buffer.get());
+ }
+ if (jpath != nullptr) {
+ ScopedUtfChars pathChars(env, jpath);
+ LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars");
+ path.assign(pathChars.c_str(), pathChars.size());
+ }
+ ScopedUtfChars packageChars(env, jpackage);
+ LOG_ALWAYS_FATAL_IF(packageChars.size() <= 0 || !packageChars.c_str(), "Failed to get path chars");
+ GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr);
+ LOG_ALWAYS_FATAL_IF(!dump, "null passed for dump pointer");
+
+ const std::string package(packageChars.c_str(), packageChars.size());
+ GraphicsStatsService::addToDump(dump, path, package, versionCode, startTime, endTime, data);
+}
+
+static void addFileToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath) {
+ ScopedUtfChars pathChars(env, jpath);
+ LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars");
+ const std::string path(pathChars.c_str(), pathChars.size());
+ GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr);
+ GraphicsStatsService::addToDump(dump, path);
+}
+
+static void finishDump(JNIEnv*, jobject, jlong dumpPtr) {
+ GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr);
+ GraphicsStatsService::finishDump(dump);
+}
+
+static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage,
+ jint versionCode, jlong startTime, jlong endTime, jbyteArray jdata) {
+ ScopedByteArrayRO buffer(env, jdata);
+ LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData),
+ "Buffer size %zu doesn't match expected %zu!", buffer.size(), sizeof(ProfileData));
+ ScopedUtfChars pathChars(env, jpath);
+ LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars");
+ ScopedUtfChars packageChars(env, jpackage);
+ LOG_ALWAYS_FATAL_IF(packageChars.size() <= 0 || !packageChars.c_str(), "Failed to get path chars");
+
+ const std::string path(pathChars.c_str(), pathChars.size());
+ const std::string package(packageChars.c_str(), packageChars.size());
+ const ProfileData* data = reinterpret_cast<const ProfileData*>(buffer.get());
+ GraphicsStatsService::saveBuffer(path, package, versionCode, startTime, endTime, data);
+}
+
+static const JNINativeMethod sMethods[] = {
+ { "nGetAshmemSize", "()I", (void*) getAshmemSize },
+ { "nCreateDump", "(IZ)J", (void*) createDump },
+ { "nAddToDump", "(JLjava/lang/String;Ljava/lang/String;IJJ[B)V", (void*) addToDump },
+ { "nAddToDump", "(JLjava/lang/String;)V", (void*) addFileToDump },
+ { "nFinishDump", "(J)V", (void*) finishDump },
+ { "nSaveBuffer", "(Ljava/lang/String;Ljava/lang/String;IJJ[B)V", (void*) saveBuffer },
+};
+
+int register_android_server_GraphicsStatsService(JNIEnv* env)
+{
+ return jniRegisterNativeMethods(env, "com/android/server/GraphicsStatsService",
+ sMethods, NELEM(sMethods));
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 1578562..4c89705 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -289,6 +289,40 @@
return env;
}
+static jobject translateLocation(JNIEnv* env, const hardware::gnss::V1_0::GnssLocation& location) {
+ JavaObject object(env, "android/location/Location", "gps");
+
+ uint16_t flags = static_cast<uint32_t>(location.gnssLocationFlags);
+ if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_LAT_LONG) {
+ SET(Latitude, location.latitudeDegrees);
+ SET(Longitude, location.longitudeDegrees);
+ }
+ if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_ALTITUDE) {
+ SET(Altitude, location.altitudeMeters);
+ }
+ if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_SPEED) {
+ SET(Speed, location.speedMetersPerSec);
+ }
+ if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_BEARING) {
+ SET(Bearing, location.bearingDegrees);
+ }
+ if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_HORIZONTAL_ACCURACY) {
+ SET(Accuracy, location.horizontalAccuracyMeters);
+ }
+ if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_VERTICAL_ACCURACY) {
+ SET(VerticalAccuracyMeters, location.verticalAccuracyMeters);
+ }
+ if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_SPEED_ACCURACY) {
+ SET(SpeedAccuracyMetersPerSecond, location.speedAccuracyMetersPerSecond);
+ }
+ if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_BEARING_ACCURACY) {
+ SET(BearingAccuracyDegrees, location.bearingAccuracyDegrees);
+ }
+ SET(Time, location.timestamp);
+
+ return object.get();
+}
+
/*
* GnssCallback class implements the callback methods for IGnss interface.
*/
@@ -321,19 +355,15 @@
Return<void> GnssCallback::gnssLocationCb(
const ::android::hardware::gnss::V1_0::GnssLocation& location) {
JNIEnv* env = getJniEnv();
+
+ jobject jLocation = translateLocation(env, location);
+ bool hasLatLong = (static_cast<uint32_t>(location.gnssLocationFlags) &
+ hardware::gnss::V1_0::GnssLocationFlags::HAS_LAT_LONG) != 0;
+
env->CallVoidMethod(mCallbacksObj,
method_reportLocation,
- location.gnssLocationFlags,
- static_cast<jdouble>(location.latitudeDegrees),
- static_cast<jdouble>(location.longitudeDegrees),
- static_cast<jdouble>(location.altitudeMeters),
- static_cast<jfloat>(location.speedMetersPerSec),
- static_cast<jfloat>(location.bearingDegrees),
- static_cast<jfloat>(location.horizontalAccuracyMeters),
- static_cast<jfloat>(location.verticalAccuracyMeters),
- static_cast<jfloat>(location.speedAccuracyMetersPerSecond),
- static_cast<jfloat>(location.bearingAccuracyDegrees),
- static_cast<jlong>(location.timestamp));
+ boolToJbool(hasLatLong),
+ jLocation);
checkAndClearExceptionFromCallback(env, __FUNCTION__);
return Void();
}
@@ -463,20 +493,12 @@
hardware::gnss::V1_0::GnssUtcTime timestamp) {
JNIEnv* env = getJniEnv();
+ jobject jLocation = translateLocation(env, location);
+
env->CallVoidMethod(mCallbacksObj,
method_reportGeofenceTransition,
geofenceId,
- location.gnssLocationFlags,
- static_cast<jdouble>(location.latitudeDegrees),
- static_cast<jdouble>(location.longitudeDegrees),
- static_cast<jdouble>(location.altitudeMeters),
- static_cast<jfloat>(location.speedMetersPerSec),
- static_cast<jfloat>(location.bearingDegrees),
- static_cast<jfloat>(location.horizontalAccuracyMeters),
- static_cast<jfloat>(location.verticalAccuracyMeters),
- static_cast<jfloat>(location.speedAccuracyMetersPerSecond),
- static_cast<jfloat>(location.bearingAccuracyDegrees),
- static_cast<jlong>(location.timestamp),
+ jLocation,
transition,
timestamp);
@@ -488,20 +510,13 @@
GeofenceAvailability status,
const android::hardware::gnss::V1_0::GnssLocation& location) {
JNIEnv* env = getJniEnv();
+
+ jobject jLocation = translateLocation(env, location);
+
env->CallVoidMethod(mCallbacksObj,
method_reportGeofenceStatus,
status,
- location.gnssLocationFlags,
- static_cast<jdouble>(location.latitudeDegrees),
- static_cast<jdouble>(location.longitudeDegrees),
- static_cast<jdouble>(location.altitudeMeters),
- static_cast<jfloat>(location.speedMetersPerSec),
- static_cast<jfloat>(location.bearingDegrees),
- static_cast<jfloat>(location.horizontalAccuracyMeters),
- static_cast<jfloat>(location.verticalAccuracyMeters),
- static_cast<jfloat>(location.speedAccuracyMetersPerSecond),
- static_cast<jfloat>(location.bearingAccuracyDegrees),
- static_cast<jlong>(location.timestamp));
+ jLocation);
checkAndClearExceptionFromCallback(env, __FUNCTION__);
return Void();
}
@@ -949,9 +964,6 @@
Return<void> gnssLocationBatchCb(
const ::android::hardware::hidl_vec<hardware::gnss::V1_0::GnssLocation> & locations)
override;
- private:
- jobject translateLocation(
- JNIEnv* env, const hardware::gnss::V1_0::GnssLocation* location);
};
Return<void> GnssBatchingCallback::gnssLocationBatchCb(
@@ -962,7 +974,7 @@
env->FindClass("android/location/Location"), nullptr);
for (uint16_t i = 0; i < locations.size(); ++i) {
- jobject jLocation = translateLocation(env, &locations[i]);
+ jobject jLocation = translateLocation(env, locations[i]);
env->SetObjectArrayElement(jLocations, i, jLocation);
env->DeleteLocalRef(jLocation);
}
@@ -975,45 +987,9 @@
return Void();
}
-// TODO: Use this common code to translate location for Geofencing and regular Location
-jobject GnssBatchingCallback::translateLocation(
- JNIEnv* env, const hardware::gnss::V1_0::GnssLocation* location) {
- JavaObject object(env, "android/location/Location", "gps");
-
- uint16_t flags = static_cast<uint32_t>(location->gnssLocationFlags);
- if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_LAT_LONG) {
- SET(Latitude, location->latitudeDegrees);
- SET(Longitude, location->longitudeDegrees);
- }
- if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_ALTITUDE) {
- SET(Altitude, location->altitudeMeters);
- }
- if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_SPEED) {
- SET(Speed, location->speedMetersPerSec);
- }
- if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_BEARING) {
- SET(Bearing, location->bearingDegrees);
- }
- if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_HORIZONTAL_ACCURACY) {
- SET(Accuracy, location->horizontalAccuracyMeters);
- }
- if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_VERTICAL_ACCURACY) {
- SET(VerticalAccuracyMeters, location->verticalAccuracyMeters);
- }
- if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_SPEED_ACCURACY) {
- SET(SpeedAccuracyMetersPerSecond, location->speedAccuracyMetersPerSecond);
- }
- if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_BEARING_ACCURACY) {
- SET(BearingAccuracyDegrees, location->bearingAccuracyDegrees);
- }
- SET(Time, location->timestamp);
-
- return object.get();
-}
-
-
static void android_location_GnssLocationProvider_class_init_native(JNIEnv* env, jclass clazz) {
- method_reportLocation = env->GetMethodID(clazz, "reportLocation", "(IDDDFFFFFFJ)V");
+ method_reportLocation = env->GetMethodID(clazz, "reportLocation",
+ "(ZLandroid/location/Location;)V");
method_reportStatus = env->GetMethodID(clazz, "reportStatus", "(I)V");
method_reportSvStatus = env->GetMethodID(clazz, "reportSvStatus", "()V");
method_reportAGpsStatus = env->GetMethodID(clazz, "reportAGpsStatus", "(II[B)V");
@@ -1027,9 +1003,9 @@
method_requestSetID = env->GetMethodID(clazz, "requestSetID", "(I)V");
method_requestUtcTime = env->GetMethodID(clazz, "requestUtcTime", "()V");
method_reportGeofenceTransition = env->GetMethodID(clazz, "reportGeofenceTransition",
- "(IIDDDFFFFFFJIJ)V");
+ "(ILandroid/location/Location;IJ)V");
method_reportGeofenceStatus = env->GetMethodID(clazz, "reportGeofenceStatus",
- "(IIDDDFFFFFFJ)V");
+ "(ILandroid/location/Location;)V");
method_reportGeofenceAddStatus = env->GetMethodID(clazz, "reportGeofenceAddStatus",
"(II)V");
method_reportGeofenceRemoveStatus = env->GetMethodID(clazz, "reportGeofenceRemoveStatus",
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 899640e..f22b330 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -46,6 +46,7 @@
int register_android_server_Watchdog(JNIEnv* env);
int register_android_server_HardwarePropertiesManagerService(JNIEnv* env);
int register_android_server_SyntheticPasswordManager(JNIEnv* env);
+int register_android_server_GraphicsStatsService(JNIEnv* env);
};
using namespace android;
@@ -87,6 +88,7 @@
register_android_server_HardwarePropertiesManagerService(env);
register_android_server_storage_AppFuse(env);
register_android_server_SyntheticPasswordManager(env);
+ register_android_server_GraphicsStatsService(env);
return JNI_VERSION_1_4;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2b5a06d..14b1741 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -161,6 +161,7 @@
import android.view.inputmethod.InputMethodManager;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
@@ -238,6 +239,8 @@
private static final String TAG_ADMIN_BROADCAST_PENDING = "admin-broadcast-pending";
+ private static final String TAG_DEFAULT_INPUT_METHOD_SET = "default-ime-set";
+
private static final String ATTR_ID = "id";
private static final String ATTR_VALUE = "value";
@@ -402,6 +405,8 @@
private final AtomicBoolean mRemoteBugreportServiceIsActive = new AtomicBoolean();
private final AtomicBoolean mRemoteBugreportSharingAccepted = new AtomicBoolean();
+ private SetupContentObserver mSetupContentObserver;
+
private final Runnable mRemoteBugreportTimeoutRunnable = new Runnable() {
@Override
public void run() {
@@ -504,6 +509,8 @@
long mLastNetworkLogsRetrievalTime = -1;
+ boolean mDefaultInputMethodSet = false;
+
// Used for initialization of users created by createAndManageUsers.
boolean mAdminBroadcastPending = false;
PersistableBundle mInitBundle = null;
@@ -1701,6 +1708,11 @@
name, def, userHandle);
}
+ String settingsSecureGetStringForUser(String name, int userHandle) {
+ return Settings.Secure.getStringForUser(mContext.getContentResolver(), name,
+ userHandle);
+ }
+
void settingsSecurePutIntForUser(String name, int value, int userHandle) {
Settings.Secure.putIntForUser(mContext.getContentResolver(),
name, value, userHandle);
@@ -1816,6 +1828,8 @@
mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler);
LocalServices.addService(DevicePolicyManagerInternal.class, mLocalService);
+
+ mSetupContentObserver = new SetupContentObserver(mHandler);
}
/**
@@ -2568,6 +2582,11 @@
out.endTag(null, TAG_PASSWORD_TOKEN_HANDLE);
}
+ if (policy.mDefaultInputMethodSet) {
+ out.startTag(null, TAG_DEFAULT_INPUT_METHOD_SET);
+ out.endTag(null, TAG_DEFAULT_INPUT_METHOD_SET);
+ }
+
out.endTag(null, "policies");
out.endDocument();
@@ -2777,6 +2796,8 @@
} else if (TAG_PASSWORD_TOKEN_HANDLE.equals(tag)) {
policy.mPasswordTokenHandle = Long.parseLong(
parser.getAttributeValue(null, ATTR_VALUE));
+ } else if (TAG_DEFAULT_INPUT_METHOD_SET.equals(tag)) {
+ policy.mDefaultInputMethodSet = true;
} else {
Slog.w(LOG_TAG, "Unknown tag: " + tag);
XmlUtils.skipCurrentTag(parser);
@@ -2897,8 +2918,8 @@
onStartUser(UserHandle.USER_SYSTEM);
- // Register an observer for watching for user setup complete.
- new SetupContentObserver(mHandler).register();
+ // Register an observer for watching for user setup complete and settings changes.
+ mSetupContentObserver.register();
// Initialize the user setup state, to handle the upgrade case.
updateUserSetupCompleteAndPaired();
@@ -6558,12 +6579,15 @@
admin.forceEphemeralUsers = false;
admin.isNetworkLoggingEnabled = false;
mUserManagerInternal.setForceEphemeralUsers(admin.forceEphemeralUsers);
- final DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM);
- policyData.mLastSecurityLogRetrievalTime = -1;
- policyData.mLastBugReportRequestTime = -1;
- policyData.mLastNetworkLogsRetrievalTime = -1;
- saveSettingsLocked(UserHandle.USER_SYSTEM);
}
+ final DevicePolicyData policyData = getUserData(userId);
+ policyData.mDefaultInputMethodSet = false;
+ saveSettingsLocked(userId);
+ final DevicePolicyData systemPolicyData = getUserData(UserHandle.USER_SYSTEM);
+ systemPolicyData.mLastSecurityLogRetrievalTime = -1;
+ systemPolicyData.mLastBugReportRequestTime = -1;
+ systemPolicyData.mLastNetworkLogsRetrievalTime = -1;
+ saveSettingsLocked(UserHandle.USER_SYSTEM);
clearUserPoliciesLocked(userId);
mOwners.clearDeviceOwner();
@@ -6623,14 +6647,16 @@
if (!mHasFeature) {
return;
}
- final UserHandle callingUser = mInjector.binderGetCallingUserHandle();
- final int userId = callingUser.getIdentifier();
+ Preconditions.checkNotNull(who, "ComponentName is null");
+
+ final int userId = mInjector.userHandleGetCallingUserId();
enforceNotManagedProfile(userId, "clear profile owner");
enforceUserUnlocked(userId);
- // Check if this is the profile owner who is calling
- final ActiveAdmin admin =
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
synchronized (this) {
+ // Check if this is the profile owner who is calling
+ final ActiveAdmin admin =
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+
final long ident = mInjector.binderClearCallingIdentity();
try {
clearProfileOwnerLocked(admin, userId);
@@ -6648,6 +6674,9 @@
admin.userRestrictions = null;
admin.defaultEnabledRestrictionsAlreadySet.clear();
}
+ final DevicePolicyData policyData = getUserData(userId);
+ policyData.mDefaultInputMethodSet = false;
+ saveSettingsLocked(userId);
clearUserPoliciesLocked(userId);
mOwners.removeProfileOwner(userId);
mOwners.writeProfileOwner(userId);
@@ -7890,7 +7919,7 @@
// Install the profile owner if not present.
if (!mIPackageManager.isPackageAvailable(adminPkg, userHandle)) {
mIPackageManager.installExistingPackageAsUser(adminPkg, userHandle,
- PackageManager.INSTALL_REASON_POLICY);
+ 0 /*installFlags*/, PackageManager.INSTALL_REASON_POLICY);
}
} catch (RemoteException e) {
Slog.e(LOG_TAG, "Failed to make remote calls for createAndManageUser, "
@@ -8209,7 +8238,7 @@
// Install the app.
mIPackageManager.installExistingPackageAsUser(packageName, userId,
- PackageManager.INSTALL_REASON_POLICY);
+ 0 /*installFlags*/, PackageManager.INSTALL_REASON_POLICY);
} catch (RemoteException re) {
// shouldn't happen
@@ -8251,7 +8280,7 @@
if (isSystemApp(mIPackageManager, packageName, parentUserId)) {
numberOfAppsInstalled++;
mIPackageManager.installExistingPackageAsUser(packageName, userId,
- PackageManager.INSTALL_REASON_POLICY);
+ 0 /*installFlags*/, PackageManager.INSTALL_REASON_POLICY);
} else {
Slog.d(LOG_TAG, "Not enabling " + packageName + " since is not a"
+ " system app");
@@ -8698,6 +8727,20 @@
long id = mInjector.binderClearCallingIdentity();
try {
+ if (Settings.Secure.DEFAULT_INPUT_METHOD.equals(setting)) {
+ final String currentValue = mInjector.settingsSecureGetStringForUser(
+ Settings.Secure.DEFAULT_INPUT_METHOD, callingUserId);
+ if (!TextUtils.equals(currentValue, value)) {
+ // Tell the content observer that the next change will be due to the owner
+ // changing the value. There is a small race condition here that we cannot
+ // avoid: Change notifications are sent asynchronously, so it is possible
+ // that there are prior notifications queued up before the one we are about
+ // to trigger. This is a corner case that will have no impact in practice.
+ mSetupContentObserver.addPendingChangeByOwnerLocked(callingUserId);
+ }
+ getUserData(callingUserId).mDefaultInputMethodSet = true;
+ saveSettingsLocked(callingUserId);
+ }
mInjector.settingsSecurePutStringForUser(setting, value, callingUserId);
} finally {
mInjector.binderRestoreCallingIdentity(id);
@@ -8838,12 +8881,16 @@
}
private class SetupContentObserver extends ContentObserver {
-
private final Uri mUserSetupComplete = Settings.Secure.getUriFor(
Settings.Secure.USER_SETUP_COMPLETE);
private final Uri mDeviceProvisioned = Settings.Global.getUriFor(
Settings.Global.DEVICE_PROVISIONED);
private final Uri mPaired = Settings.Secure.getUriFor(Settings.Secure.DEVICE_PAIRED);
+ private final Uri mDefaultImeChanged = Settings.Secure.getUriFor(
+ Settings.Secure.DEFAULT_INPUT_METHOD);
+
+ @GuardedBy("DevicePolicyManagerService.this")
+ private Set<Integer> mUserIdsWithPendingChangesByOwner = new ArraySet<>();
public SetupContentObserver(Handler handler) {
super(handler);
@@ -8855,10 +8902,15 @@
if (mIsWatch) {
mInjector.registerContentObserver(mPaired, false, this, UserHandle.USER_ALL);
}
+ mInjector.registerContentObserver(mDefaultImeChanged, false, this, UserHandle.USER_ALL);
+ }
+
+ private void addPendingChangeByOwnerLocked(int userId) {
+ mUserIdsWithPendingChangesByOwner.add(userId);
}
@Override
- public void onChange(boolean selfChange, Uri uri) {
+ public void onChange(boolean selfChange, Uri uri, int userId) {
if (mUserSetupComplete.equals(uri) || (mIsWatch && mPaired.equals(uri))) {
updateUserSetupCompleteAndPaired();
} else if (mDeviceProvisioned.equals(uri)) {
@@ -8867,6 +8919,19 @@
// is delayed until device is marked as provisioned.
setDeviceOwnerSystemPropertyLocked();
}
+ } else if (mDefaultImeChanged.equals(uri)) {
+ synchronized (DevicePolicyManagerService.this) {
+ if (mUserIdsWithPendingChangesByOwner.contains(userId)) {
+ // This change notification was triggered by the owner changing the default
+ // IME. Ignore it.
+ mUserIdsWithPendingChangesByOwner.remove(userId);
+ } else {
+ // This change notification was triggered by the user manually changing the
+ // default IME.
+ getUserData(userId).mDefaultInputMethodSet = false;
+ saveSettingsLocked(userId);
+ }
+ }
}
}
}
@@ -10745,4 +10810,15 @@
}
return false;
}
+
+ @Override
+ public boolean isDefaultInputMethodSetByOwner(@NonNull UserHandle user) {
+ final int userId = user.getIdentifier();
+ enforceProfileOwnerOrSystemUser(null);
+ if (!isCallerWithSystemUid() && mInjector.userHandleGetCallingUserId() != userId) {
+ throw new SecurityException(
+ "Only the system can use this method to query information about another user");
+ }
+ return getUserData(userId).mDefaultInputMethodSet;
+ }
}
diff --git a/services/retaildemo/java/com/android/server/retaildemo/PreloadAppsInstaller.java b/services/retaildemo/java/com/android/server/retaildemo/PreloadAppsInstaller.java
index 3fa72dc..90c58d0 100644
--- a/services/retaildemo/java/com/android/server/retaildemo/PreloadAppsInstaller.java
+++ b/services/retaildemo/java/com/android/server/retaildemo/PreloadAppsInstaller.java
@@ -104,7 +104,7 @@
}
try {
mPackageManager.installExistingPackageAsUser(packageName, userId,
- PackageManager.INSTALL_REASON_UNKNOWN);
+ 0 /*installFlags*/, PackageManager.INSTALL_REASON_UNKNOWN);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} finally {
@@ -175,4 +175,4 @@
}
}
}
-}
\ 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 e6dd13f..ca9285b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -313,6 +313,11 @@
}
@Override
+ String settingsSecureGetStringForUser(String name, int userHandle) {
+ return context.settings.settingsSecureGetStringForUser(name, userHandle);
+ }
+
+ @Override
void settingsSecurePutIntForUser(String name, int value, int userHandle) {
context.settings.settingsSecurePutIntForUser(name, value, userHandle);
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index d0e5159..d65a9bf 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -38,6 +38,7 @@
import android.content.res.Resources;
import android.graphics.Color;
import android.net.IIpConnectivityMetrics;
+import android.net.Uri;
import android.content.pm.UserInfo;
import android.net.wifi.WifiInfo;
import android.os.Build.VERSION_CODES;
@@ -3776,6 +3777,145 @@
.thenReturn(true);
assertTrue(dpm.clearResetPasswordToken(admin1));
}
+
+ public void testIsDefaultInputMethodSetByOwnerForDeviceOwner() throws Exception {
+ final String defaultIme = Settings.Secure.DEFAULT_INPUT_METHOD;
+ final Uri defaultImeUri = Settings.Secure.getUriFor(defaultIme);
+ final UserHandle firstUser = UserHandle.SYSTEM;
+ final UserHandle secondUser = UserHandle.of(DpmMockContext.CALLER_USER_HANDLE);
+
+ // Set up a Device Owner.
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+
+ // First and second user set default IMEs manually.
+ final long ident = mContext.binder.clearCallingIdentity();
+ assertFalse(dpm.isDefaultInputMethodSetByOwner(firstUser));
+ assertFalse(dpm.isDefaultInputMethodSetByOwner(secondUser));
+ mContext.binder.restoreCallingIdentity(ident);
+
+ // Device Owner changes default IME for first user.
+ when(mContext.settings.settingsSecureGetStringForUser(defaultIme, UserHandle.USER_SYSTEM))
+ .thenReturn("ime1");
+ dpm.setSecureSetting(admin1, defaultIme, "ime2");
+ verify(mContext.settings).settingsSecurePutStringForUser(defaultIme, "ime2",
+ UserHandle.USER_SYSTEM);
+ reset(mContext.settings);
+ dpms.notifyChangeToContentObserver(defaultImeUri, UserHandle.USER_SYSTEM);
+ mContext.binder.clearCallingIdentity();
+ assertTrue(dpm.isDefaultInputMethodSetByOwner(firstUser));
+ assertFalse(dpm.isDefaultInputMethodSetByOwner(secondUser));
+ mContext.binder.restoreCallingIdentity(ident);
+
+ // Second user changes default IME manually.
+ dpms.notifyChangeToContentObserver(defaultImeUri, DpmMockContext.CALLER_USER_HANDLE);
+ mContext.binder.clearCallingIdentity();
+ assertTrue(dpm.isDefaultInputMethodSetByOwner(firstUser));
+ assertFalse(dpm.isDefaultInputMethodSetByOwner(secondUser));
+ mContext.binder.restoreCallingIdentity(ident);
+
+ // First user changes default IME manually.
+ dpms.notifyChangeToContentObserver(defaultImeUri, UserHandle.USER_SYSTEM);
+ mContext.binder.clearCallingIdentity();
+ assertFalse(dpm.isDefaultInputMethodSetByOwner(firstUser));
+ assertFalse(dpm.isDefaultInputMethodSetByOwner(secondUser));
+ mContext.binder.restoreCallingIdentity(ident);
+
+ // Device Owner changes default IME for first user again.
+ when(mContext.settings.settingsSecureGetStringForUser(defaultIme, UserHandle.USER_SYSTEM))
+ .thenReturn("ime2");
+ dpm.setSecureSetting(admin1, defaultIme, "ime3");
+ verify(mContext.settings).settingsSecurePutStringForUser(defaultIme, "ime3",
+ UserHandle.USER_SYSTEM);
+ dpms.notifyChangeToContentObserver(defaultImeUri, UserHandle.USER_SYSTEM);
+ mContext.binder.clearCallingIdentity();
+ assertTrue(dpm.isDefaultInputMethodSetByOwner(firstUser));
+ assertFalse(dpm.isDefaultInputMethodSetByOwner(secondUser));
+
+ // Restarting the DPMS should not lose information.
+ initializeDpms();
+ assertTrue(dpm.isDefaultInputMethodSetByOwner(firstUser));
+ assertFalse(dpm.isDefaultInputMethodSetByOwner(secondUser));
+ mContext.binder.restoreCallingIdentity(ident);
+
+ // Device Owner can find out whether it set the default IME itself.
+ assertTrue(dpm.isDefaultInputMethodSetByOwner(firstUser));
+
+ // Removing the Device Owner should clear the information that it set the default IME.
+ clearDeviceOwner();
+ mContext.binder.clearCallingIdentity();
+ assertFalse(dpm.isDefaultInputMethodSetByOwner(firstUser));
+ assertFalse(dpm.isDefaultInputMethodSetByOwner(secondUser));
+ }
+
+ public void testIsDefaultInputMethodSetByOwnerForProfileOwner() throws Exception {
+ final String defaultIme = Settings.Secure.DEFAULT_INPUT_METHOD;
+ final Uri defaultImeUri = Settings.Secure.getUriFor(defaultIme);
+ final UserHandle firstUser = UserHandle.SYSTEM;
+ final UserHandle secondUser = UserHandle.of(DpmMockContext.CALLER_USER_HANDLE);
+
+ // Set up a profile owner.
+ setupProfileOwner();
+
+ // First and second user set default IMEs manually.
+ final long ident = mContext.binder.clearCallingIdentity();
+ assertFalse(dpm.isDefaultInputMethodSetByOwner(firstUser));
+ assertFalse(dpm.isDefaultInputMethodSetByOwner(secondUser));
+ mContext.binder.restoreCallingIdentity(ident);
+
+ // Profile Owner changes default IME for second user.
+ when(mContext.settings.settingsSecureGetStringForUser(defaultIme,
+ DpmMockContext.CALLER_USER_HANDLE)).thenReturn("ime1");
+ dpm.setSecureSetting(admin1, defaultIme, "ime2");
+ verify(mContext.settings).settingsSecurePutStringForUser(defaultIme, "ime2",
+ DpmMockContext.CALLER_USER_HANDLE);
+ reset(mContext.settings);
+ dpms.notifyChangeToContentObserver(defaultImeUri, DpmMockContext.CALLER_USER_HANDLE);
+ mContext.binder.clearCallingIdentity();
+ assertFalse(dpm.isDefaultInputMethodSetByOwner(firstUser));
+ assertTrue(dpm.isDefaultInputMethodSetByOwner(secondUser));
+ mContext.binder.restoreCallingIdentity(ident);
+
+ // First user changes default IME manually.
+ dpms.notifyChangeToContentObserver(defaultImeUri, UserHandle.USER_SYSTEM);
+ mContext.binder.clearCallingIdentity();
+ assertFalse(dpm.isDefaultInputMethodSetByOwner(firstUser));
+ assertTrue(dpm.isDefaultInputMethodSetByOwner(secondUser));
+ mContext.binder.restoreCallingIdentity(ident);
+
+ // Second user changes default IME manually.
+ dpms.notifyChangeToContentObserver(defaultImeUri, DpmMockContext.CALLER_USER_HANDLE);
+ mContext.binder.clearCallingIdentity();
+ assertFalse(dpm.isDefaultInputMethodSetByOwner(firstUser));
+ assertFalse(dpm.isDefaultInputMethodSetByOwner(secondUser));
+ mContext.binder.restoreCallingIdentity(ident);
+
+ // Profile Owner changes default IME for second user again.
+ when(mContext.settings.settingsSecureGetStringForUser(defaultIme,
+ DpmMockContext.CALLER_USER_HANDLE)).thenReturn("ime2");
+ dpm.setSecureSetting(admin1, defaultIme, "ime3");
+ verify(mContext.settings).settingsSecurePutStringForUser(defaultIme, "ime3",
+ DpmMockContext.CALLER_USER_HANDLE);
+ dpms.notifyChangeToContentObserver(defaultImeUri, DpmMockContext.CALLER_USER_HANDLE);
+ mContext.binder.clearCallingIdentity();
+ assertFalse(dpm.isDefaultInputMethodSetByOwner(firstUser));
+ assertTrue(dpm.isDefaultInputMethodSetByOwner(secondUser));
+
+ // Restarting the DPMS should not lose information.
+ initializeDpms();
+ assertFalse(dpm.isDefaultInputMethodSetByOwner(firstUser));
+ assertTrue(dpm.isDefaultInputMethodSetByOwner(secondUser));
+ mContext.binder.restoreCallingIdentity(ident);
+
+ // Profile Owner can find out whether it set the default IME itself.
+ assertTrue(dpm.isDefaultInputMethodSetByOwner(secondUser));
+
+ // Removing the Profile Owner should clear the information that it set the default IME.
+ dpm.clearProfileOwner(admin1);
+ mContext.binder.clearCallingIdentity();
+ assertFalse(dpm.isDefaultInputMethodSetByOwner(firstUser));
+ assertFalse(dpm.isDefaultInputMethodSetByOwner(secondUser));
+ }
private void setUserSetupCompleteForUser(boolean isUserSetupComplete, int userhandle) {
when(mContext.settings.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0,
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 46aaf83..258b393 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -201,6 +201,10 @@
return 0;
}
+ public String settingsSecureGetStringForUser(String name, int userHandle) {
+ return null;
+ }
+
public void settingsSecurePutIntForUser(String name, int value, int userHandle) {
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index e2e1844..100338e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -1053,7 +1053,7 @@
ret.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED;
}
if (mEphemeralPackages.contains(PackageWithUser.of(userId, packageName))) {
- ret.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_EPHEMERAL;
+ ret.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_INSTANT;
}
if (mSystemPackages.contains(packageName)) {
ret.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index baf60c5..325d99a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -388,6 +388,7 @@
ApplicationInfo.PRIVATE_FLAG_PRIVILEGED /*pkgPrivateFlags*/,
null /*installUser*/,
false /*allowInstall*/,
+ false /*instantApp*/,
null /*parentPkgName*/,
null /*childPkgNames*/,
UserManagerService.getInstance(),
@@ -428,6 +429,7 @@
0 /*pkgPrivateFlags*/,
UserHandle.SYSTEM /*installUser*/,
true /*allowInstall*/,
+ false /*instantApp*/,
null /*parentPkgName*/,
null /*childPkgNames*/,
UserManagerService.getInstance(),
@@ -471,6 +473,7 @@
0 /*pkgPrivateFlags*/,
null /*installUser*/,
false /*allowInstall*/,
+ false /*instantApp*/,
null /*parentPkgName*/,
null /*childPkgNames*/,
UserManagerService.getInstance(),
@@ -514,6 +517,7 @@
0 /*pkgPrivateFlags*/,
null /*installUser*/,
false /*allowInstall*/,
+ false /*instantApp*/,
null /*parentPkgName*/,
null /*childPkgNames*/,
UserManagerService.getInstance(),
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index e5640c7..384f49f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -140,6 +140,36 @@
assertAllFieldsExist(deserialized);
}
+ @Test
+ public void test_stringInterning() throws Exception {
+ PackageParser.Package pkg = new PackageParser.Package("foo");
+ setKnownFields(pkg);
+
+ Parcel p = Parcel.obtain();
+ pkg.writeToParcel(p, 0 /* flags */);
+
+ p.setDataPosition(0);
+ PackageParser.Package deserialized = new PackageParser.Package(p);
+
+ p.setDataPosition(0);
+ PackageParser.Package deserialized2 = new PackageParser.Package(p);
+
+ assertSame(deserialized.packageName, deserialized2.packageName);
+ assertSame(deserialized.applicationInfo.permission,
+ deserialized2.applicationInfo.permission);
+ assertSame(deserialized.requestedPermissions.get(0),
+ deserialized2.requestedPermissions.get(0));
+ assertSame(deserialized.protectedBroadcasts.get(0),
+ deserialized2.protectedBroadcasts.get(0));
+ assertSame(deserialized.usesLibraries.get(0),
+ deserialized2.usesLibraries.get(0));
+ assertSame(deserialized.usesOptionalLibraries.get(0),
+ deserialized2.usesOptionalLibraries.get(0));
+ assertSame(deserialized.mVersionName, deserialized2.mVersionName);
+ assertSame(deserialized.mSharedUserId, deserialized2.mSharedUserId);
+ }
+
+
/**
* A trivial subclass of package parser that only caches the package name, and throws away
* all other information.
@@ -380,7 +410,7 @@
assertTrue(Arrays.equals(a.splitSourceDirs, that.splitSourceDirs));
assertTrue(Arrays.equals(a.splitPublicSourceDirs, that.splitPublicSourceDirs));
assertTrue(Arrays.equals(a.resourceDirs, that.resourceDirs));
- assertEquals(a.seinfo, that.seinfo);
+ assertEquals(a.seInfo, that.seInfo);
assertTrue(Arrays.equals(a.sharedLibraryFiles, that.sharedLibraryFiles));
assertEquals(a.dataDir, that.dataDir);
assertEquals(a.deviceProtectedDataDir, that.deviceProtectedDataDir);
@@ -407,11 +437,11 @@
pkg.mTrustedOverlay = true;
pkg.use32bitAbi = true;
pkg.packageName = "foo";
- pkg.splitNames = new String[] { "foo" };
- pkg.volumeUuid = "foo";
- pkg.codePath = "foo";
- pkg.baseCodePath = "foo";
- pkg.splitCodePaths = new String[] { "foo" };
+ pkg.splitNames = new String[] { "foo2" };
+ pkg.volumeUuid = "foo3";
+ pkg.codePath = "foo4";
+ pkg.baseCodePath = "foo5";
+ pkg.splitCodePaths = new String[] { "foo6" };
pkg.splitRevisionCodes = new int[] { 100 };
pkg.splitFlags = new int[] { 100 };
pkg.splitPrivateFlags = new int[] { 100 };
@@ -428,48 +458,55 @@
pkg.providers.add(new PackageParser.Provider(dummy, new ProviderInfo()));
pkg.services.add(new PackageParser.Service(dummy, new ServiceInfo()));
pkg.instrumentation.add(new PackageParser.Instrumentation(dummy, new InstrumentationInfo()));
- pkg.requestedPermissions.add("foo");
+ pkg.requestedPermissions.add("foo7");
pkg.protectedBroadcasts = new ArrayList<>();
- pkg.protectedBroadcasts.add("foo");
+ pkg.protectedBroadcasts.add("foo8");
- pkg.parentPackage = new PackageParser.Package("foo");
+ pkg.parentPackage = new PackageParser.Package("foo9");
pkg.childPackages = new ArrayList<>();
pkg.childPackages.add(new PackageParser.Package("bar"));
+ pkg.staticSharedLibName = "foo23";
+ pkg.staticSharedLibVersion = 100;
+ pkg.usesStaticLibraries = new ArrayList<>();
+ pkg.usesStaticLibraries.add("foo23");
+ pkg.usesStaticLibrariesCertDigests = new String[] { "digest" };
+ pkg.usesStaticLibrariesVersions = new int[] { 100 };
+
pkg.libraryNames = new ArrayList<>();
- pkg.libraryNames.add("foo");
+ pkg.libraryNames.add("foo10");
pkg.usesLibraries = new ArrayList<>();
- pkg.usesLibraries.add("foo");
+ pkg.usesLibraries.add("foo11");
pkg.usesOptionalLibraries = new ArrayList<>();
- pkg.usesOptionalLibraries.add("foo");
+ pkg.usesOptionalLibraries.add("foo12");
- pkg.usesLibraryFiles = new String[] { "foo "};
+ pkg.usesLibraryFiles = new String[] { "foo13"};
pkg.mOriginalPackages = new ArrayList<>();
- pkg.mOriginalPackages.add("foo");
+ pkg.mOriginalPackages.add("foo14");
- pkg.mRealPackage = "foo";
+ pkg.mRealPackage = "foo15";
pkg.mAdoptPermissions = new ArrayList<>();
- pkg.mAdoptPermissions.add("foo");
+ pkg.mAdoptPermissions.add("foo16");
pkg.mAppMetaData = new Bundle();
- pkg.mVersionName = "foo";
- pkg.mSharedUserId = "foo";
+ pkg.mVersionName = "foo17";
+ pkg.mSharedUserId = "foo18";
pkg.mSignatures = new Signature[] { new Signature(new byte[16]) };
pkg.mCertificates = new Certificate[][] { new Certificate[] { null }};
pkg.mExtras = new Bundle();
- pkg.mRestrictedAccountType = "foo";
- pkg.mRequiredAccountType = "foo";
- pkg.mOverlayTarget = "foo";
+ pkg.mRestrictedAccountType = "foo19";
+ pkg.mRequiredAccountType = "foo20";
+ pkg.mOverlayTarget = "foo21";
pkg.mSigningKeys = new ArraySet<>();
pkg.mUpgradeKeySets = new ArraySet<>();
pkg.mKeySetMapping = new ArrayMap<>();
- pkg.cpuAbiOverride = "foo";
+ pkg.cpuAbiOverride = "foo22";
pkg.restrictUpdateHash = new byte[16];
pkg.preferredActivityFilters = new ArrayList<>();
@@ -504,7 +541,7 @@
// Sanity check for list fields: Assume they're non-null and contain precisely
// one element.
List<?> list = (List<?>) f.get(pkg);
- assertNotNull(list);
+ assertNotNull("List was null: " + f, list);
assertEquals(1, list.size());
} else if (fieldType.getComponentType() != null) {
// Sanity check for array fields: Assume they're non-null and contain precisely
@@ -514,15 +551,16 @@
} else if (fieldType == String.class) {
// String fields: Check that they're set to "foo".
String value = (String) f.get(pkg);
- assertEquals("foo", value);
+
+ assertTrue("Bad value for field: " + f, value != null && value.startsWith("foo"));
} else if (fieldType == int.class) {
// int fields: Check that they're set to 100.
int value = (int) f.get(pkg);
- assertEquals(100, value);
+ assertEquals("Bad value for field: " + f, 100, value);
} else {
// All other fields: Check that they're set.
Object o = f.get(pkg);
- assertNotNull("Field was null: " + f.getName(), o);
+ assertNotNull("Field was null: " + f, o);
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 0c53167..e4d92ba 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -75,9 +75,9 @@
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
+import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
-import android.graphics.drawable.MaskableIconDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -704,8 +704,8 @@
Drawable dr = mLauncherApps.getShortcutIconDrawable(
makeShortcutWithIcon("bmp64x64", bmp64x64_maskable), 0);
- assertTrue(dr instanceof MaskableIconDrawable);
- float viewportPercentage = 1 / (1 + 2 * MaskableIconDrawable.getExtraInsetPercentage());
+ assertTrue(dr instanceof AdaptiveIconDrawable);
+ float viewportPercentage = 1 / (1 + 2 * AdaptiveIconDrawable.getExtraInsetPercentage());
assertEquals((int) (bmp64x64_maskable.getBitmap().getWidth() * viewportPercentage),
dr.getIntrinsicWidth());
assertEquals((int) (bmp64x64_maskable.getBitmap().getHeight() * viewportPercentage),
diff --git a/services/tests/servicestests/src/com/android/server/retaildemo/PreloadAppsInstallerTest.java b/services/tests/servicestests/src/com/android/server/retaildemo/PreloadAppsInstallerTest.java
index 346dc42..a8c39c4 100644
--- a/services/tests/servicestests/src/com/android/server/retaildemo/PreloadAppsInstallerTest.java
+++ b/services/tests/servicestests/src/com/android/server/retaildemo/PreloadAppsInstallerTest.java
@@ -104,7 +104,7 @@
null, null);
// Verify that we try to install the package in system user.
verify(mIpm).installExistingPackageAsUser(path, UserHandle.USER_SYSTEM,
- PackageManager.INSTALL_REASON_UNKNOWN);
+ 0 /*installFlags*/, PackageManager.INSTALL_REASON_UNKNOWN);
}
assertEquals("DEMO_USER_SETUP should be set to 1 after preloaded apps are installed",
"1",
@@ -141,7 +141,7 @@
null, null);
// Verify that we try to install the package in system user.
verify(mIpm).installExistingPackageAsUser(path, UserHandle.USER_SYSTEM,
- PackageManager.INSTALL_REASON_UNKNOWN);
+ 0 /*installFlags*/, PackageManager.INSTALL_REASON_UNKNOWN);
}
assertEquals("DEMO_USER_SETUP should be set to 1 after preloaded apps are installed",
"1",
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
index 30f99e5..bd3271b 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -16,24 +16,25 @@
package com.android.server.wm;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import android.content.res.Configuration;
-import android.hardware.display.DisplayManagerGlobal;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import android.view.Display;
-import android.view.DisplayInfo;
-import java.util.ArrayList;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
-import static org.junit.Assert.assertEquals;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
/**
* Tests for the {@link DisplayContent} class.
@@ -54,38 +55,17 @@
exitingAppToken.mIsExiting = true;
exitingAppToken.mTask.mStack.mExitingAppTokens.add(exitingAppToken);
- final ArrayList<WindowState> windows = new ArrayList();
-
- // Test forward traversal.
- sDisplayContent.forAllWindows(w -> {windows.add(w);}, false /* traverseTopToBottom */);
-
- assertEquals(sWallpaperWindow, windows.get(0));
- assertEquals(exitingAppWindow, windows.get(1));
- assertEquals(sChildAppWindowBelow, windows.get(2));
- assertEquals(sAppWindow, windows.get(3));
- assertEquals(sChildAppWindowAbove, windows.get(4));
- assertEquals(sDockedDividerWindow, windows.get(5));
- assertEquals(sStatusBarWindow, windows.get(6));
- assertEquals(sNavBarWindow, windows.get(7));
- assertEquals(sImeWindow, windows.get(8));
- assertEquals(sImeDialogWindow, windows.get(9));
-
- // Test backward traversal.
- windows.clear();
- sDisplayContent.forAllWindows(w -> {windows.add(w);}, true /* traverseTopToBottom */);
-
- assertEquals(sWallpaperWindow, windows.get(9));
- assertEquals(exitingAppWindow, windows.get(8));
- assertEquals(sChildAppWindowBelow, windows.get(7));
- assertEquals(sAppWindow, windows.get(6));
- assertEquals(sChildAppWindowAbove, windows.get(5));
- assertEquals(sDockedDividerWindow, windows.get(4));
- assertEquals(sStatusBarWindow, windows.get(3));
- assertEquals(sNavBarWindow, windows.get(2));
- assertEquals(sImeWindow, windows.get(1));
- assertEquals(sImeDialogWindow, windows.get(0));
-
- exitingAppWindow.removeImmediately();
+ assertForAllWindowsOrder(Arrays.asList(
+ sWallpaperWindow,
+ exitingAppWindow,
+ sChildAppWindowBelow,
+ sAppWindow,
+ sChildAppWindowAbove,
+ sDockedDividerWindow,
+ sStatusBarWindow,
+ sNavBarWindow,
+ sImeWindow,
+ sImeDialogWindow));
}
@Test
@@ -95,78 +75,49 @@
sWm.mInputMethodTarget = imeAppTarget;
- final ArrayList<WindowState> windows = new ArrayList();
+ assertForAllWindowsOrder(Arrays.asList(
+ sWallpaperWindow,
+ sChildAppWindowBelow,
+ sAppWindow,
+ sChildAppWindowAbove,
+ imeAppTarget,
+ sImeWindow,
+ sImeDialogWindow,
+ sDockedDividerWindow,
+ sStatusBarWindow,
+ sNavBarWindow));
+ }
- // Test forward traversal.
- sDisplayContent.forAllWindows(w -> {windows.add(w);}, false /* traverseTopToBottom */);
+ @Test
+ public void testForAllWindows_WithChildWindowImeTarget() throws Exception {
+ sWm.mInputMethodTarget = sChildAppWindowAbove;
- assertEquals(sWallpaperWindow, windows.get(0));
- assertEquals(sChildAppWindowBelow, windows.get(1));
- assertEquals(sAppWindow, windows.get(2));
- assertEquals(sChildAppWindowAbove, windows.get(3));
- assertEquals(imeAppTarget, windows.get(4));
- assertEquals(sImeWindow, windows.get(5));
- assertEquals(sImeDialogWindow, windows.get(6));
- assertEquals(sDockedDividerWindow, windows.get(7));
- assertEquals(sStatusBarWindow, windows.get(8));
- assertEquals(sNavBarWindow, windows.get(9));
-
- // Test backward traversal.
- windows.clear();
- sDisplayContent.forAllWindows(w -> {windows.add(w);}, true /* traverseTopToBottom */);
-
- assertEquals(sWallpaperWindow, windows.get(9));
- assertEquals(sChildAppWindowBelow, windows.get(8));
- assertEquals(sAppWindow, windows.get(7));
- assertEquals(sChildAppWindowAbove, windows.get(6));
- assertEquals(imeAppTarget, windows.get(5));
- assertEquals(sImeWindow, windows.get(4));
- assertEquals(sImeDialogWindow, windows.get(3));
- assertEquals(sDockedDividerWindow, windows.get(2));
- assertEquals(sStatusBarWindow, windows.get(1));
- assertEquals(sNavBarWindow, windows.get(0));
-
- // Clean-up
- sWm.mInputMethodTarget = null;
- imeAppTarget.removeImmediately();
+ assertForAllWindowsOrder(Arrays.asList(
+ sWallpaperWindow,
+ sChildAppWindowBelow,
+ sAppWindow,
+ sChildAppWindowAbove,
+ sImeWindow,
+ sImeDialogWindow,
+ sDockedDividerWindow,
+ sStatusBarWindow,
+ sNavBarWindow));
}
@Test
public void testForAllWindows_WithStatusBarImeTarget() throws Exception {
-
sWm.mInputMethodTarget = sStatusBarWindow;
- final ArrayList<WindowState> windows = new ArrayList();
-
- // Test forward traversal.
- sDisplayContent.forAllWindows(w -> {windows.add(w);}, false /* traverseTopToBottom */);
-
- assertEquals(sWallpaperWindow, windows.get(0));
- assertEquals(sChildAppWindowBelow, windows.get(1));
- assertEquals(sAppWindow, windows.get(2));
- assertEquals(sChildAppWindowAbove, windows.get(3));
- assertEquals(sDockedDividerWindow, windows.get(4));
- assertEquals(sStatusBarWindow, windows.get(5));
- assertEquals(sImeWindow, windows.get(6));
- assertEquals(sImeDialogWindow, windows.get(7));
- assertEquals(sNavBarWindow, windows.get(8));
-
- // Test backward traversal.
- windows.clear();
- sDisplayContent.forAllWindows(w -> {windows.add(w);}, true /* traverseTopToBottom */);
-
- assertEquals(sWallpaperWindow, windows.get(8));
- assertEquals(sChildAppWindowBelow, windows.get(7));
- assertEquals(sAppWindow, windows.get(6));
- assertEquals(sChildAppWindowAbove, windows.get(5));
- assertEquals(sDockedDividerWindow, windows.get(4));
- assertEquals(sStatusBarWindow, windows.get(3));
- assertEquals(sImeWindow, windows.get(2));
- assertEquals(sImeDialogWindow, windows.get(1));
- assertEquals(sNavBarWindow, windows.get(0));
-
- // Clean-up
- sWm.mInputMethodTarget = null;
+ assertForAllWindowsOrder(Arrays.asList(
+ sWallpaperWindow,
+ sChildAppWindowBelow,
+ sAppWindow,
+ sChildAppWindowAbove,
+ sDockedDividerWindow,
+ sStatusBarWindow,
+ sImeWindow,
+ sImeDialogWindow,
+ sNavBarWindow));
}
@Test
@@ -176,38 +127,35 @@
final WindowState voiceInteractionWindow = createWindow(null, TYPE_VOICE_INTERACTION,
sDisplayContent, "voiceInteractionWindow");
- final ArrayList<WindowState> windows = new ArrayList();
+ assertForAllWindowsOrder(Arrays.asList(
+ sWallpaperWindow,
+ sChildAppWindowBelow,
+ sAppWindow,
+ sChildAppWindowAbove,
+ sDockedDividerWindow,
+ voiceInteractionWindow,
+ sStatusBarWindow,
+ sNavBarWindow,
+ sImeWindow,
+ sImeDialogWindow));
+ }
- // Test forward traversal.
- sDisplayContent.forAllWindows(w -> {windows.add(w);}, false /* traverseTopToBottom */);
+ @Test
+ public void testComputeImeTarget() throws Exception {
+ // Verify that an app window can be an ime target.
+ final WindowState appWin = createWindow(null, TYPE_APPLICATION, sDisplayContent, "appWin");
+ appWin.setHasSurface(true);
+ assertTrue(appWin.canBeImeTarget());
+ WindowState imeTarget = sDisplayContent.computeImeTarget(false /* updateImeTarget */);
+ assertEquals(appWin, imeTarget);
- assertEquals(sWallpaperWindow, windows.get(0));
- assertEquals(sChildAppWindowBelow, windows.get(1));
- assertEquals(sAppWindow, windows.get(2));
- assertEquals(sChildAppWindowAbove, windows.get(3));
- assertEquals(sDockedDividerWindow, windows.get(4));
- assertEquals(voiceInteractionWindow, windows.get(5));
- assertEquals(sStatusBarWindow, windows.get(6));
- assertEquals(sNavBarWindow, windows.get(7));
- assertEquals(sImeWindow, windows.get(8));
- assertEquals(sImeDialogWindow, windows.get(9));
-
- // Test backward traversal.
- windows.clear();
- sDisplayContent.forAllWindows(w -> {windows.add(w);}, true /* traverseTopToBottom */);
-
- assertEquals(sWallpaperWindow, windows.get(9));
- assertEquals(sChildAppWindowBelow, windows.get(8));
- assertEquals(sAppWindow, windows.get(7));
- assertEquals(sChildAppWindowAbove, windows.get(6));
- assertEquals(sDockedDividerWindow, windows.get(5));
- assertEquals(voiceInteractionWindow, windows.get(4));
- assertEquals(sStatusBarWindow, windows.get(3));
- assertEquals(sNavBarWindow, windows.get(2));
- assertEquals(sImeWindow, windows.get(1));
- assertEquals(sImeDialogWindow, windows.get(0));
-
- voiceInteractionWindow.removeImmediately();
+ // Verify that an child window can be an ime target.
+ final WindowState childWin = createWindow(appWin,
+ TYPE_APPLICATION_ATTACHED_DIALOG, "childWin");
+ childWin.setHasSurface(true);
+ assertTrue(childWin.canBeImeTarget());
+ imeTarget = sDisplayContent.computeImeTarget(false /* updateImeTarget */);
+ assertEquals(childWin, imeTarget);
}
/**
@@ -284,4 +232,24 @@
assertEquals(currentOverrideConfig.densityDpi, globalConfig.densityDpi);
assertEquals(currentOverrideConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
}
+
+ private void assertForAllWindowsOrder(List<WindowState> expectedWindows) {
+ final LinkedList<WindowState> actualWindows = new LinkedList();
+
+ // Test forward traversal.
+ sDisplayContent.forAllWindows(actualWindows::addLast, false /* traverseTopToBottom */);
+ assertEquals(expectedWindows.size(), actualWindows.size());
+ for (WindowState w : expectedWindows) {
+ assertEquals(w, actualWindows.pollFirst());
+ }
+ assertTrue(actualWindows.isEmpty());
+
+ // Test backward traversal.
+ sDisplayContent.forAllWindows(actualWindows::addLast, true /* traverseTopToBottom */);
+ assertEquals(expectedWindows.size(), actualWindows.size());
+ for (WindowState w : expectedWindows) {
+ assertEquals(w, actualWindows.pollLast());
+ }
+ assertTrue(actualWindows.isEmpty());
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java b/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java
index c029a9f..b6dc9a5 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java
@@ -37,7 +37,7 @@
@Override
public void resized(Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets,
Rect stableInsets, Rect outsets, boolean reportDraw, Configuration newConfig,
- Rect backDropFrame, boolean forceLayout, boolean alwaysConsumeNavBar)
+ Rect backDropFrame, boolean forceLayout, boolean alwaysConsumeNavBar, int displayId)
throws RemoteException {
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
index df35b7ee..5f51898 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
@@ -23,10 +23,17 @@
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import java.util.LinkedList;
+
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -167,4 +174,34 @@
assertFalse(appWindow.canBeImeTarget());
assertFalse(imeWindow.canBeImeTarget());
}
+
+ @Test
+ public void testGetWindow() throws Exception {
+ final WindowState root = createWindow(null, TYPE_APPLICATION, "root");
+ final WindowState mediaChild = createWindow(root, TYPE_APPLICATION_MEDIA, "mediaChild");
+ final WindowState mediaOverlayChild = createWindow(root,
+ TYPE_APPLICATION_MEDIA_OVERLAY, "mediaOverlayChild");
+ final WindowState attachedDialogChild = createWindow(root,
+ TYPE_APPLICATION_ATTACHED_DIALOG, "attachedDialogChild");
+ final WindowState subPanelChild = createWindow(root,
+ TYPE_APPLICATION_SUB_PANEL, "subPanelChild");
+ final WindowState aboveSubPanelChild = createWindow(root,
+ TYPE_APPLICATION_ABOVE_SUB_PANEL, "aboveSubPanelChild");
+
+ final LinkedList<WindowState> windows = new LinkedList();
+
+ root.getWindow(w -> {
+ windows.addLast(w);
+ return false;
+ });
+
+ // getWindow should have returned candidate windows in z-order.
+ assertEquals(aboveSubPanelChild, windows.pollFirst());
+ assertEquals(subPanelChild, windows.pollFirst());
+ assertEquals(attachedDialogChild, windows.pollFirst());
+ assertEquals(root, windows.pollFirst());
+ assertEquals(mediaOverlayChild, windows.pollFirst());
+ assertEquals(mediaChild, windows.pollFirst());
+ assertTrue(windows.isEmpty());
+ }
}
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 e5e3512..52e10a5 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -44,6 +44,7 @@
import static android.content.res.Configuration.EMPTY;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
@@ -60,6 +61,7 @@
import com.android.server.AttributeCache;
import java.util.HashSet;
+import java.util.LinkedList;
/**
* Common base class for window manager unit test classes.
@@ -120,6 +122,7 @@
sCommonWindows = new HashSet();
sWallpaperWindow = createCommonWindow(null, TYPE_WALLPAPER, "wallpaperWindow");
sImeWindow = createCommonWindow(null, TYPE_INPUT_METHOD, "sImeWindow");
+ sWm.mInputMethodWindow = sImeWindow;
sImeDialogWindow = createCommonWindow(null, TYPE_INPUT_METHOD_DIALOG, "sImeDialogWindow");
sStatusBarWindow = createCommonWindow(null, TYPE_STATUS_BAR, "sStatusBarWindow");
sNavBarWindow = createCommonWindow(null, TYPE_NAVIGATION_BAR, "sNavBarWindow");
@@ -133,16 +136,25 @@
@After
public void tearDown() throws Exception {
+ final LinkedList<WindowState> nonCommonWindows = new LinkedList();
sWm.mRoot.forAllWindows(w -> {
if (!sCommonWindows.contains(w)) {
- w.removeImmediately();
+ nonCommonWindows.addLast(w);
}
}, true /* traverseTopToBottom */);
+
+ while (!nonCommonWindows.isEmpty()) {
+ nonCommonWindows.pollLast().removeImmediately();
+ }
+
+ sWm.mInputMethodTarget = null;
}
private static WindowState createCommonWindow(WindowState parent, int type, String name) {
final WindowState win = createWindow(parent, type, name);
sCommonWindows.add(win);
+ // Prevent common windows from been IMe targets
+ win.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
return win;
}
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 6826975..a44860e 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -20,6 +20,7 @@
import android.app.usage.ExternalStorageStats;
import android.app.usage.IStorageStatsManager;
import android.app.usage.StorageStats;
+import android.app.usage.UsageStatsManagerInternal;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -28,25 +29,35 @@
import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.StatFs;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageEventListener;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
+import android.text.format.DateUtils;
import android.util.Slog;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
+import com.android.server.IoThread;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
+import com.android.server.storage.CacheQuotaStrategy;
public class StorageStatsService extends IStorageStatsManager.Stub {
private static final String TAG = "StorageStatsService";
private static final String PROP_VERIFY_STORAGE = "fw.verify_storage";
+ private static final long DELAY_IN_MILLIS = 30 * DateUtils.SECOND_IN_MILLIS;
+
public static class Lifecycle extends SystemService {
private StorageStatsService mService;
@@ -68,6 +79,7 @@
private final StorageManager mStorage;
private final Installer mInstaller;
+ private final H mHandler;
public StorageStatsService(Context context) {
mContext = Preconditions.checkNotNull(context);
@@ -80,6 +92,9 @@
mInstaller.onStart();
invalidateMounts();
+ mHandler = new H(IoThread.get().getLooper());
+ mHandler.sendEmptyMessageDelayed(H.MSG_CHECK_STORAGE_DELTA, DELAY_IN_MILLIS);
+
mStorage.registerListener(new StorageEventListener() {
@Override
public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
@@ -115,6 +130,17 @@
}
@Override
+ public boolean isQuotaSupported(String volumeUuid, String callingPackage) {
+ enforcePermission(Binder.getCallingUid(), callingPackage);
+
+ try {
+ return mInstaller.isQuotaSupported(volumeUuid);
+ } catch (InstallerException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
public long getTotalBytes(String volumeUuid, String callingPackage) {
enforcePermission(Binder.getCallingUid(), callingPackage);
@@ -274,4 +300,63 @@
res.cacheBytes = stats.cacheSize + stats.externalCacheSize;
return res;
}
+
+ private class H extends Handler {
+ private static final int MSG_CHECK_STORAGE_DELTA = 100;
+ /**
+ * By only triggering a re-calculation after the storage has changed sizes, we can avoid
+ * recalculating quotas too often. Minimum change delta defines the percentage of change
+ * we need to see before we recalculate.
+ */
+ private static final double MINIMUM_CHANGE_DELTA = 0.05;
+ private static final boolean DEBUG = false;
+
+ private final StatFs mStats;
+ private long mPreviousBytes;
+ private double mMinimumThresholdBytes;
+
+ public H(Looper looper) {
+ super(looper);
+ // TODO: Handle all private volumes.
+ mStats = new StatFs(Environment.getDataDirectory().getAbsolutePath());
+ mPreviousBytes = mStats.getFreeBytes();
+ mMinimumThresholdBytes = mStats.getTotalBytes() * MINIMUM_CHANGE_DELTA;
+ // TODO: Load cache quotas from a file to avoid re-doing work.
+ }
+
+ public void handleMessage(Message msg) {
+ if (DEBUG) {
+ Slog.v(TAG, ">>> handling " + msg.what);
+ }
+ switch (msg.what) {
+ case MSG_CHECK_STORAGE_DELTA: {
+ long bytesDelta = Math.abs(mPreviousBytes - mStats.getFreeBytes());
+ if (bytesDelta > mMinimumThresholdBytes) {
+ mPreviousBytes = mStats.getFreeBytes();
+ recalculateQuotas();
+ }
+ sendEmptyMessageDelayed(MSG_CHECK_STORAGE_DELTA, DELAY_IN_MILLIS);
+ break;
+ }
+ default:
+ if (DEBUG) {
+ Slog.v(TAG, ">>> default message case ");
+ }
+ return;
+ }
+ }
+
+ private void recalculateQuotas() {
+ if (DEBUG) {
+ Slog.v(TAG, ">>> recalculating quotas ");
+ }
+
+ UsageStatsManagerInternal usageStatsManager =
+ LocalServices.getService(UsageStatsManagerInternal.class);
+ CacheQuotaStrategy strategy = new CacheQuotaStrategy(
+ mContext, usageStatsManager, mInstaller);
+ // TODO: Save cache quotas to an XML file.
+ strategy.recalculateQuotas();
+ }
+ }
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 7a69803..3c743b5 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1603,5 +1603,12 @@
userStats.applyRestoredPayload(key, payload);
}
}
+
+ @Override
+ public List<UsageStats> queryUsageStatsForUser(
+ int userId, int intervalType, long beginTime, long endTime) {
+ return UsageStatsService.this.queryUsageStats(
+ userId, intervalType, beginTime, endTime);
+ }
}
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index c2c724f..7c9c0e8 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -5522,6 +5522,44 @@
}
/**
+ * Set SIM card power state. Request is equivalent to inserting or removing the card.
+ *
+ * @param powerUp True if powering up the SIM, otherwise powering down
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+ *
+ * @hide
+ **/
+ public void setSimPowerState(boolean powerUp) {
+ setSimPowerStateForSlot(getDefaultSim(), powerUp);
+ }
+
+ /**
+ * Set SIM card power state. Request is equivalent to inserting or removing the card.
+ *
+ * @param slotId SIM slot id
+ * @param powerUp True if powering up the SIM, otherwise powering down
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+ *
+ * @hide
+ **/
+ public void setSimPowerStateForSlot(int slotId, boolean powerUp) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ telephony.setSimPowerStateForSlot(slotId, powerUp);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#setSimPowerStateForSlot", e);
+ } catch (SecurityException e) {
+ Log.e(TAG, "Permission error calling ITelephony#setSimPowerStateForSlot", e);
+ }
+ }
+
+ /**
* Set baseband version for the default phone.
*
* @param version baseband version
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index 0f865a8..fe8dbfb 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -128,23 +128,23 @@
}
@Override
- public boolean isConnected(int slotId, int featureType, int sessionId, int callSessionType,
- int callType) throws RemoteException {
+ public boolean isConnected(int slotId, int featureType, int callSessionType, int callType)
+ throws RemoteException {
synchronized (mFeatures) {
MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
if (feature != null) {
- return feature.isConnected(sessionId, callSessionType, callType);
+ return feature.isConnected(callSessionType, callType);
}
}
return false;
}
@Override
- public boolean isOpened(int slotId, int featureType, int sessionId) throws RemoteException {
+ public boolean isOpened(int slotId, int featureType) throws RemoteException {
synchronized (mFeatures) {
MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
if (feature != null) {
- return feature.isOpened(sessionId);
+ return feature.isOpened();
}
}
return false;
@@ -166,23 +166,23 @@
}
@Override
- public void addRegistrationListener(int slotId, int featureType, int sessionId,
+ public void addRegistrationListener(int slotId, int featureType,
IImsRegistrationListener listener) throws RemoteException {
synchronized (mFeatures) {
MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
if (feature != null) {
- feature.addRegistrationListener(sessionId, listener);
+ feature.addRegistrationListener(listener);
}
}
}
@Override
- public void removeRegistrationListener(int slotId, int featureType, int sessionId,
+ public void removeRegistrationListener(int slotId, int featureType,
IImsRegistrationListener listener) throws RemoteException {
synchronized (mFeatures) {
MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
if (feature != null) {
- feature.removeRegistrationListener(sessionId, listener);
+ feature.removeRegistrationListener(listener);
}
}
}
@@ -224,79 +224,79 @@
}
@Override
- public IImsUt getUtInterface(int slotId, int featureType, int sessionId)
+ public IImsUt getUtInterface(int slotId, int featureType)
throws RemoteException {
synchronized (mFeatures) {
MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
if (feature != null) {
- return feature.getUtInterface(sessionId);
+ return feature.getUtInterface();
}
}
return null;
}
@Override
- public IImsConfig getConfigInterface(int slotId, int featureType, int sessionId)
+ public IImsConfig getConfigInterface(int slotId, int featureType)
throws RemoteException {
synchronized (mFeatures) {
MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
if (feature != null) {
- return feature.getConfigInterface(sessionId);
+ return feature.getConfigInterface();
}
}
return null;
}
@Override
- public void turnOnIms(int slotId, int featureType, int sessionId) throws RemoteException {
+ public void turnOnIms(int slotId, int featureType) throws RemoteException {
synchronized (mFeatures) {
MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
if (feature != null) {
- feature.turnOnIms(sessionId);
+ feature.turnOnIms();
}
}
}
@Override
- public void turnOffIms(int slotId, int featureType, int sessionId) throws RemoteException {
+ public void turnOffIms(int slotId, int featureType) throws RemoteException {
synchronized (mFeatures) {
MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
if (feature != null) {
- feature.turnOffIms(sessionId);
+ feature.turnOffIms();
}
}
}
@Override
- public IImsEcbm getEcbmInterface(int slotId, int featureType, int sessionId)
+ public IImsEcbm getEcbmInterface(int slotId, int featureType)
throws RemoteException {
synchronized (mFeatures) {
MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
if (feature != null) {
- return feature.getEcbmInterface(sessionId);
+ return feature.getEcbmInterface();
}
}
return null;
}
@Override
- public void setUiTTYMode(int slotId, int featureType, int sessionId, int uiTtyMode,
- Message onComplete) throws RemoteException {
+ public void setUiTTYMode(int slotId, int featureType, int uiTtyMode, Message onComplete)
+ throws RemoteException {
synchronized (mFeatures) {
MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
if (feature != null) {
- feature.setUiTTYMode(sessionId, uiTtyMode, onComplete);
+ feature.setUiTTYMode(uiTtyMode, onComplete);
}
}
}
@Override
- public IImsMultiEndpoint getMultiEndpointInterface(int slotId, int featureType,
- int sessionId) throws RemoteException {
+ public IImsMultiEndpoint getMultiEndpointInterface(int slotId, int featureType)
+ throws RemoteException {
synchronized (mFeatures) {
MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
if (feature != null) {
- return feature.getMultiEndpointInterface(sessionId);
+ return feature.getMultiEndpointInterface();
}
}
return null;
diff --git a/telephony/java/android/telephony/ims/ImsServiceProxy.java b/telephony/java/android/telephony/ims/ImsServiceProxy.java
index b2cdba2..38ea6e6f 100644
--- a/telephony/java/android/telephony/ims/ImsServiceProxy.java
+++ b/telephony/java/android/telephony/ims/ImsServiceProxy.java
@@ -135,40 +135,40 @@
}
@Override
- public boolean isConnected(int sessionId, int callServiceType, int callType)
+ public boolean isConnected(int callServiceType, int callType)
throws RemoteException {
synchronized (mLock) {
checkBinderConnection();
- return getServiceInterface(mBinder).isConnected(mSlotId, mSupportedFeature, sessionId,
+ return getServiceInterface(mBinder).isConnected(mSlotId, mSupportedFeature,
callServiceType, callType);
}
}
@Override
- public boolean isOpened(int sessionId) throws RemoteException {
+ public boolean isOpened() throws RemoteException {
synchronized (mLock) {
checkBinderConnection();
- return getServiceInterface(mBinder).isOpened(mSlotId, mSupportedFeature, sessionId);
+ return getServiceInterface(mBinder).isOpened(mSlotId, mSupportedFeature);
}
}
@Override
- public void addRegistrationListener(int sessionId, IImsRegistrationListener listener)
+ public void addRegistrationListener(IImsRegistrationListener listener)
throws RemoteException {
synchronized (mLock) {
checkBinderConnection();
getServiceInterface(mBinder).addRegistrationListener(mSlotId, mSupportedFeature,
- sessionId, listener);
+ listener);
}
}
@Override
- public void removeRegistrationListener(int sessionId, IImsRegistrationListener listener)
+ public void removeRegistrationListener(IImsRegistrationListener listener)
throws RemoteException {
synchronized (mLock) {
checkBinderConnection();
getServiceInterface(mBinder).removeRegistrationListener(mSlotId, mSupportedFeature,
- sessionId, listener);
+ listener);
}
}
@@ -203,64 +203,61 @@
}
@Override
- public IImsUt getUtInterface(int sessionId) throws RemoteException {
+ public IImsUt getUtInterface() throws RemoteException {
synchronized (mLock) {
checkBinderConnection();
- return getServiceInterface(mBinder).getUtInterface(mSlotId, mSupportedFeature,
- sessionId);
+ return getServiceInterface(mBinder).getUtInterface(mSlotId, mSupportedFeature);
}
}
@Override
- public IImsConfig getConfigInterface(int sessionId) throws RemoteException {
+ public IImsConfig getConfigInterface() throws RemoteException {
synchronized (mLock) {
checkBinderConnection();
- return getServiceInterface(mBinder).getConfigInterface(mSlotId, mSupportedFeature,
- sessionId);
+ return getServiceInterface(mBinder).getConfigInterface(mSlotId, mSupportedFeature);
}
}
@Override
- public void turnOnIms(int sessionId) throws RemoteException {
+ public void turnOnIms() throws RemoteException {
synchronized (mLock) {
checkBinderConnection();
- getServiceInterface(mBinder).turnOnIms(mSlotId, mSupportedFeature, sessionId);
+ getServiceInterface(mBinder).turnOnIms(mSlotId, mSupportedFeature);
}
}
@Override
- public void turnOffIms(int sessionId) throws RemoteException {
+ public void turnOffIms() throws RemoteException {
synchronized (mLock) {
checkBinderConnection();
- getServiceInterface(mBinder).turnOffIms(mSlotId, mSupportedFeature, sessionId);
+ getServiceInterface(mBinder).turnOffIms(mSlotId, mSupportedFeature);
}
}
@Override
- public IImsEcbm getEcbmInterface(int sessionId) throws RemoteException {
+ public IImsEcbm getEcbmInterface() throws RemoteException {
synchronized (mLock) {
checkBinderConnection();
- return getServiceInterface(mBinder).getEcbmInterface(mSlotId, mSupportedFeature,
- sessionId);
+ return getServiceInterface(mBinder).getEcbmInterface(mSlotId, mSupportedFeature);
}
}
@Override
- public void setUiTTYMode(int sessionId, int uiTtyMode, Message onComplete)
+ public void setUiTTYMode(int uiTtyMode, Message onComplete)
throws RemoteException {
synchronized (mLock) {
checkBinderConnection();
- getServiceInterface(mBinder).setUiTTYMode(mSlotId, mSupportedFeature, sessionId,
- uiTtyMode, onComplete);
+ getServiceInterface(mBinder).setUiTTYMode(mSlotId, mSupportedFeature, uiTtyMode,
+ onComplete);
}
}
@Override
- public IImsMultiEndpoint getMultiEndpointInterface(int sessionId) throws RemoteException {
+ public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
synchronized (mLock) {
checkBinderConnection();
return getServiceInterface(mBinder).getMultiEndpointInterface(mSlotId,
- mSupportedFeature, sessionId);
+ mSupportedFeature);
}
}
diff --git a/telephony/java/android/telephony/ims/ImsServiceProxyCompat.java b/telephony/java/android/telephony/ims/ImsServiceProxyCompat.java
index ff53858..bbd5f02 100644
--- a/telephony/java/android/telephony/ims/ImsServiceProxyCompat.java
+++ b/telephony/java/android/telephony/ims/ImsServiceProxyCompat.java
@@ -42,6 +42,8 @@
public class ImsServiceProxyCompat implements IMMTelFeature {
+ private static final int SERVICE_ID = ImsFeature.MMTEL;
+
protected final int mSlotId;
protected IBinder mBinder;
@@ -65,29 +67,28 @@
}
@Override
- public boolean isConnected(int sessionId, int callServiceType, int callType)
+ public boolean isConnected(int callServiceType, int callType)
throws RemoteException {
checkBinderConnection();
- return getServiceInterface(mBinder).isConnected(sessionId, callServiceType, callType);
+ return getServiceInterface(mBinder).isConnected(SERVICE_ID, callServiceType, callType);
}
@Override
- public boolean isOpened(int sessionId) throws RemoteException {
+ public boolean isOpened() throws RemoteException {
checkBinderConnection();
- return getServiceInterface(mBinder).isOpened(sessionId);
+ return getServiceInterface(mBinder).isOpened(SERVICE_ID);
}
@Override
- public void addRegistrationListener(int sessionId, IImsRegistrationListener listener)
+ public void addRegistrationListener(IImsRegistrationListener listener)
throws RemoteException {
checkBinderConnection();
getServiceInterface(mBinder).addRegistrationListener(mSlotId, ImsFeature.MMTEL, listener);
}
@Override
- public void removeRegistrationListener(int sessionId, IImsRegistrationListener listener)
+ public void removeRegistrationListener(IImsRegistrationListener listener)
throws RemoteException {
- checkBinderConnection();
// Not Implemented in old ImsService. If the registration listener becomes invalid, the
// ImsService will remove.
}
@@ -114,46 +115,46 @@
}
@Override
- public IImsUt getUtInterface(int sessionId) throws RemoteException {
+ public IImsUt getUtInterface() throws RemoteException {
checkBinderConnection();
- return getServiceInterface(mBinder).getUtInterface(sessionId);
+ return getServiceInterface(mBinder).getUtInterface(SERVICE_ID);
}
@Override
- public IImsConfig getConfigInterface(int sessionId) throws RemoteException {
+ public IImsConfig getConfigInterface() throws RemoteException {
checkBinderConnection();
return getServiceInterface(mBinder).getConfigInterface(mSlotId);
}
@Override
- public void turnOnIms(int sessionId) throws RemoteException {
+ public void turnOnIms() throws RemoteException {
checkBinderConnection();
getServiceInterface(mBinder).turnOnIms(mSlotId);
}
@Override
- public void turnOffIms(int sessionId) throws RemoteException {
+ public void turnOffIms() throws RemoteException {
checkBinderConnection();
getServiceInterface(mBinder).turnOffIms(mSlotId);
}
@Override
- public IImsEcbm getEcbmInterface(int sessionId) throws RemoteException {
+ public IImsEcbm getEcbmInterface() throws RemoteException {
checkBinderConnection();
- return getServiceInterface(mBinder).getEcbmInterface(sessionId);
+ return getServiceInterface(mBinder).getEcbmInterface(SERVICE_ID);
}
@Override
- public void setUiTTYMode(int sessionId, int uiTtyMode, Message onComplete)
+ public void setUiTTYMode(int uiTtyMode, Message onComplete)
throws RemoteException {
checkBinderConnection();
- getServiceInterface(mBinder).setUiTTYMode(sessionId, uiTtyMode, onComplete);
+ getServiceInterface(mBinder).setUiTTYMode(SERVICE_ID, uiTtyMode, onComplete);
}
@Override
- public IImsMultiEndpoint getMultiEndpointInterface(int sessionId) throws RemoteException {
+ public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
checkBinderConnection();
- return getServiceInterface(mBinder).getMultiEndpointInterface(sessionId);
+ return getServiceInterface(mBinder).getMultiEndpointInterface(SERVICE_ID);
}
/**
diff --git a/telephony/java/android/telephony/ims/feature/IMMTelFeature.java b/telephony/java/android/telephony/ims/feature/IMMTelFeature.java
index e180843..d65e27e 100644
--- a/telephony/java/android/telephony/ims/feature/IMMTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/IMMTelFeature.java
@@ -68,7 +68,6 @@
* Checks if the IMS service has successfully registered to the IMS network with the specified
* service & call type.
*
- * @param sessionId a session id which is obtained from {@link #startSession}
* @param callServiceType a service type that is specified in {@link ImsCallProfile}
* {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
* {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
@@ -80,31 +79,28 @@
* @return true if the specified service id is connected to the IMS network; false otherwise
* @throws RemoteException
*/
- boolean isConnected(int sessionId, int callServiceType, int callType) throws RemoteException;
+ boolean isConnected(int callServiceType, int callType) throws RemoteException;
/**
* Checks if the specified IMS service is opened.
*
- * @param sessionId a service id which is obtained from {@link #startSession}
* @return true if the specified service id is opened; false otherwise
*/
- boolean isOpened(int sessionId) throws RemoteException;
+ boolean isOpened() throws RemoteException;
/**
* Add a new registration listener for the client associated with the session Id.
- * @param sessionId a session id which is obtained from {@link #startSession}
* @param listener An implementation of IImsRegistrationListener.
*/
- void addRegistrationListener(int sessionId, IImsRegistrationListener listener)
+ void addRegistrationListener(IImsRegistrationListener listener)
throws RemoteException;
/**
* Remove a previously registered listener using {@link #addRegistrationListener} for the client
* associated with the session Id.
- * @param sessionId a session id which is obtained from {@link #startSession}
* @param listener A previously registered IImsRegistrationListener
*/
- void removeRegistrationListener(int sessionId, IImsRegistrationListener listener)
+ void removeRegistrationListener(IImsRegistrationListener listener)
throws RemoteException;
/**
@@ -152,41 +148,40 @@
/**
* @return The Ut interface for the supplementary service configuration.
*/
- IImsUt getUtInterface(int sessionId) throws RemoteException;
+ IImsUt getUtInterface() throws RemoteException;
/**
* @return The config interface for IMS Configuration
*/
- IImsConfig getConfigInterface(int sessionId) throws RemoteException;
+ IImsConfig getConfigInterface() throws RemoteException;
/**
* Signal the MMTelFeature to turn on IMS when it has been turned off using {@link #turnOffIms}
* @param sessionId a session id which is obtained from {@link #startSession}
*/
- void turnOnIms(int sessionId) throws RemoteException;
+ void turnOnIms() throws RemoteException;
/**
* Signal the MMTelFeature to turn off IMS when it has been turned on using {@link #turnOnIms}
* @param sessionId a session id which is obtained from {@link #startSession}
*/
- void turnOffIms(int sessionId) throws RemoteException;
+ void turnOffIms() throws RemoteException;
/**
* @return The Emergency call-back mode interface for emergency VoLTE calls that support it.
*/
- IImsEcbm getEcbmInterface(int sessionId) throws RemoteException;
+ IImsEcbm getEcbmInterface() throws RemoteException;
/**
* Sets the current UI TTY mode for the MMTelFeature.
- * @param sessionId a session id which is obtained from {@link #startSession}
* @param uiTtyMode An integer containing the new UI TTY Mode.
* @param onComplete A {@link Message} to be used when the mode has been set.
* @throws RemoteException
*/
- void setUiTTYMode(int sessionId, int uiTtyMode, Message onComplete) throws RemoteException;
+ void setUiTTYMode(int uiTtyMode, Message onComplete) throws RemoteException;
/**
* @return MultiEndpoint interface for DEP notifications
*/
- IImsMultiEndpoint getMultiEndpointInterface(int sessionId) throws RemoteException;
+ IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException;
}
diff --git a/telephony/java/android/telephony/ims/feature/MMTelFeature.java b/telephony/java/android/telephony/ims/feature/MMTelFeature.java
index 570cd65..a71f0bf 100644
--- a/telephony/java/android/telephony/ims/feature/MMTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MMTelFeature.java
@@ -50,21 +50,21 @@
}
@Override
- public boolean isConnected(int sessionId, int callSessionType, int callType) {
+ public boolean isConnected(int callSessionType, int callType) {
return false;
}
@Override
- public boolean isOpened(int sessionId) {
+ public boolean isOpened() {
return false;
}
@Override
- public void addRegistrationListener(int sessionId, IImsRegistrationListener listener) {
+ public void addRegistrationListener(IImsRegistrationListener listener) {
}
@Override
- public void removeRegistrationListener(int sessionId, IImsRegistrationListener listener) {
+ public void removeRegistrationListener(IImsRegistrationListener listener) {
}
@Override
@@ -84,34 +84,34 @@
}
@Override
- public IImsUt getUtInterface(int sessionId) {
+ public IImsUt getUtInterface() {
return null;
}
@Override
- public IImsConfig getConfigInterface(int sessionId) {
+ public IImsConfig getConfigInterface() {
return null;
}
@Override
- public void turnOnIms(int sessionId) {
+ public void turnOnIms() {
}
@Override
- public void turnOffIms(int sessionId) {
+ public void turnOffIms() {
}
@Override
- public IImsEcbm getEcbmInterface(int sessionId) {
+ public IImsEcbm getEcbmInterface() {
return null;
}
@Override
- public void setUiTTYMode(int sessionId, int uiTtyMode, Message onComplete) {
+ public void setUiTTYMode(int uiTtyMode, Message onComplete) {
}
@Override
- public IImsMultiEndpoint getMultiEndpointInterface(int sessionId) {
+ public IImsMultiEndpoint getMultiEndpointInterface() {
return null;
}
diff --git a/telephony/java/com/android/ims/internal/IImsServiceController.aidl b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
index b700f49..712816f 100644
--- a/telephony/java/com/android/ims/internal/IImsServiceController.aidl
+++ b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
@@ -42,24 +42,23 @@
int startSession(int slotId, int featureType, in PendingIntent incomingCallIntent,
in IImsRegistrationListener listener);
void endSession(int slotId, int featureType, int sessionId);
- boolean isConnected(int slotId, int featureType, int sessionId, int callSessionType, int callType);
- boolean isOpened(int slotId, int featureType, int sessionId);
+ boolean isConnected(int slotId, int featureType, int callSessionType, int callType);
+ boolean isOpened(int slotId, int featureType);
int getFeatureStatus(int slotId, int featureType);
- void addRegistrationListener(int slotId, int featureType, int sessionId,
+ void addRegistrationListener(int slotId, int featureType, in IImsRegistrationListener listener);
+ void removeRegistrationListener(int slotId, int featureType,
in IImsRegistrationListener listener);
- void removeRegistrationListener(int slotId, int featureType, int sessionId,
- in IImsRegistrationListener listener);
- ImsCallProfile createCallProfile(int slotId, int featureType, int sessionId, int callSessionType, int callType);
+ ImsCallProfile createCallProfile(int slotId, int featureType, int sessionId,
+ int callSessionType, int callType);
IImsCallSession createCallSession(int slotId, int featureType, int sessionId,
in ImsCallProfile profile, IImsCallSessionListener listener);
IImsCallSession getPendingCallSession(int slotId, int featureType, int sessionId,
String callId);
- IImsUt getUtInterface(int slotId, int featureType, int sessionId);
- IImsConfig getConfigInterface(int slotId, int featureType, int sessionId);
- void turnOnIms(int slotId, int featureType, int sessionId);
- void turnOffIms(int slotId, int featureType, int sessionId);
- IImsEcbm getEcbmInterface(int slotId, int featureType, int sessionId);
- void setUiTTYMode(int slotId, int featureType, int sessionId, int uiTtyMode,
- in Message onComplete);
- IImsMultiEndpoint getMultiEndpointInterface(int slotId, int featureType, int sessionId);
+ IImsUt getUtInterface(int slotId, int featureType);
+ IImsConfig getConfigInterface(int slotId, int featureType);
+ void turnOnIms(int slotId, int featureType);
+ void turnOffIms(int slotId, int featureType);
+ IImsEcbm getEcbmInterface(int slotId, int featureType);
+ void setUiTTYMode(int slotId, int featureType, int uiTtyMode, in Message onComplete);
+ IImsMultiEndpoint getMultiEndpointInterface(int slotId, int featureType);
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index e6a6178..88daf64 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1287,4 +1287,12 @@
* @hide
*/
List<ClientRequestStats> getClientRequestStats(String callingPackage, int subid);
+
+ /**
+ * Set SIM card power state. Request is equivalent to inserting or removing the card.
+ * @param slotId SIM slot id
+ * @param powerUp True if powering up the SIM, otherwise powering down
+ * @hide
+ * */
+ void setSimPowerStateForSlot(int slotId, boolean powerUp);
}
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index 9ffd92d..3f5ca84 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -739,6 +739,12 @@
throw new UnsupportedOperationException();
}
+ /** @hide */
+ @Override
+ public void updateDisplay(int displayId) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public File[] getExternalFilesDirs(String type) {
throw new UnsupportedOperationException();
@@ -759,6 +765,10 @@
throw new UnsupportedOperationException();
}
+ /** @hide **/
+ @Override
+ public File getPreloadsFileCache() { throw new UnsupportedOperationException(); }
+
@Override
public Context createDeviceProtectedStorageContext() {
throw new UnsupportedOperationException();
diff --git a/tests/SharedLibrary/client/res/layout/main.xml b/tests/SharedLibrary/client/res/layout/main.xml
index 067ef9f..7dfe965 100644
--- a/tests/SharedLibrary/client/res/layout/main.xml
+++ b/tests/SharedLibrary/client/res/layout/main.xml
@@ -15,7 +15,7 @@
xmlns:custom="http://schemas.android.com/apk/res/com.google.android.test.shared_library"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- custom:name="Professor Android"
+ custom:name="@string/name"
custom:streetNumber="44"
custom:streetName="KitKat Lane"
custom:city="AndroidVille"
diff --git a/tests/SharedLibrary/client/res/values/strings.xml b/tests/SharedLibrary/client/res/values/strings.xml
index d9efdc7..63043ed 100644
--- a/tests/SharedLibrary/client/res/values/strings.xml
+++ b/tests/SharedLibrary/client/res/values/strings.xml
@@ -16,5 +16,6 @@
<resources>
<string name="app_title">SharedLibrary client</string>
+ <string name="name">Professor Android</string>
<string name="changes">@com.google.android.test.shared_library:string/shared_string</string>
</resources>
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index cf5badc..881ac87 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -721,11 +721,6 @@
XMLNode::attribute_entry* existingEntry = node->editAttribute(ns, attr);
if (existingEntry != NULL) {
if (replaceExisting) {
- if (kIsDebug) {
- printf("Info: AndroidManifest.xml already defines %s (in %s);"
- " overwriting existing value from manifest.\n",
- String8(attr).string(), String8(ns).string());
- }
existingEntry->string = String16(value);
return true;
}
@@ -737,10 +732,6 @@
return false;
}
- fprintf(stderr, "Warning: AndroidManifest.xml already defines %s (in %s);"
- " using existing value in manifest.\n",
- String8(attr).string(), String8(ns).string());
-
// don't stop the build.
return true;
}
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index 3d7bd94..407550b 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -94,7 +94,9 @@
// The resource table needs to be reserialized since it might have changed.
if (path == "resources.arsc") {
BigBuffer buffer = BigBuffer(1024);
- TableFlattener flattener(&buffer);
+ // TODO(adamlesinski): How to determine if there were sparse entries (and if to encode
+ // with sparse entries) b/35389232.
+ TableFlattener flattener({}, &buffer);
if (!flattener.Consume(context, table_.get())) {
return false;
}
diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h
index 98ba94b..c2ee252 100644
--- a/tools/aapt2/SdkConstants.h
+++ b/tools/aapt2/SdkConstants.h
@@ -25,7 +25,7 @@
namespace aapt {
-enum {
+enum : int {
SDK_CUPCAKE = 3,
SDK_DONUT = 4,
SDK_ECLAIR = 5,
@@ -49,7 +49,7 @@
SDK_MARSHMALLOW = 23,
SDK_NOUGAT = 24,
SDK_NOUGAT_MR1 = 25,
- SDK_O = 26, // STOPSHIP Replace with real version
+ SDK_O = 26, // STOPSHIP Replace with real version
};
size_t FindAttributeSdkLevel(const ResourceId& id);
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
index 19d030e..697b07f 100644
--- a/tools/aapt2/flatten/TableFlattener.cpp
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -26,6 +26,7 @@
#include "ResourceTable.h"
#include "ResourceValues.h"
+#include "SdkConstants.h"
#include "ValueVisitor.h"
#include "flatten/ChunkWriter.h"
#include "flatten/ResourceTypeExtensions.h"
@@ -216,8 +217,11 @@
class PackageFlattener {
public:
- PackageFlattener(IDiagnostics* diag, ResourceTablePackage* package)
- : diag_(diag), package_(package) {}
+ PackageFlattener(IAaptContext* context, ResourceTablePackage* package, bool use_sparse_entries)
+ : context_(context),
+ diag_(context->GetDiagnostics()),
+ package_(package),
+ use_sparse_entries_(use_sparse_entries) {}
bool FlattenPackage(BigBuffer* buffer) {
ChunkWriter pkg_writer(buffer);
@@ -298,9 +302,12 @@
return true;
}
- bool FlattenConfig(const ResourceTableType* type,
- const ConfigDescription& config,
- std::vector<FlatEntry>* entries, BigBuffer* buffer) {
+ bool FlattenConfig(const ResourceTableType* type, const ConfigDescription& config,
+ const size_t num_total_entries, std::vector<FlatEntry>* entries,
+ BigBuffer* buffer) {
+ CHECK(num_total_entries != 0);
+ CHECK(num_total_entries <= std::numeric_limits<uint16_t>::max());
+
ChunkWriter type_writer(buffer);
ResTable_type* type_header =
type_writer.StartChunk<ResTable_type>(RES_TABLE_TYPE_TYPE);
@@ -308,39 +315,60 @@
type_header->config = config;
type_header->config.swapHtoD();
- auto max_accum = [](uint32_t max,
- const std::unique_ptr<ResourceEntry>& a) -> uint32_t {
- return std::max(max, (uint32_t)a->id.value());
- };
+ std::vector<uint32_t> offsets;
+ offsets.resize(num_total_entries, 0xffffffffu);
- // Find the largest entry ID. That is how many entries we will have.
- const uint32_t entry_count =
- std::accumulate(type->entries.begin(), type->entries.end(), 0,
- max_accum) +
- 1;
-
- type_header->entryCount = util::HostToDevice32(entry_count);
- uint32_t* indices = type_writer.NextBlock<uint32_t>(entry_count);
-
- CHECK((size_t)entry_count <= std::numeric_limits<uint16_t>::max());
- memset(indices, 0xff, entry_count * sizeof(uint32_t));
-
- type_header->entriesStart = util::HostToDevice32(type_writer.size());
-
- const size_t entry_start = type_writer.buffer()->size();
+ BigBuffer values_buffer(512);
for (FlatEntry& flat_entry : *entries) {
- CHECK(flat_entry.entry->id.value() < entry_count);
- indices[flat_entry.entry->id.value()] =
- util::HostToDevice32(type_writer.buffer()->size() - entry_start);
- if (!FlattenValue(&flat_entry, type_writer.buffer())) {
+ CHECK(static_cast<size_t>(flat_entry.entry->id.value()) < num_total_entries);
+ offsets[flat_entry.entry->id.value()] = values_buffer.size();
+ if (!FlattenValue(&flat_entry, &values_buffer)) {
diag_->Error(DiagMessage()
<< "failed to flatten resource '"
- << ResourceNameRef(package_->name, type->type,
- flat_entry.entry->name)
+ << ResourceNameRef(package_->name, type->type, flat_entry.entry->name)
<< "' for configuration '" << config << "'");
return false;
}
}
+
+ bool sparse_encode = use_sparse_entries_;
+
+ // Only sparse encode if the entries will be read on platforms O+.
+ sparse_encode =
+ sparse_encode && (context_->GetMinSdkVersion() >= SDK_O || config.sdkVersion >= SDK_O);
+
+ // Only sparse encode if the offsets are representable in 2 bytes.
+ sparse_encode =
+ sparse_encode && (values_buffer.size() / 4u) <= std::numeric_limits<uint16_t>::max();
+
+ // Only sparse encode if the ratio of populated entries to total entries is below some
+ // threshold.
+ sparse_encode =
+ sparse_encode && ((100 * entries->size()) / num_total_entries) < kSparseEncodingThreshold;
+
+ if (sparse_encode) {
+ type_header->entryCount = util::HostToDevice32(entries->size());
+ type_header->flags |= ResTable_type::FLAG_SPARSE;
+ ResTable_sparseTypeEntry* indices =
+ type_writer.NextBlock<ResTable_sparseTypeEntry>(entries->size());
+ for (size_t i = 0; i < num_total_entries; i++) {
+ if (offsets[i] != ResTable_type::NO_ENTRY) {
+ CHECK((offsets[i] & 0x03) == 0);
+ indices->idx = util::HostToDevice16(i);
+ indices->offset = util::HostToDevice16(offsets[i] / 4u);
+ indices++;
+ }
+ }
+ } else {
+ type_header->entryCount = util::HostToDevice32(num_total_entries);
+ uint32_t* indices = type_writer.NextBlock<uint32_t>(num_total_entries);
+ for (size_t i = 0; i < num_total_entries; i++) {
+ indices[i] = util::HostToDevice32(offsets[i]);
+ }
+ }
+
+ type_header->entriesStart = util::HostToDevice32(type_writer.size());
+ type_writer.buffer()->AppendBuffer(std::move(values_buffer));
type_writer.Finish();
return true;
}
@@ -370,8 +398,7 @@
CHECK(bool(entry->id)) << "entry must have an ID set";
sorted_entries.push_back(entry.get());
}
- std::sort(sorted_entries.begin(), sorted_entries.end(),
- cmp_ids<ResourceEntry>);
+ std::sort(sorted_entries.begin(), sorted_entries.end(), cmp_ids<ResourceEntry>);
return sorted_entries;
}
@@ -443,22 +470,22 @@
type_pool_.MakeRef(ToString(type->type));
std::vector<ResourceEntry*> sorted_entries = CollectAndSortEntries(type);
-
if (!FlattenTypeSpec(type, &sorted_entries, buffer)) {
return false;
}
+ // Since the entries are sorted by ID, the last ID will be the largest.
+ const size_t num_entries = sorted_entries.back()->id.value() + 1;
+
// The binary resource table lists resource entries for each
// configuration.
// We store them inverted, where a resource entry lists the values for
// each
// configuration available. Here we reverse this to match the binary
// table.
- std::map<ConfigDescription, std::vector<FlatEntry>>
- config_to_entry_list_map;
+ std::map<ConfigDescription, std::vector<FlatEntry>> config_to_entry_list_map;
for (ResourceEntry* entry : sorted_entries) {
- const uint32_t key_index =
- (uint32_t)key_pool_.MakeRef(entry->name).index();
+ const uint32_t key_index = (uint32_t)key_pool_.MakeRef(entry->name).index();
// Group values by configuration.
for (auto& config_value : entry->values) {
@@ -469,7 +496,7 @@
// Flatten a configuration value.
for (auto& entry : config_to_entry_list_map) {
- if (!FlattenConfig(type, entry.first, &entry.second, buffer)) {
+ if (!FlattenConfig(type, entry.first, num_entries, &entry.second, buffer)) {
return false;
}
}
@@ -477,8 +504,10 @@
return true;
}
+ IAaptContext* context_;
IDiagnostics* diag_;
ResourceTablePackage* package_;
+ bool use_sparse_entries_;
StringPool type_pool_;
StringPool key_pool_;
};
@@ -513,7 +542,7 @@
// Flatten each package.
for (auto& package : table->packages) {
- PackageFlattener flattener(context->GetDiagnostics(), package.get());
+ PackageFlattener flattener(context, package.get(), options_.use_sparse_entries);
if (!flattener.FlattenPackage(&package_buffer)) {
return false;
}
diff --git a/tools/aapt2/flatten/TableFlattener.h b/tools/aapt2/flatten/TableFlattener.h
index 53f52c2..223aef8 100644
--- a/tools/aapt2/flatten/TableFlattener.h
+++ b/tools/aapt2/flatten/TableFlattener.h
@@ -25,15 +25,29 @@
namespace aapt {
+// The percentage of used entries for a type for which using a sparse encoding is
+// preferred.
+constexpr const size_t kSparseEncodingThreshold = 60;
+
+struct TableFlattenerOptions {
+ // When true, types for configurations with a sparse set of entries are encoded
+ // as a sparse map of entry ID and offset to actual data.
+ // This is only available on platforms O+ and will only be respected when
+ // minSdk is O+.
+ bool use_sparse_entries = false;
+};
+
class TableFlattener : public IResourceTableConsumer {
public:
- explicit TableFlattener(BigBuffer* buffer) : buffer_(buffer) {}
+ explicit TableFlattener(const TableFlattenerOptions& options, BigBuffer* buffer)
+ : options_(options), buffer_(buffer) {}
bool Consume(IAaptContext* context, ResourceTable* table) override;
private:
DISALLOW_COPY_AND_ASSIGN(TableFlattener);
+ TableFlattenerOptions options_;
BigBuffer* buffer_;
};
diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp
index c726240..ff71742 100644
--- a/tools/aapt2/flatten/TableFlattener_test.cpp
+++ b/tools/aapt2/flatten/TableFlattener_test.cpp
@@ -16,7 +16,10 @@
#include "flatten/TableFlattener.h"
+#include "android-base/stringprintf.h"
+
#include "ResourceUtils.h"
+#include "SdkConstants.h"
#include "test/Test.h"
#include "unflatten/BinaryResourceParser.h"
#include "util/Util.h"
@@ -34,32 +37,40 @@
.Build();
}
- ::testing::AssertionResult Flatten(ResourceTable* table,
- ResTable* out_table) {
+ ::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions& options,
+ ResourceTable* table, std::string* out_content) {
BigBuffer buffer(1024);
- TableFlattener flattener(&buffer);
- if (!flattener.Consume(context_.get(), table)) {
+ TableFlattener flattener(options, &buffer);
+ if (!flattener.Consume(context, table)) {
return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
}
+ *out_content = buffer.to_string();
+ return ::testing::AssertionSuccess();
+ }
- std::unique_ptr<uint8_t[]> data = util::Copy(buffer);
- if (out_table->add(data.get(), buffer.size(), -1, true) != NO_ERROR) {
+ ::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions& options,
+ ResourceTable* table, ResTable* out_table) {
+ std::string content;
+ auto result = Flatten(context, options, table, &content);
+ if (!result) {
+ return result;
+ }
+
+ if (out_table->add(content.data(), content.size(), -1, true) != NO_ERROR) {
return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
}
return ::testing::AssertionSuccess();
}
- ::testing::AssertionResult Flatten(ResourceTable* table,
- ResourceTable* out_table) {
- BigBuffer buffer(1024);
- TableFlattener flattener(&buffer);
- if (!flattener.Consume(context_.get(), table)) {
- return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
+ ::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions options,
+ ResourceTable* table, ResourceTable* out_table) {
+ std::string content;
+ auto result = Flatten(context, options, table, &content);
+ if (!result) {
+ return result;
}
- std::unique_ptr<uint8_t[]> data = util::Copy(buffer);
- BinaryResourceParser parser(context_.get(), out_table, {}, data.get(),
- buffer.size());
+ BinaryResourceParser parser(context, out_table, {}, content.data(), content.size());
if (!parser.Parse()) {
return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
}
@@ -127,7 +138,7 @@
return ::testing::AssertionSuccess();
}
- private:
+ protected:
std::unique_ptr<IAaptContext> context_;
};
@@ -153,7 +164,7 @@
.Build();
ResTable res_table;
- ASSERT_TRUE(Flatten(table.get(), &res_table));
+ ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &res_table));
EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020000),
{}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
@@ -200,7 +211,7 @@
.Build();
ResTable res_table;
- ASSERT_TRUE(Flatten(table.get(), &res_table));
+ ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &res_table));
EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020001),
{}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
@@ -222,7 +233,7 @@
.Build();
ResourceTable result;
- ASSERT_TRUE(Flatten(table.get(), &result));
+ ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &result));
Attribute* actualAttr =
test::GetValue<Attribute>(&result, "android:attr/foo");
@@ -233,4 +244,119 @@
EXPECT_EQ(attr.max_int, actualAttr->max_int);
}
+static std::unique_ptr<ResourceTable> BuildTableWithSparseEntries(
+ IAaptContext* context, const ConfigDescription& sparse_config, float load) {
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId(context->GetCompilationPackage(), context->GetPackageId())
+ .Build();
+
+ // Add regular entries.
+ int stride = static_cast<int>(1.0f / load);
+ for (int i = 0; i < 100; i++) {
+ const ResourceName name = test::ParseNameOrDie(
+ base::StringPrintf("%s:string/foo_%d", context->GetCompilationPackage().data(), i));
+ const ResourceId resid(context->GetPackageId(), 0x02, static_cast<uint16_t>(i));
+ const auto value =
+ util::make_unique<BinaryPrimitive>(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(i));
+ CHECK(table->AddResource(name, resid, ConfigDescription::DefaultConfig(), "",
+ std::unique_ptr<Value>(value->Clone(nullptr)),
+ context->GetDiagnostics()));
+
+ // Every few entries, write out a sparse_config value. This will give us the desired load.
+ if (i % stride == 0) {
+ CHECK(table->AddResource(name, resid, sparse_config, "",
+ std::unique_ptr<Value>(value->Clone(nullptr)),
+ context->GetDiagnostics()));
+ }
+ }
+ return table;
+}
+
+TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkO) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .SetCompilationPackage("android")
+ .SetPackageId(0x01)
+ .SetMinSdkVersion(SDK_O)
+ .Build();
+
+ const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
+ auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
+
+ TableFlattenerOptions options;
+ options.use_sparse_entries = true;
+
+ std::string no_sparse_contents;
+ ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
+
+ std::string sparse_contents;
+ ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
+
+ EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
+
+ // Attempt to parse the sparse contents.
+
+ ResourceTable sparse_table;
+ BinaryResourceParser parser(context.get(), &sparse_table, Source("test.arsc"),
+ sparse_contents.data(), sparse_contents.size());
+ ASSERT_TRUE(parser.Parse());
+
+ auto value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_0",
+ sparse_config);
+ ASSERT_NE(nullptr, value);
+ EXPECT_EQ(0u, value->value.data);
+
+ ASSERT_EQ(nullptr, test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_1",
+ sparse_config));
+
+ value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_4",
+ sparse_config);
+ ASSERT_NE(nullptr, value);
+ EXPECT_EQ(4u, value->value.data);
+}
+
+TEST_F(TableFlattenerTest, FlattenSparseEntryWithConfigSdkVersionO) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .SetCompilationPackage("android")
+ .SetPackageId(0x01)
+ .SetMinSdkVersion(SDK_LOLLIPOP)
+ .Build();
+
+ const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB-v26");
+ auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
+
+ TableFlattenerOptions options;
+ options.use_sparse_entries = true;
+
+ std::string no_sparse_contents;
+ ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
+
+ std::string sparse_contents;
+ ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
+
+ EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
+}
+
+TEST_F(TableFlattenerTest, DoNotUseSparseEntryForDenseConfig) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .SetCompilationPackage("android")
+ .SetPackageId(0x01)
+ .SetMinSdkVersion(SDK_O)
+ .Build();
+
+ const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
+ auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.80f);
+
+ TableFlattenerOptions options;
+ options.use_sparse_entries = true;
+
+ std::string no_sparse_contents;
+ ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
+
+ std::string sparse_contents;
+ ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
+
+ EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size());
+}
+
} // namespace aapt
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 0501a3b..f07e20b 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -96,6 +96,9 @@
// Products to use/filter on.
std::unordered_set<std::string> products;
+ // Flattening options.
+ TableFlattenerOptions table_flattener_options;
+
// Split APK options.
TableSplitterOptions table_splitter_options;
std::vector<SplitConstraints> split_constraints;
@@ -874,7 +877,7 @@
bool FlattenTable(ResourceTable* table, IArchiveWriter* writer) {
BigBuffer buffer(1024);
- TableFlattener flattener(&buffer);
+ TableFlattener flattener(options_.table_flattener_options, &buffer);
if (!flattener.Consume(context_, table)) {
return false;
}
@@ -1870,24 +1873,19 @@
.RequiredFlag("-o", "Output path", &options.output_path)
.RequiredFlag("--manifest", "Path to the Android manifest to build",
&options.manifest_path)
- .OptionalFlagList("-I", "Adds an Android APK to link against",
- &options.include_paths)
- .OptionalFlagList(
- "-R",
- "Compilation unit to link, using `overlay` semantics.\n"
- "The last conflicting resource given takes precedence.",
- &overlay_arg_list)
+ .OptionalFlagList("-I", "Adds an Android APK to link against", &options.include_paths)
+ .OptionalFlagList("-R",
+ "Compilation unit to link, using `overlay` semantics.\n"
+ "The last conflicting resource given takes precedence.",
+ &overlay_arg_list)
.OptionalFlag("--java", "Directory in which to generate R.java",
&options.generate_java_class_path)
- .OptionalFlag("--proguard",
- "Output file for generated Proguard rules",
+ .OptionalFlag("--proguard", "Output file for generated Proguard rules",
&options.generate_proguard_rules_path)
- .OptionalFlag(
- "--proguard-main-dex",
- "Output file for generated Proguard rules for the main dex",
- &options.generate_main_dex_proguard_rules_path)
- .OptionalSwitch("--no-auto-version",
- "Disables automatic style and layout SDK versioning",
+ .OptionalFlag("--proguard-main-dex",
+ "Output file for generated Proguard rules for the main dex",
+ &options.generate_main_dex_proguard_rules_path)
+ .OptionalSwitch("--no-auto-version", "Disables automatic style and layout SDK versioning",
&options.no_auto_version)
.OptionalSwitch("--no-version-vectors",
"Disables automatic versioning of vector drawables. "
@@ -1903,25 +1901,22 @@
"Disables automatic deduping of resources with\n"
"identical values across compatible configurations.",
&options.no_resource_deduping)
- .OptionalSwitch(
- "-x",
- "Legacy flag that specifies to use the package identifier 0x01",
- &legacy_x_flag)
- .OptionalSwitch("-z",
- "Require localization of strings marked 'suggested'",
+ .OptionalSwitch("--enable-sparse-encoding",
+ "Enables encoding sparse entries using a binary search tree.\n"
+ "This decreases APK size at the cost of resource retrieval performance.",
+ &options.table_flattener_options.use_sparse_entries)
+ .OptionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01",
+ &legacy_x_flag)
+ .OptionalSwitch("-z", "Require localization of strings marked 'suggested'",
&require_localization)
- .OptionalFlag(
- "-c",
- "Comma separated list of configurations to include. The default\n"
- "is all configurations",
- &configs)
- .OptionalFlag(
- "--preferred-density",
- "Selects the closest matching density and strips out all others.",
- &preferred_density)
- .OptionalFlag("--product",
- "Comma separated list of product names to keep",
- &product_list)
+ .OptionalFlag("-c",
+ "Comma separated list of configurations to include. The default\n"
+ "is all configurations",
+ &configs)
+ .OptionalFlag("--preferred-density",
+ "Selects the closest matching density and strips out all others.",
+ &preferred_density)
+ .OptionalFlag("--product", "Comma separated list of product names to keep", &product_list)
.OptionalSwitch("--output-to-dir",
"Outputs the APK contents to a directory specified "
"by -o",
@@ -1935,11 +1930,10 @@
"Default minimum SDK version to use for "
"AndroidManifest.xml",
&options.manifest_fixer_options.min_sdk_version_default)
- .OptionalFlag(
- "--target-sdk-version",
- "Default target SDK version to use for "
- "AndroidManifest.xml",
- &options.manifest_fixer_options.target_sdk_version_default)
+ .OptionalFlag("--target-sdk-version",
+ "Default target SDK version to use for "
+ "AndroidManifest.xml",
+ &options.manifest_fixer_options.target_sdk_version_default)
.OptionalFlag("--version-code",
"Version code (integer) to inject into the "
"AndroidManifest.xml if none is present",
@@ -1948,8 +1942,7 @@
"Version name to inject into the AndroidManifest.xml "
"if none is present",
&options.manifest_fixer_options.version_name_default)
- .OptionalSwitch("--static-lib", "Generate a static Android library",
- &options.static_lib)
+ .OptionalSwitch("--static-lib", "Generate a static Android library", &options.static_lib)
.OptionalSwitch("--no-static-lib-packages",
"Merge all library resources under the app's package",
&options.no_static_lib_packages)
@@ -1957,14 +1950,12 @@
"Generates R.java without the final modifier.\n"
"This is implied when --static-lib is specified.",
&options.generate_non_final_ids)
- .OptionalFlag("--stable-ids",
- "File containing a list of name to ID mapping.",
+ .OptionalFlag("--stable-ids", "File containing a list of name to ID mapping.",
&stable_id_file_path)
- .OptionalFlag(
- "--emit-ids",
- "Emit a file at the given path with a list of name to ID\n"
- "mappings, suitable for use with --stable-ids.",
- &options.resource_id_map_path)
+ .OptionalFlag("--emit-ids",
+ "Emit a file at the given path with a list of name to ID\n"
+ "mappings, suitable for use with --stable-ids.",
+ &options.resource_id_map_path)
.OptionalFlag("--private-symbols",
"Package name to use when generating R.java for "
"private symbols.\n"
@@ -1972,8 +1963,7 @@
"the application's "
"package name",
&options.private_symbols)
- .OptionalFlag("--custom-package",
- "Custom Java package under which to generate R.java",
+ .OptionalFlag("--custom-package", "Custom Java package under which to generate R.java",
&options.custom_java_package)
.OptionalFlagList("--extra-packages",
"Generate the same R.java but with different "
@@ -1987,23 +1977,19 @@
"Allows the addition of new resources in "
"overlays without <add-resource> tags",
&options.auto_add_overlay)
- .OptionalFlag("--rename-manifest-package",
- "Renames the package in AndroidManifest.xml",
+ .OptionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml",
&options.manifest_fixer_options.rename_manifest_package)
- .OptionalFlag(
- "--rename-instrumentation-target-package",
- "Changes the name of the target package for instrumentation. "
- "Most useful "
- "when used\nin conjunction with --rename-manifest-package",
- &options.manifest_fixer_options
- .rename_instrumentation_target_package)
+ .OptionalFlag("--rename-instrumentation-target-package",
+ "Changes the name of the target package for instrumentation. "
+ "Most useful "
+ "when used\nin conjunction with --rename-manifest-package",
+ &options.manifest_fixer_options.rename_instrumentation_target_package)
.OptionalFlagList("-0", "File extensions not to compress",
&options.extensions_to_not_compress)
- .OptionalFlagList(
- "--split",
- "Split resources matching a set of configs out to a "
- "Split APK.\nSyntax: path/to/output.apk:<config>[,<config>[...]]",
- &split_args)
+ .OptionalFlagList("--split",
+ "Split resources matching a set of configs out to a "
+ "Split APK.\nSyntax: path/to/output.apk:<config>[,<config>[...]]",
+ &split_args)
.OptionalSwitch("-v", "Enables verbose logging", &verbose);
if (!flags.Parse("aapt2 link", args, &std::cerr)) {
diff --git a/tools/aapt2/util/BigBuffer.cpp b/tools/aapt2/util/BigBuffer.cpp
index ef99dca..75fa789 100644
--- a/tools/aapt2/util/BigBuffer.cpp
+++ b/tools/aapt2/util/BigBuffer.cpp
@@ -76,4 +76,12 @@
return blocks_.back().buffer.get();
}
+std::string BigBuffer::to_string() const {
+ std::string result;
+ for (const Block& block : blocks_) {
+ result.append(block.buffer.get(), block.buffer.get() + block.size);
+ }
+ return result;
+}
+
} // namespace aapt
diff --git a/tools/aapt2/util/BigBuffer.h b/tools/aapt2/util/BigBuffer.h
index d23c41d..3045255 100644
--- a/tools/aapt2/util/BigBuffer.h
+++ b/tools/aapt2/util/BigBuffer.h
@@ -19,6 +19,7 @@
#include <cstring>
#include <memory>
+#include <string>
#include <type_traits>
#include <vector>
@@ -116,6 +117,8 @@
const_iterator begin() const;
const_iterator end() const;
+ std::string to_string() const;
+
private:
DISALLOW_COPY_AND_ASSIGN(BigBuffer);
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
index 9bc8e18..2c7e936 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
@@ -638,6 +638,9 @@
return mContext.getProjectResourceValue(ResourceType.ID, idName, defValue);
}
}
+ else if (value.startsWith("@aapt:_aapt")) {
+ return mContext.getLayoutlibCallback().getResourceId(ResourceType.AAPT, value);
+ }
// not a direct id valid reference. First check if it's an enum (this is a corner case
// for attributes that have a reference|enum type), then fallback to resolve
diff --git a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
index 6e3a8e8..c20ee12 100644
--- a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
@@ -131,9 +131,16 @@
if (resourceInfo != null) {
String attributeName = resourceInfo.getSecond();
RenderResources renderResources = resources.mContext.getRenderResources();
- return Pair.of(attributeName, platformResFlag_out[0] ?
+ ResourceValue value = platformResFlag_out[0] ?
renderResources.getFrameworkResource(resourceInfo.getFirst(), attributeName) :
- renderResources.getProjectResource(resourceInfo.getFirst(), attributeName));
+ renderResources.getProjectResource(resourceInfo.getFirst(), attributeName);
+
+ if (value == null) {
+ // Unable to resolve the attribute, just leave the unresolved value
+ value = new ResourceValue(resourceInfo.getFirst(), attributeName, attributeName,
+ platformResFlag_out[0]);
+ }
+ return Pair.of(attributeName, value);
}
return null;
diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
index fb24c01..ff5a5e9 100644
--- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
@@ -314,7 +314,7 @@
@LayoutlibDelegate
/*package*/ static boolean nAddFontFromAssetManager(long builderPtr, AssetManager mgr, String path,
- int cookie, boolean isAsset) {
+ int cookie, boolean isAsset, int weight, boolean isItalic) {
FontFamily_Delegate ffd = sManager.getDelegate(builderPtr);
if (ffd == null) {
return false;
@@ -355,8 +355,12 @@
Font font = Font.createFont(Font.TRUETYPE_FONT, fontStream);
fontInfo = new FontInfo();
fontInfo.mFont = font;
- fontInfo.mWeight = font.isBold() ? BOLD_FONT_WEIGHT : DEFAULT_FONT_WEIGHT;
- fontInfo.mIsItalic = font.isItalic();
+ if (weight == 0) {
+ fontInfo.mWeight = font.isBold() ? BOLD_FONT_WEIGHT : DEFAULT_FONT_WEIGHT;
+ } else {
+ fontInfo.mWeight = weight;
+ }
+ fontInfo.mIsItalic = weight == 0 ? font.isItalic() : isItalic;
ffd.addFont(fontInfo);
return true;
} catch (IOException e) {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
index 91668af..e10f20d 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
@@ -100,7 +100,8 @@
mView.setText(text);
}
- private void setGravity(int gravity) {
+ @SuppressWarnings("WeakerAccess") // This method is used from Studio
+ public void setGravity(int gravity) {
mView.setGravity(gravity);
}
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 16bf724..3d5d5c6 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -65,6 +65,7 @@
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.graphics.Bitmap;
+import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.hardware.display.DisplayManager;
import android.net.Uri;
@@ -381,6 +382,18 @@
return true;
}
+ String stringValue = value.getValue();
+ if (!stringValue.isEmpty()) {
+ if (stringValue.charAt(0) == '#') {
+ outValue.type = TypedValue.TYPE_INT_COLOR_ARGB8;
+ outValue.data = Color.parseColor(value.getValue());
+ }
+ else if (stringValue.charAt(0) == '@') {
+ outValue.type = TypedValue.TYPE_REFERENCE;
+ }
+
+ }
+
int a;
// if this is a framework value.
if (value.isFramework()) {
@@ -399,7 +412,7 @@
}
// If the value is not a valid reference, fallback to pass the value as a string.
- outValue.string = value.getValue();
+ outValue.string = stringValue;
return true;
}
@@ -1412,6 +1425,12 @@
}
@Override
+ public File getPreloadsFileCache() {
+ // pass
+ return null;
+ }
+
+ @Override
public ContentResolver getContentResolver() {
if (mContentResolver == null) {
mContentResolver = new BridgeContentResolver(this);
@@ -1888,6 +1907,11 @@
}
@Override
+ public void updateDisplay(int displayId) {
+ // pass
+ }
+
+ @Override
public int getUserId() {
return 0; // not used
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
index a51ad2e..4689491 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
@@ -50,7 +50,7 @@
@Override
public void resized(Rect rect, Rect rect2, Rect rect3, Rect rect4, Rect rect5, Rect rect6,
- boolean b, Configuration configuration, Rect rect7, boolean b2, boolean b3)
+ boolean b, Configuration configuration, Rect rect7, boolean b2, boolean b3, int i0)
throws RemoteException {
// pass for now.
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
index b3a2d3e..f1e7b51 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
@@ -286,18 +286,15 @@
Density density = Density.MEDIUM;
if (value instanceof DensityBasedResourceValue) {
- density =
- ((DensityBasedResourceValue)value).getResourceDensity();
+ density = ((DensityBasedResourceValue) value).getResourceDensity();
}
-
if (lowerCaseValue.endsWith(NinePatch.EXTENSION_9PATCH)) {
File file = new File(stringValue);
if (file.isFile()) {
try {
- return getNinePatchDrawable(
- new FileInputStream(file), density, value.isFramework(),
- stringValue, context);
+ return getNinePatchDrawable(new FileInputStream(file), density,
+ value.isFramework(), stringValue, context);
} catch (IOException e) {
// failed to read the file, we'll return null below.
Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
@@ -306,30 +303,28 @@
}
return null;
- } else if (lowerCaseValue.endsWith(".xml")) {
+ } else if (lowerCaseValue.endsWith(".xml") || stringValue.startsWith("@aapt:_aapt/")) {
// create a block parser for the file
- File f = new File(stringValue);
- if (f.isFile()) {
- try {
- // let the framework inflate the Drawable from the XML file.
- XmlPullParser parser = ParserFactory.create(f);
-
- BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
- parser, context, value.isFramework());
- try {
- return Drawable.createFromXml(context.getResources(), blockParser, theme);
- } finally {
- blockParser.ensurePopped();
+ try {
+ XmlPullParser parser = context.getLayoutlibCallback().getParser(value);
+ if (parser == null) {
+ File drawableFile = new File(stringValue);
+ if (drawableFile.isFile()) {
+ parser = ParserFactory.create(drawableFile);
}
- } catch (Exception e) {
- // this is an error and not warning since the file existence is checked before
- // attempting to parse it.
- Bridge.getLog().error(null, "Failed to parse file " + stringValue,
- e, null /*data*/);
}
- } else {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- String.format("File %s does not exist (or is not a file)", stringValue),
+
+ BridgeXmlBlockParser blockParser =
+ new BridgeXmlBlockParser(parser, context, value.isFramework());
+ try {
+ return Drawable.createFromXml(context.getResources(), blockParser, theme);
+ } finally {
+ blockParser.ensurePopped();
+ }
+ } catch (Exception e) {
+ // this is an error and not warning since the file existence is checked before
+ // attempting to parse it.
+ Bridge.getLog().error(null, "Failed to parse file " + stringValue, e,
null /*data*/);
}
@@ -342,8 +337,8 @@
value.isFramework() ? null : context.getProjectKey());
if (bitmap == null) {
- bitmap = Bitmap_Delegate.createBitmap(bmpFile, false /*isMutable*/,
- density);
+ bitmap =
+ Bitmap_Delegate.createBitmap(bmpFile, false /*isMutable*/, density);
Bridge.setCachedBitmap(stringValue, bitmap,
value.isFramework() ? null : context.getProjectKey());
}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
index 913519c..9e60f0f 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
@@ -36,6 +36,7 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.util.DisplayMetrics;
+import android.util.TypedValue;
import java.lang.reflect.Field;
import java.util.concurrent.TimeUnit;
@@ -43,6 +44,7 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -334,6 +336,7 @@
AssetManager assetManager = AssetManager.getSystem();
DisplayMetrics metrics = new DisplayMetrics();
Configuration configuration = RenderAction.getConfiguration(params);
+ //noinspection deprecation
Resources resources = new Resources(assetManager, metrics, configuration);
resources.mLayoutlibCallback = params.getLayoutlibCallback();
resources.mContext =
@@ -370,6 +373,7 @@
AssetManager assetManager = AssetManager.getSystem();
DisplayMetrics metrics = new DisplayMetrics();
Configuration configuration = RenderAction.getConfiguration(params);
+ //noinspection deprecation
Resources resources = new Resources(assetManager, metrics, configuration);
resources.mLayoutlibCallback = params.getLayoutlibCallback();
resources.mContext =
@@ -390,4 +394,34 @@
// TODO: styles seem to be broken in TextView
renderAndVerify("fonts_test.xml", "font_test.png");
}
+
+ @Test
+ public void testColorTypedValue() throws Exception {
+ // Setup
+ // Create the layout pull parser for our resources (empty.xml can not be part of the test
+ // app as it won't compile).
+ LayoutPullParser parser = new LayoutPullParser("/empty.xml");
+ // Create LayoutLibCallback.
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(RenderTestBase.getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+ SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4,
+ layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
+ AssetManager assetManager = AssetManager.getSystem();
+ DisplayMetrics metrics = new DisplayMetrics();
+ Configuration configuration = RenderAction.getConfiguration(params);
+ //noinspection deprecation
+ Resources resources = new Resources(assetManager, metrics, configuration);
+ resources.mLayoutlibCallback = params.getLayoutlibCallback();
+ resources.mContext =
+ new BridgeContext(params.getProjectKey(), metrics, params.getResources(),
+ params.getAssets(), params.getLayoutlibCallback(), configuration,
+ params.getTargetSdkVersion(), params.isRtlSupported());
+
+ TypedValue outValue = new TypedValue();
+ resources.mContext.resolveThemeAttribute(android.R.attr.colorPrimary, outValue, true);
+ assertEquals(TypedValue.TYPE_INT_COLOR_ARGB8, outValue.type);
+ assertNotEquals(0, outValue.data);
+ assertTrue(sRenderMessages.isEmpty());
+ }
}
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 97a15e4..3eb9934 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -855,6 +855,7 @@
* This network is disabled because EAP-TLS failure
*/
public static final int DISABLED_TLS_VERSION_MISMATCH = 7;
+ // Values above are for temporary disablement; values below are for permanent disablement.
/**
* This network is disabled due to absence of user credentials
*/
@@ -979,6 +980,28 @@
private boolean mHasEverConnected;
/**
+ * Boolean indicating whether {@link com.android.server.wifi.RecommendedNetworkEvaluator}
+ * chose not to connect to this network in the last qualified network selection process.
+ */
+ private boolean mNotRecommended;
+
+ /**
+ * Set whether {@link com.android.server.wifi.RecommendedNetworkEvaluator} does not
+ * recommend connecting to this network.
+ */
+ public void setNotRecommended(boolean notRecommended) {
+ mNotRecommended = notRecommended;
+ }
+
+ /**
+ * Returns whether {@link com.android.server.wifi.RecommendedNetworkEvaluator} does not
+ * recommend connecting to this network.
+ */
+ public boolean isNotRecommended() {
+ return mNotRecommended;
+ }
+
+ /**
* set whether this network is visible in latest Qualified Network Selection
* @param seen value set to candidate
*/
@@ -1301,6 +1324,7 @@
dest.writeInt(CONNECT_CHOICE_NOT_EXISTS);
}
dest.writeInt(getHasEverConnected() ? 1 : 0);
+ dest.writeInt(isNotRecommended() ? 1 : 0);
}
public void readFromParcel(Parcel in) {
@@ -1320,6 +1344,7 @@
setConnectChoiceTimestamp(INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
}
setHasEverConnected(in.readInt() != 0);
+ setNotRecommended(in.readInt() != 0);
}
}
diff --git a/wifi/java/android/net/wifi/aware/ConfigRequest.java b/wifi/java/android/net/wifi/aware/ConfigRequest.java
index 6a5957b..cc14ab2 100644
--- a/wifi/java/android/net/wifi/aware/ConfigRequest.java
+++ b/wifi/java/android/net/wifi/aware/ConfigRequest.java
@@ -19,6 +19,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Arrays;
+
/**
* Defines a request object to configure a Wi-Fi Aware network. Built using
* {@link ConfigRequest.Builder}. Configuration is requested using
@@ -41,6 +43,18 @@
public static final int CLUSTER_ID_MAX = 0xFFFF;
/**
+ * Indices for configuration variables which are specified per band.
+ */
+ public static final int NAN_BAND_24GHZ = 0;
+ public static final int NAN_BAND_5GHZ = 1;
+
+ /**
+ * Magic values for Discovery Window (DW) interval configuration
+ */
+ public static final int DW_INTERVAL_NOT_INIT = -1;
+ public static final int DW_DISABLE = 0; // only valid for 5GHz
+
+ /**
* Indicates whether 5G band support is requested.
*/
public final boolean mSupport5gBand;
@@ -62,19 +76,26 @@
*/
public final int mClusterHigh;
+ /**
+ * Specifies the discovery window interval for the device on NAN_BAND_*.
+ */
+ public final int mDiscoveryWindowInterval[];
+
private ConfigRequest(boolean support5gBand, int masterPreference, int clusterLow,
- int clusterHigh) {
+ int clusterHigh, int discoveryWindowInterval[]) {
mSupport5gBand = support5gBand;
mMasterPreference = masterPreference;
mClusterLow = clusterLow;
mClusterHigh = clusterHigh;
+ mDiscoveryWindowInterval = discoveryWindowInterval;
}
@Override
public String toString() {
return "ConfigRequest [mSupport5gBand=" + mSupport5gBand + ", mMasterPreference="
+ mMasterPreference + ", mClusterLow=" + mClusterLow + ", mClusterHigh="
- + mClusterHigh + "]";
+ + mClusterHigh + ", mDiscoveryWindowInterval="
+ + Arrays.toString(mDiscoveryWindowInterval) + "]";
}
@Override
@@ -88,6 +109,7 @@
dest.writeInt(mMasterPreference);
dest.writeInt(mClusterLow);
dest.writeInt(mClusterHigh);
+ dest.writeIntArray(mDiscoveryWindowInterval);
}
public static final Creator<ConfigRequest> CREATOR = new Creator<ConfigRequest>() {
@@ -102,7 +124,10 @@
int masterPreference = in.readInt();
int clusterLow = in.readInt();
int clusterHigh = in.readInt();
- return new ConfigRequest(support5gBand, masterPreference, clusterLow, clusterHigh);
+ int discoveryWindowInterval[] = in.createIntArray();
+
+ return new ConfigRequest(support5gBand, masterPreference, clusterLow, clusterHigh,
+ discoveryWindowInterval);
}
};
@@ -119,17 +144,8 @@
ConfigRequest lhs = (ConfigRequest) o;
return mSupport5gBand == lhs.mSupport5gBand && mMasterPreference == lhs.mMasterPreference
- && mClusterLow == lhs.mClusterLow && mClusterHigh == lhs.mClusterHigh;
- }
-
- /**
- * Checks whether the configuration's settings are non-default.
- *
- * @return true if any of the settings are non-default.
- */
- public boolean isNonDefault() {
- return mSupport5gBand || mMasterPreference != 0 || mClusterLow != CLUSTER_ID_MIN
- || mClusterHigh != CLUSTER_ID_MAX;
+ && mClusterLow == lhs.mClusterLow && mClusterHigh == lhs.mClusterHigh
+ && Arrays.equals(mDiscoveryWindowInterval, lhs.mDiscoveryWindowInterval);
}
@Override
@@ -140,6 +156,7 @@
result = 31 * result + mMasterPreference;
result = 31 * result + mClusterLow;
result = 31 * result + mClusterHigh;
+ result = 31 * result + Arrays.hashCode(mDiscoveryWindowInterval);
return result;
}
@@ -173,6 +190,23 @@
throw new IllegalArgumentException(
"Invalid argument combination - must have Cluster Low <= Cluster High");
}
+ if (mDiscoveryWindowInterval.length != 2) {
+ throw new IllegalArgumentException(
+ "Invalid discovery window interval: must have 2 elements (2.4 & 5");
+ }
+ if (mDiscoveryWindowInterval[NAN_BAND_24GHZ] != DW_INTERVAL_NOT_INIT &&
+ (mDiscoveryWindowInterval[NAN_BAND_24GHZ] < 1 // valid for 2.4GHz: [1-5]
+ || mDiscoveryWindowInterval[NAN_BAND_24GHZ] > 5)) {
+ throw new IllegalArgumentException(
+ "Invalid discovery window interval for 2.4GHz: valid is UNSET or [1,5]");
+ }
+ if (mDiscoveryWindowInterval[NAN_BAND_5GHZ] != DW_INTERVAL_NOT_INIT &&
+ (mDiscoveryWindowInterval[NAN_BAND_5GHZ] < 0 // valid for 5GHz: [0-5]
+ || mDiscoveryWindowInterval[NAN_BAND_5GHZ] > 5)) {
+ throw new IllegalArgumentException(
+ "Invalid discovery window interval for 5GHz: valid is UNSET or [0,5]");
+ }
+
}
/**
@@ -183,6 +217,7 @@
private int mMasterPreference = 0;
private int mClusterLow = CLUSTER_ID_MIN;
private int mClusterHigh = CLUSTER_ID_MAX;
+ private int mDiscoveryWindowInterval[] = {DW_INTERVAL_NOT_INIT, DW_INTERVAL_NOT_INIT};
/**
* Specify whether 5G band support is required in this request. Disabled by default.
@@ -271,6 +306,33 @@
}
/**
+ * The discovery window interval specifies the discovery windows in which the device will be
+ * awake. The configuration enables trading off latency vs. power (higher interval means
+ * higher discovery latency but lower power).
+ *
+ * @param band Either {@link #NAN_BAND_24GHZ} or {@link #NAN_BAND_5GHZ}.
+ * @param interval A value of 1, 2, 3, 4, or 5 indicating an interval of 2^(interval-1). For
+ * the 5GHz band a value of 0 indicates that the device will not be awake
+ * for any discovery windows.
+ *
+ * @return The builder itself to facilitate chaining operations
+ * {@code builder.setDiscoveryWindowInterval(...).setMasterPreference(...)}.
+ */
+ public Builder setDiscoveryWindowInterval(int band, int interval) {
+ if (band != NAN_BAND_24GHZ && band != NAN_BAND_5GHZ) {
+ throw new IllegalArgumentException("Invalid band value");
+ }
+ if ((band == NAN_BAND_24GHZ && (interval < 1 || interval > 5))
+ || (band == NAN_BAND_5GHZ && (interval < 0 || interval > 5))) {
+ throw new IllegalArgumentException(
+ "Invalid interval value: 2.4 GHz [1,5] or 5GHz [0,5]");
+ }
+
+ mDiscoveryWindowInterval[band] = interval;
+ return this;
+ }
+
+ /**
* Build {@link ConfigRequest} given the current requests made on the
* builder.
*/
@@ -280,7 +342,8 @@
"Invalid argument combination - must have Cluster Low <= Cluster High");
}
- return new ConfigRequest(mSupport5gBand, mMasterPreference, mClusterLow, mClusterHigh);
+ return new ConfigRequest(mSupport5gBand, mMasterPreference, mClusterLow, mClusterHigh,
+ mDiscoveryWindowInterval);
}
}
}
diff --git a/wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl b/wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl
index 794c142..0f4910f 100644
--- a/wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl
+++ b/wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl
@@ -34,8 +34,6 @@
interface IWifiAwareManager
{
// Aware API
- void enableUsage();
- void disableUsage();
boolean isUsageEnabled();
Characteristics getCharacteristics();
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
index a9e38ce..0eb6a3d 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
@@ -253,36 +253,6 @@
}
/**
- * Enable the usage of the Aware API. Doesn't actually turn on Aware cluster formation - that
- * only happens when an attach is attempted. {@link #ACTION_WIFI_AWARE_STATE_CHANGED} broadcast
- * will be triggered.
- *
- * @hide
- */
- public void enableUsage() {
- try {
- mService.enableUsage();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Disable the usage of the Aware API. All attempts to attach() will be rejected. All open
- * connections and sessions will be terminated. {@link #ACTION_WIFI_AWARE_STATE_CHANGED}
- * broadcast will be triggered.
- *
- * @hide
- */
- public void disableUsage() {
- try {
- mService.disableUsage();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* Returns the current status of Aware API: whether or not Aware is available. To track
* changes in the state of Aware API register for the
* {@link #ACTION_WIFI_AWARE_STATE_CHANGED} broadcast.
diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
index a396d87..7f68f6f 100644
--- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
@@ -100,26 +100,6 @@
*/
/**
- * Validate pass-through of enableUsage() API.
- */
- @Test
- public void testEnableUsage() throws Exception {
- mDut.enableUsage();
-
- verify(mockAwareService).enableUsage();
- }
-
- /**
- * Validate pass-through of disableUsage() API.
- */
- @Test
- public void testDisableUsage() throws Exception {
- mDut.disableUsage();
-
- verify(mockAwareService).disableUsage();
- }
-
- /**
* Validate pass-through of isUsageEnabled() API.
*/
@Test
@@ -566,6 +546,12 @@
collector.checkThat("mMasterPreference", 0,
equalTo(configRequest.mMasterPreference));
collector.checkThat("mSupport5gBand", false, equalTo(configRequest.mSupport5gBand));
+ collector.checkThat("mDiscoveryWindowInterval.length", 2,
+ equalTo(configRequest.mDiscoveryWindowInterval.length));
+ collector.checkThat("mDiscoveryWindowInterval[2.4GHz]", ConfigRequest.DW_INTERVAL_NOT_INIT,
+ equalTo(configRequest.mDiscoveryWindowInterval[ConfigRequest.NAN_BAND_24GHZ]));
+ collector.checkThat("mDiscoveryWindowInterval[5Hz]", ConfigRequest.DW_INTERVAL_NOT_INIT,
+ equalTo(configRequest.mDiscoveryWindowInterval[ConfigRequest.NAN_BAND_5GHZ]));
}
@Test
@@ -574,10 +560,12 @@
final int clusterLow = 5;
final int masterPreference = 55;
final boolean supportBand5g = true;
+ final int dwWindow5GHz = 3;
ConfigRequest configRequest = new ConfigRequest.Builder().setClusterHigh(clusterHigh)
.setClusterLow(clusterLow).setMasterPreference(masterPreference)
.setSupport5gBand(supportBand5g)
+ .setDiscoveryWindowInterval(ConfigRequest.NAN_BAND_5GHZ, dwWindow5GHz)
.build();
collector.checkThat("mClusterHigh", clusterHigh, equalTo(configRequest.mClusterHigh));
@@ -585,6 +573,12 @@
collector.checkThat("mMasterPreference", masterPreference,
equalTo(configRequest.mMasterPreference));
collector.checkThat("mSupport5gBand", supportBand5g, equalTo(configRequest.mSupport5gBand));
+ collector.checkThat("mDiscoveryWindowInterval.length", 2,
+ equalTo(configRequest.mDiscoveryWindowInterval.length));
+ collector.checkThat("mDiscoveryWindowInterval[2.4GHz]", ConfigRequest.DW_INTERVAL_NOT_INIT,
+ equalTo(configRequest.mDiscoveryWindowInterval[ConfigRequest.NAN_BAND_24GHZ]));
+ collector.checkThat("mDiscoveryWindowInterval[5GHz]", dwWindow5GHz,
+ equalTo(configRequest.mDiscoveryWindowInterval[ConfigRequest.NAN_BAND_5GHZ]));
}
@Test(expected = IllegalArgumentException.class)
@@ -633,16 +627,44 @@
new ConfigRequest.Builder().setClusterLow(100).setClusterHigh(5).build();
}
+ @Test(expected = IllegalArgumentException.class)
+ public void testConfigRequestBuilderDwIntervalInvalidBand() {
+ new ConfigRequest.Builder().setDiscoveryWindowInterval(5, 1).build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConfigRequestBuilderDwIntervalInvalidValueZero() {
+ new ConfigRequest.Builder().setDiscoveryWindowInterval(ConfigRequest.NAN_BAND_24GHZ,
+ 0).build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConfigRequestBuilderDwIntervalInvalidValueLarge() {
+ new ConfigRequest.Builder().setDiscoveryWindowInterval(ConfigRequest.NAN_BAND_5GHZ,
+ 6).build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConfigRequestBuilderDwIntervalInvalidValueLargeValidate() {
+ ConfigRequest cr = new ConfigRequest.Builder().build();
+ cr.mDiscoveryWindowInterval[ConfigRequest.NAN_BAND_5GHZ] = 6;
+ cr.validate();
+ }
+
@Test
public void testConfigRequestParcel() {
final int clusterHigh = 189;
final int clusterLow = 25;
final int masterPreference = 177;
final boolean supportBand5g = true;
+ final int dwWindow24GHz = 1;
+ final int dwWindow5GHz = 5;
ConfigRequest configRequest = new ConfigRequest.Builder().setClusterHigh(clusterHigh)
.setClusterLow(clusterLow).setMasterPreference(masterPreference)
.setSupport5gBand(supportBand5g)
+ .setDiscoveryWindowInterval(ConfigRequest.NAN_BAND_24GHZ, dwWindow24GHz)
+ .setDiscoveryWindowInterval(ConfigRequest.NAN_BAND_5GHZ, dwWindow5GHz)
.build();
Parcel parcelW = Parcel.obtain();