Merge "Update "accessing files" notification resources."
diff --git a/Android.mk b/Android.mk
index f58caac..e94bebb 100644
--- a/Android.mk
+++ b/Android.mk
@@ -97,6 +97,7 @@
core/java/android/app/trust/ITrustManager.aidl \
core/java/android/app/trust/ITrustListener.aidl \
core/java/android/app/backup/IBackupManager.aidl \
+ core/java/android/app/backup/IBackupObserver.aidl \
core/java/android/app/backup/IFullBackupRestoreObserver.aidl \
core/java/android/app/backup/IRestoreObserver.aidl \
core/java/android/app/backup/IRestoreSession.aidl \
diff --git a/api/current.txt b/api/current.txt
index b89a17d..2befa93 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1183,6 +1183,7 @@
field public static final int summaryOn = 16843247; // 0x10101ef
field public static final int supportsAssist = 16844016; // 0x10104f0
field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1
+ field public static final int supportsLocalInteraction = 16844048; // 0x1010510
field public static final int supportsPictureInPicture = 16844024; // 0x10104f8
field public static final int supportsRtl = 16843695; // 0x10103af
field public static final int supportsSwitchingToNextInputMethod = 16843755; // 0x10103eb
@@ -2612,6 +2613,7 @@
public abstract class AccessibilityService extends android.app.Service {
ctor public AccessibilityService();
+ method public final void disableSelf();
method public final boolean dispatchGesture(android.accessibilityservice.GestureDescription, android.accessibilityservice.AccessibilityService.GestureResultCallback, android.os.Handler);
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
@@ -3435,6 +3437,7 @@
method public boolean isDestroyed();
method public boolean isFinishing();
method public boolean isImmersive();
+ method public boolean isLocalVoiceInteractionSupported();
method public boolean isTaskRoot();
method public boolean isVoiceInteraction();
method public boolean isVoiceInteractionRoot();
@@ -3476,6 +3479,8 @@
method public boolean onKeyMultiple(int, int, android.view.KeyEvent);
method public boolean onKeyShortcut(int, android.view.KeyEvent);
method public boolean onKeyUp(int, android.view.KeyEvent);
+ method public void onLocalVoiceInteractionStarted();
+ method public void onLocalVoiceInteractionStopped();
method public void onLowMemory();
method public boolean onMenuItemSelected(int, android.view.MenuItem);
method public boolean onMenuOpened(int, android.view.Menu);
@@ -3590,12 +3595,14 @@
method public void startIntentSenderForResult(android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
method public void startIntentSenderFromChild(android.app.Activity, android.content.IntentSender, int, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException;
method public void startIntentSenderFromChild(android.app.Activity, android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
+ method public void startLocalVoiceInteraction(android.os.Bundle);
method public void startLockTask();
method public deprecated void startManagingCursor(android.database.Cursor);
method public boolean startNextMatchingActivity(android.content.Intent);
method public boolean startNextMatchingActivity(android.content.Intent, android.os.Bundle);
method public void startPostponedEnterTransition();
method public void startSearch(java.lang.String, boolean, android.os.Bundle, boolean);
+ method public void stopLocalVoiceInteraction();
method public void stopLockTask();
method public deprecated void stopManagingCursor(android.database.Cursor);
method public void takeKeyEvents(boolean);
@@ -5685,10 +5692,13 @@
method public android.graphics.drawable.Drawable peekFastDrawable();
method public void sendWallpaperCommand(android.os.IBinder, java.lang.String, int, int, int, android.os.Bundle);
method public void setBitmap(android.graphics.Bitmap) throws java.io.IOException;
- method public void setBitmap(android.graphics.Bitmap, android.graphics.Rect, boolean) throws java.io.IOException;
+ method public int setBitmap(android.graphics.Bitmap, android.graphics.Rect, boolean) throws java.io.IOException;
+ method public int setBitmap(android.graphics.Bitmap, android.graphics.Rect, boolean, int) throws java.io.IOException;
method public void setResource(int) throws java.io.IOException;
+ method public int setResource(int, int) throws java.io.IOException;
method public void setStream(java.io.InputStream) throws java.io.IOException;
- method public void setStream(java.io.InputStream, android.graphics.Rect, boolean) throws java.io.IOException;
+ method public int setStream(java.io.InputStream, android.graphics.Rect, boolean) throws java.io.IOException;
+ method public int setStream(java.io.InputStream, android.graphics.Rect, boolean, int) throws java.io.IOException;
method public void setWallpaperOffsetSteps(float, float);
method public void setWallpaperOffsets(android.os.IBinder, float, float);
method public void suggestDesiredDimensions(int, int);
@@ -5699,6 +5709,8 @@
field public static final java.lang.String COMMAND_SECONDARY_TAP = "android.wallpaper.secondaryTap";
field public static final java.lang.String COMMAND_TAP = "android.wallpaper.tap";
field public static final java.lang.String EXTRA_LIVE_WALLPAPER_COMPONENT = "android.service.wallpaper.extra.LIVE_WALLPAPER_COMPONENT";
+ field public static final int FLAG_SET_LOCK = 2; // 0x2
+ field public static final int FLAG_SET_SYSTEM = 1; // 0x1
field public static final java.lang.String WALLPAPER_PREVIEW_META_DATA = "android.wallpaper.preview";
}
@@ -5806,6 +5818,7 @@
method public java.lang.String getLongSupportMessage(android.content.ComponentName);
method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
method public long getMaximumTimeToLock(android.content.ComponentName);
+ method public int getOrganizationColor(android.content.ComponentName);
method public boolean getPackageSuspended(android.content.ComponentName, java.lang.String);
method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName);
method public long getPasswordExpiration(android.content.ComponentName);
@@ -5874,6 +5887,7 @@
method public void setMasterVolumeMuted(android.content.ComponentName, boolean);
method public void setMaximumFailedPasswordsForWipe(android.content.ComponentName, int);
method public void setMaximumTimeToLock(android.content.ComponentName, long);
+ method public void setOrganizationColor(android.content.ComponentName, int);
method public boolean setPackageSuspended(android.content.ComponentName, java.lang.String, boolean);
method public void setPasswordExpirationTimeout(android.content.ComponentName, long);
method public void setPasswordHistoryLength(android.content.ComponentName, int);
@@ -8426,6 +8440,7 @@
field public static final java.lang.String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
field public static final java.lang.String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
field public static final java.lang.String ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
+ field public static final java.lang.String ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
field public static final java.lang.String ACTION_PACKAGES_SUSPENDED = "android.intent.action.PACKAGES_SUSPENDED";
field public static final java.lang.String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED";
field public static final java.lang.String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED";
@@ -8448,6 +8463,7 @@
field public static final java.lang.String ACTION_PROCESS_TEXT = "android.intent.action.PROCESS_TEXT";
field public static final java.lang.String ACTION_PROVIDER_CHANGED = "android.intent.action.PROVIDER_CHANGED";
field public static final java.lang.String ACTION_QUICK_CLOCK = "android.intent.action.QUICK_CLOCK";
+ field public static final java.lang.String ACTION_QUICK_VIEW = "android.intent.action.QUICK_VIEW";
field public static final java.lang.String ACTION_REBOOT = "android.intent.action.REBOOT";
field public static final java.lang.String ACTION_RUN = "android.intent.action.RUN";
field public static final java.lang.String ACTION_SCREEN_OFF = "android.intent.action.SCREEN_OFF";
@@ -8541,6 +8557,7 @@
field public static final java.lang.String EXTRA_DONT_KILL_APP = "android.intent.extra.DONT_KILL_APP";
field public static final java.lang.String EXTRA_EMAIL = "android.intent.extra.EMAIL";
field public static final java.lang.String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT";
+ field public static final java.lang.String EXTRA_INDEX = "android.intent.extra.INDEX";
field public static final java.lang.String EXTRA_INITIAL_INTENTS = "android.intent.extra.INITIAL_INTENTS";
field public static final java.lang.String EXTRA_INSTALLER_PACKAGE_NAME = "android.intent.extra.INSTALLER_PACKAGE_NAME";
field public static final java.lang.String EXTRA_INTENT = "android.intent.extra.INTENT";
@@ -9334,6 +9351,7 @@
public class LauncherApps {
method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle);
+ method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int, android.os.UserHandle);
method public boolean isActivityEnabled(android.content.ComponentName, android.os.UserHandle);
method public boolean isPackageEnabled(java.lang.String, android.os.UserHandle);
method public void registerCallback(android.content.pm.LauncherApps.Callback);
@@ -9350,7 +9368,9 @@
method public abstract void onPackageChanged(java.lang.String, android.os.UserHandle);
method public abstract void onPackageRemoved(java.lang.String, android.os.UserHandle);
method public abstract void onPackagesAvailable(java.lang.String[], android.os.UserHandle, boolean);
+ method public void onPackagesSuspended(java.lang.String[], android.os.UserHandle);
method public abstract void onPackagesUnavailable(java.lang.String[], android.os.UserHandle, boolean);
+ method public void onPackagesUnsuspended(java.lang.String[], android.os.UserHandle);
}
public class PackageInfo implements android.os.Parcelable {
@@ -13568,6 +13588,7 @@
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> CONTROL_MAX_REGIONS_AE;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> CONTROL_MAX_REGIONS_AF;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> CONTROL_MAX_REGIONS_AWB;
+ field public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Integer>> CONTROL_POST_RAW_SENSITIVITY_BOOST_RANGE;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> DEPTH_DEPTH_IS_EXCLUSIVE;
field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> EDGE_AVAILABLE_EDGE_MODES;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> FLASH_INFO_AVAILABLE;
@@ -13647,9 +13668,11 @@
method public abstract void close();
method public abstract android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int) throws android.hardware.camera2.CameraAccessException;
method public abstract void createCaptureSession(java.util.List<android.view.Surface>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method public abstract void createCaptureSessionByOutputConfiguration(java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public abstract void createConstrainedHighSpeedCaptureSession(java.util.List<android.view.Surface>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public abstract android.hardware.camera2.CaptureRequest.Builder createReprocessCaptureRequest(android.hardware.camera2.TotalCaptureResult) throws android.hardware.camera2.CameraAccessException;
method public abstract void createReprocessableCaptureSession(android.hardware.camera2.params.InputConfiguration, java.util.List<android.view.Surface>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method public abstract void createReprocessableCaptureSessionWithConfigurations(android.hardware.camera2.params.InputConfiguration, java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public abstract java.lang.String getId();
field public static final int TEMPLATE_MANUAL = 6; // 0x6
field public static final int TEMPLATE_PREVIEW = 1; // 0x1
@@ -13927,6 +13950,7 @@
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_CAPTURE_INTENT;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_MODE;
+ field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_SCENE_MODE;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_VIDEO_STABILIZATION_MODE;
field public static final android.os.Parcelable.Creator<android.hardware.camera2.CaptureRequest> CREATOR;
@@ -14005,6 +14029,7 @@
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_CAPTURE_INTENT;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_MODE;
+ field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_SCENE_MODE;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_VIDEO_STABILIZATION_MODE;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EDGE_MODE;
@@ -14146,6 +14171,17 @@
field public static final int METERING_WEIGHT_MIN = 0; // 0x0
}
+ public final class OutputConfiguration implements android.os.Parcelable {
+ ctor public OutputConfiguration(android.view.Surface);
+ method public int describeContents();
+ method public android.view.Surface getSurface();
+ method public int getSurfaceSetId();
+ method public void setSurfaceSetId(int);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR;
+ field public static final int SURFACE_SET_ID_INVALID = -1; // 0xffffffff
+ }
+
public final class RggbChannelVector {
ctor public RggbChannelVector(float, float, float, float);
method public void copyTo(float[], int);
@@ -19293,6 +19329,7 @@
method public void adjustVolume(int, int);
method public void dispatchMediaKeyEvent(android.view.KeyEvent);
method public int generateAudioSessionId();
+ method public android.media.AudioRecordConfiguration[] getActiveRecordConfigurations();
method public android.media.AudioDeviceInfo[] getDevices(int);
method public int getMode();
method public java.lang.String getParameters(java.lang.String);
@@ -19315,6 +19352,7 @@
method public void playSoundEffect(int);
method public void playSoundEffect(int, float);
method public void registerAudioDeviceCallback(android.media.AudioDeviceCallback, android.os.Handler);
+ method public void registerAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback, android.os.Handler);
method public deprecated void registerMediaButtonEventReceiver(android.content.ComponentName);
method public deprecated void registerMediaButtonEventReceiver(android.app.PendingIntent);
method public deprecated void registerRemoteControlClient(android.media.RemoteControlClient);
@@ -19338,6 +19376,7 @@
method public void stopBluetoothSco();
method public void unloadSoundEffects();
method public void unregisterAudioDeviceCallback(android.media.AudioDeviceCallback);
+ method public void unregisterAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback);
method public deprecated void unregisterMediaButtonEventReceiver(android.content.ComponentName);
method public deprecated void unregisterMediaButtonEventReceiver(android.app.PendingIntent);
method public deprecated void unregisterRemoteControlClient(android.media.RemoteControlClient);
@@ -19434,6 +19473,11 @@
field public static final deprecated int VIBRATE_TYPE_RINGER = 0; // 0x0
}
+ public static abstract class AudioManager.AudioRecordingCallback {
+ ctor public AudioManager.AudioRecordingCallback();
+ method public void onRecordConfigChanged();
+ }
+
public static abstract interface AudioManager.OnAudioFocusChangeListener {
method public abstract void onAudioFocusChange(int);
}
@@ -19504,6 +19548,14 @@
method public abstract void onRoutingChanged(android.media.AudioRecord);
}
+ public class AudioRecordConfiguration implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getAudioSessionId();
+ method public int getClientAudioSource();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.media.AudioRecordConfiguration> CREATOR;
+ }
+
public abstract interface AudioRouting {
method public abstract void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public abstract android.media.AudioDeviceInfo getPreferredDevice();
@@ -22632,6 +22684,7 @@
}
public class MtpDeviceInfo {
+ method public final int[] getEventsSupported();
method public final java.lang.String getManufacturer();
method public final java.lang.String getModel();
method public final int[] getOperationsSupported();
@@ -22738,6 +22791,7 @@
method public android.net.NetworkInfo getNetworkInfo(android.net.Network);
method public deprecated int getNetworkPreference();
method public static deprecated android.net.Network getProcessDefaultNetwork();
+ method public int getRestrictBackgroundStatus();
method public boolean isActiveNetworkMetered();
method public boolean isDefaultNetworkActive();
method public static deprecated boolean isNetworkTypeValid(int);
@@ -22772,6 +22826,9 @@
field public static final java.lang.String EXTRA_NO_CONNECTIVITY = "noConnectivity";
field public static final java.lang.String EXTRA_OTHER_NETWORK_INFO = "otherNetwork";
field public static final java.lang.String EXTRA_REASON = "reason";
+ field public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1; // 0x1
+ field public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3; // 0x3
+ field public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2; // 0x2
field public static final int TYPE_BLUETOOTH = 7; // 0x7
field public static final int TYPE_DUMMY = 8; // 0x8
field public static final int TYPE_ETHERNET = 9; // 0x9
@@ -22798,6 +22855,9 @@
method public abstract void onNetworkActive();
}
+ public static abstract class ConnectivityManager.RestrictBackgroundStatus implements java.lang.annotation.Annotation {
+ }
+
public class Credentials {
ctor public Credentials(int, int, int);
method public int getGid();
@@ -28586,6 +28646,7 @@
field public static final java.lang.String DISALLOW_OUTGOING_CALLS = "no_outgoing_calls";
field public static final java.lang.String DISALLOW_REMOVE_USER = "no_remove_user";
field public static final java.lang.String DISALLOW_SAFE_BOOT = "no_safe_boot";
+ field public static final java.lang.String DISALLOW_SET_USER_ICON = "no_set_user_icon";
field public static final java.lang.String DISALLOW_SHARE_LOCATION = "no_share_location";
field public static final java.lang.String DISALLOW_SMS = "no_sms";
field public static final java.lang.String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps";
@@ -33966,6 +34027,7 @@
method public void setTheme(int);
method public void show(android.os.Bundle, int);
method public void startVoiceActivity(android.content.Intent);
+ field public static final int SHOW_SOURCE_ACTIVITY = 16; // 0x10
field public static final int SHOW_SOURCE_APPLICATION = 8; // 0x8
field public static final int SHOW_SOURCE_ASSIST_GESTURE = 4; // 0x4
field public static final int SHOW_WITH_ASSIST = 1; // 0x1
@@ -35593,6 +35655,7 @@
field public static final java.lang.String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
field public static final java.lang.String ACTION_SHOW_CALL_ACCESSIBILITY_SETTINGS = "android.telecom.action.SHOW_CALL_ACCESSIBILITY_SETTINGS";
field public static final java.lang.String ACTION_SHOW_CALL_SETTINGS = "android.telecom.action.SHOW_CALL_SETTINGS";
+ field public static final java.lang.String ACTION_SHOW_MISSED_CALLS_NOTIFICATION = "android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION";
field public static final java.lang.String ACTION_SHOW_RESPOND_VIA_SMS_SETTINGS = "android.telecom.action.SHOW_RESPOND_VIA_SMS_SETTINGS";
field public static final char DTMF_CHARACTER_PAUSE = 44; // 0x002c ','
field public static final char DTMF_CHARACTER_WAIT = 59; // 0x003b ';'
@@ -35603,6 +35666,8 @@
field public static final java.lang.String EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME = "android.telecom.extra.CHANGE_DEFAULT_DIALER_PACKAGE_NAME";
field public static final java.lang.String EXTRA_INCOMING_CALL_ADDRESS = "android.telecom.extra.INCOMING_CALL_ADDRESS";
field public static final java.lang.String EXTRA_INCOMING_CALL_EXTRAS = "android.telecom.extra.INCOMING_CALL_EXTRAS";
+ field public static final java.lang.String EXTRA_NOTIFICATION_COUNT = "android.telecom.extra.NOTIFICATION_COUNT";
+ field public static final java.lang.String EXTRA_NOTIFICATION_PHONE_NUMBER = "android.telecom.extra.NOTIFICATION_PHONE_NUMBER";
field public static final java.lang.String EXTRA_OUTGOING_CALL_EXTRAS = "android.telecom.extra.OUTGOING_CALL_EXTRAS";
field public static final java.lang.String EXTRA_PHONE_ACCOUNT_HANDLE = "android.telecom.extra.PHONE_ACCOUNT_HANDLE";
field public static final java.lang.String EXTRA_START_CALL_WITH_SPEAKERPHONE = "android.telecom.extra.START_CALL_WITH_SPEAKERPHONE";
@@ -42857,6 +42922,7 @@
method public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo getCollectionInfo();
method public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo getCollectionItemInfo();
method public java.lang.CharSequence getContentDescription();
+ method public int getDrawingOrder();
method public java.lang.CharSequence getError();
method public android.os.Bundle getExtras();
method public int getInputType();
@@ -42919,6 +42985,7 @@
method public void setContentInvalid(boolean);
method public void setContextClickable(boolean);
method public void setDismissable(boolean);
+ method public void setDrawingOrder(int);
method public void setEditable(boolean);
method public void setEnabled(boolean);
method public void setError(java.lang.CharSequence);
@@ -43583,6 +43650,7 @@
field public android.os.Bundle extras;
field public int fieldId;
field public java.lang.String fieldName;
+ field public android.util.LocaleList hintLocales;
field public java.lang.CharSequence hintText;
field public int imeOptions;
field public int initialCapsMode;
@@ -43590,7 +43658,6 @@
field public int initialSelStart;
field public int inputType;
field public java.lang.CharSequence label;
- field public android.util.LocaleList locales;
field public java.lang.String packageName;
field public java.lang.String privateImeOptions;
}
@@ -44067,6 +44134,30 @@
method public abstract android.view.View getFullScreenView(int, android.content.Context);
}
+ public class ServiceWorkerClient {
+ ctor public ServiceWorkerClient();
+ method public android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebResourceRequest);
+ }
+
+ public abstract class ServiceWorkerController {
+ ctor public ServiceWorkerController();
+ method public static android.webkit.ServiceWorkerController getInstance();
+ method public abstract android.webkit.ServiceWorkerWebSettings getServiceWorkerWebSettings();
+ method public abstract void setServiceWorkerClient(android.webkit.ServiceWorkerClient);
+ }
+
+ public abstract class ServiceWorkerWebSettings {
+ ctor public ServiceWorkerWebSettings();
+ method public abstract boolean getAllowContentAccess();
+ method public abstract boolean getAllowFileAccess();
+ method public abstract boolean getBlockNetworkLoads();
+ method public abstract int getCacheMode();
+ method public abstract void setAllowContentAccess(boolean);
+ method public abstract void setAllowFileAccess(boolean);
+ method public abstract void setBlockNetworkLoads(boolean);
+ method public abstract void setCacheMode(int);
+ }
+
public class SslErrorHandler extends android.os.Handler {
method public void cancel();
method public void proceed();
@@ -46837,6 +46928,7 @@
method public int getHyphenationFrequency();
method public int getImeActionId();
method public java.lang.CharSequence getImeActionLabel();
+ method public android.util.LocaleList getImeHintLocales();
method public int getImeOptions();
method public boolean getIncludeFontPadding();
method public android.os.Bundle getInputExtras(boolean);
@@ -46943,6 +47035,7 @@
method public void setHorizontallyScrolling(boolean);
method public void setHyphenationFrequency(int);
method public void setImeActionLabel(java.lang.CharSequence, int);
+ method public void setImeHintLocales(android.util.LocaleList);
method public void setImeOptions(int);
method public void setIncludeFontPadding(boolean);
method public void setInputExtras(int) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
@@ -47896,10 +47989,8 @@
}
public final class Console implements java.io.Flushable {
- method public static java.io.Console console();
method public void flush();
method public java.io.Console format(java.lang.String, java.lang.Object...);
- method public static synchronized java.io.Console getConsole();
method public java.io.Console printf(java.lang.String, java.lang.Object...);
method public java.lang.String readLine(java.lang.String, java.lang.Object...);
method public java.lang.String readLine();
@@ -48183,7 +48274,6 @@
public class InterruptedIOException extends java.io.IOException {
ctor public InterruptedIOException();
ctor public InterruptedIOException(java.lang.String);
- ctor public InterruptedIOException(java.lang.Throwable);
field public int bytesTransferred;
}
@@ -48854,7 +48944,6 @@
method public long longValue();
method public static byte parseByte(java.lang.String, int) throws java.lang.NumberFormatException;
method public static byte parseByte(java.lang.String) throws java.lang.NumberFormatException;
- method public static java.lang.String toHexString(byte, boolean);
method public static java.lang.String toString(byte);
method public static java.lang.Byte valueOf(byte);
method public static java.lang.Byte valueOf(java.lang.String, int) throws java.lang.NumberFormatException;
@@ -51006,7 +51095,6 @@
public class BindException extends java.net.SocketException {
ctor public BindException(java.lang.String);
ctor public BindException();
- ctor public BindException(java.lang.String, java.lang.Throwable);
}
public abstract class CacheRequest {
@@ -51024,7 +51112,6 @@
public class ConnectException extends java.net.SocketException {
ctor public ConnectException(java.lang.String);
ctor public ConnectException();
- ctor public ConnectException(java.lang.String, java.lang.Throwable);
}
public abstract class ContentHandler {
@@ -51104,7 +51191,6 @@
method public void disconnect();
method public synchronized boolean getBroadcast() throws java.net.SocketException;
method public java.nio.channels.DatagramChannel getChannel();
- method public final java.io.FileDescriptor getFileDescriptor$();
method public java.net.InetAddress getInetAddress();
method public java.net.InetAddress getLocalAddress();
method public int getLocalPort();
@@ -51181,7 +51267,6 @@
method public boolean hasExpired();
method public boolean isHttpOnly();
method public static java.util.List<java.net.HttpCookie> parse(java.lang.String);
- method public static java.util.List<java.net.HttpCookie> parse(java.lang.String, boolean);
method public void setComment(java.lang.String);
method public void setCommentURL(java.lang.String);
method public void setDiscard(boolean);
@@ -51274,9 +51359,6 @@
}
public final class Inet4Address extends java.net.InetAddress {
- field public static final java.net.InetAddress ALL;
- field public static final java.net.InetAddress ANY;
- field public static final java.net.InetAddress LOOPBACK;
}
public final class Inet6Address extends java.net.InetAddress {
@@ -51285,8 +51367,6 @@
method public int getScopeId();
method public java.net.NetworkInterface getScopedInterface();
method public boolean isIPv4CompatibleAddress();
- field public static final java.net.InetAddress ANY;
- field public static final java.net.InetAddress LOOPBACK;
}
public class InetAddress implements java.io.Serializable {
@@ -51414,13 +51494,11 @@
public class PortUnreachableException extends java.net.SocketException {
ctor public PortUnreachableException(java.lang.String);
ctor public PortUnreachableException();
- ctor public PortUnreachableException(java.lang.String, java.lang.Throwable);
}
public class ProtocolException extends java.io.IOException {
ctor public ProtocolException(java.lang.String);
ctor public ProtocolException();
- ctor public ProtocolException(java.lang.String, java.lang.Throwable);
}
public abstract interface ProtocolFamily {
@@ -51555,8 +51633,6 @@
public class SocketException extends java.io.IOException {
ctor public SocketException(java.lang.String);
ctor public SocketException();
- ctor public SocketException(java.lang.Throwable);
- ctor public SocketException(java.lang.String, java.lang.Throwable);
}
public abstract class SocketImpl implements java.net.SocketOptions {
@@ -51626,8 +51702,6 @@
public class SocketTimeoutException extends java.io.InterruptedIOException {
ctor public SocketTimeoutException(java.lang.String);
ctor public SocketTimeoutException();
- ctor public SocketTimeoutException(java.lang.Throwable);
- ctor public SocketTimeoutException(java.lang.String, java.lang.Throwable);
}
public final class StandardProtocolFamily extends java.lang.Enum implements java.net.ProtocolFamily {
@@ -59572,7 +59646,6 @@
ctor public JarFile(java.io.File, boolean, int) throws java.io.IOException;
method public java.util.jar.JarEntry getJarEntry(java.lang.String);
method public java.util.jar.Manifest getManifest() throws java.io.IOException;
- method public boolean hasClassPathAttribute() throws java.io.IOException;
field public static final java.lang.String MANIFEST_NAME = "META-INF/MANIFEST.MF";
}
diff --git a/api/system-current.txt b/api/system-current.txt
index 664bcd8..ede4658 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1282,6 +1282,7 @@
field public static final int summaryOn = 16843247; // 0x10101ef
field public static final int supportsAssist = 16844016; // 0x10104f0
field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1
+ field public static final int supportsLocalInteraction = 16844048; // 0x1010510
field public static final int supportsPictureInPicture = 16844024; // 0x10104f8
field public static final int supportsRtl = 16843695; // 0x10103af
field public static final int supportsSwitchingToNextInputMethod = 16843755; // 0x10103eb
@@ -2714,6 +2715,7 @@
public abstract class AccessibilityService extends android.app.Service {
ctor public AccessibilityService();
+ method public final void disableSelf();
method public final boolean dispatchGesture(android.accessibilityservice.GestureDescription, android.accessibilityservice.AccessibilityService.GestureResultCallback, android.os.Handler);
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
@@ -3550,6 +3552,7 @@
method public boolean isDestroyed();
method public boolean isFinishing();
method public boolean isImmersive();
+ method public boolean isLocalVoiceInteractionSupported();
method public boolean isTaskRoot();
method public boolean isVoiceInteraction();
method public boolean isVoiceInteractionRoot();
@@ -3592,6 +3595,8 @@
method public boolean onKeyMultiple(int, int, android.view.KeyEvent);
method public boolean onKeyShortcut(int, android.view.KeyEvent);
method public boolean onKeyUp(int, android.view.KeyEvent);
+ method public void onLocalVoiceInteractionStarted();
+ method public void onLocalVoiceInteractionStopped();
method public void onLowMemory();
method public boolean onMenuItemSelected(int, android.view.MenuItem);
method public boolean onMenuOpened(int, android.view.Menu);
@@ -3706,12 +3711,14 @@
method public void startIntentSenderForResult(android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
method public void startIntentSenderFromChild(android.app.Activity, android.content.IntentSender, int, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException;
method public void startIntentSenderFromChild(android.app.Activity, android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
+ method public void startLocalVoiceInteraction(android.os.Bundle);
method public void startLockTask();
method public deprecated void startManagingCursor(android.database.Cursor);
method public boolean startNextMatchingActivity(android.content.Intent);
method public boolean startNextMatchingActivity(android.content.Intent, android.os.Bundle);
method public void startPostponedEnterTransition();
method public void startSearch(java.lang.String, boolean, android.os.Bundle, boolean);
+ method public void stopLocalVoiceInteraction();
method public void stopLockTask();
method public deprecated void stopManagingCursor(android.database.Cursor);
method public void takeKeyEvents(boolean);
@@ -5817,12 +5824,15 @@
method public android.graphics.drawable.Drawable peekFastDrawable();
method public void sendWallpaperCommand(android.os.IBinder, java.lang.String, int, int, int, android.os.Bundle);
method public void setBitmap(android.graphics.Bitmap) throws java.io.IOException;
- method public void setBitmap(android.graphics.Bitmap, android.graphics.Rect, boolean) throws java.io.IOException;
+ method public int setBitmap(android.graphics.Bitmap, android.graphics.Rect, boolean) throws java.io.IOException;
+ method public int setBitmap(android.graphics.Bitmap, android.graphics.Rect, boolean, int) throws java.io.IOException;
method public void setDisplayOffset(android.os.IBinder, int, int);
method public void setDisplayPadding(android.graphics.Rect);
method public void setResource(int) throws java.io.IOException;
+ method public int setResource(int, int) throws java.io.IOException;
method public void setStream(java.io.InputStream) throws java.io.IOException;
- method public void setStream(java.io.InputStream, android.graphics.Rect, boolean) throws java.io.IOException;
+ method public int setStream(java.io.InputStream, android.graphics.Rect, boolean) throws java.io.IOException;
+ method public int setStream(java.io.InputStream, android.graphics.Rect, boolean, int) throws java.io.IOException;
method public boolean setWallpaperComponent(android.content.ComponentName);
method public void setWallpaperOffsetSteps(float, float);
method public void setWallpaperOffsets(android.os.IBinder, float, float);
@@ -5834,6 +5844,8 @@
field public static final java.lang.String COMMAND_SECONDARY_TAP = "android.wallpaper.secondaryTap";
field public static final java.lang.String COMMAND_TAP = "android.wallpaper.tap";
field public static final java.lang.String EXTRA_LIVE_WALLPAPER_COMPONENT = "android.service.wallpaper.extra.LIVE_WALLPAPER_COMPONENT";
+ field public static final int FLAG_SET_LOCK = 2; // 0x2
+ field public static final int FLAG_SET_SYSTEM = 1; // 0x1
field public static final java.lang.String WALLPAPER_PREVIEW_META_DATA = "android.wallpaper.preview";
}
@@ -5940,11 +5952,13 @@
method public deprecated android.content.ComponentName getDeviceInitializerComponent();
method public java.lang.String getDeviceOwner();
method public java.lang.String getDeviceOwnerLockScreenInfo();
+ method public java.lang.String getDeviceOwnerNameOnAnyUser();
method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
method public int getKeyguardDisabledFeatures(android.content.ComponentName);
method public java.lang.String getLongSupportMessage(android.content.ComponentName);
method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
method public long getMaximumTimeToLock(android.content.ComponentName);
+ method public int getOrganizationColor(android.content.ComponentName);
method public boolean getPackageSuspended(android.content.ComponentName, java.lang.String);
method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName);
method public long getPasswordExpiration(android.content.ComponentName);
@@ -6019,6 +6033,7 @@
method public void setMasterVolumeMuted(android.content.ComponentName, boolean);
method public void setMaximumFailedPasswordsForWipe(android.content.ComponentName, int);
method public void setMaximumTimeToLock(android.content.ComponentName, long);
+ method public void setOrganizationColor(android.content.ComponentName, int);
method public boolean setPackageSuspended(android.content.ComponentName, java.lang.String, boolean);
method public void setPasswordExpirationTimeout(android.content.ComponentName, long);
method public void setPasswordHistoryLength(android.content.ComponentName, int);
@@ -6291,12 +6306,36 @@
method public static void dataChanged(java.lang.String);
method public long getAvailableRestoreToken(java.lang.String);
method public java.lang.String getCurrentTransport();
+ method public boolean isAppEligibleForBackup(java.lang.String);
method public boolean isBackupEnabled();
method public java.lang.String[] listAllTransports();
+ method public int requestBackup(java.lang.String[], android.app.backup.BackupObserver);
method public int requestRestore(android.app.backup.RestoreObserver);
method public java.lang.String selectBackupTransport(java.lang.String);
method public void setAutoRestore(boolean);
method public void setBackupEnabled(boolean);
+ field public static final int ERROR_AGENT_FAILURE = -1003; // 0xfffffc15
+ field public static final int ERROR_BACKUP_NOT_ALLOWED = -2001; // 0xfffff82f
+ field public static final int ERROR_PACKAGE_NOT_FOUND = -2002; // 0xfffff82e
+ field public static final int ERROR_TRANSPORT_ABORTED = -1000; // 0xfffffc18
+ field public static final int ERROR_TRANSPORT_PACKAGE_REJECTED = -1002; // 0xfffffc16
+ field public static final int SUCCESS = 0; // 0x0
+ }
+
+ public abstract class BackupObserver {
+ ctor public BackupObserver();
+ method public void backupFinished(int);
+ method public void onResult(java.lang.String, int);
+ method public void onUpdate(java.lang.String, android.app.backup.BackupProgress);
+ }
+
+ public class BackupProgress implements android.os.Parcelable {
+ ctor public BackupProgress(long, long);
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.backup.BackupProgress> CREATOR;
+ field public final long bytesExpected;
+ field public final long bytesTransferred;
}
public class BackupTransport {
@@ -6317,9 +6356,12 @@
method public int getNextFullRestoreDataChunk(android.os.ParcelFileDescriptor);
method public int getRestoreData(android.os.ParcelFileDescriptor);
method public int initializeDevice();
+ method public boolean isAppEligibleForBackup(android.content.pm.PackageInfo, boolean);
method public java.lang.String name();
method public android.app.backup.RestoreDescription nextRestorePackage();
+ method public int performBackup(android.content.pm.PackageInfo, android.os.ParcelFileDescriptor, int);
method public int performBackup(android.content.pm.PackageInfo, android.os.ParcelFileDescriptor);
+ method public int performFullBackup(android.content.pm.PackageInfo, android.os.ParcelFileDescriptor, int);
method public int performFullBackup(android.content.pm.PackageInfo, android.os.ParcelFileDescriptor);
method public long requestBackupTime();
method public long requestFullBackupTime();
@@ -6328,6 +6370,7 @@
method public java.lang.String transportDirName();
field public static final int AGENT_ERROR = -1003; // 0xfffffc15
field public static final int AGENT_UNKNOWN = -1004; // 0xfffffc14
+ field public static final int FLAG_USER_INITIATED = 1; // 0x1
field public static final int NO_MORE_DATA = -1; // 0xffffffff
field public static final int TRANSPORT_ERROR = -1000; // 0xfffffc18
field public static final int TRANSPORT_NOT_INITIALIZED = -1001; // 0xfffffc17
@@ -8698,6 +8741,7 @@
field public static final java.lang.String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
field public static final java.lang.String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
field public static final java.lang.String ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
+ field public static final java.lang.String ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
field public static final java.lang.String ACTION_PACKAGES_SUSPENDED = "android.intent.action.PACKAGES_SUSPENDED";
field public static final java.lang.String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED";
field public static final java.lang.String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED";
@@ -8721,6 +8765,7 @@
field public static final java.lang.String ACTION_PROVIDER_CHANGED = "android.intent.action.PROVIDER_CHANGED";
field public static final java.lang.String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART";
field public static final java.lang.String ACTION_QUICK_CLOCK = "android.intent.action.QUICK_CLOCK";
+ field public static final java.lang.String ACTION_QUICK_VIEW = "android.intent.action.QUICK_VIEW";
field public static final java.lang.String ACTION_REBOOT = "android.intent.action.REBOOT";
field public static final java.lang.String ACTION_RESOLVE_EPHEMERAL_PACKAGE = "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE";
field public static final java.lang.String ACTION_RUN = "android.intent.action.RUN";
@@ -8818,6 +8863,7 @@
field public static final java.lang.String EXTRA_EPHEMERAL_FAILURE = "android.intent.extra.EPHEMERAL_FAILURE";
field public static final java.lang.String EXTRA_EPHEMERAL_SUCCESS = "android.intent.extra.EPHEMERAL_SUCCESS";
field public static final java.lang.String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT";
+ field public static final java.lang.String EXTRA_INDEX = "android.intent.extra.INDEX";
field public static final java.lang.String EXTRA_INITIAL_INTENTS = "android.intent.extra.INITIAL_INTENTS";
field public static final java.lang.String EXTRA_INSTALLER_PACKAGE_NAME = "android.intent.extra.INSTALLER_PACKAGE_NAME";
field public static final java.lang.String EXTRA_INTENT = "android.intent.extra.INTENT";
@@ -9645,6 +9691,7 @@
public class LauncherApps {
method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle);
+ method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int, android.os.UserHandle);
method public boolean isActivityEnabled(android.content.ComponentName, android.os.UserHandle);
method public boolean isPackageEnabled(java.lang.String, android.os.UserHandle);
method public void registerCallback(android.content.pm.LauncherApps.Callback);
@@ -9661,7 +9708,9 @@
method public abstract void onPackageChanged(java.lang.String, android.os.UserHandle);
method public abstract void onPackageRemoved(java.lang.String, android.os.UserHandle);
method public abstract void onPackagesAvailable(java.lang.String[], android.os.UserHandle, boolean);
+ method public void onPackagesSuspended(java.lang.String[], android.os.UserHandle);
method public abstract void onPackagesUnavailable(java.lang.String[], android.os.UserHandle, boolean);
+ method public void onPackagesUnsuspended(java.lang.String[], android.os.UserHandle);
}
public class PackageInfo implements android.os.Parcelable {
@@ -13939,6 +13988,7 @@
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> CONTROL_MAX_REGIONS_AE;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> CONTROL_MAX_REGIONS_AF;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> CONTROL_MAX_REGIONS_AWB;
+ field public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Integer>> CONTROL_POST_RAW_SENSITIVITY_BOOST_RANGE;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> DEPTH_DEPTH_IS_EXCLUSIVE;
field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> EDGE_AVAILABLE_EDGE_MODES;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> FLASH_INFO_AVAILABLE;
@@ -14018,9 +14068,11 @@
method public abstract void close();
method public abstract android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int) throws android.hardware.camera2.CameraAccessException;
method public abstract void createCaptureSession(java.util.List<android.view.Surface>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method public abstract void createCaptureSessionByOutputConfiguration(java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public abstract void createConstrainedHighSpeedCaptureSession(java.util.List<android.view.Surface>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public abstract android.hardware.camera2.CaptureRequest.Builder createReprocessCaptureRequest(android.hardware.camera2.TotalCaptureResult) throws android.hardware.camera2.CameraAccessException;
method public abstract void createReprocessableCaptureSession(android.hardware.camera2.params.InputConfiguration, java.util.List<android.view.Surface>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method public abstract void createReprocessableCaptureSessionWithConfigurations(android.hardware.camera2.params.InputConfiguration, java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public abstract java.lang.String getId();
field public static final int TEMPLATE_MANUAL = 6; // 0x6
field public static final int TEMPLATE_PREVIEW = 1; // 0x1
@@ -14298,6 +14350,7 @@
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_CAPTURE_INTENT;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_MODE;
+ field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_SCENE_MODE;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_VIDEO_STABILIZATION_MODE;
field public static final android.os.Parcelable.Creator<android.hardware.camera2.CaptureRequest> CREATOR;
@@ -14376,6 +14429,7 @@
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_CAPTURE_INTENT;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_MODE;
+ field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_SCENE_MODE;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_VIDEO_STABILIZATION_MODE;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EDGE_MODE;
@@ -14517,6 +14571,24 @@
field public static final int METERING_WEIGHT_MIN = 0; // 0x0
}
+ public final class OutputConfiguration implements android.os.Parcelable {
+ ctor public OutputConfiguration(android.view.Surface);
+ ctor public OutputConfiguration(android.view.Surface, int);
+ ctor public OutputConfiguration(android.hardware.camera2.params.OutputConfiguration);
+ method public int describeContents();
+ method public int getRotation();
+ method public android.view.Surface getSurface();
+ method public int getSurfaceSetId();
+ method public void setSurfaceSetId(int);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR;
+ field public static final int ROTATION_0 = 0; // 0x0
+ field public static final int ROTATION_180 = 2; // 0x2
+ field public static final int ROTATION_270 = 3; // 0x3
+ field public static final int ROTATION_90 = 1; // 0x1
+ field public static final int SURFACE_SET_ID_INVALID = -1; // 0xffffffff
+ }
+
public final class RggbChannelVector {
ctor public RggbChannelVector(float, float, float, float);
method public void copyTo(float[], int);
@@ -20592,6 +20664,7 @@
method public void adjustVolume(int, int);
method public void dispatchMediaKeyEvent(android.view.KeyEvent);
method public int generateAudioSessionId();
+ method public android.media.AudioRecordConfiguration[] getActiveRecordConfigurations();
method public android.media.AudioDeviceInfo[] getDevices(int);
method public int getMode();
method public java.lang.String getParameters(java.lang.String);
@@ -20616,6 +20689,7 @@
method public void playSoundEffect(int, float);
method public void registerAudioDeviceCallback(android.media.AudioDeviceCallback, android.os.Handler);
method public int registerAudioPolicy(android.media.audiopolicy.AudioPolicy);
+ method public void registerAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback, android.os.Handler);
method public deprecated void registerMediaButtonEventReceiver(android.content.ComponentName);
method public deprecated void registerMediaButtonEventReceiver(android.app.PendingIntent);
method public deprecated void registerRemoteControlClient(android.media.RemoteControlClient);
@@ -20642,6 +20716,7 @@
method public void unloadSoundEffects();
method public void unregisterAudioDeviceCallback(android.media.AudioDeviceCallback);
method public void unregisterAudioPolicyAsync(android.media.audiopolicy.AudioPolicy);
+ method public void unregisterAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback);
method public deprecated void unregisterMediaButtonEventReceiver(android.content.ComponentName);
method public deprecated void unregisterMediaButtonEventReceiver(android.app.PendingIntent);
method public deprecated void unregisterRemoteControlClient(android.media.RemoteControlClient);
@@ -20741,6 +20816,11 @@
field public static final deprecated int VIBRATE_TYPE_RINGER = 0; // 0x0
}
+ public static abstract class AudioManager.AudioRecordingCallback {
+ ctor public AudioManager.AudioRecordingCallback();
+ method public void onRecordConfigChanged();
+ }
+
public static abstract interface AudioManager.OnAudioFocusChangeListener {
method public abstract void onAudioFocusChange(int);
}
@@ -20814,6 +20894,14 @@
method public abstract void onRoutingChanged(android.media.AudioRecord);
}
+ public class AudioRecordConfiguration implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getAudioSessionId();
+ method public int getClientAudioSource();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.media.AudioRecordConfiguration> CREATOR;
+ }
+
public abstract interface AudioRouting {
method public abstract void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public abstract android.media.AudioDeviceInfo getPreferredDevice();
@@ -24190,6 +24278,7 @@
}
public class MtpDeviceInfo {
+ method public final int[] getEventsSupported();
method public final java.lang.String getManufacturer();
method public final java.lang.String getModel();
method public final int[] getOperationsSupported();
@@ -24297,6 +24386,7 @@
method public android.net.NetworkInfo getNetworkInfo(android.net.Network);
method public deprecated int getNetworkPreference();
method public static deprecated android.net.Network getProcessDefaultNetwork();
+ method public int getRestrictBackgroundStatus();
method public boolean isActiveNetworkMetered();
method public boolean isDefaultNetworkActive();
method public static deprecated boolean isNetworkTypeValid(int);
@@ -24331,6 +24421,9 @@
field public static final java.lang.String EXTRA_NO_CONNECTIVITY = "noConnectivity";
field public static final java.lang.String EXTRA_OTHER_NETWORK_INFO = "otherNetwork";
field public static final java.lang.String EXTRA_REASON = "reason";
+ field public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1; // 0x1
+ field public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3; // 0x3
+ field public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2; // 0x2
field public static final int TYPE_BLUETOOTH = 7; // 0x7
field public static final int TYPE_DUMMY = 8; // 0x8
field public static final int TYPE_ETHERNET = 9; // 0x9
@@ -24357,6 +24450,9 @@
method public abstract void onNetworkActive();
}
+ public static abstract class ConnectivityManager.RestrictBackgroundStatus implements java.lang.annotation.Annotation {
+ }
+
public class Credentials {
ctor public Credentials(int, int, int);
method public int getGid();
@@ -30612,6 +30708,7 @@
field public static final java.lang.String DISALLOW_OUTGOING_CALLS = "no_outgoing_calls";
field public static final java.lang.String DISALLOW_REMOVE_USER = "no_remove_user";
field public static final java.lang.String DISALLOW_SAFE_BOOT = "no_safe_boot";
+ field public static final java.lang.String DISALLOW_SET_USER_ICON = "no_set_user_icon";
field public static final java.lang.String DISALLOW_SHARE_LOCATION = "no_share_location";
field public static final java.lang.String DISALLOW_SMS = "no_sms";
field public static final java.lang.String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps";
@@ -36180,6 +36277,7 @@
method public void setTheme(int);
method public void show(android.os.Bundle, int);
method public void startVoiceActivity(android.content.Intent);
+ field public static final int SHOW_SOURCE_ACTIVITY = 16; // 0x10
field public static final int SHOW_SOURCE_APPLICATION = 8; // 0x8
field public static final int SHOW_SOURCE_ASSIST_GESTURE = 4; // 0x4
field public static final int SHOW_WITH_ASSIST = 1; // 0x1
@@ -45258,6 +45356,7 @@
method public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo getCollectionInfo();
method public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo getCollectionItemInfo();
method public java.lang.CharSequence getContentDescription();
+ method public int getDrawingOrder();
method public java.lang.CharSequence getError();
method public android.os.Bundle getExtras();
method public int getInputType();
@@ -45320,6 +45419,7 @@
method public void setContentInvalid(boolean);
method public void setContextClickable(boolean);
method public void setDismissable(boolean);
+ method public void setDrawingOrder(int);
method public void setEditable(boolean);
method public void setEnabled(boolean);
method public void setError(java.lang.CharSequence);
@@ -45984,6 +46084,7 @@
field public android.os.Bundle extras;
field public int fieldId;
field public java.lang.String fieldName;
+ field public android.util.LocaleList hintLocales;
field public java.lang.CharSequence hintText;
field public int imeOptions;
field public int initialCapsMode;
@@ -45991,7 +46092,6 @@
field public int initialSelStart;
field public int inputType;
field public java.lang.CharSequence label;
- field public android.util.LocaleList locales;
field public java.lang.String packageName;
field public java.lang.String privateImeOptions;
}
@@ -46522,6 +46622,30 @@
method public abstract android.view.View getFullScreenView(int, android.content.Context);
}
+ public class ServiceWorkerClient {
+ ctor public ServiceWorkerClient();
+ method public android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebResourceRequest);
+ }
+
+ public abstract class ServiceWorkerController {
+ ctor public ServiceWorkerController();
+ method public static android.webkit.ServiceWorkerController getInstance();
+ method public abstract android.webkit.ServiceWorkerWebSettings getServiceWorkerWebSettings();
+ method public abstract void setServiceWorkerClient(android.webkit.ServiceWorkerClient);
+ }
+
+ public abstract class ServiceWorkerWebSettings {
+ ctor public ServiceWorkerWebSettings();
+ method public abstract boolean getAllowContentAccess();
+ method public abstract boolean getAllowFileAccess();
+ method public abstract boolean getBlockNetworkLoads();
+ method public abstract int getCacheMode();
+ method public abstract void setAllowContentAccess(boolean);
+ method public abstract void setAllowFileAccess(boolean);
+ method public abstract void setBlockNetworkLoads(boolean);
+ method public abstract void setCacheMode(int);
+ }
+
public class SslErrorHandler extends android.os.Handler {
ctor public SslErrorHandler();
method public void cancel();
@@ -46534,12 +46658,18 @@
method public abstract void deleteKey(android.net.Uri, android.webkit.ValueCallback<java.lang.Boolean>);
method public abstract void enableTokenBinding();
method public static android.webkit.TokenBindingService getInstance();
- method public abstract void getKey(android.net.Uri, java.lang.String, android.webkit.ValueCallback<java.security.KeyPair>);
+ method public abstract void getKey(android.net.Uri, java.lang.String[], android.webkit.ValueCallback<android.webkit.TokenBindingService.TokenBindingKey>);
field public static final java.lang.String KEY_ALGORITHM_ECDSAP256 = "ECDSAP256";
field public static final java.lang.String KEY_ALGORITHM_RSA2048_PKCS_1_5 = "RSA2048_PKCS_1.5";
field public static final java.lang.String KEY_ALGORITHM_RSA2048_PSS = "RSA2048PSS";
}
+ public static abstract class TokenBindingService.TokenBindingKey {
+ ctor public TokenBindingService.TokenBindingKey();
+ method public abstract java.lang.String getAlgorithm();
+ method public abstract java.security.KeyPair getKeyPair();
+ }
+
public final class URLUtil {
ctor public URLUtil();
method public static java.lang.String composeSearchUrl(java.lang.String, java.lang.String, java.lang.String);
@@ -47161,6 +47291,7 @@
method public abstract android.webkit.WebViewProvider createWebView(android.webkit.WebView, android.webkit.WebView.PrivateAccess);
method public abstract android.webkit.CookieManager getCookieManager();
method public abstract android.webkit.GeolocationPermissions getGeolocationPermissions();
+ method public abstract android.webkit.ServiceWorkerController getServiceWorkerController();
method public abstract android.webkit.WebViewFactoryProvider.Statics getStatics();
method public abstract android.webkit.TokenBindingService getTokenBindingService();
method public abstract android.webkit.WebIconDatabase getWebIconDatabase();
@@ -49564,6 +49695,7 @@
method public int getHyphenationFrequency();
method public int getImeActionId();
method public java.lang.CharSequence getImeActionLabel();
+ method public android.util.LocaleList getImeHintLocales();
method public int getImeOptions();
method public boolean getIncludeFontPadding();
method public android.os.Bundle getInputExtras(boolean);
@@ -49670,6 +49802,7 @@
method public void setHorizontallyScrolling(boolean);
method public void setHyphenationFrequency(int);
method public void setImeActionLabel(java.lang.CharSequence, int);
+ method public void setImeHintLocales(android.util.LocaleList);
method public void setImeOptions(int);
method public void setIncludeFontPadding(boolean);
method public void setInputExtras(int) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
@@ -50623,10 +50756,8 @@
}
public final class Console implements java.io.Flushable {
- method public static java.io.Console console();
method public void flush();
method public java.io.Console format(java.lang.String, java.lang.Object...);
- method public static synchronized java.io.Console getConsole();
method public java.io.Console printf(java.lang.String, java.lang.Object...);
method public java.lang.String readLine(java.lang.String, java.lang.Object...);
method public java.lang.String readLine();
@@ -50910,7 +51041,6 @@
public class InterruptedIOException extends java.io.IOException {
ctor public InterruptedIOException();
ctor public InterruptedIOException(java.lang.String);
- ctor public InterruptedIOException(java.lang.Throwable);
field public int bytesTransferred;
}
@@ -51581,7 +51711,6 @@
method public long longValue();
method public static byte parseByte(java.lang.String, int) throws java.lang.NumberFormatException;
method public static byte parseByte(java.lang.String) throws java.lang.NumberFormatException;
- method public static java.lang.String toHexString(byte, boolean);
method public static java.lang.String toString(byte);
method public static java.lang.Byte valueOf(byte);
method public static java.lang.Byte valueOf(java.lang.String, int) throws java.lang.NumberFormatException;
@@ -53733,7 +53862,6 @@
public class BindException extends java.net.SocketException {
ctor public BindException(java.lang.String);
ctor public BindException();
- ctor public BindException(java.lang.String, java.lang.Throwable);
}
public abstract class CacheRequest {
@@ -53751,7 +53879,6 @@
public class ConnectException extends java.net.SocketException {
ctor public ConnectException(java.lang.String);
ctor public ConnectException();
- ctor public ConnectException(java.lang.String, java.lang.Throwable);
}
public abstract class ContentHandler {
@@ -53831,7 +53958,6 @@
method public void disconnect();
method public synchronized boolean getBroadcast() throws java.net.SocketException;
method public java.nio.channels.DatagramChannel getChannel();
- method public final java.io.FileDescriptor getFileDescriptor$();
method public java.net.InetAddress getInetAddress();
method public java.net.InetAddress getLocalAddress();
method public int getLocalPort();
@@ -53908,7 +54034,6 @@
method public boolean hasExpired();
method public boolean isHttpOnly();
method public static java.util.List<java.net.HttpCookie> parse(java.lang.String);
- method public static java.util.List<java.net.HttpCookie> parse(java.lang.String, boolean);
method public void setComment(java.lang.String);
method public void setCommentURL(java.lang.String);
method public void setDiscard(boolean);
@@ -54001,9 +54126,6 @@
}
public final class Inet4Address extends java.net.InetAddress {
- field public static final java.net.InetAddress ALL;
- field public static final java.net.InetAddress ANY;
- field public static final java.net.InetAddress LOOPBACK;
}
public final class Inet6Address extends java.net.InetAddress {
@@ -54012,8 +54134,6 @@
method public int getScopeId();
method public java.net.NetworkInterface getScopedInterface();
method public boolean isIPv4CompatibleAddress();
- field public static final java.net.InetAddress ANY;
- field public static final java.net.InetAddress LOOPBACK;
}
public class InetAddress implements java.io.Serializable {
@@ -54141,13 +54261,11 @@
public class PortUnreachableException extends java.net.SocketException {
ctor public PortUnreachableException(java.lang.String);
ctor public PortUnreachableException();
- ctor public PortUnreachableException(java.lang.String, java.lang.Throwable);
}
public class ProtocolException extends java.io.IOException {
ctor public ProtocolException(java.lang.String);
ctor public ProtocolException();
- ctor public ProtocolException(java.lang.String, java.lang.Throwable);
}
public abstract interface ProtocolFamily {
@@ -54282,8 +54400,6 @@
public class SocketException extends java.io.IOException {
ctor public SocketException(java.lang.String);
ctor public SocketException();
- ctor public SocketException(java.lang.Throwable);
- ctor public SocketException(java.lang.String, java.lang.Throwable);
}
public abstract class SocketImpl implements java.net.SocketOptions {
@@ -54353,8 +54469,6 @@
public class SocketTimeoutException extends java.io.InterruptedIOException {
ctor public SocketTimeoutException(java.lang.String);
ctor public SocketTimeoutException();
- ctor public SocketTimeoutException(java.lang.Throwable);
- ctor public SocketTimeoutException(java.lang.String, java.lang.Throwable);
}
public final class StandardProtocolFamily extends java.lang.Enum implements java.net.ProtocolFamily {
@@ -62299,7 +62413,6 @@
ctor public JarFile(java.io.File, boolean, int) throws java.io.IOException;
method public java.util.jar.JarEntry getJarEntry(java.lang.String);
method public java.util.jar.Manifest getManifest() throws java.io.IOException;
- method public boolean hasClassPathAttribute() throws java.io.IOException;
field public static final java.lang.String MANIFEST_NAME = "META-INF/MANIFEST.MF";
}
diff --git a/api/test-current.txt b/api/test-current.txt
index 55468e2..80b6c198 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1183,6 +1183,7 @@
field public static final int summaryOn = 16843247; // 0x10101ef
field public static final int supportsAssist = 16844016; // 0x10104f0
field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1
+ field public static final int supportsLocalInteraction = 16844048; // 0x1010510
field public static final int supportsPictureInPicture = 16844024; // 0x10104f8
field public static final int supportsRtl = 16843695; // 0x10103af
field public static final int supportsSwitchingToNextInputMethod = 16843755; // 0x10103eb
@@ -2612,6 +2613,7 @@
public abstract class AccessibilityService extends android.app.Service {
ctor public AccessibilityService();
+ method public final void disableSelf();
method public final boolean dispatchGesture(android.accessibilityservice.GestureDescription, android.accessibilityservice.AccessibilityService.GestureResultCallback, android.os.Handler);
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
@@ -3435,6 +3437,7 @@
method public boolean isDestroyed();
method public boolean isFinishing();
method public boolean isImmersive();
+ method public boolean isLocalVoiceInteractionSupported();
method public boolean isTaskRoot();
method public boolean isVoiceInteraction();
method public boolean isVoiceInteractionRoot();
@@ -3476,6 +3479,8 @@
method public boolean onKeyMultiple(int, int, android.view.KeyEvent);
method public boolean onKeyShortcut(int, android.view.KeyEvent);
method public boolean onKeyUp(int, android.view.KeyEvent);
+ method public void onLocalVoiceInteractionStarted();
+ method public void onLocalVoiceInteractionStopped();
method public void onLowMemory();
method public boolean onMenuItemSelected(int, android.view.MenuItem);
method public boolean onMenuOpened(int, android.view.Menu);
@@ -3590,12 +3595,14 @@
method public void startIntentSenderForResult(android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
method public void startIntentSenderFromChild(android.app.Activity, android.content.IntentSender, int, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException;
method public void startIntentSenderFromChild(android.app.Activity, android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
+ method public void startLocalVoiceInteraction(android.os.Bundle);
method public void startLockTask();
method public deprecated void startManagingCursor(android.database.Cursor);
method public boolean startNextMatchingActivity(android.content.Intent);
method public boolean startNextMatchingActivity(android.content.Intent, android.os.Bundle);
method public void startPostponedEnterTransition();
method public void startSearch(java.lang.String, boolean, android.os.Bundle, boolean);
+ method public void stopLocalVoiceInteraction();
method public void stopLockTask();
method public deprecated void stopManagingCursor(android.database.Cursor);
method public void takeKeyEvents(boolean);
@@ -5687,10 +5694,13 @@
method public android.graphics.drawable.Drawable peekFastDrawable();
method public void sendWallpaperCommand(android.os.IBinder, java.lang.String, int, int, int, android.os.Bundle);
method public void setBitmap(android.graphics.Bitmap) throws java.io.IOException;
- method public void setBitmap(android.graphics.Bitmap, android.graphics.Rect, boolean) throws java.io.IOException;
+ method public int setBitmap(android.graphics.Bitmap, android.graphics.Rect, boolean) throws java.io.IOException;
+ method public int setBitmap(android.graphics.Bitmap, android.graphics.Rect, boolean, int) throws java.io.IOException;
method public void setResource(int) throws java.io.IOException;
+ method public int setResource(int, int) throws java.io.IOException;
method public void setStream(java.io.InputStream) throws java.io.IOException;
- method public void setStream(java.io.InputStream, android.graphics.Rect, boolean) throws java.io.IOException;
+ method public int setStream(java.io.InputStream, android.graphics.Rect, boolean) throws java.io.IOException;
+ method public int setStream(java.io.InputStream, android.graphics.Rect, boolean, int) throws java.io.IOException;
method public void setWallpaperOffsetSteps(float, float);
method public void setWallpaperOffsets(android.os.IBinder, float, float);
method public void suggestDesiredDimensions(int, int);
@@ -5701,6 +5711,8 @@
field public static final java.lang.String COMMAND_SECONDARY_TAP = "android.wallpaper.secondaryTap";
field public static final java.lang.String COMMAND_TAP = "android.wallpaper.tap";
field public static final java.lang.String EXTRA_LIVE_WALLPAPER_COMPONENT = "android.service.wallpaper.extra.LIVE_WALLPAPER_COMPONENT";
+ field public static final int FLAG_SET_LOCK = 2; // 0x2
+ field public static final int FLAG_SET_SYSTEM = 1; // 0x1
field public static final java.lang.String WALLPAPER_PREVIEW_META_DATA = "android.wallpaper.preview";
}
@@ -5808,6 +5820,7 @@
method public java.lang.String getLongSupportMessage(android.content.ComponentName);
method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
method public long getMaximumTimeToLock(android.content.ComponentName);
+ method public int getOrganizationColor(android.content.ComponentName);
method public boolean getPackageSuspended(android.content.ComponentName, java.lang.String);
method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName);
method public long getPasswordExpiration(android.content.ComponentName);
@@ -5876,6 +5889,7 @@
method public void setMasterVolumeMuted(android.content.ComponentName, boolean);
method public void setMaximumFailedPasswordsForWipe(android.content.ComponentName, int);
method public void setMaximumTimeToLock(android.content.ComponentName, long);
+ method public void setOrganizationColor(android.content.ComponentName, int);
method public boolean setPackageSuspended(android.content.ComponentName, java.lang.String, boolean);
method public void setPasswordExpirationTimeout(android.content.ComponentName, long);
method public void setPasswordHistoryLength(android.content.ComponentName, int);
@@ -8431,6 +8445,7 @@
field public static final java.lang.String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
field public static final java.lang.String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
field public static final java.lang.String ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
+ field public static final java.lang.String ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
field public static final java.lang.String ACTION_PACKAGES_SUSPENDED = "android.intent.action.PACKAGES_SUSPENDED";
field public static final java.lang.String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED";
field public static final java.lang.String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED";
@@ -8453,6 +8468,7 @@
field public static final java.lang.String ACTION_PROCESS_TEXT = "android.intent.action.PROCESS_TEXT";
field public static final java.lang.String ACTION_PROVIDER_CHANGED = "android.intent.action.PROVIDER_CHANGED";
field public static final java.lang.String ACTION_QUICK_CLOCK = "android.intent.action.QUICK_CLOCK";
+ field public static final java.lang.String ACTION_QUICK_VIEW = "android.intent.action.QUICK_VIEW";
field public static final java.lang.String ACTION_REBOOT = "android.intent.action.REBOOT";
field public static final java.lang.String ACTION_RUN = "android.intent.action.RUN";
field public static final java.lang.String ACTION_SCREEN_OFF = "android.intent.action.SCREEN_OFF";
@@ -8546,6 +8562,7 @@
field public static final java.lang.String EXTRA_DONT_KILL_APP = "android.intent.extra.DONT_KILL_APP";
field public static final java.lang.String EXTRA_EMAIL = "android.intent.extra.EMAIL";
field public static final java.lang.String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT";
+ field public static final java.lang.String EXTRA_INDEX = "android.intent.extra.INDEX";
field public static final java.lang.String EXTRA_INITIAL_INTENTS = "android.intent.extra.INITIAL_INTENTS";
field public static final java.lang.String EXTRA_INSTALLER_PACKAGE_NAME = "android.intent.extra.INSTALLER_PACKAGE_NAME";
field public static final java.lang.String EXTRA_INTENT = "android.intent.extra.INTENT";
@@ -9341,6 +9358,7 @@
public class LauncherApps {
method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle);
+ method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int, android.os.UserHandle);
method public boolean isActivityEnabled(android.content.ComponentName, android.os.UserHandle);
method public boolean isPackageEnabled(java.lang.String, android.os.UserHandle);
method public void registerCallback(android.content.pm.LauncherApps.Callback);
@@ -9357,7 +9375,9 @@
method public abstract void onPackageChanged(java.lang.String, android.os.UserHandle);
method public abstract void onPackageRemoved(java.lang.String, android.os.UserHandle);
method public abstract void onPackagesAvailable(java.lang.String[], android.os.UserHandle, boolean);
+ method public void onPackagesSuspended(java.lang.String[], android.os.UserHandle);
method public abstract void onPackagesUnavailable(java.lang.String[], android.os.UserHandle, boolean);
+ method public void onPackagesUnsuspended(java.lang.String[], android.os.UserHandle);
}
public class PackageInfo implements android.os.Parcelable {
@@ -13576,6 +13596,7 @@
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> CONTROL_MAX_REGIONS_AE;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> CONTROL_MAX_REGIONS_AF;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> CONTROL_MAX_REGIONS_AWB;
+ field public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Integer>> CONTROL_POST_RAW_SENSITIVITY_BOOST_RANGE;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> DEPTH_DEPTH_IS_EXCLUSIVE;
field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> EDGE_AVAILABLE_EDGE_MODES;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> FLASH_INFO_AVAILABLE;
@@ -13655,9 +13676,11 @@
method public abstract void close();
method public abstract android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int) throws android.hardware.camera2.CameraAccessException;
method public abstract void createCaptureSession(java.util.List<android.view.Surface>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method public abstract void createCaptureSessionByOutputConfiguration(java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public abstract void createConstrainedHighSpeedCaptureSession(java.util.List<android.view.Surface>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public abstract android.hardware.camera2.CaptureRequest.Builder createReprocessCaptureRequest(android.hardware.camera2.TotalCaptureResult) throws android.hardware.camera2.CameraAccessException;
method public abstract void createReprocessableCaptureSession(android.hardware.camera2.params.InputConfiguration, java.util.List<android.view.Surface>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method public abstract void createReprocessableCaptureSessionWithConfigurations(android.hardware.camera2.params.InputConfiguration, java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public abstract java.lang.String getId();
field public static final int TEMPLATE_MANUAL = 6; // 0x6
field public static final int TEMPLATE_PREVIEW = 1; // 0x1
@@ -13935,6 +13958,7 @@
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_CAPTURE_INTENT;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_MODE;
+ field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_SCENE_MODE;
field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_VIDEO_STABILIZATION_MODE;
field public static final android.os.Parcelable.Creator<android.hardware.camera2.CaptureRequest> CREATOR;
@@ -14013,6 +14037,7 @@
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_CAPTURE_INTENT;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_MODE;
+ field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_SCENE_MODE;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_VIDEO_STABILIZATION_MODE;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EDGE_MODE;
@@ -14154,6 +14179,17 @@
field public static final int METERING_WEIGHT_MIN = 0; // 0x0
}
+ public final class OutputConfiguration implements android.os.Parcelable {
+ ctor public OutputConfiguration(android.view.Surface);
+ method public int describeContents();
+ method public android.view.Surface getSurface();
+ method public int getSurfaceSetId();
+ method public void setSurfaceSetId(int);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR;
+ field public static final int SURFACE_SET_ID_INVALID = -1; // 0xffffffff
+ }
+
public final class RggbChannelVector {
ctor public RggbChannelVector(float, float, float, float);
method public void copyTo(float[], int);
@@ -19301,6 +19337,7 @@
method public void adjustVolume(int, int);
method public void dispatchMediaKeyEvent(android.view.KeyEvent);
method public int generateAudioSessionId();
+ method public android.media.AudioRecordConfiguration[] getActiveRecordConfigurations();
method public android.media.AudioDeviceInfo[] getDevices(int);
method public int getMode();
method public java.lang.String getParameters(java.lang.String);
@@ -19323,6 +19360,7 @@
method public void playSoundEffect(int);
method public void playSoundEffect(int, float);
method public void registerAudioDeviceCallback(android.media.AudioDeviceCallback, android.os.Handler);
+ method public void registerAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback, android.os.Handler);
method public deprecated void registerMediaButtonEventReceiver(android.content.ComponentName);
method public deprecated void registerMediaButtonEventReceiver(android.app.PendingIntent);
method public deprecated void registerRemoteControlClient(android.media.RemoteControlClient);
@@ -19346,6 +19384,7 @@
method public void stopBluetoothSco();
method public void unloadSoundEffects();
method public void unregisterAudioDeviceCallback(android.media.AudioDeviceCallback);
+ method public void unregisterAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback);
method public deprecated void unregisterMediaButtonEventReceiver(android.content.ComponentName);
method public deprecated void unregisterMediaButtonEventReceiver(android.app.PendingIntent);
method public deprecated void unregisterRemoteControlClient(android.media.RemoteControlClient);
@@ -19442,6 +19481,11 @@
field public static final deprecated int VIBRATE_TYPE_RINGER = 0; // 0x0
}
+ public static abstract class AudioManager.AudioRecordingCallback {
+ ctor public AudioManager.AudioRecordingCallback();
+ method public void onRecordConfigChanged();
+ }
+
public static abstract interface AudioManager.OnAudioFocusChangeListener {
method public abstract void onAudioFocusChange(int);
}
@@ -19512,6 +19556,14 @@
method public abstract void onRoutingChanged(android.media.AudioRecord);
}
+ public class AudioRecordConfiguration implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getAudioSessionId();
+ method public int getClientAudioSource();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.media.AudioRecordConfiguration> CREATOR;
+ }
+
public abstract interface AudioRouting {
method public abstract void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public abstract android.media.AudioDeviceInfo getPreferredDevice();
@@ -22640,6 +22692,7 @@
}
public class MtpDeviceInfo {
+ method public final int[] getEventsSupported();
method public final java.lang.String getManufacturer();
method public final java.lang.String getModel();
method public final int[] getOperationsSupported();
@@ -22746,6 +22799,7 @@
method public android.net.NetworkInfo getNetworkInfo(android.net.Network);
method public deprecated int getNetworkPreference();
method public static deprecated android.net.Network getProcessDefaultNetwork();
+ method public int getRestrictBackgroundStatus();
method public boolean isActiveNetworkMetered();
method public boolean isDefaultNetworkActive();
method public static deprecated boolean isNetworkTypeValid(int);
@@ -22780,6 +22834,9 @@
field public static final java.lang.String EXTRA_NO_CONNECTIVITY = "noConnectivity";
field public static final java.lang.String EXTRA_OTHER_NETWORK_INFO = "otherNetwork";
field public static final java.lang.String EXTRA_REASON = "reason";
+ field public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1; // 0x1
+ field public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3; // 0x3
+ field public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2; // 0x2
field public static final int TYPE_BLUETOOTH = 7; // 0x7
field public static final int TYPE_DUMMY = 8; // 0x8
field public static final int TYPE_ETHERNET = 9; // 0x9
@@ -22806,6 +22863,9 @@
method public abstract void onNetworkActive();
}
+ public static abstract class ConnectivityManager.RestrictBackgroundStatus implements java.lang.annotation.Annotation {
+ }
+
public class Credentials {
ctor public Credentials(int, int, int);
method public int getGid();
@@ -28595,6 +28655,7 @@
field public static final java.lang.String DISALLOW_OUTGOING_CALLS = "no_outgoing_calls";
field public static final java.lang.String DISALLOW_REMOVE_USER = "no_remove_user";
field public static final java.lang.String DISALLOW_SAFE_BOOT = "no_safe_boot";
+ field public static final java.lang.String DISALLOW_SET_USER_ICON = "no_set_user_icon";
field public static final java.lang.String DISALLOW_SHARE_LOCATION = "no_share_location";
field public static final java.lang.String DISALLOW_SMS = "no_sms";
field public static final java.lang.String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps";
@@ -33980,6 +34041,7 @@
method public void setTheme(int);
method public void show(android.os.Bundle, int);
method public void startVoiceActivity(android.content.Intent);
+ field public static final int SHOW_SOURCE_ACTIVITY = 16; // 0x10
field public static final int SHOW_SOURCE_APPLICATION = 8; // 0x8
field public static final int SHOW_SOURCE_ASSIST_GESTURE = 4; // 0x4
field public static final int SHOW_WITH_ASSIST = 1; // 0x1
@@ -35607,6 +35669,7 @@
field public static final java.lang.String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
field public static final java.lang.String ACTION_SHOW_CALL_ACCESSIBILITY_SETTINGS = "android.telecom.action.SHOW_CALL_ACCESSIBILITY_SETTINGS";
field public static final java.lang.String ACTION_SHOW_CALL_SETTINGS = "android.telecom.action.SHOW_CALL_SETTINGS";
+ field public static final java.lang.String ACTION_SHOW_MISSED_CALLS_NOTIFICATION = "android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION";
field public static final java.lang.String ACTION_SHOW_RESPOND_VIA_SMS_SETTINGS = "android.telecom.action.SHOW_RESPOND_VIA_SMS_SETTINGS";
field public static final char DTMF_CHARACTER_PAUSE = 44; // 0x002c ','
field public static final char DTMF_CHARACTER_WAIT = 59; // 0x003b ';'
@@ -35617,6 +35680,8 @@
field public static final java.lang.String EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME = "android.telecom.extra.CHANGE_DEFAULT_DIALER_PACKAGE_NAME";
field public static final java.lang.String EXTRA_INCOMING_CALL_ADDRESS = "android.telecom.extra.INCOMING_CALL_ADDRESS";
field public static final java.lang.String EXTRA_INCOMING_CALL_EXTRAS = "android.telecom.extra.INCOMING_CALL_EXTRAS";
+ field public static final java.lang.String EXTRA_NOTIFICATION_COUNT = "android.telecom.extra.NOTIFICATION_COUNT";
+ field public static final java.lang.String EXTRA_NOTIFICATION_PHONE_NUMBER = "android.telecom.extra.NOTIFICATION_PHONE_NUMBER";
field public static final java.lang.String EXTRA_OUTGOING_CALL_EXTRAS = "android.telecom.extra.OUTGOING_CALL_EXTRAS";
field public static final java.lang.String EXTRA_PHONE_ACCOUNT_HANDLE = "android.telecom.extra.PHONE_ACCOUNT_HANDLE";
field public static final java.lang.String EXTRA_START_CALL_WITH_SPEAKERPHONE = "android.telecom.extra.START_CALL_WITH_SPEAKERPHONE";
@@ -42873,6 +42938,7 @@
method public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo getCollectionInfo();
method public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo getCollectionItemInfo();
method public java.lang.CharSequence getContentDescription();
+ method public int getDrawingOrder();
method public java.lang.CharSequence getError();
method public android.os.Bundle getExtras();
method public int getInputType();
@@ -42935,6 +43001,7 @@
method public void setContentInvalid(boolean);
method public void setContextClickable(boolean);
method public void setDismissable(boolean);
+ method public void setDrawingOrder(int);
method public void setEditable(boolean);
method public void setEnabled(boolean);
method public void setError(java.lang.CharSequence);
@@ -43599,6 +43666,7 @@
field public android.os.Bundle extras;
field public int fieldId;
field public java.lang.String fieldName;
+ field public android.util.LocaleList hintLocales;
field public java.lang.CharSequence hintText;
field public int imeOptions;
field public int initialCapsMode;
@@ -43606,7 +43674,6 @@
field public int initialSelStart;
field public int inputType;
field public java.lang.CharSequence label;
- field public android.util.LocaleList locales;
field public java.lang.String packageName;
field public java.lang.String privateImeOptions;
}
@@ -44083,6 +44150,30 @@
method public abstract android.view.View getFullScreenView(int, android.content.Context);
}
+ public class ServiceWorkerClient {
+ ctor public ServiceWorkerClient();
+ method public android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebResourceRequest);
+ }
+
+ public abstract class ServiceWorkerController {
+ ctor public ServiceWorkerController();
+ method public static android.webkit.ServiceWorkerController getInstance();
+ method public abstract android.webkit.ServiceWorkerWebSettings getServiceWorkerWebSettings();
+ method public abstract void setServiceWorkerClient(android.webkit.ServiceWorkerClient);
+ }
+
+ public abstract class ServiceWorkerWebSettings {
+ ctor public ServiceWorkerWebSettings();
+ method public abstract boolean getAllowContentAccess();
+ method public abstract boolean getAllowFileAccess();
+ method public abstract boolean getBlockNetworkLoads();
+ method public abstract int getCacheMode();
+ method public abstract void setAllowContentAccess(boolean);
+ method public abstract void setAllowFileAccess(boolean);
+ method public abstract void setBlockNetworkLoads(boolean);
+ method public abstract void setCacheMode(int);
+ }
+
public class SslErrorHandler extends android.os.Handler {
method public void cancel();
method public void proceed();
@@ -46853,6 +46944,7 @@
method public int getHyphenationFrequency();
method public int getImeActionId();
method public java.lang.CharSequence getImeActionLabel();
+ method public android.util.LocaleList getImeHintLocales();
method public int getImeOptions();
method public boolean getIncludeFontPadding();
method public android.os.Bundle getInputExtras(boolean);
@@ -46959,6 +47051,7 @@
method public void setHorizontallyScrolling(boolean);
method public void setHyphenationFrequency(int);
method public void setImeActionLabel(java.lang.CharSequence, int);
+ method public void setImeHintLocales(android.util.LocaleList);
method public void setImeOptions(int);
method public void setIncludeFontPadding(boolean);
method public void setInputExtras(int) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
@@ -47912,10 +48005,8 @@
}
public final class Console implements java.io.Flushable {
- method public static java.io.Console console();
method public void flush();
method public java.io.Console format(java.lang.String, java.lang.Object...);
- method public static synchronized java.io.Console getConsole();
method public java.io.Console printf(java.lang.String, java.lang.Object...);
method public java.lang.String readLine(java.lang.String, java.lang.Object...);
method public java.lang.String readLine();
@@ -48199,7 +48290,6 @@
public class InterruptedIOException extends java.io.IOException {
ctor public InterruptedIOException();
ctor public InterruptedIOException(java.lang.String);
- ctor public InterruptedIOException(java.lang.Throwable);
field public int bytesTransferred;
}
@@ -48870,7 +48960,6 @@
method public long longValue();
method public static byte parseByte(java.lang.String, int) throws java.lang.NumberFormatException;
method public static byte parseByte(java.lang.String) throws java.lang.NumberFormatException;
- method public static java.lang.String toHexString(byte, boolean);
method public static java.lang.String toString(byte);
method public static java.lang.Byte valueOf(byte);
method public static java.lang.Byte valueOf(java.lang.String, int) throws java.lang.NumberFormatException;
@@ -51022,7 +51111,6 @@
public class BindException extends java.net.SocketException {
ctor public BindException(java.lang.String);
ctor public BindException();
- ctor public BindException(java.lang.String, java.lang.Throwable);
}
public abstract class CacheRequest {
@@ -51040,7 +51128,6 @@
public class ConnectException extends java.net.SocketException {
ctor public ConnectException(java.lang.String);
ctor public ConnectException();
- ctor public ConnectException(java.lang.String, java.lang.Throwable);
}
public abstract class ContentHandler {
@@ -51120,7 +51207,6 @@
method public void disconnect();
method public synchronized boolean getBroadcast() throws java.net.SocketException;
method public java.nio.channels.DatagramChannel getChannel();
- method public final java.io.FileDescriptor getFileDescriptor$();
method public java.net.InetAddress getInetAddress();
method public java.net.InetAddress getLocalAddress();
method public int getLocalPort();
@@ -51197,7 +51283,6 @@
method public boolean hasExpired();
method public boolean isHttpOnly();
method public static java.util.List<java.net.HttpCookie> parse(java.lang.String);
- method public static java.util.List<java.net.HttpCookie> parse(java.lang.String, boolean);
method public void setComment(java.lang.String);
method public void setCommentURL(java.lang.String);
method public void setDiscard(boolean);
@@ -51290,9 +51375,6 @@
}
public final class Inet4Address extends java.net.InetAddress {
- field public static final java.net.InetAddress ALL;
- field public static final java.net.InetAddress ANY;
- field public static final java.net.InetAddress LOOPBACK;
}
public final class Inet6Address extends java.net.InetAddress {
@@ -51301,8 +51383,6 @@
method public int getScopeId();
method public java.net.NetworkInterface getScopedInterface();
method public boolean isIPv4CompatibleAddress();
- field public static final java.net.InetAddress ANY;
- field public static final java.net.InetAddress LOOPBACK;
}
public class InetAddress implements java.io.Serializable {
@@ -51430,13 +51510,11 @@
public class PortUnreachableException extends java.net.SocketException {
ctor public PortUnreachableException(java.lang.String);
ctor public PortUnreachableException();
- ctor public PortUnreachableException(java.lang.String, java.lang.Throwable);
}
public class ProtocolException extends java.io.IOException {
ctor public ProtocolException(java.lang.String);
ctor public ProtocolException();
- ctor public ProtocolException(java.lang.String, java.lang.Throwable);
}
public abstract interface ProtocolFamily {
@@ -51571,8 +51649,6 @@
public class SocketException extends java.io.IOException {
ctor public SocketException(java.lang.String);
ctor public SocketException();
- ctor public SocketException(java.lang.Throwable);
- ctor public SocketException(java.lang.String, java.lang.Throwable);
}
public abstract class SocketImpl implements java.net.SocketOptions {
@@ -51642,8 +51718,6 @@
public class SocketTimeoutException extends java.io.InterruptedIOException {
ctor public SocketTimeoutException(java.lang.String);
ctor public SocketTimeoutException();
- ctor public SocketTimeoutException(java.lang.Throwable);
- ctor public SocketTimeoutException(java.lang.String, java.lang.Throwable);
}
public final class StandardProtocolFamily extends java.lang.Enum implements java.net.ProtocolFamily {
@@ -59588,7 +59662,6 @@
ctor public JarFile(java.io.File, boolean, int) throws java.io.IOException;
method public java.util.jar.JarEntry getJarEntry(java.lang.String);
method public java.util.jar.Manifest getManifest() throws java.io.IOException;
- method public boolean hasClassPathAttribute() throws java.io.IOException;
field public static final java.lang.String MANIFEST_NAME = "META-INF/MANIFEST.MF";
}
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 9728e38..67d63a4 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -16,8 +16,11 @@
package com.android.commands.bmgr;
+import android.app.backup.BackupManager;
+import android.app.backup.BackupProgress;
import android.app.backup.RestoreSet;
import android.app.backup.IBackupManager;
+import android.app.backup.IBackupObserver;
import android.app.backup.IRestoreObserver;
import android.app.backup.IRestoreSession;
import android.os.RemoteException;
@@ -108,6 +111,11 @@
return;
}
+ if ("backupnow".equals(op)) {
+ doBackupNow();
+ return;
+ }
+
System.err.println("Unknown command");
showUsage();
}
@@ -189,6 +197,88 @@
}
}
+ class BackupObserver extends IBackupObserver.Stub {
+ boolean done = false;
+
+ @Override
+ public void onUpdate(String currentPackage, BackupProgress backupProgress) {
+ System.out.println(
+ "onUpdate: " + currentPackage + " with progress: " + backupProgress.bytesTransferred
+ + "/" + backupProgress.bytesExpected);
+ }
+
+ @Override
+ public void onResult(String currentPackage, int status) {
+ System.out.println("onResult: " + currentPackage + " with result: "
+ + convertBackupStatusToString(status));
+ }
+
+ @Override
+ public void backupFinished(int status) {
+ System.out.println("backupFinished: " + convertBackupStatusToString(status));
+ synchronized (this) {
+ done = true;
+ this.notify();
+ }
+ }
+
+ public void waitForCompletion() {
+ // The backupFinished() callback will throw the 'done' flag; we
+ // just sit and wait on that notification.
+ synchronized (this) {
+ while (!this.done) {
+ try {
+ this.wait();
+ } catch (InterruptedException ex) {
+ }
+ }
+ }
+ }
+
+ }
+
+ private static String convertBackupStatusToString(int errorCode) {
+ switch (errorCode) {
+ case BackupManager.SUCCESS:
+ return "Success";
+ case BackupManager.ERROR_BACKUP_NOT_ALLOWED:
+ return "Backup is not allowed";
+ case BackupManager.ERROR_PACKAGE_NOT_FOUND:
+ return "Package not found";
+ case BackupManager.ERROR_TRANSPORT_ABORTED:
+ return "Transport error";
+ case BackupManager.ERROR_TRANSPORT_PACKAGE_REJECTED:
+ return "Transport rejected package";
+ case BackupManager.ERROR_AGENT_FAILURE:
+ return "Agent error";
+ default:
+ return "Unknown error";
+ }
+ }
+
+ private void doBackupNow() {
+ String pkg;
+ ArrayList<String> allPkgs = new ArrayList<String>();
+ while ((pkg = nextArg()) != null) {
+ allPkgs.add(pkg);
+ }
+ if (allPkgs.size() > 0) {
+ try {
+ BackupObserver observer = new BackupObserver();
+ int err = mBmgr.requestBackup(allPkgs.toArray(new String[allPkgs.size()]), observer);
+ if (err == 0) {
+ // Off and running -- wait for the backup to complete
+ observer.waitForCompletion();
+ } else {
+ System.err.println("Unable to run backup");
+ }
+ } catch (RemoteException e) {
+ System.err.println(e.toString());
+ System.err.println(BMGR_NOT_RUNNING_ERR);
+ }
+ }
+ }
+
private void doTransport() {
try {
String which = nextArg();
@@ -528,5 +618,10 @@
System.err.println("");
System.err.println("The 'fullbackup' command induces a full-data stream backup for one or more");
System.err.println("packages. The data is sent via the currently active transport.");
+ System.err.println("");
+ System.err.println("The 'backupnow' command runs an immediate backup for one or more packages.");
+ System.err.println("For each package it will run key/value or full data backup ");
+ System.err.println("depending on the package's manifest declarations.");
+ System.err.println("The data is sent via the currently active transport.");
}
}
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 3293c26..8bc17f8 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -541,6 +541,22 @@
}
/**
+ * This method allows accessibility service turn itself off
+ * and the service will become disabled from the Settings.
+ */
+ public final void disableSelf() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
+ if (connection != null) {
+ try {
+ connection.disableSelf();
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
+ /**
* Returns the magnification controller, which may be used to query and
* modify the state of display magnification.
* <p>
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index a65b87b..e58ef2f 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -65,6 +65,8 @@
boolean performGlobalAction(int action);
+ oneway void disableSelf();
+
oneway void setOnKeyEventResult(boolean handled, int sequence);
float getMagnificationScale();
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index e312596..e36a427 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1267,6 +1267,15 @@
mCalled = true;
}
+ void setVoiceInteractor(IVoiceInteractor voiceInteractor) {
+ if (voiceInteractor == null) {
+ mVoiceInteractor = null;
+ } else {
+ mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
+ Looper.myLooper());
+ }
+ }
+
/**
* Check whether this activity is running as part of a voice interaction with the user.
* If true, it should perform its interaction with the user through the
@@ -1301,6 +1310,62 @@
}
/**
+ * Queries whether the currently enabled voice interaction service supports returning
+ * a voice interactor for use by the activity. This is valid only for the duration of the
+ * activity.
+ *
+ * @return whether the current voice interaction service supports local voice interaction
+ */
+ public boolean isLocalVoiceInteractionSupported() {
+ try {
+ return ActivityManagerNative.getDefault().supportsLocalVoiceInteraction();
+ } catch (RemoteException re) {
+ }
+ return false;
+ }
+
+ /**
+ * Starts a local voice interaction session. When ready,
+ * {@link #onLocalVoiceInteractionStarted()} is called. You can pass a bundle of private options
+ * to the registered voice interaction service.
+ * @param privateOptions a Bundle of private arguments to the current voice interaction service
+ */
+ public void startLocalVoiceInteraction(Bundle privateOptions) {
+ try {
+ ActivityManagerNative.getDefault().startLocalVoiceInteraction(mToken, privateOptions);
+ } catch (RemoteException re) {
+ }
+ }
+
+ /**
+ * Callback to indicate that {@link #startLocalVoiceInteraction(Bundle)} has resulted in a
+ * voice interaction session being started. You can now retrieve a voice interactor using
+ * {@link #getVoiceInteractor()}.
+ */
+ public void onLocalVoiceInteractionStarted() {
+ Log.i(TAG, "onLocalVoiceInteractionStarted! " + getVoiceInteractor());
+ }
+
+ /**
+ * Callback to indicate that the local voice interaction has stopped for some
+ * reason.
+ */
+ public void onLocalVoiceInteractionStopped() {
+ Log.i(TAG, "onLocalVoiceInteractionStopped :( " + getVoiceInteractor());
+ }
+
+ /**
+ * Request to terminate the current voice interaction that was previously started
+ * using {@link #startLocalVoiceInteraction(Bundle)}.
+ */
+ public void stopLocalVoiceInteraction() {
+ try {
+ ActivityManagerNative.getDefault().stopLocalVoiceInteraction(mToken);
+ } catch (RemoteException re) {
+ }
+ }
+
+ /**
* This is called for activities that set launchMode to "singleTop" in
* their package, or if a client used the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP}
* flag when calling {@link #startActivity}. In either case, when the
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 373a23f..4fa654f 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -18,6 +18,10 @@
import android.annotation.NonNull;
import android.content.ComponentName;
+import android.os.IBinder;
+import android.service.voice.IVoiceInteractionSession;
+
+import com.android.internal.app.IVoiceInteractor;
/**
* Activity manager local system service interface.
@@ -64,4 +68,8 @@
* @param userId The user being cleaned up.
*/
public abstract void onUserRemoved(int userId);
+
+ public abstract void onLocalVoiceInteractionStarted(IBinder callingActivity,
+ IVoiceInteractionSession mSession,
+ IVoiceInteractor mInteractor);
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 63b6825..42ff8e8 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -315,6 +315,34 @@
return true;
}
+ case START_LOCAL_VOICE_INTERACTION_TRANSACTION:
+ {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder token = data.readStrongBinder();
+ Bundle options = data.readBundle();
+ startLocalVoiceInteraction(token, options);
+ reply.writeNoException();
+ return true;
+ }
+
+ case STOP_LOCAL_VOICE_INTERACTION_TRANSACTION:
+ {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder token = data.readStrongBinder();
+ stopLocalVoiceInteraction(token);
+ reply.writeNoException();
+ return true;
+ }
+
+ case SUPPORTS_LOCAL_VOICE_INTERACTION_TRANSACTION:
+ {
+ data.enforceInterface(IActivityManager.descriptor);
+ boolean result = supportsLocalVoiceInteraction();
+ reply.writeNoException();
+ reply.writeInt(result? 1 : 0);
+ return true;
+ }
+
case START_NEXT_MATCHING_ACTIVITY_TRANSACTION:
{
data.enforceInterface(IActivityManager.descriptor);
@@ -3136,6 +3164,43 @@
data.recycle();
return result;
}
+
+ public void startLocalVoiceInteraction(IBinder callingActivity, Bundle options)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(callingActivity);
+ data.writeBundle(options);
+ mRemote.transact(START_LOCAL_VOICE_INTERACTION_TRANSACTION, data, reply, 0);
+ reply.readException();
+ reply.recycle();
+ data.recycle();
+ }
+
+ public void stopLocalVoiceInteraction(IBinder callingActivity) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(callingActivity);
+ mRemote.transact(STOP_LOCAL_VOICE_INTERACTION_TRANSACTION, data, reply, 0);
+ reply.readException();
+ reply.recycle();
+ data.recycle();
+ }
+
+ public boolean supportsLocalVoiceInteraction() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(SUPPORTS_LOCAL_VOICE_INTERACTION_TRANSACTION, data, reply, 0);
+ reply.readException();
+ int result = reply.readInt();
+ reply.recycle();
+ data.recycle();
+ return result != 0;
+ }
+
public boolean startNextMatchingActivity(IBinder callingActivity,
Intent intent, Bundle options) throws RemoteException {
Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 93122dd..f3e1fc3 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -83,6 +83,7 @@
import android.util.ArrayMap;
import android.util.DisplayMetrics;
import android.util.EventLog;
+import android.util.LocaleList;
import android.util.Log;
import android.util.LogPrinter;
import android.util.Pair;
@@ -128,7 +129,6 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.TimeZone;
@@ -1254,6 +1254,15 @@
throws RemoteException {
sendMessage(H.PICTURE_IN_PICTURE_MODE_CHANGED, token, pipMode ? 1 : 0);
}
+
+ @Override
+ public void scheduleLocalVoiceInteractionStarted(IBinder token,
+ IVoiceInteractor voiceInteractor) throws RemoteException {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = token;
+ args.arg2 = voiceInteractor;
+ sendMessage(H.LOCAL_VOICE_INTERACTION_STARTED, args);
+ }
}
private int getLifecycleSeq() {
@@ -1317,6 +1326,7 @@
public static final int STOP_BINDER_TRACKING_AND_DUMP = 151;
public static final int MULTI_WINDOW_MODE_CHANGED = 152;
public static final int PICTURE_IN_PICTURE_MODE_CHANGED = 153;
+ public static final int LOCAL_VOICE_INTERACTION_STARTED = 154;
String codeToString(int code) {
if (DEBUG_MESSAGES) {
@@ -1372,6 +1382,7 @@
case ENTER_ANIMATION_COMPLETE: return "ENTER_ANIMATION_COMPLETE";
case MULTI_WINDOW_MODE_CHANGED: return "MULTI_WINDOW_MODE_CHANGED";
case PICTURE_IN_PICTURE_MODE_CHANGED: return "PICTURE_IN_PICTURE_MODE_CHANGED";
+ case LOCAL_VOICE_INTERACTION_STARTED: return "LOCAL_VOICE_INTERACTION_STARTED";
}
}
return Integer.toString(code);
@@ -1621,6 +1632,10 @@
case PICTURE_IN_PICTURE_MODE_CHANGED:
handlePictureInPictureModeChanged((IBinder) msg.obj, msg.arg1 == 1);
break;
+ case LOCAL_VOICE_INTERACTION_STARTED:
+ handleLocalVoiceInteractionStarted((IBinder) ((SomeArgs) msg.obj).arg1,
+ (IVoiceInteractor) ((SomeArgs) msg.obj).arg2);
+ break;
}
Object obj = msg.obj;
if (obj instanceof SomeArgs) {
@@ -2878,6 +2893,19 @@
}
}
+ private void handleLocalVoiceInteractionStarted(IBinder token, IVoiceInteractor interactor) {
+ final ActivityClientRecord r = mActivities.get(token);
+ if (r != null) {
+ r.voiceInteractor = interactor;
+ r.activity.setVoiceInteractor(interactor);
+ if (interactor == null) {
+ r.activity.onLocalVoiceInteractionStopped();
+ } else {
+ r.activity.onLocalVoiceInteractionStarted();
+ }
+ }
+ }
+
private static final ThreadLocal<Intent> sCurrentBroadcastIntent = new ThreadLocal<Intent>();
/**
@@ -3300,6 +3328,17 @@
}
r.activity.performResume();
+ // If there is a pending relaunch that was requested when the activity was paused,
+ // it will put the activity into paused state when it finally happens. Since the
+ // activity resumed before being relaunched, we don't want that to happen, so we
+ // need to clear the request to relaunch paused.
+ for (int i = mRelaunchingActivities.size() - 1; i >= 0; i--) {
+ final ActivityClientRecord relaunching = mRelaunchingActivities.get(i);
+ if (relaunching.token == r.token && relaunching.startsNotResumed) {
+ relaunching.startsNotResumed = false;
+ }
+ }
+
EventLog.writeEvent(LOG_AM_ON_RESUME_CALLED,
UserHandle.myUserId(), r.activity.getComponentName().getClassName());
@@ -3528,6 +3567,7 @@
private void handlePauseActivity(IBinder token, boolean finished,
boolean userLeaving, int configChanges, boolean dontReport, int seq) {
ActivityClientRecord r = mActivities.get(token);
+ if (DEBUG_ORDER) Slog.d(TAG, "handlePauseActivity " + r + ", seq: " + seq);
if (!checkAndUpdateLifecycleSeq(seq, r, "pauseActivity")) {
return;
}
@@ -4167,6 +4207,7 @@
synchronized (mResourcesManager) {
for (int i=0; i<mRelaunchingActivities.size(); i++) {
ActivityClientRecord r = mRelaunchingActivities.get(i);
+ if (DEBUG_ORDER) Slog.d(TAG, "requestRelaunchActivity: " + this + ", trying: " + r);
if (r.token == token) {
target = r;
if (pendingResults != null) {
@@ -4197,14 +4238,19 @@
}
if (target == null) {
+ if (DEBUG_ORDER) Slog.d(TAG, "requestRelaunchActivity: target is null, fromServer:"
+ + fromServer);
target = new ActivityClientRecord();
target.token = token;
target.pendingResults = pendingResults;
target.pendingIntents = pendingNewIntents;
target.mPreserveWindow = preserveWindow;
if (!fromServer) {
- ActivityClientRecord existing = mActivities.get(token);
+ final ActivityClientRecord existing = mActivities.get(token);
+ if (DEBUG_ORDER) Slog.d(TAG, "requestRelaunchActivity: " + existing);
if (existing != null) {
+ if (DEBUG_ORDER) Slog.d(TAG, "requestRelaunchActivity: paused= "
+ + existing.paused);;
target.startsNotResumed = existing.paused;
target.overrideConfig = existing.overrideConfig;
}
@@ -4227,8 +4273,8 @@
target.pendingConfigChanges |= configChanges;
target.relaunchSeq = getLifecycleSeq();
}
- if (DEBUG_ORDER) Slog.d(TAG, "relaunchActivity " + ActivityThread.this
- + " operation received seq: " + target.relaunchSeq);
+ if (DEBUG_ORDER) Slog.d(TAG, "relaunchActivity " + ActivityThread.this + ", target "
+ + target + " operation received seq: " + target.relaunchSeq);
}
private void handleRelaunchActivity(ActivityClientRecord tmp) {
@@ -4887,9 +4933,9 @@
TimeZone.setDefault(null);
/*
- * Initialize the default locale in this process for the reasons we set the time zone.
+ * Initialize the default locales in this process for the reasons we set the time zone.
*/
- Locale.setDefault(data.config.locale);
+ LocaleList.setDefault(data.config.getLocales());
/*
* Update the system configuration since its preloaded and might not
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 0afca9d..220fb607 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1099,13 +1099,24 @@
@Override
public Drawable getUserBadgeForDensity(UserHandle user, int density) {
+ return getManagedProfileIconForDensity(user, density,
+ com.android.internal.R.drawable.ic_corp_badge);
+ }
+
+ @Override
+ public Drawable getUserBadgeForDensityNoBackground(UserHandle user, int density) {
+ return getManagedProfileIconForDensity(user, density,
+ com.android.internal.R.drawable.ic_corp_badge_no_background);
+ }
+
+ private Drawable getManagedProfileIconForDensity(UserHandle user, int density,
+ int drawableId) {
UserInfo userInfo = getUserIfProfile(user.getIdentifier());
if (userInfo != null && userInfo.isManagedProfile()) {
if (density <= 0) {
density = mContext.getResources().getDisplayMetrics().densityDpi;
}
- return Resources.getSystem().getDrawableForDensity(
- com.android.internal.R.drawable.ic_corp_badge, density);
+ return Resources.getSystem().getDrawableForDensity(drawableId, density);
}
return null;
}
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index 5951c8d..9be7f23 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -425,6 +425,16 @@
return true;
}
+ case SCHEDULE_LOCAL_VOICE_INTERACTION_STARTED_TRANSACTION:
+ {
+ data.enforceInterface(IApplicationThread.descriptor);
+ IBinder token = data.readStrongBinder();
+ IVoiceInteractor voiceInteractor = IVoiceInteractor.Stub.asInterface(
+ data.readStrongBinder());
+ scheduleLocalVoiceInteractionStarted(token, voiceInteractor);
+ return true;
+ }
+
case PROFILER_CONTROL_TRANSACTION:
{
data.enforceInterface(IApplicationThread.descriptor);
@@ -1101,6 +1111,17 @@
data.recycle();
}
+ public final void scheduleLocalVoiceInteractionStarted(IBinder token,
+ IVoiceInteractor voiceInteractor) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(IApplicationThread.descriptor);
+ data.writeStrongBinder(token);
+ data.writeStrongBinder(voiceInteractor != null ? voiceInteractor.asBinder() : null);
+ mRemote.transact(SCHEDULE_LOCAL_VOICE_INTERACTION_STARTED_TRANSACTION, data, null,
+ IBinder.FLAG_ONEWAY);
+ data.recycle();
+ }
+
public void updateTimeZone() throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index e163b1c..20eaf0b 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -26,6 +26,7 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -1416,11 +1417,30 @@
* at this point. If you want to do work once the activity itself is
* created, see {@link #onActivityCreated(Bundle)}.
*
+ * <p>If your app's <code>targetSdkVersion</code> is 23 or lower, child fragments
+ * being restored from the savedInstanceState are restored after <code>onCreate</code>
+ * returns. When targeting N or above and running on an N or newer platform version
+ * they are restored by <code>Fragment.onCreate</code>.</p>
+ *
* @param savedInstanceState If the fragment is being re-created from
* a previous saved state, this is the state.
*/
public void onCreate(@Nullable Bundle savedInstanceState) {
mCalled = true;
+ final Context context = getContext();
+ final int version = context != null ? context.getApplicationInfo().targetSdkVersion : 0;
+ if (version >= Build.VERSION_CODES.N) {
+ if (savedInstanceState != null) {
+ Parcelable p = savedInstanceState.getParcelable(Activity.FRAGMENTS_TAG);
+ if (p != null) {
+ if (mChildFragmentManager == null) {
+ instantiateChildFragmentManager();
+ }
+ mChildFragmentManager.restoreAllState(p, null);
+ mChildFragmentManager.dispatchCreate();
+ }
+ }
+ }
}
/**
@@ -2210,14 +2230,18 @@
throw new SuperNotCalledException("Fragment " + this
+ " did not call through to super.onCreate()");
}
- if (savedInstanceState != null) {
- Parcelable p = savedInstanceState.getParcelable(Activity.FRAGMENTS_TAG);
- if (p != null) {
- if (mChildFragmentManager == null) {
- instantiateChildFragmentManager();
+ final Context context = getContext();
+ final int version = context != null ? context.getApplicationInfo().targetSdkVersion : 0;
+ if (version < Build.VERSION_CODES.N) {
+ if (savedInstanceState != null) {
+ Parcelable p = savedInstanceState.getParcelable(Activity.FRAGMENTS_TAG);
+ if (p != null) {
+ if (mChildFragmentManager == null) {
+ instantiateChildFragmentManager();
+ }
+ mChildFragmentManager.restoreAllState(p, null);
+ mChildFragmentManager.dispatchCreate();
}
- mChildFragmentManager.restoreAllState(p, null);
- mChildFragmentManager.dispatchCreate();
}
}
}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 5bb2cf5..22de2ff 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -591,6 +591,12 @@
public boolean isAppForeground(int uid) throws RemoteException;
+ public void startLocalVoiceInteraction(IBinder token, Bundle options) throws RemoteException;
+
+ public void stopLocalVoiceInteraction(IBinder token) throws RemoteException;
+
+ public boolean supportsLocalVoiceInteraction() throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -963,4 +969,7 @@
int GET_GRANTED_URI_PERMISSIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 360;
int CLEAR_GRANTED_URI_PERMISSIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 361;
int IS_APP_FOREGROUND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 362;
+ int START_LOCAL_VOICE_INTERACTION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 363;
+ int STOP_LOCAL_VOICE_INTERACTION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 364;
+ int SUPPORTS_LOCAL_VOICE_INTERACTION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 365;
}
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index dc67026..6d64bd0 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -154,6 +154,7 @@
void stopBinderTrackingAndDump(FileDescriptor fd) throws RemoteException;
void scheduleMultiWindowModeChanged(IBinder token, boolean multiWindowMode) throws RemoteException;
void schedulePictureInPictureModeChanged(IBinder token, boolean multiWindowMode) throws RemoteException;
+ void scheduleLocalVoiceInteractionStarted(IBinder token, IVoiceInteractor voiceInteractor) throws RemoteException;
String descriptor = "android.app.IApplicationThread";
@@ -216,4 +217,5 @@
int STOP_BINDER_TRACKING_AND_DUMP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+57;
int SCHEDULE_MULTI_WINDOW_MODE_CHANGED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+58;
int SCHEDULE_PICTURE_IN_PICTURE_MODE_CHANGED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+59;
+ int SCHEDULE_LOCAL_VOICE_INTERACTION_STARTED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+60;
}
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index ccba250..7a0e7f6 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -28,37 +28,47 @@
/**
* Set the wallpaper.
+ *
+ * If 'extras' is non-null, on successful return it will contain:
+ * EXTRA_SET_WALLPAPER_ID : integer ID that the new wallpaper will have
+ *
+ * 'which' is some combination of:
+ * FLAG_SET_SYSTEM
+ * FLAG_SET_LOCK
*/
- ParcelFileDescriptor setWallpaper(String name, in String callingPackage);
+ ParcelFileDescriptor setWallpaper(String name, in String callingPackage,
+ out Bundle extras, int which);
/**
- * Set the live wallpaper.
+ * Set the live wallpaper. This only affects the system wallpaper.
*/
void setWallpaperComponentChecked(in ComponentName name, in String callingPackage);
/**
- * Set the live wallpaper.
+ * Set the live wallpaper. This only affects the system wallpaper.
*/
void setWallpaperComponent(in ComponentName name);
/**
- * Get the wallpaper.
+ * Get the system wallpaper.
*/
ParcelFileDescriptor getWallpaper(IWallpaperManagerCallback cb,
out Bundle outParams);
/**
- * Get information about a live wallpaper.
+ * If the current system wallpaper is a live wallpaper component, return the
+ * information about that wallpaper. Otherwise, if it is a static image,
+ * simply return null.
*/
WallpaperInfo getWallpaperInfo();
/**
- * Clear the wallpaper.
+ * Clear the system wallpaper.
*/
void clearWallpaper(in String callingPackage);
/**
- * Return whether there is a wallpaper set with the given name.
+ * Return whether the current system wallpaper has the given name.
*/
boolean hasNamedWallpaper(String name);
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 7184337..24a3470 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -545,7 +545,7 @@
* returning the resulting activity or till the timeOut period expires.
* If the timeOut expires before the activity is started, return null.
*
- * @param timeOut Time to wait before the activity is created.
+ * @param timeOut Time to wait in milliseconds before the activity is created.
*
* @return Activity
*/
diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java
index 3cda973..f33af39 100644
--- a/core/java/android/app/IntentService.java
+++ b/core/java/android/app/IntentService.java
@@ -17,6 +17,7 @@
package android.app;
import android.annotation.WorkerThread;
+import android.annotation.Nullable;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
@@ -113,7 +114,7 @@
}
@Override
- public void onStart(Intent intent, int startId) {
+ public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
@@ -127,7 +128,7 @@
* @see android.app.Service#onStartCommand
*/
@Override
- public int onStartCommand(Intent intent, int flags, int startId) {
+ public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
@@ -139,10 +140,11 @@
/**
* Unless you provide binding for your service, you don't need to implement this
- * method, because the default implementation returns null.
+ * method, because the default implementation returns null.
* @see android.app.Service#onBind
*/
@Override
+ @Nullable
public IBinder onBind(Intent intent) {
return null;
}
@@ -158,7 +160,11 @@
*
* @param intent The value passed to {@link
* android.content.Context#startService(Intent)}.
+ * This may be null if the service is being restarted after
+ * its process has gone away; see
+ * {@link android.app.Service#onStartCommand}
+ * for details.
*/
@WorkerThread
- protected abstract void onHandleIntent(Intent intent);
+ protected abstract void onHandleIntent(@Nullable Intent intent);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index a392abd..0f3aad9 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2998,7 +2998,7 @@
private Drawable getProfileBadgeDrawable() {
// Note: This assumes that the current user can read the profile badge of the
// originating user.
- return mContext.getPackageManager().getUserBadgeForDensity(
+ return mContext.getPackageManager().getUserBadgeForDensityNoBackground(
new UserHandle(mContext.getUserId()), 0);
}
@@ -3016,24 +3016,13 @@
return bitmap;
}
- private boolean addProfileBadge(RemoteViews contentView, int resId) {
+ private void bindProfileBadge(RemoteViews contentView) {
Bitmap profileBadge = getProfileBadge();
- contentView.setViewVisibility(R.id.profile_badge_large_template, View.GONE);
- contentView.setViewVisibility(R.id.profile_badge_line3, View.GONE);
-
if (profileBadge != null) {
- contentView.setImageViewBitmap(resId, profileBadge);
- contentView.setViewVisibility(resId, View.VISIBLE);
-
- // Make sure Line 3 is visible. As badge will be here if there
- // is no text to display.
- if (resId == R.id.profile_badge_line3) {
- contentView.setViewVisibility(R.id.line3, View.VISIBLE);
- }
- return true;
+ contentView.setImageViewBitmap(R.id.profile_badge, profileBadge);
+ contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE);
}
- return false;
}
private void resetStandardTemplate(RemoteViews contentView) {
@@ -3042,9 +3031,10 @@
contentView.setViewVisibility(R.id.right_icon, View.GONE);
contentView.setViewVisibility(R.id.title, View.GONE);
contentView.setTextViewText(R.id.title, null);
+ contentView.setViewVisibility(R.id.text, View.GONE);
contentView.setTextViewText(R.id.text, null);
- contentView.setViewVisibility(R.id.line3, View.GONE);
contentView.setViewVisibility(R.id.text_line_1, View.GONE);
+ contentView.setTextViewText(R.id.text_line_1, null);
contentView.setViewVisibility(R.id.progress, View.GONE);
}
@@ -3062,11 +3052,13 @@
contentView.setViewVisibility(R.id.sub_text_divider, View.GONE);
contentView.setViewVisibility(R.id.content_info_divider, View.GONE);
contentView.setViewVisibility(R.id.time_divider, View.GONE);
+ contentView.setImageViewIcon(R.id.profile_badge, null);
+ contentView.setViewVisibility(R.id.profile_badge, View.GONE);
}
private void resetContentMargins(RemoteViews contentView) {
contentView.setViewLayoutMarginEnd(R.id.line1, 0);
- contentView.setViewLayoutMarginEnd(R.id.line3, 0);
+ contentView.setViewLayoutMarginEnd(R.id.text, 0);
}
private RemoteViews applyStandardTemplate(int resId) {
@@ -3081,7 +3073,6 @@
resetStandardTemplate(contentView);
- boolean showLine3 = false;
final Bundle ex = mN.extras;
bindNotificationHeader(contentView);
@@ -3093,19 +3084,13 @@
}
boolean showProgress = handleProgressBar(hasProgress, contentView, ex);
if (ex.getCharSequence(EXTRA_TEXT) != null) {
- contentView.setTextViewText(showProgress ? R.id.text_line_1 : R.id.text,
- processLegacyText(ex.getCharSequence(EXTRA_TEXT)));
- if (showProgress) {
- contentView.setViewVisibility(R.id.text_line_1, View.VISIBLE);
- }
- showLine3 = !showProgress;
+ int textId = showProgress ? com.android.internal.R.id.text_line_1
+ : com.android.internal.R.id.text;
+ contentView.setTextViewText(textId, processLegacyText(
+ ex.getCharSequence(EXTRA_TEXT)));
+ contentView.setViewVisibility(textId, View.VISIBLE);
}
- // We want to add badge to first line of text.
- if (addProfileBadge(contentView, R.id.profile_badge_line3)) {
- showLine3 = true;
- }
- // Note getStandardView may hide line 3 again.
- contentView.setViewVisibility(R.id.line3, showLine3 ? View.VISIBLE : View.GONE);
+
setContentMinHeight(contentView, showProgress || mN.mLargeIcon != null);
return contentView;
@@ -3157,7 +3142,7 @@
int endMargin = mContext.getResources().getDimensionPixelSize(
R.dimen.notification_content_picture_margin);
contentView.setViewLayoutMarginEnd(R.id.line1, endMargin);
- contentView.setViewLayoutMarginEnd(R.id.line3, endMargin);
+ contentView.setViewLayoutMarginEnd(R.id.text, endMargin);
contentView.setViewLayoutMarginEnd(R.id.progress, endMargin);
}
}
@@ -3170,6 +3155,7 @@
bindContentInfo(contentView);
bindHeaderChronometerAndTime(contentView);
bindExpandButton(contentView);
+ bindProfileBadge(contentView);
}
private void bindChildCountColor(RemoteViews contentView) {
@@ -3729,10 +3715,6 @@
contentView.setViewVisibility(R.id.line1, View.VISIBLE);
}
- // Clear text in case we use the line to show the profile badge.
- contentView.setTextViewText(com.android.internal.R.id.text, "");
- contentView.setViewVisibility(com.android.internal.R.id.line3, View.GONE);
-
return contentView;
}
@@ -3939,7 +3921,7 @@
RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource());
if (mSummaryTextSet) {
contentView.setTextViewText(R.id.text, mBuilder.processLegacyText(mSummaryText));
- contentView.setViewVisibility(R.id.line3, View.VISIBLE);
+ contentView.setViewVisibility(R.id.text, View.VISIBLE);
}
mBuilder.setContentMinHeight(contentView, mBuilder.mN.mLargeIcon != null);
@@ -3948,8 +3930,6 @@
}
contentView.setImageViewBitmap(R.id.big_picture, mPicture);
-
- mBuilder.addProfileBadge(contentView, R.id.profile_badge_line3);
return contentView;
}
@@ -4083,9 +4063,6 @@
contentView.setViewVisibility(R.id.big_text,
TextUtils.isEmpty(bigTextText) ? View.GONE : View.VISIBLE);
contentView.setInt(R.id.big_text, "setMaxLines", calculateMaxLines());
-
- mBuilder.addProfileBadge(contentView, R.id.profile_badge_large_template);
-
contentView.setBoolean(R.id.big_text, "setHasImage", mBuilder.mN.mLargeIcon != null);
return contentView;
@@ -4183,7 +4160,7 @@
* @hide
*/
public RemoteViews makeBigContentView() {
- // Remove the content text so line3 disappears unless you have a summary
+ // Remove the content text so it disappears unless you have a summary
// Nasty
CharSequence oldBuilderContentText = mBuilder.mN.extras.getCharSequence(EXTRA_TEXT);
mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, null);
@@ -4222,7 +4199,6 @@
}
i++;
}
- mBuilder.addProfileBadge(contentView, R.id.profile_badge_large_template);
handleInboxImageMargin(contentView, rowIds[0]);
@@ -4433,7 +4409,7 @@
private void handleImage(RemoteViews contentView) {
if (mBuilder.mN.mLargeIcon != null) {
contentView.setViewLayoutMarginEnd(R.id.line1, 0);
- contentView.setViewLayoutMarginEnd(R.id.line3, 0);
+ contentView.setViewLayoutMarginEnd(R.id.text, 0);
}
}
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 3187984..260216c 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -27,6 +27,7 @@
import android.hardware.display.DisplayManagerGlobal;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
+import android.util.LocaleList;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -34,7 +35,6 @@
import android.view.DisplayAdjustments;
import java.lang.ref.WeakReference;
-import java.util.Locale;
/** @hide */
public class ResourcesManager {
@@ -284,8 +284,9 @@
}
// set it for java, this also affects newly created Resources
- if (config.locale != null) {
- Locale.setDefault(config.locale);
+ final LocaleList localeList = config.getLocales();
+ if (!localeList.isEmpty()) {
+ LocaleList.setDefault(localeList);
}
Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat);
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 5b5bba4..f103576 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -16,6 +16,7 @@
package android.app;
+import android.annotation.IntDef;
import android.annotation.RawRes;
import android.annotation.SystemApi;
import android.content.ComponentName;
@@ -60,6 +61,8 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
@@ -144,7 +147,33 @@
* and y arguments are the location of the drop.
*/
public static final String COMMAND_DROP = "android.home.drop";
-
+
+ /**
+ * Extra passed back from setWallpaper() giving the new wallpaper's assigned ID.
+ * @hide
+ */
+ public static final String EXTRA_NEW_WALLPAPER_ID = "android.service.wallpaper.extra.ID";
+
+ // flags for which kind of wallpaper to set
+
+ /** @hide */
+ @IntDef(flag = true, value = {
+ FLAG_SET_SYSTEM,
+ FLAG_SET_LOCK
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SetWallpaperFlags {}
+
+ /**
+ * Flag: use the supplied imagery as the general system wallpaper.
+ */
+ public static final int FLAG_SET_SYSTEM = 1 << 0;
+
+ /**
+ * Flag: use the supplied imagery as the lock-screen wallpaper.
+ */
+ public static final int FLAG_SET_LOCK = 1 << 1;
+
private final Context mContext;
/**
@@ -717,20 +746,41 @@
* wallpaper.
*/
public void setResource(@RawRes int resid) throws IOException {
+ setResource(resid, FLAG_SET_SYSTEM);
+ }
+
+ /**
+ * Version of {@link #setResource(int)} that takes an optional Bundle for returning
+ * metadata about the operation to the caller.
+ *
+ * @param resid
+ * @param which Flags indicating which wallpaper(s) to configure with the new imagery.
+ *
+ * @see #FLAG_SET_LOCK
+ * @see #FLAG_SET_SYSTEM
+ *
+ * @return An integer ID assigned to the newly active wallpaper; or zero on failure.
+ *
+ * @throws IOException
+ */
+ public int setResource(@RawRes int resid, @SetWallpaperFlags int which)
+ throws IOException {
if (sGlobals.mService == null) {
Log.w(TAG, "WallpaperService not running");
- return;
+ return 0;
}
+ final Bundle result = new Bundle();
try {
Resources resources = mContext.getResources();
/* Set the wallpaper to the default values */
ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(
- "res:" + resources.getResourceName(resid), mContext.getOpPackageName());
+ "res:" + resources.getResourceName(resid),
+ mContext.getOpPackageName(), result, which);
if (fd != null) {
FileOutputStream fos = null;
try {
fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
- setWallpaper(resources.openRawResource(resid), fos);
+ copyStreamToWallpaperFile(resources.openRawResource(resid), fos);
} finally {
IoUtils.closeQuietly(fos);
}
@@ -738,6 +788,7 @@
} catch (RemoteException e) {
// Ignore
}
+ return result.getInt(EXTRA_NEW_WALLPAPER_ID, 0);
}
/**
@@ -753,7 +804,7 @@
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#SET_WALLPAPER}.
*
- * @param bitmap The bitmap to save.
+ * @param bitmap The bitmap to be used as the new system wallpaper.
*
* @throws IOException If an error occurs when attempting to set the wallpaper
* to the provided image.
@@ -775,42 +826,72 @@
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#SET_WALLPAPER}.
*
- * @param fullImage A bitmap that will supply the wallpaper imagery.
+ * @param fullImage A bitmap that will supply the wallpaper imagery.
* @param visibleCropHint The rectangular subregion of {@code fullImage} that should be
* displayed as wallpaper. Passing {@code null} for this parameter means that
* the full image should be displayed if possible given the image's and device's
- * aspect ratios, etc.
+ * aspect ratios, etc.
* @param allowBackup {@code true} if the OS is permitted to back up this wallpaper
* image for restore to a future device; {@code false} otherwise.
*
+ * @return An integer ID assigned to the newly active wallpaper; or zero on failure.
+ *
* @throws IOException If an error occurs when attempting to set the wallpaper
* to the provided image.
* @throws IllegalArgumentException If the {@code visibleCropHint} rectangle is
* empty or invalid.
*/
- public void setBitmap(Bitmap fullImage, Rect visibleCropHint, boolean allowBackup)
+ public int setBitmap(Bitmap fullImage, Rect visibleCropHint, boolean allowBackup)
+ throws IOException {
+ return setBitmap(fullImage, visibleCropHint, allowBackup, FLAG_SET_SYSTEM);
+ }
+
+ /**
+ /**
+ * Version of {@link #setBitmap(Bitmap, Rect, boolean)} that allows the caller
+ * to specify which of the supported wallpaper categories to set.
+ *
+ * @param fullImage A bitmap that will supply the wallpaper imagery.
+ * @param visibleCropHint The rectangular subregion of {@code fullImage} that should be
+ * displayed as wallpaper. Passing {@code null} for this parameter means that
+ * the full image should be displayed if possible given the image's and device's
+ * aspect ratios, etc.
+ * @param allowBackup {@code true} if the OS is permitted to back up this wallpaper
+ * image for restore to a future device; {@code false} otherwise.
+ * @param which Flags indicating which wallpaper(s) to configure with the new imagery.
+ *
+ * @see #FLAG_SET_LOCK_WALLPAPER
+ * @see #FLAG_SET_SYSTEM_WALLPAPER
+ *
+ * @return An integer ID assigned to the newly active wallpaper; or zero on failure.
+ *
+ * @throws IOException
+ */
+ public int setBitmap(Bitmap fullImage, Rect visibleCropHint,
+ boolean allowBackup, @SetWallpaperFlags int which)
throws IOException {
validateRect(visibleCropHint);
if (sGlobals.mService == null) {
Log.w(TAG, "WallpaperService not running");
- return;
+ return 0;
}
+ final Bundle result = new Bundle();
try {
ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
- mContext.getOpPackageName());
- if (fd == null) {
- return;
- }
- FileOutputStream fos = null;
- try {
- fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
- fullImage.compress(Bitmap.CompressFormat.PNG, 90, fos);
- } finally {
- IoUtils.closeQuietly(fos);
+ mContext.getOpPackageName(), result, which);
+ if (fd != null) {
+ FileOutputStream fos = null;
+ try {
+ fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+ fullImage.compress(Bitmap.CompressFormat.PNG, 90, fos);
+ } finally {
+ IoUtils.closeQuietly(fos);
+ }
}
} catch (RemoteException e) {
// Ignore
}
+ return result.getInt(EXTRA_NEW_WALLPAPER_ID, 0);
}
private final void validateRect(Rect rect) {
@@ -843,7 +924,7 @@
setStream(bitmapData, null, true);
}
- private void setWallpaper(InputStream data, FileOutputStream fos)
+ private void copyStreamToWallpaperFile(InputStream data, FileOutputStream fos)
throws IOException {
byte[] buffer = new byte[32768];
int amt;
@@ -877,29 +958,55 @@
* @throws IllegalArgumentException If the {@code visibleCropHint} rectangle is
* empty or invalid.
*/
- public void setStream(InputStream bitmapData, Rect visibleCropHint, boolean allowBackup)
+ public int setStream(InputStream bitmapData, Rect visibleCropHint, boolean allowBackup)
throws IOException {
+ return setStream(bitmapData, visibleCropHint, allowBackup, FLAG_SET_SYSTEM);
+ }
+
+ /**
+ * Version of {@link #setStream(InputStream, Rect, boolean)} that allows the caller
+ * to specify which of the supported wallpaper categories to set.
+ *
+ * @param bitmapData A stream containing the raw data to install as a wallpaper.
+ * @param visibleCropHint The rectangular subregion of the streamed image that should be
+ * displayed as wallpaper. Passing {@code null} for this parameter means that
+ * the full image should be displayed if possible given the image's and device's
+ * aspect ratios, etc.
+ * @param allowBackup {@code true} if the OS is permitted to back up this wallpaper
+ * image for restore to a future device; {@code false} otherwise.
+ * @param which Flags indicating which wallpaper(s) to configure with the new imagery.
+ *
+ * @see #FLAG_SET_LOCK_WALLPAPER
+ * @see #FLAG_SET_SYSTEM_WALLPAPER
+ *
+ * @throws IOException
+ */
+ public int setStream(InputStream bitmapData, Rect visibleCropHint,
+ boolean allowBackup, @SetWallpaperFlags int which)
+ throws IOException {
validateRect(visibleCropHint);
if (sGlobals.mService == null) {
Log.w(TAG, "WallpaperService not running");
- return;
+ return 0;
}
+ final Bundle result = new Bundle();
try {
ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
- mContext.getOpPackageName());
- if (fd == null) {
- return;
- }
- FileOutputStream fos = null;
- try {
- fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
- setWallpaper(bitmapData, fos);
- } finally {
- IoUtils.closeQuietly(fos);
+ mContext.getOpPackageName(), result, which);
+ if (fd != null) {
+ FileOutputStream fos = null;
+ try {
+ fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+ copyStreamToWallpaperFile(bitmapData, fos);
+ } finally {
+ IoUtils.closeQuietly(fos);
+ }
}
} catch (RemoteException e) {
// Ignore
}
+
+ return result.getInt(EXTRA_NEW_WALLPAPER_ID, 0);
}
/**
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index e3d7c3d..a655bfd 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1053,6 +1053,18 @@
public static final int PASSWORD_QUALITY_COMPLEX = 0x60000;
/**
+ * Constant for {@link #setPasswordQuality}: the user is not allowed to
+ * modify password. In case this password quality is set, the password is
+ * managed by a profile owner. The profile owner can set any password,
+ * as if {@link #PASSWORD_QUALITY_UNSPECIFIED} is used. Note
+ * that quality constants are ordered so that higher values are more
+ * restrictive. The value of {@link #PASSWORD_QUALITY_MANAGED} is
+ * the highest.
+ * @hide
+ */
+ public static final int PASSWORD_QUALITY_MANAGED = 0x80000;
+
+ /**
* Called by an application that is administering the device to set the
* password restrictions it is imposing. After setting this, the user
* will not be able to enter a new password that is not at least as
@@ -3045,6 +3057,7 @@
*
* @hide
*/
+ @SystemApi
public String getDeviceOwnerNameOnAnyUser() {
if (mService != null) {
try {
@@ -5129,4 +5142,55 @@
return null;
}
}
+
+ /**
+ * Called by a profile owner of a managed profile to set the color used for customization.
+ * This color is used as background color of the confirm credentials screen for that user.
+ * The default color is {@link android.graphics.Color#GRAY}.
+ *
+ * <p>The confirm credentials screen can be created using
+ * {@link android.app.KeyguardManager#createConfirmDeviceCredentialIntent}.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param color The 32bit representation of the color to be used.
+ */
+ public void setOrganizationColor(@NonNull ComponentName admin, int color) {
+ try {
+ mService.setOrganizationColor(admin, color);
+ } catch (RemoteException re) {
+ Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, re);
+ }
+ }
+
+ /**
+ * Called by a profile owner of a managed profile to retrieve the color used for customization.
+ * This color is used as background color of the confirm credentials screen for that user.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @return The 32bit representation of the color to be used.
+ */
+ public int getOrganizationColor(@NonNull ComponentName admin) {
+ try {
+ return mService.getOrganizationColor(admin);
+ } catch (RemoteException re) {
+ Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, re);
+ return 0;
+ }
+ }
+
+ /**
+ * @hide
+ * Retrieve the customization color for a given user.
+ *
+ * @param userHandle The user id of the user we're interested in.
+ * @return The 32bit representation of the color to be used.
+ */
+ public int getOrganizationColorForUser(int userHandle) {
+ try {
+ return mService.getOrganizationColorForUser(userHandle);
+ } catch (RemoteException re) {
+ Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, re);
+ return 0;
+ }
+ }
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 08cab884..82115a2 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -263,4 +263,8 @@
String getLongSupportMessageForUser(in ComponentName admin, int userHandle);
boolean isSeparateProfileChallengeAllowed(int userHandle);
+
+ void setOrganizationColor(in ComponentName admin, in int color);
+ int getOrganizationColor(in ComponentName admin);
+ int getOrganizationColorForUser(int userHandle);
}
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 8b79305..c27eaa4f 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -17,13 +17,13 @@
package android.app.backup;
import android.annotation.SystemApi;
-import android.app.backup.RestoreSession;
-import android.app.backup.IBackupManager;
-import android.app.backup.IRestoreSession;
import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
+import android.util.Pair;
/**
* The interface through which an application interacts with the Android backup service to
@@ -59,6 +59,65 @@
public class BackupManager {
private static final String TAG = "BackupManager";
+ // BackupObserver status codes
+ /**
+ * Indicates that backup succeeded.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int SUCCESS = 0;
+
+ /**
+ * Indicates that backup is either not enabled at all or
+ * backup for the package was rejected by backup service
+ * or backup transport,
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int ERROR_BACKUP_NOT_ALLOWED = -2001;
+
+ /**
+ * The requested app is not installed on the device.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int ERROR_PACKAGE_NOT_FOUND = -2002;
+
+ /**
+ * The transport for some reason was not in a good state and
+ * aborted the entire backup request. This is a transient
+ * failure and should not be retried immediately.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int ERROR_TRANSPORT_ABORTED = BackupTransport.TRANSPORT_ERROR;
+
+ /**
+ * Returned when the transport was unable to process the
+ * backup request for a given package, for example if the
+ * transport hit a transient network failure. The remaining
+ * packages provided to {@link #requestBackup(String[], BackupObserver)}
+ * will still be attempted.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int ERROR_TRANSPORT_PACKAGE_REJECTED =
+ BackupTransport.TRANSPORT_PACKAGE_REJECTED;
+
+ /**
+ * The {@link BackupAgent} for the requested package failed for some reason
+ * and didn't provide appropriate backup data.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int ERROR_AGENT_FAILURE = BackupTransport.AGENT_ERROR;
+
private Context mContext;
private static IBackupManager sService;
@@ -365,4 +424,117 @@
}
return 0;
}
+
+ /**
+ * Ask the framework whether this app is eligible for backup.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ *
+ * @param packageName The name of the package.
+ * @return Whether this app is eligible for backup.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean isAppEligibleForBackup(String packageName) {
+ checkServiceBinder();
+ if (sService != null) {
+ try {
+ return sService.isAppEligibleForBackup(packageName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "isAppEligibleForBackup(pkg) couldn't connect");
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Request an immediate backup, providing an observer to which results of the backup operation
+ * will be published. The Android backup system will decide for each package whether it will
+ * be full app data backup or key/value-pair-based backup.
+ *
+ * <p>If this method returns {@link BackupManager#SUCCESS}, the OS will attempt to backup all
+ * provided packages using the remote transport.
+ *
+ * @param packages List of package names to backup.
+ * @param observer The {@link BackupObserver} to receive callbacks during the backup
+ * operation.
+ * @return {@link BackupManager#SUCCESS} on success; nonzero on error.
+ * @exception IllegalArgumentException on null or empty {@code packages} param.
+ *
+ * @hide
+ */
+ @SystemApi
+ public int requestBackup(String[] packages, BackupObserver observer) {
+ checkServiceBinder();
+ if (sService != null) {
+ try {
+ BackupObserverWrapper observerWrapper =
+ new BackupObserverWrapper(mContext, observer);
+ return sService.requestBackup(packages, observerWrapper);
+ } catch (RemoteException e) {
+ Log.e(TAG, "requestBackup() couldn't connect");
+ }
+ }
+ return -1;
+ }
+
+ /*
+ * We wrap incoming binder calls with a private class implementation that
+ * redirects them into main-thread actions. This serializes the backup
+ * progress callbacks nicely within the usual main-thread lifecycle pattern.
+ */
+ @SystemApi
+ private class BackupObserverWrapper extends IBackupObserver.Stub {
+ final Handler mHandler;
+ final BackupObserver mObserver;
+
+ static final int MSG_UPDATE = 1;
+ static final int MSG_RESULT = 2;
+ static final int MSG_FINISHED = 3;
+
+ BackupObserverWrapper(Context context, BackupObserver observer) {
+ mHandler = new Handler(context.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_UPDATE:
+ Pair<String, BackupProgress> obj =
+ (Pair<String, BackupProgress>) msg.obj;
+ mObserver.onUpdate(obj.first, obj.second);
+ break;
+ case MSG_RESULT:
+ mObserver.onResult((String)msg.obj, msg.arg1);
+ break;
+ case MSG_FINISHED:
+ mObserver.backupFinished(msg.arg1);
+ break;
+ default:
+ Log.w(TAG, "Unknown message: " + msg);
+ break;
+ }
+ }
+ };
+ mObserver = observer;
+ }
+
+ // Binder calls into this object just enqueue on the main-thread handler
+ @Override
+ public void onUpdate(String currentPackage, BackupProgress backupProgress) {
+ mHandler.sendMessage(
+ mHandler.obtainMessage(MSG_UPDATE, Pair.create(currentPackage, backupProgress)));
+ }
+
+ @Override
+ public void onResult(String currentPackage, int status) {
+ mHandler.sendMessage(
+ mHandler.obtainMessage(MSG_FINISHED, status, 0, currentPackage));
+ }
+
+ @Override
+ public void backupFinished(int status) {
+ mHandler.sendMessage(
+ mHandler.obtainMessage(MSG_FINISHED, status, 0));
+ }
+ }
}
diff --git a/core/java/android/app/backup/BackupObserver.java b/core/java/android/app/backup/BackupObserver.java
new file mode 100644
index 0000000..0dd071e
--- /dev/null
+++ b/core/java/android/app/backup/BackupObserver.java
@@ -0,0 +1,59 @@
+/*
+ * 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
+ */
+
+package android.app.backup;
+
+import android.annotation.SystemApi;
+
+/**
+ * Callback class for receiving progress reports during a backup operation. These
+ * methods will all be called on your application's main thread.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class BackupObserver {
+ /**
+ * This method could be called several times for packages with full data backup.
+ * It will tell how much of backup data is already saved and how much is expected.
+ *
+ * @param currentBackupPackage The name of the package that now being backuped.
+ * @param backupProgress Current progress of backup for the package.
+ */
+ public void onUpdate(String currentBackupPackage, BackupProgress backupProgress) {
+ }
+
+ /**
+ * The backup of single package has completed. This method will be called at most one time
+ * for each package and could be not called if backup is failed before and
+ * backupFinished() is called.
+ *
+ * @param currentBackupPackage The name of the package that was backuped.
+ * @param status Zero on success; a nonzero error code if the backup operation failed.
+ */
+ public void onResult(String currentBackupPackage, int status) {
+ }
+
+ /**
+ * The backup process has completed. This method will always be called,
+ * even if no individual package backup operations were attempted.
+ *
+ * @param status Zero on success; a nonzero error code if the backup operation
+ * as a whole failed.
+ */
+ public void backupFinished(int status) {
+ }
+}
diff --git a/core/java/android/app/backup/BackupProgress.aidl b/core/java/android/app/backup/BackupProgress.aidl
new file mode 100644
index 0000000..c10b9a2
--- /dev/null
+++ b/core/java/android/app/backup/BackupProgress.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+package android.app.backup;
+
+parcelable BackupProgress;
\ No newline at end of file
diff --git a/core/java/android/app/backup/BackupProgress.java b/core/java/android/app/backup/BackupProgress.java
new file mode 100644
index 0000000..32e6212
--- /dev/null
+++ b/core/java/android/app/backup/BackupProgress.java
@@ -0,0 +1,69 @@
+/*
+ * 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
+ */
+
+package android.app.backup;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Information about current progress of full data backup
+ * Used in {@link BackupObserver#onUpdate(String, BackupProgress)}
+ *
+ * @hide
+ */
+@SystemApi
+public class BackupProgress implements Parcelable {
+
+ /**
+ * Expected size of data in full backup.
+ */
+ public final long bytesExpected;
+ /**
+ * Amount of backup data that is already saved in backup.
+ */
+ public final long bytesTransferred;
+
+ public BackupProgress(long _bytesExpected, long _bytesTransferred) {
+ bytesExpected = _bytesExpected;
+ bytesTransferred = _bytesTransferred;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeLong(bytesExpected);
+ out.writeLong(bytesTransferred);
+ }
+
+ public static final Creator<BackupProgress> CREATOR = new Creator<BackupProgress>() {
+ public BackupProgress createFromParcel(Parcel in) {
+ return new BackupProgress(in);
+ }
+
+ public BackupProgress[] newArray(int size) {
+ return new BackupProgress[size];
+ }
+ };
+
+ private BackupProgress(Parcel in) {
+ bytesExpected = in.readLong();
+ bytesTransferred = in.readLong();
+ }
+}
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index 954ccef..aca115f 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -50,6 +50,10 @@
public static final int AGENT_ERROR = -1003;
public static final int AGENT_UNKNOWN = -1004;
+ // Indicates that operation was initiated by user, not a scheduled one.
+ // Transport should ignore its own moratoriums for call with this flag set.
+ public static final int FLAG_USER_INITIATED = 1;
+
IBackupTransport mBinderImpl = new TransportImpl();
public IBinder getBinder() {
@@ -228,13 +232,10 @@
*
* @param packageInfo The identity of the application whose data is being backed up.
* This specifically includes the signature list for the package.
- * @param data The data stream that resulted from invoking the application's
+ * @param inFd Descriptor of file with data that resulted from invoking the application's
* BackupService.doBackup() method. This may be a pipe rather than a file on
* persistent media, so it may not be seekable.
- * @param wipeAllFirst When true, <i>all</i> backed-up data for the current device/account
- * must be erased prior to the storage of the data provided here. The purpose of this
- * is to provide a guarantee that no stale data exists in the restore set when the
- * device begins providing incremental backups.
+ * @param flags {@link BackupTransport#FLAG_USER_INITIATED} or 0.
* @return one of {@link BackupTransport#TRANSPORT_OK} (OK so far),
* {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED} (to suppress backup of this
* specific package, but allow others to proceed),
@@ -242,6 +243,14 @@
* {@link BackupTransport#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has
* become lost due to inactivity purge or some other reason and needs re-initializing)
*/
+ public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags) {
+ return performBackup(packageInfo, inFd);
+ }
+
+ /**
+ * Legacy version of {@link #performBackup(PackageInfo, ParcelFileDescriptor, int)} that
+ * doesn't use flags parameter.
+ */
public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd) {
return BackupTransport.TRANSPORT_ERROR;
}
@@ -392,11 +401,21 @@
* close this file descriptor now; otherwise it should be cached for use during
* succeeding calls to {@link #sendBackupData(int)}, and closed in response to
* {@link #finishBackup()}.
+ * @param flags {@link BackupTransport#FLAG_USER_INITIATED} or 0.
* @return TRANSPORT_PACKAGE_REJECTED to indicate that the stated application is not
* to be backed up; TRANSPORT_OK to indicate that the OS may proceed with delivering
* backup data; TRANSPORT_ERROR to indicate a fatal error condition that precludes
* performing a backup at this time.
*/
+ public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket,
+ int flags) {
+ return performFullBackup(targetPackage, socket);
+ }
+
+ /**
+ * Legacy version of {@link #performFullBackup(PackageInfo, ParcelFileDescriptor, int)} that
+ * doesn't use flags parameter.
+ */
public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket) {
return BackupTransport.TRANSPORT_PACKAGE_REJECTED;
}
@@ -463,6 +482,18 @@
"Transport cancelFullBackup() not implemented");
}
+ /**
+ * Ask the transport whether this app is eligible for backup.
+ *
+ * @param targetPackage The identity of the application.
+ * @param isFullBackup If set, transport should check if app is eligible for full data backup,
+ * otherwise to check if eligible for key-value backup.
+ * @return Whether this app is eligible for backup.
+ */
+ public boolean isAppEligibleForBackup(PackageInfo targetPackage, boolean isFullBackup) {
+ return true;
+ }
+
// ------------------------------------------------------------------------------------
// Full restore interfaces
@@ -568,9 +599,9 @@
}
@Override
- public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd)
+ public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags)
throws RemoteException {
- return BackupTransport.this.performBackup(packageInfo, inFd);
+ return BackupTransport.this.performBackup(packageInfo, inFd, flags);
}
@Override
@@ -619,8 +650,9 @@
}
@Override
- public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket) throws RemoteException {
- return BackupTransport.this.performFullBackup(targetPackage, socket);
+ public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket,
+ int flags) throws RemoteException {
+ return BackupTransport.this.performFullBackup(targetPackage, socket, flags);
}
@Override
@@ -639,6 +671,12 @@
}
@Override
+ public boolean isAppEligibleForBackup(PackageInfo targetPackage, boolean isFullBackup)
+ throws RemoteException {
+ return BackupTransport.this.isAppEligibleForBackup(targetPackage, isFullBackup);
+ }
+
+ @Override
public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) {
return BackupTransport.this.getNextFullRestoreDataChunk(socket);
}
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 87e4ef1..5d4cc6f 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -16,6 +16,7 @@
package android.app.backup;
+import android.app.backup.IBackupObserver;
import android.app.backup.IFullBackupRestoreObserver;
import android.app.backup.IRestoreSession;
import android.os.ParcelFileDescriptor;
@@ -326,4 +327,29 @@
* no suitable data is available.
*/
long getAvailableRestoreToken(String packageName);
+
+ /**
+ * Ask the framework whether this app is eligible for backup.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ *
+ * @param packageName The name of the package.
+ * @return Whether this app is eligible for backup.
+ */
+ boolean isAppEligibleForBackup(String packageName);
+
+ /**
+ * Request an immediate backup, providing an observer to which results of the backup operation
+ * will be published. The Android backup system will decide for each package whether it will
+ * be full app data backup or key/value-pair-based backup.
+ *
+ * <p>If this method returns zero (meaning success), the OS will attempt to backup all provided
+ * packages using the remote transport.
+ *
+ * @param observer The {@link BackupObserver} to receive callbacks during the backup
+ * operation.
+ *
+ * @return Zero on success; nonzero on error.
+ */
+ int requestBackup(in String[] packages, IBackupObserver observer);
}
diff --git a/core/java/android/app/backup/IBackupObserver.aidl b/core/java/android/app/backup/IBackupObserver.aidl
new file mode 100644
index 0000000..821a589
--- /dev/null
+++ b/core/java/android/app/backup/IBackupObserver.aidl
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+package android.app.backup;
+
+import android.app.backup.BackupProgress;
+
+/**
+ * Callback class for receiving progress reports during a backup operation. These
+ * methods will all be called on your application's main thread.
+ *
+ * @hide
+ */
+oneway interface IBackupObserver {
+ /**
+ * This method could be called several times for packages with full data backup.
+ * It will tell how much of backup data is already saved and how much is expected.
+ *
+ * @param currentBackupPackage The name of the package that now being backuped.
+ * @param backupProgress Current progress of backup for the package.
+ */
+ void onUpdate(String currentPackage, in BackupProgress backupProgress);
+
+ /**
+ * The backup of single package has completed. This method will be called at most one time
+ * for each package and could be not called if backup is failed before and
+ * backupFinished() is called.
+ *
+ * @param currentBackupPackage The name of the package that was backuped.
+ * @param status Zero on success; a nonzero error code if the backup operation failed.
+ */
+ void onResult(String currentPackage, int status);
+
+ /**
+ * The backup process has completed. This method will always be called,
+ * even if no individual package backup operations were attempted.
+ *
+ * @param status Zero on success; a nonzero error code if the backup operation
+ * as a whole failed.
+ */
+ void backupFinished(int status);
+}
diff --git a/core/java/android/app/backup/RestoreSet.java b/core/java/android/app/backup/RestoreSet.java
index aacaf7c..4a6316c 100644
--- a/core/java/android/app/backup/RestoreSet.java
+++ b/core/java/android/app/backup/RestoreSet.java
@@ -58,7 +58,6 @@
token = _token;
}
-
// Parcelable implementation
public int describeContents() {
return 0;
diff --git a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java
index 002f63f..c73bc3c 100644
--- a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java
+++ b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java
@@ -196,7 +196,7 @@
public String toString(boolean loggable) {
StringBuilder builder = new StringBuilder("BluetoothHeadsetClientCall{mDevice: ");
- builder.append(loggable ? mDevice.hashCode() : mDevice);
+ builder.append(loggable ? mDevice : mDevice.hashCode());
builder.append(", mId: ");
builder.append(mId);
builder.append(", mUUID: ");
@@ -214,7 +214,7 @@
default: builder.append(mState); break;
}
builder.append(", mNumber: ");
- builder.append(loggable ? mNumber.hashCode() : mNumber);
+ builder.append(loggable ? mNumber : mNumber.hashCode());
builder.append(", mMultiParty: ");
builder.append(mMultiParty);
builder.append(", mOutgoing: ");
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 1e7512d..40d7604 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -41,6 +41,7 @@
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Environment;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
@@ -663,10 +664,13 @@
public static final String ACTION_DEFAULT = ACTION_VIEW;
/**
- * Activity Action: Quick view the data.
- * <p>Input: {@link #getData} is URI from which to retrieve data.
+ * Activity Action: Quick view the data. Launches a quick viewer for
+ * a URI or a list of URIs.
+ * <p>Input: {@link #getData} is a mandatory content URI of the item to
+ * preview. {@link #getClipData} contains an optional list of content URIs
+ * if there is more than one item to preview. {@link #EXTRA_INDEX} is an
+ * optional index of the URI in the clip data to show first.
* <p>Output: nothing.
- * @hide
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_QUICK_VIEW = "android.intent.action.QUICK_VIEW";
@@ -3186,6 +3190,38 @@
public static final String
ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
+ /**
+ * Activity Action: Give access to a standard storage directory after obtaining the user's
+ * approval.
+ * <p>
+ * When invoked, the system will ask the user to grant access to the requested directory (and
+ * its descendants).
+ * <p>
+ * To gain access to descendant (child, grandchild, etc) documents, use
+ * {@link DocumentsContract#buildDocumentUriUsingTree(Uri, String)} and
+ * {@link DocumentsContract#buildChildDocumentsUriUsingTree(Uri, String)} with the returned URI.
+ * <p>
+ * Input: full path to a standard directory, in the form of
+ * {@code STORAGE_ROOT + STANDARD_DIRECTORY}, where {@code STORAGE_ROOT} is the physical path of
+ * a storage container, and {@code STANDARD_DIRECTORY} is one of
+ * {@link Environment#DIRECTORY_MUSIC}, {@link Environment#DIRECTORY_PODCASTS},
+ * {@link Environment#DIRECTORY_RINGTONES}, {@link Environment#DIRECTORY_ALARMS},
+ * {@link Environment#DIRECTORY_NOTIFICATIONS}, {@link Environment#DIRECTORY_PICTURES},
+ * {@link Environment#DIRECTORY_MOVIES}, {@link Environment#DIRECTORY_DOWNLOADS},
+ * {@link Environment#DIRECTORY_DCIM}, or {@link Environment#DIRECTORY_DOCUMENTS}
+ * <p>
+ * For example, to open the "Pictures" folder in the default external storage, the intent's data
+ * would be: {@code Uri.fromFile(new File(Environment.getExternalStorageDirectory(),
+ * Environment.DIRECTORY_PICTURES))}.
+ * <p>
+ * Output: The URI representing the requested directory tree.
+ *
+ * @see DocumentsContract
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String
+ ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
+
/** {@hide} */
public static final String ACTION_MASTER_CLEAR = "android.intent.action.MASTER_CLEAR";
@@ -4126,7 +4162,10 @@
public static final String EXTRA_SIM_ACTIVATION_RESPONSE =
"android.intent.extra.SIM_ACTIVATION_RESPONSE";
- /** {@hide} */
+ /**
+ * Optional index with semantics depending on the intent action.
+ * @see #ACTION_QUICK_VIEW
+ */
public static final String EXTRA_INDEX = "android.intent.extra.INDEX";
/**
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 6586426..cc266c5 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -18,6 +18,7 @@
import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.IOnAppsChangedListener;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
@@ -40,4 +41,5 @@
in Bundle opts, in UserHandle user);
boolean isPackageEnabled(String packageName, in UserHandle user);
boolean isActivityEnabled(in ComponentName component, in UserHandle user);
+ ApplicationInfo getApplicationInfo(String packageName, int flags, in UserHandle user);
}
diff --git a/core/java/android/content/pm/IOnAppsChangedListener.aidl b/core/java/android/content/pm/IOnAppsChangedListener.aidl
index 796b58d..1303696 100644
--- a/core/java/android/content/pm/IOnAppsChangedListener.aidl
+++ b/core/java/android/content/pm/IOnAppsChangedListener.aidl
@@ -27,4 +27,6 @@
void onPackageChanged(in UserHandle user, String packageName);
void onPackagesAvailable(in UserHandle user, in String[] packageNames, boolean replacing);
void onPackagesUnavailable(in UserHandle user, in String[] packageNames, boolean replacing);
+ void onPackagesSuspended(in UserHandle user, in String[] packageNames);
+ void onPackagesUnsuspended(in UserHandle user, in String[] packageNames);
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 90a1198..2c6b604 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -429,6 +429,12 @@
void performFstrimIfNeeded();
/**
+ * Ask the package manager to extract packages if needed, to save
+ * the VM unzipping the APK in memory during launch.
+ */
+ void extractPackagesIfNeeded();
+
+ /**
* Notify the package manager that a package is going to be used.
*/
void notifyPackageUse(String packageName);
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 6e67af4..8c7d327 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -21,6 +21,7 @@
import android.content.Intent;
import android.content.pm.ILauncherApps;
import android.content.pm.IOnAppsChangedListener;
+import android.content.pm.PackageManager.ApplicationInfoFlags;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Rect;
import android.os.Bundle;
@@ -123,6 +124,30 @@
*/
abstract public void onPackagesUnavailable(String[] packageNames, UserHandle user,
boolean replacing);
+
+ /**
+ * Indicates that one or more packages have been suspended. For
+ * example, this can happen when a Device Administrator suspends
+ * an applicaton.
+ *
+ * @param packageNames The names of the packages that have just been
+ * suspended.
+ * @param user The UserHandle of the profile that generated the change.
+ */
+ public void onPackagesSuspended(String[] packageNames, UserHandle user) {
+ }
+
+ /**
+ * Indicates that one or more packages have been unsuspended. For
+ * example, this can happen when a Device Administrator unsuspends
+ * an applicaton.
+ *
+ * @param packageNames The names of the packages that have just been
+ * unsuspended.
+ * @param user The UserHandle of the profile that generated the change.
+ */
+ public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
+ }
}
/** @hide */
@@ -243,6 +268,25 @@
}
/**
+ * Retrieve all of the information we know about a particular package / application.
+ *
+ * @param packageName The package of the application
+ * @param flags Additional option flags {@link PackageManager#getApplicationInfo}
+ * @param user The UserHandle of the profile.
+ *
+ * @return An {@link ApplicationInfo} containing information about the package or
+ * null of the package isn't found.
+ */
+ public ApplicationInfo getApplicationInfo(String packageName, @ApplicationInfoFlags int flags,
+ UserHandle user) {
+ try {
+ return mService.getApplicationInfo(packageName, flags, user);
+ } catch (RemoteException re) {
+ throw new RuntimeException("Failed to call LauncherAppsService", re);
+ }
+ }
+
+ /**
* Checks if the activity exists and it enabled for a profile.
*
* @param component The activity to check.
@@ -400,7 +444,33 @@
for (CallbackMessageHandler callback : mCallbacks) {
callback.postOnPackagesUnavailable(packageNames, user, replacing);
}
- }
+ }
+ }
+
+ @Override
+ public void onPackagesSuspended(UserHandle user, String[] packageNames)
+ throws RemoteException {
+ if (DEBUG) {
+ Log.d(TAG, "onPackagesSuspended " + user.getIdentifier() + "," + packageNames);
+ }
+ synchronized (LauncherApps.this) {
+ for (CallbackMessageHandler callback : mCallbacks) {
+ callback.postOnPackagesSuspended(packageNames, user);
+ }
+ }
+ }
+
+ @Override
+ public void onPackagesUnsuspended(UserHandle user, String[] packageNames)
+ throws RemoteException {
+ if (DEBUG) {
+ Log.d(TAG, "onPackagesUnsuspended " + user.getIdentifier() + "," + packageNames);
+ }
+ synchronized (LauncherApps.this) {
+ for (CallbackMessageHandler callback : mCallbacks) {
+ callback.postOnPackagesUnsuspended(packageNames, user);
+ }
+ }
}
};
@@ -410,6 +480,8 @@
private static final int MSG_CHANGED = 3;
private static final int MSG_AVAILABLE = 4;
private static final int MSG_UNAVAILABLE = 5;
+ private static final int MSG_SUSPENDED = 6;
+ private static final int MSG_UNSUSPENDED = 7;
private LauncherApps.Callback mCallback;
@@ -447,6 +519,12 @@
case MSG_UNAVAILABLE:
mCallback.onPackagesUnavailable(info.packageNames, info.user, info.replacing);
break;
+ case MSG_SUSPENDED:
+ mCallback.onPackagesSuspended(info.packageNames, info.user);
+ break;
+ case MSG_UNSUSPENDED:
+ mCallback.onPackagesUnsuspended(info.packageNames, info.user);
+ break;
}
}
@@ -488,5 +566,19 @@
info.user = user;
obtainMessage(MSG_UNAVAILABLE, info).sendToTarget();
}
+
+ public void postOnPackagesSuspended(String[] packageNames, UserHandle user) {
+ CallbackInfo info = new CallbackInfo();
+ info.packageNames = packageNames;
+ info.user = user;
+ obtainMessage(MSG_SUSPENDED, info).sendToTarget();
+ }
+
+ public void postOnPackagesUnsuspended(String[] packageNames, UserHandle user) {
+ CallbackInfo info = new CallbackInfo();
+ info.packageNames = packageNames;
+ info.user = user;
+ obtainMessage(MSG_UNSUSPENDED, info).sendToTarget();
+ }
}
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index ba4d14c..89f6870 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4268,6 +4268,22 @@
/**
* If the target user is a managed profile of the calling user or the caller
+ * is itself a managed profile, then this returns a drawable to use as a small
+ * icon to include in a view to distinguish it from the original icon. This version
+ * doesn't have background protection and should be used over a light background instead of
+ * a badge.
+ *
+ * @param user The target user.
+ * @param density The optional desired density for the badge as per
+ * {@link android.util.DisplayMetrics#densityDpi}. If not provided
+ * the density of the current display is used.
+ * @return the drawable or null if no drawable is required.
+ * @hide
+ */
+ public abstract Drawable getUserBadgeForDensityNoBackground(UserHandle user, int density);
+
+ /**
+ * If the target user is a managed profile of the calling user or the caller
* is itself a managed profile, then this returns a copy of the label with
* badging for accessibility services like talkback. E.g. passing in "Email"
* and it might return "Work Email" for Email in the work profile.
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index bb0a04f..2695dfd 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -743,6 +743,26 @@
new Key<int[]>("android.control.availableModes", int[].class);
/**
+ * <p>Range of boosts for {@link CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST android.control.postRawSensitivityBoost} supported
+ * by this camera device.</p>
+ * <p>Devices support post RAW sensitivity boost will advertise
+ * {@link CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST android.control.postRawSensitivityBoost} key for controling
+ * post RAW sensitivity boost.</p>
+ * <p>This key will be <code>null</code> for devices that do not support any RAW format
+ * outputs. For devices that do support RAW format outputs, this key will always
+ * present, and if a device does not support post RAW sensitivity boost, it will
+ * list <code>(100, 100)</code> in this key.</p>
+ * <p><b>Units</b>: ISO arithmetic units, the same as {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST
+ * @see CaptureRequest#SENSOR_SENSITIVITY
+ */
+ @PublicKey
+ public static final Key<android.util.Range<Integer>> CONTROL_POST_RAW_SENSITIVITY_BOOST_RANGE =
+ new Key<android.util.Range<Integer>>("android.control.postRawSensitivityBoostRange", new TypeReference<android.util.Range<Integer>>() {{ }});
+
+ /**
* <p>List of edge enhancement modes for {@link CaptureRequest#EDGE_MODE android.edge.mode} that are supported by this camera
* device.</p>
* <p>Full-capability camera devices must always support OFF; camera devices that support
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 852a4d9..63bcb31 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -421,8 +421,6 @@
*
* @see #createCaptureSession
* @see OutputConfiguration
- *
- * @hide
*/
public abstract void createCaptureSessionByOutputConfiguration(
List<OutputConfiguration> outputConfigurations,
@@ -587,6 +585,22 @@
throws CameraAccessException;
/**
+ * Create a new reprocessable camera capture session by providing the desired reprocessing
+ * input configuration and output {@link OutputConfiguration}
+ * to the camera device.
+ *
+ * @see #createReprocessableCaptureSession
+ * @see OutputConfiguration
+ *
+ */
+ public abstract void createReprocessableCaptureSessionWithConfigurations(
+ @NonNull InputConfiguration inputConfig,
+ @NonNull List<OutputConfiguration> outputs,
+ @NonNull CameraCaptureSession.StateCallback callback,
+ @Nullable Handler handler)
+ throws CameraAccessException;
+
+ /**
* <p>Create a new constrained high speed capture session.</p>
*
* <p>The application can use normal capture session (created via {@link #createCaptureSession})
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 67835a0..c3cae65 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1590,6 +1590,41 @@
new Key<Integer>("android.control.videoStabilizationMode", int.class);
/**
+ * <p>The amount of additional sesnsitivity boost applied to output images
+ * after RAW sensor data is captured.</p>
+ * <p>Some camera devices support additional digital sensitivity boosting in the
+ * camera processing pipeline after sensor RAW image is captured.
+ * Such a boost will be applied to YUV/JPEG format output images but will not
+ * have effect on RAW output formats like RAW_SENSOR, RAW10, RAW12 or RAW_OPAQUE.</p>
+ * <p>This key is optional. Applications can assume there is no boost applied
+ * after RAW is captured if this key is not available.
+ * When this key is available, the sensitivity boost value must be within
+ * {@link CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST android.control.postRawSensitivityBoost}.</p>
+ * <p>If the camera device cannot apply the exact boost requested, it will reduce the
+ * boost to the nearest supported value.
+ * The final boost value used will be available in the output capture result.</p>
+ * <p>For devices that support post RAW sensitivity boost, the YUV/JPEG output images
+ * of such device will have the total sensitivity of
+ * <code>{@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity} * android.control.ispSensitivity / 100</code>
+ * The sensitivity of RAW format images will always be <code>{@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}</code></p>
+ * <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to
+ * OFF; otherwise the auto-exposure algorithm will override this value.</p>
+ * <p><b>Units</b>: ISO arithmetic units, the same as {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}</p>
+ * <p><b>Range of valid values:</b><br>
+ * {@link CameraCharacteristics#CONTROL_POST_RAW_SENSITIVITY_BOOST_RANGE android.control.postRawSensitivityBoostRange}</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_MODE
+ * @see CaptureRequest#CONTROL_MODE
+ * @see CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST
+ * @see CameraCharacteristics#CONTROL_POST_RAW_SENSITIVITY_BOOST_RANGE
+ * @see CaptureRequest#SENSOR_SENSITIVITY
+ */
+ @PublicKey
+ public static final Key<Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST =
+ new Key<Integer>("android.control.postRawSensitivityBoost", int.class);
+
+ /**
* <p>Operation mode for edge
* enhancement.</p>
* <p>Edge enhancement improves sharpness and details in the captured image. OFF means
@@ -2244,6 +2279,8 @@
* requested, it will reduce the gain to the nearest supported
* value. The final sensitivity used will be available in the
* output capture result.</p>
+ * <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to
+ * OFF; otherwise the auto-exposure algorithm will override this value.</p>
* <p><b>Units</b>: ISO arithmetic units</p>
* <p><b>Range of valid values:</b><br>
* {@link CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE android.sensor.info.sensitivityRange}</p>
@@ -2252,6 +2289,8 @@
* Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the
* {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
*
+ * @see CaptureRequest#CONTROL_AE_MODE
+ * @see CaptureRequest#CONTROL_MODE
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
* @see CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE
* @see CameraCharacteristics#SENSOR_MAX_ANALOG_SENSITIVITY
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 3c2d503..7b9d1a3 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2097,6 +2097,41 @@
new Key<Integer>("android.control.videoStabilizationMode", int.class);
/**
+ * <p>The amount of additional sesnsitivity boost applied to output images
+ * after RAW sensor data is captured.</p>
+ * <p>Some camera devices support additional digital sensitivity boosting in the
+ * camera processing pipeline after sensor RAW image is captured.
+ * Such a boost will be applied to YUV/JPEG format output images but will not
+ * have effect on RAW output formats like RAW_SENSOR, RAW10, RAW12 or RAW_OPAQUE.</p>
+ * <p>This key is optional. Applications can assume there is no boost applied
+ * after RAW is captured if this key is not available.
+ * When this key is available, the sensitivity boost value must be within
+ * {@link CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST android.control.postRawSensitivityBoost}.</p>
+ * <p>If the camera device cannot apply the exact boost requested, it will reduce the
+ * boost to the nearest supported value.
+ * The final boost value used will be available in the output capture result.</p>
+ * <p>For devices that support post RAW sensitivity boost, the YUV/JPEG output images
+ * of such device will have the total sensitivity of
+ * <code>{@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity} * android.control.ispSensitivity / 100</code>
+ * The sensitivity of RAW format images will always be <code>{@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}</code></p>
+ * <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to
+ * OFF; otherwise the auto-exposure algorithm will override this value.</p>
+ * <p><b>Units</b>: ISO arithmetic units, the same as {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}</p>
+ * <p><b>Range of valid values:</b><br>
+ * {@link CameraCharacteristics#CONTROL_POST_RAW_SENSITIVITY_BOOST_RANGE android.control.postRawSensitivityBoostRange}</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_MODE
+ * @see CaptureRequest#CONTROL_MODE
+ * @see CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST
+ * @see CameraCharacteristics#CONTROL_POST_RAW_SENSITIVITY_BOOST_RANGE
+ * @see CaptureRequest#SENSOR_SENSITIVITY
+ */
+ @PublicKey
+ public static final Key<Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST =
+ new Key<Integer>("android.control.postRawSensitivityBoost", int.class);
+
+ /**
* <p>Operation mode for edge
* enhancement.</p>
* <p>Edge enhancement improves sharpness and details in the captured image. OFF means
@@ -3086,6 +3121,8 @@
* requested, it will reduce the gain to the nearest supported
* value. The final sensitivity used will be available in the
* output capture result.</p>
+ * <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to
+ * OFF; otherwise the auto-exposure algorithm will override this value.</p>
* <p><b>Units</b>: ISO arithmetic units</p>
* <p><b>Range of valid values:</b><br>
* {@link CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE android.sensor.info.sensitivityRange}</p>
@@ -3094,6 +3131,8 @@
* Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the
* {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
*
+ * @see CaptureRequest#CONTROL_AE_MODE
+ * @see CaptureRequest#CONTROL_MODE
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
* @see CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE
* @see CameraCharacteristics#SENSOR_MAX_ANALOG_SENSITIVITY
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index c3a4954..3aba0d1 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -485,7 +485,13 @@
Log.d(TAG, "createCaptureSessionByOutputConfiguration");
}
- createCaptureSessionInternal(null, outputConfigurations, callback, handler,
+ // OutputConfiguration objects aren't immutable, make a copy before using.
+ List<OutputConfiguration> currentOutputs = new ArrayList<OutputConfiguration>();
+ for (OutputConfiguration output : outputConfigurations) {
+ currentOutputs.add(new OutputConfiguration(output));
+ }
+
+ createCaptureSessionInternal(null, currentOutputs, callback, handler,
/*isConstrainedHighSpeed*/false);
}
@@ -510,6 +516,34 @@
}
@Override
+ public void createReprocessableCaptureSessionWithConfigurations(InputConfiguration inputConfig,
+ List<OutputConfiguration> outputs,
+ android.hardware.camera2.CameraCaptureSession.StateCallback callback, Handler handler)
+ throws CameraAccessException {
+ if (DEBUG) {
+ Log.d(TAG, "createReprocessableCaptureSessionWithConfigurations");
+ }
+
+ if (inputConfig == null) {
+ throw new IllegalArgumentException("inputConfig cannot be null when creating a " +
+ "reprocessable capture session");
+ }
+
+ if (outputs == null) {
+ throw new IllegalArgumentException("Output configurations cannot be null when " +
+ "creating a reprocessable capture session");
+ }
+
+ // OutputConfiguration objects aren't immutable, make a copy before using.
+ List<OutputConfiguration> currentOutputs = new ArrayList<OutputConfiguration>();
+ for (OutputConfiguration output : outputs) {
+ currentOutputs.add(new OutputConfiguration(output));
+ }
+ createCaptureSessionInternal(inputConfig, currentOutputs,
+ callback, handler, /*isConstrainedHighSpeed*/false);
+ }
+
+ @Override
public void createConstrainedHighSpeedCaptureSession(List<Surface> outputs,
android.hardware.camera2.CameraCaptureSession.StateCallback callback, Handler handler)
throws CameraAccessException {
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index a04cdce..4407e55 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -17,6 +17,7 @@
package android.hardware.camera2.params;
+import android.annotation.SystemApi;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.utils.HashCodeHelpers;
import android.hardware.camera2.utils.SurfaceUtils;
@@ -32,39 +33,58 @@
* A class for describing camera output, which contains a {@link Surface} and its specific
* configuration for creating capture session.
*
- * @see CameraDevice#createCaptureSession
+ * @see CameraDevice#createCaptureSessionByOutputConfiguration
*
- * @hide
*/
public final class OutputConfiguration implements Parcelable {
/**
* Rotation constant: 0 degree rotation (no rotation)
+ *
+ * @hide
*/
+ @SystemApi
public static final int ROTATION_0 = 0;
/**
* Rotation constant: 90 degree counterclockwise rotation.
+ *
+ * @hide
*/
+ @SystemApi
public static final int ROTATION_90 = 1;
/**
* Rotation constant: 180 degree counterclockwise rotation.
+ *
+ * @hide
*/
+ @SystemApi
public static final int ROTATION_180 = 2;
/**
* Rotation constant: 270 degree counterclockwise rotation.
+ *
+ * @hide
*/
+ @SystemApi
public static final int ROTATION_270 = 3;
/**
- * Create a new immutable SurfaceConfiguration instance.
+ * Invalid surface set ID.
+ *
+ *<p>An {@link OutputConfiguration} with this value indicates that the included surface
+ *doesn't belong to any surface set.</p>
+ */
+ public static final int SURFACE_SET_ID_INVALID = -1;
+
+ /**
+ * Create a new {@link OutputConfiguration} instance with a {@link Surface}.
*
* @param surface
* A Surface for camera to output to.
*
- * <p>This constructor creates a default configuration</p>
+ * <p>This constructor creates a default configuration.</p>
*
*/
public OutputConfiguration(Surface surface) {
@@ -72,7 +92,7 @@
}
/**
- * Create a new immutable SurfaceConfiguration instance.
+ * Create a new {@link OutputConfiguration} instance.
*
* <p>This constructor takes an argument for desired camera rotation</p>
*
@@ -87,11 +107,13 @@
* application should set rotation to {@code ROTATION_90} and make sure the
* corresponding Surface size is 720x1280. Note that {@link CameraDevice} might
* throw {@code IllegalArgumentException} if device cannot perform such rotation.
- *
+ * @hide
*/
+ @SystemApi
public OutputConfiguration(Surface surface, int rotation) {
checkNotNull(surface, "Surface must not be null");
checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant");
+ mSurfaceSetId = SURFACE_SET_ID_INVALID;
mSurface = surface;
mRotation = rotation;
mConfiguredSize = SurfaceUtils.getSurfaceSize(surface);
@@ -100,13 +122,37 @@
}
/**
+ * Create a new {@link OutputConfiguration} instance with another {@link OutputConfiguration}
+ * instance.
+ *
+ * @param other Another {@link OutputConfiguration} instance to be copied.
+ *
+ * @hide
+ */
+ @SystemApi
+ public OutputConfiguration(OutputConfiguration other) {
+ if (other == null) {
+ throw new IllegalArgumentException("OutputConfiguration shouldn't be null");
+ }
+
+ this.mSurface = other.mSurface;
+ this.mRotation = other.mRotation;
+ this.mSurfaceSetId = other.mSurfaceSetId;
+ this.mConfiguredDataspace = other.mConfiguredDataspace;
+ this.mConfiguredFormat = other.mConfiguredFormat;
+ this.mConfiguredSize = other.mConfiguredSize;
+ }
+
+ /**
* Create an OutputConfiguration from Parcel.
*/
private OutputConfiguration(Parcel source) {
int rotation = source.readInt();
+ int surfaceSetId = source.readInt();
Surface surface = Surface.CREATOR.createFromParcel(source);
checkNotNull(surface, "Surface must not be null");
checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant");
+ mSurfaceSetId = surfaceSetId;
mSurface = surface;
mRotation = rotation;
mConfiguredSize = SurfaceUtils.getSurfaceSize(mSurface);
@@ -128,11 +174,46 @@
*
* @return the rotation associated with this {@link OutputConfiguration}.
* Value will be one of ROTATION_[0, 90, 180, 270]
+ *
+ * @hide
*/
+ @SystemApi
public int getRotation() {
return mRotation;
}
+ /**
+ * Set the surface set ID to this {@link OutputConfiguration}.
+ *
+ * <p>
+ * A surface set ID is used to identify which surface set this output surface belongs to. A
+ * surface set is a group of output surfaces that are not intended to receive camera output
+ * buffer streams simultaneously. The {@link CameraDevice} may be able to share the buffers used
+ * by all the surfaces from the same surface set, therefore may save the overall memory
+ * footprint. The application should only set the same set ID for the streams that are not
+ * simultaneously streaming. A negative ID indicates that this surface doesn't belong to any
+ * surface set. The default value will be {@value #SURFACE_SET_ID_INVALID}.
+ * </p>
+ *
+ * @param setId
+ */
+ public void setSurfaceSetId(int setId) {
+ if (setId < 0) {
+ setId = SURFACE_SET_ID_INVALID;
+ }
+ mSurfaceSetId = setId;
+ }
+
+ /**
+ * Get the surface set Id associated with this {@link OutputConfiguration}.
+ *
+ * @return the surface set Id associated with this {@link OutputConfiguration}.
+ * Value will be one of ROTATION_[0, 90, 180, 270]
+ */
+ public int getSurfaceSetId() {
+ return mSurfaceSetId;
+ }
+
public static final Parcelable.Creator<OutputConfiguration> CREATOR =
new Parcelable.Creator<OutputConfiguration>() {
@Override
@@ -163,6 +244,7 @@
throw new IllegalArgumentException("dest must not be null");
}
dest.writeInt(mRotation);
+ dest.writeInt(mSurfaceSetId);
mSurface.writeToParcel(dest, flags);
}
@@ -187,7 +269,8 @@
mRotation == other.mRotation &&
mConfiguredSize.equals(other.mConfiguredSize) &&
mConfiguredFormat == other.mConfiguredFormat &&
- mConfiguredDataspace == other.mConfiguredDataspace;
+ mConfiguredDataspace == other.mConfiguredDataspace &&
+ mSurfaceSetId == other.mSurfaceSetId;
}
return false;
}
@@ -203,6 +286,7 @@
private static final String TAG = "OutputConfiguration";
private final Surface mSurface;
private final int mRotation;
+ private int mSurfaceSetId;
// The size, format, and dataspace of the surface when OutputConfiguration is created.
private final Size mConfiguredSize;
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 5d969b1..c4ee347 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -25,6 +25,8 @@
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.PointerIcon;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
/** @hide */
interface IInputManager {
@@ -59,6 +61,11 @@
String keyboardLayoutDescriptor);
void removeKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
String keyboardLayoutDescriptor);
+ KeyboardLayout getKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
+ in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype);
+ void setKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
+ in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype,
+ String keyboardLayoutDescriptor);
// Registers an input devices changed listener.
void registerInputDevicesChangedListener(IInputDevicesChangedListener listener);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 9972f49..cbe3412 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -16,7 +16,7 @@
package android.hardware.input;
-import android.view.PointerIcon;
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
@@ -40,6 +40,9 @@
import android.util.SparseArray;
import android.view.InputDevice;
import android.view.InputEvent;
+import android.view.PointerIcon;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -643,6 +646,51 @@
}
}
+
+ /**
+ * Gets the keyboard layout for the specified input device and IME subtype.
+ *
+ * @param identifier The identifier for the input device.
+ * @param inputMethodInfo The input method.
+ * @param inputMethodSubtype The input method subtype.
+ *
+ * @return The associated {@link KeyboardLayout}, or null if one has not been set.
+ *
+ * @hide
+ */
+ public KeyboardLayout getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+ InputMethodInfo inputMethodInfo, InputMethodSubtype inputMethodSubtype) {
+ try {
+ return mIm.getKeyboardLayoutForInputDevice(
+ identifier, inputMethodInfo, inputMethodSubtype);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Could not set keyboard layout.", ex);
+ return null;
+ }
+ }
+
+ /**
+ * Sets the keyboard layout for the specified input device and IME subtype pair.
+ *
+ * @param identifier The identifier for the input device.
+ * @param inputMethodInfo The input method with which to associate the keyboard layout.
+ * @param inputMethodSubtype The input method subtype which which to associate the keyboard
+ * layout.
+ * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to set
+ *
+ * @hide
+ */
+ public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+ InputMethodInfo inputMethodInfo, InputMethodSubtype inputMethodSubtype,
+ String keyboardLayoutDescriptor) {
+ try {
+ mIm.setKeyboardLayoutForInputDevice(identifier, inputMethodInfo,
+ inputMethodSubtype, keyboardLayoutDescriptor);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Could not set keyboard layout.", ex);
+ }
+ }
+
/**
* Gets the TouchCalibration applied to the specified input device's coordinates.
*
diff --git a/core/java/android/hardware/input/TouchCalibration.java b/core/java/android/hardware/input/TouchCalibration.java
index 025fad0..15503ed 100644
--- a/core/java/android/hardware/input/TouchCalibration.java
+++ b/core/java/android/hardware/input/TouchCalibration.java
@@ -123,4 +123,10 @@
Float.floatToIntBits(mYScale) ^
Float.floatToIntBits(mYOffset);
}
+
+ @Override
+ public String toString() {
+ return String.format("[%f, %f, %f, %f, %f, %f]",
+ mXScale, mXYMix, mXOffset, mYXMix, mYScale, mYOffset);
+ }
}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index c4f0847..5584cde2a 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -16,7 +16,7 @@
package android.net;
import static com.android.internal.util.Preconditions.checkNotNull;
-
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
@@ -49,6 +49,8 @@
import libcore.net.event.NetworkEventDispatcher;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger;
@@ -512,6 +514,7 @@
private final Context mContext;
private INetworkManagementService mNMService;
+ private INetworkPolicyManager mNPManager;
/**
* Tests if a given integer represents a valid network type.
@@ -3025,4 +3028,58 @@
return NetworkUtils.bindProcessToNetworkForHostResolution(
network == null ? NETID_UNSET : network.netId);
}
+
+ /**
+ * Device is not restricting metered network activity while application is running on
+ * background.
+ */
+ public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1;
+
+ /**
+ * Device is restricting metered network activity while application is running on background,
+ * but application is allowed to bypass it.
+ * <p>
+ * In this state, application should take action to mitigate metered network access.
+ * For example, a music streaming application should switch to a low-bandwidth bitrate.
+ */
+ public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2;
+
+ /**
+ * Device is restricting metered network activity while application is running on background.
+ * In this state, application should not try to use the network while running on background,
+ * because it would be denied.
+ */
+ public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3;
+
+ @IntDef(flag = false, value = {
+ RESTRICT_BACKGROUND_STATUS_DISABLED,
+ RESTRICT_BACKGROUND_STATUS_WHITELISTED,
+ RESTRICT_BACKGROUND_STATUS_ENABLED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RestrictBackgroundStatus {
+ }
+
+ private INetworkPolicyManager getNetworkPolicyManager() {
+ synchronized (this) {
+ if (mNPManager != null) {
+ return mNPManager;
+ }
+ mNPManager = INetworkPolicyManager.Stub.asInterface(ServiceManager
+ .getService(Context.NETWORK_POLICY_SERVICE));
+ return mNPManager;
+ }
+ }
+
+ /**
+ * Determines if the calling application is subject to metered network restrictions while
+ * running on background.
+ */
+ public @RestrictBackgroundStatus int getRestrictBackgroundStatus() {
+ try {
+ return getNetworkPolicyManager().getRestrictBackgroundByCaller();
+ } catch (RemoteException e) {
+ return RESTRICT_BACKGROUND_STATUS_DISABLED;
+ }
+ }
}
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index 06aa616..a57fac3 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -56,6 +56,12 @@
void addRestrictBackgroundWhitelistedUid(int uid);
void removeRestrictBackgroundWhitelistedUid(int uid);
int[] getRestrictBackgroundWhitelistedUids();
+ /** Gets the restrict background status based on the caller's UID:
+ 1 - disabled
+ 2 - whitelisted
+ 3 - enabled
+ */
+ int getRestrictBackgroundByCaller();
void setDeviceIdleMode(boolean enabled);
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 2ca9ab8a..ba215bb 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -390,7 +390,7 @@
* type.
*/
public static String DIRECTORY_MUSIC = "Music";
-
+
/**
* Standard directory in which to place any audio files that should be
* in the list of podcasts that the user can select (not as regular
@@ -479,6 +479,37 @@
public static String DIRECTORY_DOCUMENTS = "Documents";
/**
+ * List of standard storage directories.
+ * <p>
+ * Each of its values have its own constant:
+ * <ul>
+ * <li>{@link #DIRECTORY_MUSIC}
+ * <li>{@link #DIRECTORY_PODCASTS}
+ * <li>{@link #DIRECTORY_ALARMS}
+ * <li>{@link #DIRECTORY_RINGTONES}
+ * <li>{@link #DIRECTORY_NOTIFICATIONS}
+ * <li>{@link #DIRECTORY_PICTURES}
+ * <li>{@link #DIRECTORY_MOVIES}
+ * <li>{@link #DIRECTORY_DOWNLOADS}
+ * <li>{@link #DIRECTORY_DCIM}
+ * <li>{@link #DIRECTORY_DOCUMENTS}
+ * </ul>
+ * @hide
+ */
+ public static final String[] STANDARD_DIRECTORIES = {
+ DIRECTORY_MUSIC,
+ DIRECTORY_PODCASTS,
+ DIRECTORY_RINGTONES,
+ DIRECTORY_ALARMS,
+ DIRECTORY_NOTIFICATIONS,
+ DIRECTORY_PICTURES,
+ DIRECTORY_MOVIES,
+ DIRECTORY_DOWNLOADS,
+ DIRECTORY_DCIM,
+ DIRECTORY_DOCUMENTS
+ };
+
+ /**
* Get a top-level shared/external storage directory for placing files of a
* particular type. This is where the user will typically place and manage
* their own files, so you should be careful about what you put here to
@@ -495,13 +526,13 @@
* </p>
* {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
* public_picture}
- *
+ *
* @param type The type of storage directory to return. Should be one of
* {@link #DIRECTORY_MUSIC}, {@link #DIRECTORY_PODCASTS},
* {@link #DIRECTORY_RINGTONES}, {@link #DIRECTORY_ALARMS},
* {@link #DIRECTORY_NOTIFICATIONS}, {@link #DIRECTORY_PICTURES},
- * {@link #DIRECTORY_MOVIES}, {@link #DIRECTORY_DOWNLOADS}, or
- * {@link #DIRECTORY_DCIM}. May not be null.
+ * {@link #DIRECTORY_MOVIES}, {@link #DIRECTORY_DOWNLOADS},
+ * {@link #DIRECTORY_DCIM}, or {@link #DIRECTORY_DOCUMENTS}. May not be null.
* @return Returns the File path for the directory. Note that this directory
* may not yet exist, so you must make sure it exists before using
* it such as with {@link File#mkdirs File.mkdirs()}.
@@ -657,7 +688,7 @@
/**
* Returns the current state of the primary shared/external storage media.
- *
+ *
* @see #getExternalStorageDirectory()
* @return one of {@link #MEDIA_UNKNOWN}, {@link #MEDIA_REMOVED},
* {@link #MEDIA_UNMOUNTED}, {@link #MEDIA_CHECKING},
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index f01f597..fe834a1 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -529,6 +529,21 @@
public static final String DISALLOW_DATA_ROAMING = "no_data_roaming";
/**
+ * Specifies if a user is not allowed to change their icon. Device owner and profile owner
+ * can set this restriction. When it is set by device owner, only the target user will be
+ * affected. The default value is <code>false</code>.
+ *
+ * <p>Key for user restrictions.
+ *
+ * <p>Type: Boolean
+ *
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_SET_USER_ICON = "no_set_user_icon";
+
+ /**
* Allows apps in the parent profile to handle web links from the managed profile.
*
* This user restriction has an effect only in a managed profile.
diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java
index 898b6cf..f765336 100644
--- a/core/java/android/os/UserManagerInternal.java
+++ b/core/java/android/os/UserManagerInternal.java
@@ -17,6 +17,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.Bitmap;
/**
* @hide Only for use within the system server.
@@ -81,4 +82,13 @@
* whether the user is managed by profile owner.
*/
public abstract void setUserManaged(int userId, boolean isManaged);
+
+ /**
+ * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to omit
+ * restriction check, because DevicePolicyManager must always be able to set user icon
+ * regardless of any restriction.
+ * Also called by {@link com.android.server.pm.UserManagerService} because the logic of setting
+ * the icon is in this method.
+ */
+ public abstract void setUserIcon(int userId, Bitmap bitmap);
}
diff --git a/core/java/android/print/PrintFileDocumentAdapter.java b/core/java/android/print/PrintFileDocumentAdapter.java
index 5d655bf..747400d 100644
--- a/core/java/android/print/PrintFileDocumentAdapter.java
+++ b/core/java/android/print/PrintFileDocumentAdapter.java
@@ -46,7 +46,7 @@
*/
public class PrintFileDocumentAdapter extends PrintDocumentAdapter {
- private static final String LOG_TAG = "PrintedFileDocumentAdapter";
+ private static final String LOG_TAG = "PrintedFileDocAdapter";
private final Context mContext;
diff --git a/core/java/android/printservice/CustomPrinterIconCallback.java b/core/java/android/printservice/CustomPrinterIconCallback.java
index ea9ea8b..6b9d0d8 100644
--- a/core/java/android/printservice/CustomPrinterIconCallback.java
+++ b/core/java/android/printservice/CustomPrinterIconCallback.java
@@ -31,7 +31,7 @@
/** The printer the call back is for */
private final @NonNull PrinterId mPrinterId;
private final @NonNull IPrintServiceClient mObserver;
- private static final String LOG_TAG = "CustomPrinterIconCallback";
+ private static final String LOG_TAG = "CustomPrinterIconCB";
/**
* Create a callback class to be used once a icon is loaded
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 6a5d857c..e7c4a07 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -38,6 +38,7 @@
import android.telecom.TelecomManager;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
+import android.util.Log;
import com.android.internal.telephony.CallerInfo;
import com.android.internal.telephony.PhoneConstants;
@@ -49,6 +50,7 @@
*/
public class CallLog {
private static final String LOG_TAG = "CallLog";
+ private static final boolean VERBOSE_LOG = false; // DON'T SUBMIT WITH TRUE.
public static final String AUTHORITY = "call_log";
@@ -58,6 +60,17 @@
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY);
+
+ /**
+ * The "shadow" provider stores calllog when the real calllog provider is encrypted. The
+ * real provider will alter copy from it when it starts, and remove the entries in the shadow.
+ *
+ * <p>See the comment in {@link Calls#addCall} for the details.
+ *
+ * @hide
+ */
+ public static final String SHADOW_AUTHORITY = "call_log_shadow";
+
/**
* Contains the recent calls.
*/
@@ -68,6 +81,10 @@
public static final Uri CONTENT_URI =
Uri.parse("content://call_log/calls");
+ /** @hide */
+ public static final Uri SHADOW_CONTENT_URI =
+ Uri.parse("content://call_log_shadow/calls");
+
/**
* The content:// style URL for filtering this table on phone numbers
*/
@@ -458,8 +475,10 @@
public static Uri addCall(CallerInfo ci, Context context, String number,
int presentation, int callType, int features, PhoneAccountHandle accountHandle,
long start, int duration, Long dataUsage) {
- return addCall(ci, context, number, "", presentation, callType, features, accountHandle,
- start, duration, dataUsage, false, null, false);
+ return addCall(ci, context, number, /* postDialDigits =*/ "", presentation,
+ callType, features, accountHandle,
+ start, duration, dataUsage, /* addForAllUsers =*/ false,
+ /* userToBeInsertedTo =*/ null, /* is_read =*/ false);
}
@@ -495,7 +514,7 @@
boolean addForAllUsers, UserHandle userToBeInsertedTo) {
return addCall(ci, context, number, postDialDigits, presentation, callType, features,
accountHandle, start, duration, dataUsage, addForAllUsers, userToBeInsertedTo,
- false);
+ /* is_read =*/ false);
}
/**
@@ -526,13 +545,18 @@
* Used for call log restore of missed calls.
*
* @result The URI of the call log entry belonging to the user that made or received this
- * call.
+ * call. This could be of the shadow provider. Do not return it to non-system apps,
+ * as they don't have permissions.
* {@hide}
*/
public static Uri addCall(CallerInfo ci, Context context, String number,
String postDialDigits, int presentation, int callType, int features,
PhoneAccountHandle accountHandle, long start, int duration, Long dataUsage,
boolean addForAllUsers, UserHandle userToBeInsertedTo, boolean is_read) {
+ if (VERBOSE_LOG) {
+ Log.v(LOG_TAG, String.format("Add call: number=%s, user=%s, for all=%s",
+ number, userToBeInsertedTo, addForAllUsers));
+ }
final ContentResolver resolver = context.getContentResolver();
int numberPresentation = PRESENTATION_ALLOWED;
@@ -647,41 +671,104 @@
}
}
+ /*
+ Writing the calllog works in the following way:
+ - All user entries
+ - if user-0 is encrypted, insert to user-0's shadow only.
+ (other users should also be encrypted, so nothing to do for other users.)
+ - if user-0 is decrypted, insert to user-0's real provider, as well as
+ all other users that are running and decrypted and should have calllog.
+
+ - Single user entry.
+ - If the target user is encryted, insert to its shadow.
+ - Otherwise insert to its real provider.
+
+ When the (real) calllog provider starts, it copies entries that it missed from
+ elsewhere.
+ - When user-0's (real) provider starts, it copies from user-0's shadow, and clears
+ the shadow.
+
+ - When other users (real) providers start, unless it shouldn't have calllog entries,
+ - Copy from the user's shadow, and clears the shadow.
+ - Copy from user-0's entries that are FOR_ALL_USERS = 1. (and don't clear it.)
+ */
+
Uri result = null;
+ final UserManager userManager = context.getSystemService(UserManager.class);
+ final int currentUserId = userManager.getUserHandle();
+
if (addForAllUsers) {
- // Insert the entry for all currently running users, in order to trigger any
- // ContentObservers currently set on the call log.
- final UserManager userManager = (UserManager) context.getSystemService(
- Context.USER_SERVICE);
- List<UserInfo> users = userManager.getUsers(true);
- final int currentUserId = userManager.getUserHandle();
+ // First, insert to the system user.
+ final Uri uriForSystem = addEntryAndRemoveExpiredEntries(
+ context, userManager, UserHandle.SYSTEM, values);
+ if (uriForSystem == null
+ || SHADOW_AUTHORITY.equals(uriForSystem.getAuthority())) {
+ // This means the system user is still encrypted and the entry has inserted
+ // into the shadow. This means other users are still all encrypted.
+ // Nothing further to do; just return null.
+ return null;
+ }
+ if (UserHandle.USER_SYSTEM == currentUserId) {
+ result = uriForSystem;
+ }
+
+ // Otherwise, insert to all other users that are running and unlocked.
+
+ final List<UserInfo> users = userManager.getUsers(true);
+
final int count = users.size();
for (int i = 0; i < count; i++) {
- final UserInfo user = users.get(i);
- final UserHandle userHandle = user.getUserHandle();
+ final UserInfo userInfo = users.get(i);
+ final UserHandle userHandle = userInfo.getUserHandle();
+ final int userId = userHandle.getIdentifier();
+
+ if (userHandle.isSystem()) {
+ // Already written.
+ continue;
+ }
+
+ if (!shouldHaveSharedCallLogEntries(context, userManager, userId)) {
+ // Shouldn't have calllog entries.
+ continue;
+ }
+
+ // For other users, we write only when they're running *and* decrypted.
+ // Other providers will copy from the system user's real provider, when they
+ // start.
if (userManager.isUserRunning(userHandle)
- && !userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
- userHandle)
- && !user.isManagedProfile()) {
- Uri uri = addEntryAndRemoveExpiredEntries(context,
- ContentProvider.maybeAddUserId(CONTENT_URI, user.id), values);
- if (user.id == currentUserId) {
+ && userManager.isUserUnlocked(userHandle)) {
+ final Uri uri = addEntryAndRemoveExpiredEntries(context, userManager,
+ userHandle, values);
+ if (userId == currentUserId) {
result = uri;
}
}
}
} else {
- Uri uri = CONTENT_URI;
- if (userToBeInsertedTo != null) {
- uri = ContentProvider
- .maybeAddUserId(CONTENT_URI, userToBeInsertedTo.getIdentifier());
- }
- result = addEntryAndRemoveExpiredEntries(context, uri, values);
+ // Single-user entry. Just write to that user, assuming it's running. If the
+ // user is encrypted, we write to the shadow calllog.
+
+ final UserHandle targetUserHandle = userToBeInsertedTo != null
+ ? userToBeInsertedTo
+ : UserHandle.of(currentUserId);
+ result = addEntryAndRemoveExpiredEntries(context, userManager, targetUserHandle,
+ values);
}
return result;
}
+ /** @hide */
+ public static boolean shouldHaveSharedCallLogEntries(Context context,
+ UserManager userManager, int userId) {
+ if (userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
+ UserHandle.of(userId))) {
+ return false;
+ }
+ final UserInfo userInfo = userManager.getUserInfo(userId);
+ return userInfo != null && !userInfo.isManagedProfile();
+ }
+
/**
* Query the call log database for the last dialed number.
* @param context Used to get the content resolver.
@@ -707,14 +794,31 @@
}
}
- private static Uri addEntryAndRemoveExpiredEntries(Context context, Uri uri,
- ContentValues values) {
+ private static Uri addEntryAndRemoveExpiredEntries(Context context, UserManager userManager,
+ UserHandle user, ContentValues values) {
final ContentResolver resolver = context.getContentResolver();
- Uri result = resolver.insert(uri, values);
- resolver.delete(uri, "_id IN " +
- "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER
- + " LIMIT -1 OFFSET 500)", null);
- return result;
+
+ final Uri uri = ContentProvider.maybeAddUserId(
+ userManager.isUserUnlocked(user) ? CONTENT_URI : SHADOW_CONTENT_URI,
+ user.getIdentifier());
+
+ if (VERBOSE_LOG) {
+ Log.v(LOG_TAG, String.format("Inserting to %s", uri));
+ }
+
+ try {
+ final Uri result = resolver.insert(uri, values);
+ resolver.delete(uri, "_id IN " +
+ "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER
+ + " LIMIT -1 OFFSET 500)", null);
+ return result;
+ } catch (IllegalArgumentException e) {
+ Log.w(LOG_TAG, "Failed to insert calllog", e);
+ // Even though we make sure the target user is running and decrypted before calling
+ // this method, there's a chance that the user just got shut down, in which case
+ // we'll still get "IllegalArgumentException: Unknown URL content://call_log/calls".
+ return null;
+ }
}
private static void updateDataUsageStatForData(ContentResolver resolver, String dataId) {
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index a401ac2..88cc8a2 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -62,7 +62,7 @@
* All client apps must hold a valid URI permission grant to access documents,
* typically issued when a user makes a selection through
* {@link Intent#ACTION_OPEN_DOCUMENT}, {@link Intent#ACTION_CREATE_DOCUMENT},
- * or {@link Intent#ACTION_OPEN_DOCUMENT_TREE}.
+ * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, or {@link Intent#ACTION_OPEN_EXTERNAL_DIRECTORY}.
*
* @see DocumentsProvider
*/
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3042aa9..80cf4bb 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3278,6 +3278,7 @@
WIFI_STATIC_DNS2,
BLUETOOTH_DISCOVERABILITY,
BLUETOOTH_DISCOVERABILITY_TIMEOUT,
+ FONT_SCALE,
DIM_SCREEN,
SCREEN_OFF_TIMEOUT,
SCREEN_BRIGHTNESS,
diff --git a/core/java/android/service/voice/VoiceInteractionManagerInternal.java b/core/java/android/service/voice/VoiceInteractionManagerInternal.java
new file mode 100644
index 0000000..b38067b
--- /dev/null
+++ b/core/java/android/service/voice/VoiceInteractionManagerInternal.java
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+package android.service.voice;
+
+import android.os.Bundle;
+import android.os.IBinder;
+
+
+/**
+ * @hide
+ * Private interface to the VoiceInteractionManagerService for use by ActivityManagerService.
+ */
+public abstract class VoiceInteractionManagerInternal {
+
+ /**
+ * Start a new voice interaction session when requested from within an activity
+ * by Activity.startLocalVoiceInteraction()
+ * @param callingActivity The binder token representing the calling activity.
+ * @param options
+ */
+ public abstract void startLocalVoiceInteraction(IBinder callingActivity, Bundle options);
+
+ /**
+ * Returns whether the currently selected voice interaction service supports local voice
+ * interaction for launching from an Activity.
+ */
+ public abstract boolean supportsLocalVoiceInteraction();
+
+ public abstract void stopLocalVoiceInteraction(IBinder callingActivity);
+}
\ No newline at end of file
diff --git a/core/java/android/service/voice/VoiceInteractionServiceInfo.java b/core/java/android/service/voice/VoiceInteractionServiceInfo.java
index ebe3f47..a9db32b 100644
--- a/core/java/android/service/voice/VoiceInteractionServiceInfo.java
+++ b/core/java/android/service/voice/VoiceInteractionServiceInfo.java
@@ -45,6 +45,7 @@
private String mSettingsActivity;
private boolean mSupportsAssist;
private boolean mSupportsLaunchFromKeyguard;
+ private boolean mSupportsLocalInteraction;
public VoiceInteractionServiceInfo(PackageManager pm, ComponentName comp)
throws PackageManager.NameNotFoundException {
@@ -70,6 +71,10 @@
}
public VoiceInteractionServiceInfo(PackageManager pm, ServiceInfo si) {
+ if (si == null) {
+ mParseError = "Service not available";
+ return;
+ }
if (!Manifest.permission.BIND_VOICE_INTERACTION.equals(si.permission)) {
mParseError = "Service does not require permission "
+ Manifest.permission.BIND_VOICE_INTERACTION;
@@ -114,6 +119,8 @@
mSupportsLaunchFromKeyguard = array.getBoolean(com.android.internal.
R.styleable.VoiceInteractionService_supportsLaunchVoiceAssistFromKeyguard,
false);
+ mSupportsLocalInteraction = array.getBoolean(com.android.internal.
+ R.styleable.VoiceInteractionService_supportsLocalInteraction, false);
array.recycle();
if (mSessionService == null) {
mParseError = "No sessionService specified";
@@ -168,4 +175,8 @@
public boolean getSupportsLaunchFromKeyguard() {
return mSupportsLaunchFromKeyguard;
}
+
+ public boolean getSupportsLocalInteraction() {
+ return mSupportsLocalInteraction;
+ }
}
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index ec14740..0c6a0c6 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -17,6 +17,7 @@
package android.service.voice;
import android.annotation.Nullable;
+import android.app.Activity;
import android.app.Dialog;
import android.app.Instrumentation;
import android.app.VoiceInteractor;
@@ -49,6 +50,7 @@
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.widget.FrameLayout;
+
import com.android.internal.app.IVoiceInteractionManagerService;
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractor;
@@ -75,7 +77,7 @@
*/
public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCallbacks2 {
static final String TAG = "VoiceInteractionSession";
- static final boolean DEBUG = false;
+ static final boolean DEBUG = true;
/**
* Flag received in {@link #onShow}: originator requested that the session be started with
@@ -101,6 +103,13 @@
*/
public static final int SHOW_SOURCE_APPLICATION = 1<<3;
+ /**
+ * Flag for use with {@link #onShow}: indicates that an Activity has invoked the voice
+ * interaction service for a local interaction using
+ * {@link Activity#startLocalVoiceInteraction(Bundle)}.
+ */
+ public static final int SHOW_SOURCE_ACTIVITY = 1<<4;
+
final Context mContext;
final HandlerCaller mHandlerCaller;
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 2c4241b..692d848 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1826,7 +1826,11 @@
return ArrayUtils.emptyArray(type);
}
- return text.getSpans(start, end, type);
+ if(text instanceof SpannableStringBuilder) {
+ return ((SpannableStringBuilder) text).getSpans(start, end, type, false);
+ } else {
+ return text.getSpans(start, end, type);
+ }
}
private char getEllipsisChar(TextUtils.TruncateAt method) {
diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
index 4267238..e34560b8 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.text.style.ParagraphStyle;
import android.util.Log;
import com.android.internal.util.ArrayUtils;
@@ -66,11 +67,15 @@
TextUtils.getChars(text, start, end, mText, 0);
mSpanCount = 0;
+ mSpanInsertCount = 0;
mSpans = EmptyArray.OBJECT;
mSpanStarts = EmptyArray.INT;
mSpanEnds = EmptyArray.INT;
mSpanFlags = EmptyArray.INT;
mSpanMax = EmptyArray.INT;
+ mSpanOrder = EmptyArray.INT;
+ mPrioSortBuffer = EmptyArray.INT;
+ mOrderSortBuffer = EmptyArray.INT;
if (text instanceof Spanned) {
Spanned sp = (Spanned) text;
@@ -234,6 +239,7 @@
// Documentation from interface
public void clear() {
replace(0, length(), "", 0, 0);
+ mSpanInsertCount = 0;
}
// Documentation from interface
@@ -256,6 +262,7 @@
if (mIndexOfSpan != null) {
mIndexOfSpan.clear();
}
+ mSpanInsertCount = 0;
}
// Documentation from interface
@@ -485,6 +492,7 @@
System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, count);
System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, count);
System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, count);
+ System.arraycopy(mSpanOrder, i + 1, mSpanOrder, i, count);
mSpanCount--;
@@ -712,9 +720,6 @@
end += mGapLength;
}
- int count = mSpanCount;
- Object[] spans = mSpans;
-
if (mIndexOfSpan != null) {
Integer index = mIndexOfSpan.get(what);
if (index != null) {
@@ -744,8 +749,10 @@
mSpanStarts = GrowingArrayUtils.append(mSpanStarts, mSpanCount, start);
mSpanEnds = GrowingArrayUtils.append(mSpanEnds, mSpanCount, end);
mSpanFlags = GrowingArrayUtils.append(mSpanFlags, mSpanCount, flags);
+ mSpanOrder = GrowingArrayUtils.append(mSpanOrder, mSpanCount, mSpanInsertCount);
invalidateIndex(mSpanCount);
mSpanCount++;
+ mSpanInsertCount++;
// Make sure there is enough room for empty interior nodes.
// This magic formula computes the size of the smallest perfect binary
// tree no smaller than mSpanCount.
@@ -837,6 +844,25 @@
*/
@SuppressWarnings("unchecked")
public <T> T[] getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind) {
+ return getSpans(queryStart, queryEnd, kind, true);
+ }
+
+ /**
+ * Return an array of the spans of the specified type that overlap
+ * the specified range of the buffer. The kind may be Object.class to get
+ * a list of all the spans regardless of type.
+ *
+ * @param queryStart Start index.
+ * @param queryEnd End index.
+ * @param kind Class type to search for.
+ * @param sort If true the results are sorted by the insertion order.
+ * @param <T>
+ * @return Array of the spans. Empty array if no results are found.
+ *
+ * @hide
+ */
+ public <T> T[] getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind,
+ boolean sort) {
if (kind == null) return (T[]) ArrayUtils.emptyArray(Object.class);
if (mSpanCount == 0) return ArrayUtils.emptyArray(kind);
int count = countSpans(queryStart, queryEnd, kind, treeRoot());
@@ -846,7 +872,13 @@
// Safe conversion, but requires a suppressWarning
T[] ret = (T[]) Array.newInstance(kind, count);
- getSpansRec(queryStart, queryEnd, kind, treeRoot(), ret, 0);
+ if (sort) {
+ mPrioSortBuffer = checkSortBuffer(mPrioSortBuffer, count);
+ mOrderSortBuffer = checkSortBuffer(mOrderSortBuffer, count);
+ }
+ getSpansRec(queryStart, queryEnd, kind, treeRoot(), ret, mPrioSortBuffer,
+ mOrderSortBuffer, 0, sort);
+ if (sort) sort(ret, mPrioSortBuffer, mOrderSortBuffer);
return ret;
}
@@ -876,7 +908,7 @@
if (spanEnd >= queryStart &&
(spanStart == spanEnd || queryStart == queryEnd ||
(spanStart != queryEnd && spanEnd != queryStart)) &&
- kind.isInstance(mSpans[i])) {
+ (Object.class == kind || kind.isInstance(mSpans[i]))) {
count++;
}
if ((i & 1) != 0) {
@@ -887,9 +919,25 @@
return count;
}
+ /**
+ * Fills the result array with the spans found under the current interval tree node.
+ *
+ * @param queryStart Start index for the interval query.
+ * @param queryEnd End index for the interval query.
+ * @param kind Class type to search for.
+ * @param i Index of the current tree node.
+ * @param ret Array to be filled with results.
+ * @param priority Buffer to keep record of the priorities of spans found.
+ * @param insertionOrder Buffer to keep record of the insertion orders of spans found.
+ * @param count The number of found spans.
+ * @param sort Flag to fill the priority and insertion order buffers. If false then
+ * the spans with priority flag will be sorted in the result array.
+ * @param <T>
+ * @return The total number of spans found.
+ */
@SuppressWarnings("unchecked")
private <T> int getSpansRec(int queryStart, int queryEnd, Class<T> kind,
- int i, T[] ret, int count) {
+ int i, T[] ret, int[] priority, int[] insertionOrder, int count, boolean sort) {
if ((i & 1) != 0) {
// internal tree node
int left = leftChild(i);
@@ -898,7 +946,8 @@
spanMax -= mGapLength;
}
if (spanMax >= queryStart) {
- count = getSpansRec(queryStart, queryEnd, kind, left, ret, count);
+ count = getSpansRec(queryStart, queryEnd, kind, left, ret, priority,
+ insertionOrder, count, sort);
}
}
if (i >= mSpanCount) return count;
@@ -914,36 +963,137 @@
if (spanEnd >= queryStart &&
(spanStart == spanEnd || queryStart == queryEnd ||
(spanStart != queryEnd && spanEnd != queryStart)) &&
- kind.isInstance(mSpans[i])) {
- int prio = mSpanFlags[i] & SPAN_PRIORITY;
- if (prio != 0) {
- int j;
-
- for (j = 0; j < count; j++) {
- int p = getSpanFlags(ret[j]) & SPAN_PRIORITY;
-
- if (prio > p) {
- break;
- }
- }
-
- System.arraycopy(ret, j, ret, j + 1, count - j);
- // Safe conversion thanks to the isInstance test above
- ret[j] = (T) mSpans[i];
- } else {
- // Safe conversion thanks to the isInstance test above
+ (Object.class == kind || kind.isInstance(mSpans[i]))) {
+ int spanPriority = mSpanFlags[i] & SPAN_PRIORITY;
+ if(sort) {
ret[count] = (T) mSpans[i];
+ priority[count] = spanPriority;
+ insertionOrder[count] = mSpanOrder[i];
+ } else if (spanPriority != 0) {
+ //insertion sort for elements with priority
+ int j = 0;
+ for (; j < count; j++) {
+ int p = getSpanFlags(ret[j]) & SPAN_PRIORITY;
+ if (spanPriority > p) break;
+ }
+ System.arraycopy(ret, j, ret, j + 1, count - j);
+ ret[j] = (T) mSpans[i];
}
count++;
}
if (count < ret.length && (i & 1) != 0) {
- count = getSpansRec(queryStart, queryEnd, kind, rightChild(i), ret, count);
+ count = getSpansRec(queryStart, queryEnd, kind, rightChild(i), ret, priority,
+ insertionOrder, count, sort);
}
}
return count;
}
/**
+ * Check the size of the buffer and grow if required.
+ *
+ * @param buffer Buffer to be checked.
+ * @param size Required size.
+ * @return Same buffer instance if the current size is greater than required size. Otherwise a
+ * new instance is created and returned.
+ */
+ private final int[] checkSortBuffer(int[] buffer, int size) {
+ if(size > buffer.length) {
+ return ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(size));
+ }
+ return buffer;
+ }
+
+ /**
+ * An iterative heap sort implementation. It will sort the spans using first their priority
+ * then insertion order. A span with higher priority will be before a span with lower
+ * priority. If priorities are the same, the spans will be sorted with insertion order. A
+ * span with a lower insertion order will be before a span with a higher insertion order.
+ *
+ * @param array Span array to be sorted.
+ * @param priority Priorities of the spans
+ * @param insertionOrder Insertion orders of the spans
+ * @param <T> Span object type.
+ * @param <T>
+ */
+ private final <T> void sort(T[] array, int[] priority, int[] insertionOrder) {
+ int size = array.length;
+ for (int i = size / 2 - 1; i >= 0; i--) {
+ siftDown(i, array, size, priority, insertionOrder);
+ }
+
+ for (int i = size - 1; i > 0; i--) {
+ T v = array[0];
+ int prio = priority[0];
+ int insertOrder = insertionOrder[0];
+ array[0] = array[i];
+ priority[0] = priority[i];
+ insertionOrder[0] = insertionOrder[i];
+ siftDown(0, array, i, priority, insertionOrder);
+ array[i] = v;
+ priority[i] = prio;
+ insertionOrder[i] = insertOrder;
+ }
+ }
+
+ /**
+ * Helper function for heap sort.
+ *
+ * @param index Index of the element to sift down.
+ * @param array Span array to be sorted.
+ * @param size Current heap size.
+ * @param priority Priorities of the spans
+ * @param insertionOrder Insertion orders of the spans
+ * @param <T> Span object type.
+ */
+ private final <T> void siftDown(int index, T[] array, int size, int[] priority,
+ int[] insertionOrder) {
+ T v = array[index];
+ int prio = priority[index];
+ int insertOrder = insertionOrder[index];
+
+ int left = 2 * index + 1;
+ while (left < size) {
+ if (left < size - 1 && compareSpans(left, left + 1, priority, insertionOrder) < 0) {
+ left++;
+ }
+ if (compareSpans(index, left, priority, insertionOrder) >= 0) {
+ break;
+ }
+ array[index] = array[left];
+ priority[index] = priority[left];
+ insertionOrder[index] = insertionOrder[left];
+ index = left;
+ left = 2 * index + 1;
+ }
+ array[index] = v;
+ priority[index] = prio;
+ insertionOrder[index] = insertOrder;
+ }
+
+ /**
+ * Compare two span elements in an array. Comparison is based first on the priority flag of
+ * the span, and then the insertion order of the span.
+ *
+ * @param left Index of the element to compare.
+ * @param right Index of the other element to compare.
+ * @param priority Priorities of the spans
+ * @param insertionOrder Insertion orders of the spans
+ * @return
+ */
+ private final int compareSpans(int left, int right, int[] priority,
+ int[] insertionOrder) {
+ int priority1 = priority[left];
+ int priority2 = priority[right];
+ if (priority1 == priority2) {
+ return Integer.compare(insertionOrder[left], insertionOrder[right]);
+ }
+ // since high priority has to be before a lower priority, the arguments to compare are
+ // opposite of the insertion order check.
+ return Integer.compare(priority2, priority1);
+ }
+
+ /**
* Return the next offset after <code>start</code> but less than or
* equal to <code>limit</code> where a span of the specified type
* begins or ends.
@@ -1509,18 +1659,21 @@
int start = mSpanStarts[i];
int end = mSpanEnds[i];
int flags = mSpanFlags[i];
+ int insertionOrder = mSpanOrder[i];
int j = i;
do {
mSpans[j] = mSpans[j - 1];
mSpanStarts[j] = mSpanStarts[j - 1];
mSpanEnds[j] = mSpanEnds[j - 1];
mSpanFlags[j] = mSpanFlags[j - 1];
+ mSpanOrder[j] = mSpanOrder[j - 1];
j--;
} while (j > 0 && start < mSpanStarts[j - 1]);
mSpans[j] = span;
mSpanStarts[j] = start;
mSpanEnds[j] = end;
mSpanFlags[j] = flags;
+ mSpanOrder[j] = insertionOrder;
invalidateIndex(j);
}
}
@@ -1558,6 +1711,11 @@
private int[] mSpanEnds;
private int[] mSpanMax; // see calcMax() for an explanation of what this array stores
private int[] mSpanFlags;
+ private int[] mSpanOrder; // store the order of span insertion
+ private int mSpanInsertCount; // counter for the span insertion
+ private int[] mPrioSortBuffer; // buffer used to sort getSpans result
+ private int[] mOrderSortBuffer; // buffer used to sort getSpans result
+
private int mSpanCount;
private IdentityHashMap<Object, Integer> mIndexOfSpan;
private int mLowWaterMark; // indices below this have not been touched
diff --git a/core/java/android/text/SpannableStringInternal.java b/core/java/android/text/SpannableStringInternal.java
index 5c5deb4..47e71be 100644
--- a/core/java/android/text/SpannableStringInternal.java
+++ b/core/java/android/text/SpannableStringInternal.java
@@ -36,24 +36,99 @@
mSpanData = EmptyArray.INT;
if (source instanceof Spanned) {
- Spanned sp = (Spanned) source;
- Object[] spans = sp.getSpans(start, end, Object.class);
-
- for (int i = 0; i < spans.length; i++) {
- int st = sp.getSpanStart(spans[i]);
- int en = sp.getSpanEnd(spans[i]);
- int fl = sp.getSpanFlags(spans[i]);
-
- if (st < start)
- st = start;
- if (en > end)
- en = end;
-
- setSpan(spans[i], st - start, en - start, fl);
+ if (source instanceof SpannableStringInternal) {
+ copySpans((SpannableStringInternal) source, start, end);
+ } else {
+ copySpans((Spanned) source, start, end);
}
}
}
+ /**
+ * Copies another {@link Spanned} object's spans between [start, end] into this object.
+ *
+ * @param src Source object to copy from.
+ * @param start Start index in the source object.
+ * @param end End index in the source object.
+ */
+ private final void copySpans(Spanned src, int start, int end) {
+ Object[] spans = src.getSpans(start, end, Object.class);
+
+ for (int i = 0; i < spans.length; i++) {
+ int st = src.getSpanStart(spans[i]);
+ int en = src.getSpanEnd(spans[i]);
+ int fl = src.getSpanFlags(spans[i]);
+
+ if (st < start)
+ st = start;
+ if (en > end)
+ en = end;
+
+ setSpan(spans[i], st - start, en - start, fl);
+ }
+ }
+
+ /**
+ * Copies a {@link SpannableStringInternal} object's spans between [start, end] into this
+ * object.
+ *
+ * @param src Source object to copy from.
+ * @param start Start index in the source object.
+ * @param end End index in the source object.
+ */
+ private final void copySpans(SpannableStringInternal src, int start, int end) {
+ if (start == 0 && end == src.length()) {
+ mSpans = ArrayUtils.newUnpaddedObjectArray(src.mSpans.length);
+ mSpanData = new int[src.mSpanData.length];
+ mSpanCount = src.mSpanCount;
+ System.arraycopy(src.mSpans, 0, mSpans, 0, src.mSpans.length);
+ System.arraycopy(src.mSpanData, 0, mSpanData, 0, mSpanData.length);
+ } else {
+ int count = 0;
+ int[] srcData = src.mSpanData;
+ int limit = src.mSpanCount;
+ for (int i = 0; i < limit; i++) {
+ int spanStart = srcData[i * COLUMNS + START];
+ int spanEnd = srcData[i * COLUMNS + END];
+ if (isOutOfCopyRange(start, end, spanStart, spanEnd)) continue;
+ count++;
+ }
+
+ if (count == 0) return;
+
+ Object[] srcSpans = src.mSpans;
+ mSpanCount = count;
+ mSpans = ArrayUtils.newUnpaddedObjectArray(mSpanCount);
+ mSpanData = new int[mSpanCount * COLUMNS];
+ for (int i = 0, j = 0; i < limit; i++) {
+ int spanStart = srcData[i * COLUMNS + START];
+ int spanEnd = srcData[i * COLUMNS + END];
+ if (isOutOfCopyRange(start, end, spanStart, spanEnd)) continue;
+ if (spanStart < start) spanStart = start;
+ if (spanEnd > end) spanEnd = end;
+
+ mSpans[j] = srcSpans[i];
+ mSpanData[j * COLUMNS + START] = spanStart - start;
+ mSpanData[j * COLUMNS + END] = spanEnd - start;
+ mSpanData[j * COLUMNS + FLAGS] = srcData[i * COLUMNS + FLAGS];
+ j++;
+ }
+ }
+ }
+
+ /**
+ * Checks if [spanStart, spanEnd] interval is excluded from [start, end].
+ *
+ * @return True if excluded, false if included.
+ */
+ private final boolean isOutOfCopyRange(int start, int end, int spanStart, int spanEnd) {
+ if (spanStart > end || spanEnd < start) return true;
+ if (spanStart != spanEnd && start != end) {
+ if (spanStart == end || spanEnd == start) return true;
+ }
+ return false;
+ }
+
public final int length() {
return mText.length();
}
@@ -234,7 +309,7 @@
}
// verify span class as late as possible, since it is expensive
- if (kind != null && !kind.isInstance(spans[i])) {
+ if (kind != null && kind != Object.class && !kind.isInstance(spans[i])) {
continue;
}
diff --git a/core/java/android/util/LocaleList.java b/core/java/android/util/LocaleList.java
index 24883e3..8a2d015 100644
--- a/core/java/android/util/LocaleList.java
+++ b/core/java/android/util/LocaleList.java
@@ -301,10 +301,19 @@
// is a pseudo-locale. So this is not a match.
return 0;
}
+ final String supportedScr = getLikelyScript(supported);
+ if (supportedScr.isEmpty()) {
+ // If we can't guess a script, we don't know enough about the locales' language to find
+ // if the locales match. So we fall back to old behavior of matching, which considered
+ // locales with different regions different.
+ final String supportedRegion = supported.getCountry();
+ return (supportedRegion.isEmpty() ||
+ supportedRegion.equals(desired.getCountry()))
+ ? 1 : 0;
+ }
+ final String desiredScr = getLikelyScript(desired);
// There is no match if the two locales use different scripts. This will most imporantly
// take care of traditional vs simplified Chinese.
- final String supportedScr = getLikelyScript(supported);
- final String desiredScr = getLikelyScript(desired);
return supportedScr.equals(desiredScr) ? 1 : 0;
}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 1a5de7e..1703ed1 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -186,8 +186,8 @@
/**
* Initiate the drag operation itself
*/
- boolean performDrag(IWindow window, IBinder dragToken, float touchX, float touchY,
- float thumbCenterX, float thumbCenterY, in ClipData data);
+ boolean performDrag(IWindow window, IBinder dragToken, int touchSource,
+ float touchX, float touchY, float thumbCenterX, float thumbCenterY, in ClipData data);
/**
* Report the result of a drop action targeted to the given window.
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index 54fa764..1c0ea0f 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -25,6 +25,8 @@
import android.widget.RemoteViews;
import android.widget.TextView;
+import com.android.internal.R;
+
import java.util.ArrayList;
/**
@@ -44,6 +46,7 @@
private ImageView mExpandButton;
private View mIcon;
private TextView mChildCount;
+ private View mProfileBadge;
private int mIconColor;
private int mOriginalNotificationColor;
private boolean mGroupHeader;
@@ -76,6 +79,7 @@
mExpandButton = (ImageView) findViewById(com.android.internal.R.id.expand_button);
mIcon = findViewById(com.android.internal.R.id.icon);
mChildCount = (TextView) findViewById(com.android.internal.R.id.number_of_children);
+ mProfileBadge = findViewById(com.android.internal.R.id.profile_badge);
}
@Override
@@ -120,12 +124,29 @@
}
totalWidth = givenWidth;
}
+ if (mProfileBadge.getVisibility() != View.GONE) {
+ totalWidth = givenWidth;
+ }
setMeasuredDimension(totalWidth, givenHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
+ if (mProfileBadge.getVisibility() != View.GONE) {
+ int paddingEnd = getPaddingEnd();
+ if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+ mProfileBadge.layout(paddingEnd,
+ mProfileBadge.getTop(),
+ paddingEnd + mProfileBadge.getMeasuredWidth(),
+ mProfileBadge.getBottom());
+ } else {
+ mProfileBadge.layout(getWidth() - paddingEnd - mProfileBadge.getMeasuredWidth(),
+ mProfileBadge.getTop(),
+ getWidth() - paddingEnd,
+ mProfileBadge.getBottom());
+ }
+ }
updateTouchListener();
}
@@ -305,4 +326,20 @@
}
return this;
}
+
+ public ImageView getExpandButton() {
+ return mExpandButton;
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
+ public boolean isInTouchRect(float x, float y) {
+ if (mExpandClickListener == null) {
+ return false;
+ }
+ return mTouchListener.isInside(x, y);
+ }
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 0b8018b..fff141a 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -24,6 +24,7 @@
import android.annotation.FloatRange;
import android.annotation.IdRes;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.LayoutRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -6691,6 +6692,68 @@
}
info.addAction(AccessibilityAction.ACTION_SHOW_ON_SCREEN);
+ populateAccessibilityNodeInfoDrawingOrderInParent(info);
+ }
+
+ /**
+ * Determine the order in which this view will be drawn relative to its siblings for a11y
+ *
+ * @param info The info whose drawing order should be populated
+ */
+ private void populateAccessibilityNodeInfoDrawingOrderInParent(AccessibilityNodeInfo info) {
+ int drawingOrderInParent = 1;
+ // Iterate up the hierarchy if parents are not important for a11y
+ View viewAtDrawingLevel = this;
+ final ViewParent parent = getParentForAccessibility();
+ while (viewAtDrawingLevel != parent) {
+ final ViewParent currentParent = viewAtDrawingLevel.getParent();
+ if (!(currentParent instanceof ViewGroup)) {
+ // Should only happen for the Decor
+ drawingOrderInParent = 0;
+ break;
+ } else {
+ final ViewGroup parentGroup = (ViewGroup) currentParent;
+ final int childCount = parentGroup.getChildCount();
+ if (childCount > 1) {
+ List<View> preorderedList = parentGroup.buildOrderedChildList();
+ if (preorderedList != null) {
+ final int childDrawIndex = preorderedList.indexOf(viewAtDrawingLevel);
+ for (int i = 0; i < childDrawIndex; i++) {
+ drawingOrderInParent += numViewsForAccessibility(preorderedList.get(i));
+ }
+ } else {
+ final int childIndex = parentGroup.indexOfChild(viewAtDrawingLevel);
+ final boolean customOrder = parentGroup.isChildrenDrawingOrderEnabled();
+ final int childDrawIndex = ((childIndex >= 0) && customOrder) ? parentGroup
+ .getChildDrawingOrder(childCount, childIndex) : childIndex;
+ final int numChildrenToIterate = customOrder ? childCount : childDrawIndex;
+ if (childDrawIndex != 0) {
+ for (int i = 0; i < numChildrenToIterate; i++) {
+ final int otherDrawIndex = (customOrder ?
+ parentGroup.getChildDrawingOrder(childCount, i) : i);
+ if (otherDrawIndex < childDrawIndex) {
+ drawingOrderInParent +=
+ numViewsForAccessibility(parentGroup.getChildAt(i));
+ }
+ }
+ }
+ }
+ }
+ }
+ viewAtDrawingLevel = (View) currentParent;
+ }
+ info.setDrawingOrder(drawingOrderInParent);
+ }
+
+ private static int numViewsForAccessibility(View view) {
+ if (view != null) {
+ if (view.includeForAccessibility()) {
+ return 1;
+ } else if (view instanceof ViewGroup) {
+ return ((ViewGroup) view).getNumChildrenForAccessibility();
+ }
+ }
+ return 0;
}
private View findLabelForView(View view, int labeledId) {
@@ -19996,7 +20059,7 @@
root.getLastTouchPoint(shadowSize);
okay = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, mAttachInfo.mDragToken,
- shadowSize.x, shadowSize.y,
+ root.getLastTouchSource(), shadowSize.x, shadowSize.y,
shadowTouchPoint.x, shadowTouchPoint.y, data);
if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "performDrag returned " + okay);
}
@@ -21501,6 +21564,11 @@
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
+ /** @hide */
+ @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MeasureSpecMode {}
+
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
@@ -21541,7 +21609,8 @@
* @param mode the mode of the measure specification
* @return the measure specification based on size and mode
*/
- public static int makeMeasureSpec(int size, int mode) {
+ public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
+ @MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
@@ -21570,7 +21639,9 @@
* {@link android.view.View.MeasureSpec#AT_MOST} or
* {@link android.view.View.MeasureSpec#EXACTLY}
*/
+ @MeasureSpecMode
public static int getMode(int measureSpec) {
+ //noinspection ResourceType
return (measureSpec & MODE_MASK);
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index f674298..27e2ea3 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -700,9 +700,6 @@
mGroupFlags |= (focusability & FLAG_MASK_FOCUSABILITY);
}
- /**
- * {@inheritDoc}
- */
@Override
void handleFocusGainInternal(int direction, Rect previouslyFocusedRect) {
if (mFocused != null) {
@@ -712,9 +709,7 @@
super.handleFocusGainInternal(direction, previouslyFocusedRect);
}
- /**
- * {@inheritDoc}
- */
+ @Override
public void requestChildFocus(View child, View focused) {
if (DBG) {
System.out.println(this + " requestChildFocus()");
@@ -739,9 +734,7 @@
}
}
- /**
- * {@inheritDoc}
- */
+ @Override
public void focusableViewAvailable(View v) {
if (mParent != null
// shortcut: don't report a new focusable view if we block our descendants from
@@ -760,9 +753,7 @@
}
}
- /**
- * {@inheritDoc}
- */
+ @Override
public boolean showContextMenuForChild(View originalView) {
return mParent != null && mParent.showContextMenuForChild(originalView);
}
@@ -772,9 +763,6 @@
return mParent != null && mParent.showContextMenuForChild(originalView, x, y);
}
- /**
- * {@inheritDoc}
- */
@Override
public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback) {
if ((mGroupFlags & FLAG_START_ACTION_MODE_FOR_CHILD_IS_TYPED) == 0) {
@@ -791,9 +779,6 @@
}
}
- /**
- * {@inheritDoc}
- */
@Override
public ActionMode startActionModeForChild(
View originalView, ActionMode.Callback callback, int type) {
@@ -848,6 +833,7 @@
* @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and
* FOCUS_RIGHT, or 0 for not applicable.
*/
+ @Override
public View focusSearch(View focused, int direction) {
if (isRootNamespace()) {
// root namespace means we should consider ourselves the top of the
@@ -860,16 +846,11 @@
return null;
}
- /**
- * {@inheritDoc}
- */
+ @Override
public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
return false;
}
- /**
- * {@inheritDoc}
- */
@Override
public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
ViewParent parent = mParent;
@@ -921,6 +902,7 @@
/**
* Called when a child view has changed whether or not it is tracking transient state.
*/
+ @Override
public void childHasTransientStateChanged(View child, boolean childHasTransientState) {
final boolean oldHasTransientState = hasTransientState();
if (childHasTransientState) {
@@ -945,18 +927,13 @@
return mChildCountWithTransientState > 0 || super.hasTransientState();
}
- /**
- * {@inheritDoc}
- */
@Override
public boolean dispatchUnhandledMove(View focused, int direction) {
return mFocused != null &&
mFocused.dispatchUnhandledMove(focused, direction);
}
- /**
- * {@inheritDoc}
- */
+ @Override
public void clearChildFocus(View child) {
if (DBG) {
System.out.println(this + " clearChildFocus()");
@@ -968,9 +945,6 @@
}
}
- /**
- * {@inheritDoc}
- */
@Override
public void clearFocus() {
if (DBG) {
@@ -985,9 +959,6 @@
}
}
- /**
- * {@inheritDoc}
- */
@Override
void unFocus(View focused) {
if (DBG) {
@@ -1054,9 +1025,6 @@
return null;
}
- /**
- * {@inheritDoc}
- */
@Override
public boolean hasFocusable() {
if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) {
@@ -1083,9 +1051,6 @@
return false;
}
- /**
- * {@inheritDoc}
- */
@Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
final int focusableCount = views.size();
@@ -1195,9 +1160,6 @@
return null;
}
- /**
- * {@inheritDoc}
- */
@Override
public void dispatchWindowFocusChanged(boolean hasFocus) {
super.dispatchWindowFocusChanged(hasFocus);
@@ -1208,9 +1170,6 @@
}
}
- /**
- * {@inheritDoc}
- */
@Override
public void addTouchables(ArrayList<View> views) {
super.addTouchables(views);
@@ -1239,9 +1198,6 @@
}
}
- /**
- * {@inheritDoc}
- */
@Override
public void dispatchDisplayHint(int hint) {
super.dispatchDisplayHint(hint);
@@ -1287,9 +1243,6 @@
}
}
- /**
- * {@inheritDoc}
- */
@Override
protected void dispatchVisibilityChanged(View changedView, int visibility) {
super.dispatchVisibilityChanged(changedView, visibility);
@@ -1300,9 +1253,6 @@
}
}
- /**
- * {@inheritDoc}
- */
@Override
public void dispatchWindowVisibilityChanged(int visibility) {
super.dispatchWindowVisibilityChanged(visibility);
@@ -1313,9 +1263,6 @@
}
}
- /**
- * {@inheritDoc}
- */
@Override
public void dispatchConfigurationChanged(Configuration newConfig) {
super.dispatchConfigurationChanged(newConfig);
@@ -1326,9 +1273,7 @@
}
}
- /**
- * {@inheritDoc}
- */
+ @Override
public void recomputeViewAttributes(View child) {
if (mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {
ViewParent parent = mParent;
@@ -1350,9 +1295,7 @@
}
}
- /**
- * {@inheritDoc}
- */
+ @Override
public void bringChildToFront(View child) {
final int index = indexOfChild(child);
if (index >= 0) {
@@ -1369,9 +1312,6 @@
return mLocalPoint;
}
- /**
- * {@inheritDoc}
- */
// TODO: Write real docs
@Override
public boolean dispatchDragEvent(DragEvent event) {
@@ -1461,6 +1401,10 @@
root.setDragFocus(target);
final int action = event.mAction;
+ // Position should not be available for ACTION_DRAG_ENTERED and ACTION_DRAG_EXITED.
+ event.mX = 0;
+ event.mY = 0;
+
// If we've dragged off of a child view or this window, send it the EXITED message
if (mCurrentDragView != null) {
final View view = mCurrentDragView;
@@ -1489,6 +1433,8 @@
}
}
event.mAction = action; // restore the event's original state
+ event.mX = tx;
+ event.mY = ty;
}
// Dispatch the actual drag location notice, localized into its coordinates
@@ -1631,9 +1577,6 @@
return changed;
}
- /**
- * {@inheritDoc}
- */
@Override
public boolean dispatchKeyEventPreIme(KeyEvent event) {
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
@@ -1646,9 +1589,6 @@
return false;
}
- /**
- * {@inheritDoc}
- */
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (mInputEventConsistencyVerifier != null) {
@@ -1673,9 +1613,6 @@
return false;
}
- /**
- * {@inheritDoc}
- */
@Override
public boolean dispatchKeyShortcutEvent(KeyEvent event) {
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
@@ -1688,9 +1625,6 @@
return false;
}
- /**
- * {@inheritDoc}
- */
@Override
public boolean dispatchTrackballEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
@@ -1725,10 +1659,9 @@
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
- final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
- final View child = (preorderedList == null)
- ? children[childIndex] : preorderedList.get(childIndex);
- PointF point = getLocalPoint();
+ final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+ final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
+ final PointF point = getLocalPoint();
if (isTransformedTouchPointInView(x, y, child, point)) {
final PointerIcon pointerIcon = child.getPointerIcon(event, point.x, point.y);
if (pointerIcon != null) {
@@ -1744,9 +1677,22 @@
return super.getPointerIcon(event, x, y);
}
- /**
- * {@inheritDoc}
- */
+ private int getAndVerifyPreorderedIndex(int childrenCount, int i, boolean customOrder) {
+ final int childIndex;
+ if (customOrder) {
+ final int childIndex1 = getChildDrawingOrder(childrenCount, i);
+ if (childIndex1 >= childrenCount) {
+ throw new IndexOutOfBoundsException("getChildDrawingOrder() "
+ + "returned invalid index " + childIndex1
+ + " (child count is " + childrenCount + ")");
+ }
+ childIndex = childIndex1;
+ } else {
+ childIndex = i;
+ }
+ return childIndex;
+ }
+
@SuppressWarnings({"ConstantConditions"})
@Override
protected boolean dispatchHoverEvent(MotionEvent event) {
@@ -1774,9 +1720,10 @@
final View[] children = mChildren;
HoverTarget lastHoverTarget = null;
for (int i = childrenCount - 1; i >= 0; i--) {
- int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
- final View child = (preorderedList == null)
- ? children[childIndex] : preorderedList.get(childIndex);
+ final int childIndex = getAndVerifyPreorderedIndex(
+ childrenCount, i, customOrder);
+ final View child = getAndVerifyPreorderedView(
+ preorderedList, children, childIndex);
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
@@ -2045,9 +1992,6 @@
return MotionEvent.obtainNoHistory(event);
}
- /**
- * {@inheritDoc}
- */
@Override
protected boolean dispatchGenericPointerEvent(MotionEvent event) {
// Send the event to the child under the pointer.
@@ -2061,9 +2005,8 @@
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
- int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
- final View child = (preorderedList == null)
- ? children[childIndex] : preorderedList.get(childIndex);
+ final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+ final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
@@ -2081,9 +2024,6 @@
return super.dispatchGenericPointerEvent(event);
}
- /**
- * {@inheritDoc}
- */
@Override
protected boolean dispatchGenericFocusedEvent(MotionEvent event) {
// Send the event to the focused child or to this view group if it has focus.
@@ -2124,9 +2064,6 @@
return handled;
}
- /**
- * {@inheritDoc}
- */
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
@@ -2216,10 +2153,10 @@
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
- final int childIndex = customOrder
- ? getChildDrawingOrder(childrenCount, i) : i;
- final View child = (preorderedList == null)
- ? children[childIndex] : preorderedList.get(childIndex);
+ final int childIndex = getAndVerifyPreorderedIndex(
+ childrenCount, i, customOrder);
+ final View child = getAndVerifyPreorderedView(
+ preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
@@ -2397,7 +2334,7 @@
* Resets the cancel next up flag.
* Returns true if the flag was previously set.
*/
- private static boolean resetCancelNextUpFlag(View view) {
+ private static boolean resetCancelNextUpFlag(@NonNull View view) {
if ((view.mPrivateFlags & PFLAG_CANCEL_NEXT_UP_EVENT) != 0) {
view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
return true;
@@ -2450,7 +2387,7 @@
* Gets the touch target for specified child view.
* Returns null if not found.
*/
- private TouchTarget getTouchTarget(View child) {
+ private TouchTarget getTouchTarget(@NonNull View child) {
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
if (target.child == child) {
return target;
@@ -2463,8 +2400,8 @@
* Adds a touch target for specified child to the beginning of the list.
* Assumes the target child is not already present.
*/
- private TouchTarget addTouchTarget(View child, int pointerIdBits) {
- TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
+ private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
+ final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
@@ -2526,7 +2463,7 @@
* Returns true if a child view can receive pointer events.
* @hide
*/
- private static boolean canViewReceivePointerEvents(View child) {
+ private static boolean canViewReceivePointerEvents(@NonNull View child) {
return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null;
}
@@ -2721,9 +2658,7 @@
}
}
- /**
- * {@inheritDoc}
- */
+ @Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
@@ -2893,9 +2828,6 @@
}
}
- /**
- * {@inheritDoc}
- */
@Override
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
@@ -2962,6 +2894,7 @@
* adds in all child views of the view group, in addition to calling the default View
* implementation.
*/
+ @Override
public void dispatchProvideStructure(ViewStructure structure) {
super.dispatchProvideStructure(structure);
if (!isAssistBlocked()) {
@@ -2976,7 +2909,7 @@
for (int i=0; i<childrenCount; i++) {
int childIndex;
try {
- childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
+ childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
} catch (IndexOutOfBoundsException e) {
childIndex = i;
if (mContext.getApplicationInfo().targetSdkVersion
@@ -3021,9 +2954,10 @@
throw e;
}
}
- final View child = (preorderedList == null)
- ? children[childIndex] : preorderedList.get(childIndex);
- ViewStructure cstructure = structure.newChild(i);
+
+ final View child = getAndVerifyPreorderedView(
+ preorderedList, children, childIndex);
+ final ViewStructure cstructure = structure.newChild(i);
child.dispatchProvideStructure(cstructure);
}
}
@@ -3031,6 +2965,21 @@
}
}
+ private static View getAndVerifyPreorderedView(ArrayList<View> preorderedList, View[] children,
+ int childIndex) {
+ final View child;
+ if (preorderedList != null) {
+ child = preorderedList.get(childIndex);
+ if (child == null) {
+ throw new RuntimeException("Invalid preorderedList contained null child at index "
+ + childIndex);
+ }
+ } else {
+ child = children[childIndex];
+ }
+ return child;
+ }
+
/** @hide */
@Override
public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
@@ -3083,6 +3032,26 @@
}
/**
+ * Counts the number of children of this View that will be sent to an accessibility service.
+ *
+ * @return The number of children an {@code AccessibilityNodeInfo} rooted at this View
+ * would have.
+ */
+ int getNumChildrenForAccessibility() {
+ int numChildrenForAccessibility = 0;
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (child.includeForAccessibility()) {
+ numChildrenForAccessibility++;
+ } else if (child instanceof ViewGroup) {
+ numChildrenForAccessibility += ((ViewGroup) child)
+ .getNumChildrenForAccessibility();
+ }
+ }
+ return numChildrenForAccessibility;
+ }
+
+ /**
* {@inheritDoc}
*
* <p>Subclasses should always call <code>super.onNestedPrePerformAccessibilityAction</code></p>
@@ -3098,9 +3067,6 @@
return false;
}
- /**
- * {@inheritDoc}
- */
@Override
void dispatchDetachedFromWindow() {
// If we still have a touch target, we are still in the process of
@@ -3152,9 +3118,6 @@
}
}
- /**
- * {@inheritDoc}
- */
@Override
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
super.dispatchSaveInstanceState(container);
@@ -3180,9 +3143,6 @@
super.dispatchSaveInstanceState(container);
}
- /**
- * {@inheritDoc}
- */
@Override
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
super.dispatchRestoreInstanceState(container);
@@ -3255,6 +3215,7 @@
return mLayoutMode == LAYOUT_MODE_OPTICAL_BOUNDS;
}
+ @Override
Insets computeOpticalInsets() {
if (isLayoutModeOptical()) {
int left = 0;
@@ -3386,9 +3347,6 @@
}
}
- /**
- * {@inheritDoc}
- */
@Override
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
@@ -3459,9 +3417,9 @@
transientIndex = -1;
}
}
- int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
- final View child = (preorderedList == null)
- ? children[childIndex] : preorderedList.get(childIndex);
+
+ final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+ final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
@@ -3514,6 +3472,7 @@
// drawChild() after the animation is over
mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;
final Runnable end = new Runnable() {
+ @Override
public void run() {
notifyAnimationListener();
}
@@ -3580,21 +3539,21 @@
* children.
*/
ArrayList<View> buildOrderedChildList() {
- final int count = mChildrenCount;
- if (count <= 1 || !hasChildWithZ()) return null;
+ final int childrenCount = mChildrenCount;
+ if (childrenCount <= 1 || !hasChildWithZ()) return null;
if (mPreSortedChildren == null) {
- mPreSortedChildren = new ArrayList<View>(count);
+ mPreSortedChildren = new ArrayList<>(childrenCount);
} else {
- mPreSortedChildren.ensureCapacity(count);
+ mPreSortedChildren.ensureCapacity(childrenCount);
}
- final boolean useCustomOrder = isChildrenDrawingOrderEnabled();
- for (int i = 0; i < mChildrenCount; i++) {
+ final boolean customOrder = isChildrenDrawingOrderEnabled();
+ for (int i = 0; i < childrenCount; i++) {
// add next child (in child order) to end of list
- int childIndex = useCustomOrder ? getChildDrawingOrder(mChildrenCount, i) : i;
- View nextChild = mChildren[childIndex];
- float currentZ = nextChild.getZ();
+ final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+ final View nextChild = mChildren[childIndex];
+ final float currentZ = nextChild.getZ();
// insert ahead of any Views with greater Z
int insertIndex = i;
@@ -3612,6 +3571,7 @@
if (mAnimationListener != null) {
final Runnable end = new Runnable() {
+ @Override
public void run() {
mAnimationListener.onAnimationEnd(mLayoutAnimationController.getAnimation());
}
@@ -3761,9 +3721,6 @@
return hasBooleanFlag(FLAG_CLIP_TO_PADDING);
}
- /**
- * {@inheritDoc}
- */
@Override
public void dispatchSetSelected(boolean selected) {
final View[] children = mChildren;
@@ -3773,9 +3730,6 @@
}
}
- /**
- * {@inheritDoc}
- */
@Override
public void dispatchSetActivated(boolean activated) {
final View[] children = mChildren;
@@ -4180,6 +4134,7 @@
* @param child the child view to add
* @param params the layout parameters to set on the child
*/
+ @Override
public void addView(View child, LayoutParams params) {
addView(child, -1, params);
}
@@ -4212,9 +4167,7 @@
addViewInner(child, index, params, false);
}
- /**
- * {@inheritDoc}
- */
+ @Override
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (!checkLayoutParams(params)) {
throw new IllegalArgumentException("Invalid LayoutParams supplied to " + this);
@@ -4225,9 +4178,6 @@
view.setLayoutParams(params);
}
- /**
- * {@inheritDoc}
- */
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p != null;
}
@@ -4574,6 +4524,7 @@
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
*/
+ @Override
public void removeView(View view) {
if (removeViewInternal(view)) {
requestLayout();
@@ -5075,6 +5026,7 @@
* Don't call or override this method. It is used for the implementation of
* the view hierarchy.
*/
+ @Override
public final void invalidateChild(View child, final Rect dirty) {
ViewParent parent = this;
@@ -5183,6 +5135,7 @@
* if this ViewGroup is already fully invalidated or if the dirty rectangle
* does not intersect with this ViewGroup's bounds.
*/
+ @Override
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||
(mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {
@@ -5476,9 +5429,7 @@
notifySubtreeAccessibilityStateChangedIfNeeded();
}
- /**
- * {@inheritDoc}
- */
+ @Override
public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) {
// It doesn't make a whole lot of sense to call this on a view that isn't attached,
// but for some simple tests it can be useful. If we don't have attach info this
@@ -5538,9 +5489,6 @@
return rectIsVisible;
}
- /**
- * {@inheritDoc}
- */
@Override
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
@@ -5554,9 +5502,6 @@
}
}
- /**
- * {@inheritDoc}
- */
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
@@ -5921,9 +5866,6 @@
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
- /**
- * {@inheritDoc}
- */
@Override
protected void debug(int depth) {
super.debug(depth);
@@ -6148,6 +6090,7 @@
}
break;
}
+ //noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
@@ -6340,9 +6283,6 @@
return mSuppressLayout;
}
- /**
- * {@inheritDoc}
- */
@Override
public boolean gatherTransparentRegion(Region region) {
// If no transparent regions requested, we are always opaque.
@@ -6366,9 +6306,7 @@
return meOpaque || noneOfTheChildrenAreTransparent;
}
- /**
- * {@inheritDoc}
- */
+ @Override
public void requestTransparentRegion(View child) {
if (child != null) {
child.mPrivateFlags |= View.PFLAG_REQUEST_TRANSPARENT_REGIONS;
@@ -6494,6 +6432,7 @@
* If {@link #addStatesFromChildren} is true, refreshes this group's
* drawable state (to include the states from its children).
*/
+ @Override
public void childDrawableStateChanged(View child) {
if ((mGroupFlags & FLAG_ADD_STATES_FROM_CHILDREN) != 0) {
refreshDrawableState();
@@ -7239,9 +7178,6 @@
a.recycle();
}
- /**
- * {@inheritDoc}
- */
public MarginLayoutParams(int width, int height) {
super(width, height);
@@ -7271,9 +7207,6 @@
this.mMarginFlags = source.mMarginFlags;
}
- /**
- * {@inheritDoc}
- */
public MarginLayoutParams(LayoutParams source) {
super(source);
@@ -7580,7 +7513,11 @@
private TouchTarget() {
}
- public static TouchTarget obtain(View child, int pointerIdBits) {
+ public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {
+ if (child == null) {
+ throw new IllegalArgumentException("child must be non-null");
+ }
+
final TouchTarget target;
synchronized (sRecycleLock) {
if (sRecycleBin == null) {
@@ -7598,6 +7535,10 @@
}
public void recycle() {
+ if (child == null) {
+ throw new IllegalStateException("already recycled once");
+ }
+
synchronized (sRecycleLock) {
if (sRecycledCount < MAX_RECYCLED) {
next = sRecycleBin;
@@ -7627,7 +7568,11 @@
private HoverTarget() {
}
- public static HoverTarget obtain(View child) {
+ public static HoverTarget obtain(@NonNull View child) {
+ if (child == null) {
+ throw new IllegalArgumentException("child must be non-null");
+ }
+
final HoverTarget target;
synchronized (sRecycleLock) {
if (sRecycleBin == null) {
@@ -7635,7 +7580,7 @@
} else {
target = sRecycleBin;
sRecycleBin = target.next;
- sRecycledCount--;
+ sRecycledCount--;
target.next = null;
}
}
@@ -7644,6 +7589,10 @@
}
public void recycle() {
+ if (child == null) {
+ throw new IllegalStateException("already recycled once");
+ }
+
synchronized (sRecycleLock) {
if (sRecycledCount < MAX_RECYCLED) {
next = sRecycleBin;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2db482d..131ad4c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -339,6 +339,7 @@
volatile Object mLocalDragState;
final PointF mDragPoint = new PointF();
final PointF mLastTouchPoint = new PointF();
+ int mLastTouchSource;
private boolean mProfileRendering;
private Choreographer.FrameCallback mRenderProfiler;
@@ -3589,9 +3590,7 @@
// tell the window manager
try {
- if (!isInLocalFocusMode()) {
- mWindowSession.setInTouchMode(inTouchMode);
- }
+ mWindowSession.setInTouchMode(inTouchMode);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -4124,6 +4123,7 @@
if (event.isTouchEvent()) {
mLastTouchPoint.x = event.getRawX();
mLastTouchPoint.y = event.getRawY();
+ mLastTouchSource = event.getSource();
}
return FORWARD;
}
@@ -5477,6 +5477,10 @@
outLocation.y = (int) mLastTouchPoint.y;
}
+ public int getLastTouchSource() {
+ return mLastTouchSource;
+ }
+
public void setDragFocus(View newDragTarget) {
if (mCurrentDragView != newDragTarget) {
mCurrentDragView = newDragTarget;
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 4a1142f..6e38b32 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -441,12 +441,6 @@
public int getCameraLensCoverState();
/**
- * Switch the keyboard layout for the given device.
- * Direction should be +1 or -1 to go to the next or previous keyboard layout.
- */
- public void switchKeyboardLayout(int deviceId, int direction);
-
- /**
* Switch the input method, to be precise, input method subtype.
*
* @param forwardDirection {@code true} to rotate in a forward direction.
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 1735e1b..1327ea1 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -647,6 +647,7 @@
private int mBooleanProperties;
private final Rect mBoundsInParent = new Rect();
private final Rect mBoundsInScreen = new Rect();
+ private int mDrawingOrderInParent;
private CharSequence mPackageName;
private CharSequence mClassName;
@@ -1892,6 +1893,37 @@
}
/**
+ * Get the drawing order of the view corresponding it this node.
+ * <p>
+ * Drawing order is determined only within the node's parent, so this index is only relative
+ * to its siblings.
+ * <p>
+ * In some cases, the drawing order is essentially simultaneous, so it is possible for two
+ * siblings to return the same value. It is also possible that values will be skipped.
+ *
+ * @return The drawing position of the view corresponding to this node relative to its siblings.
+ */
+ public int getDrawingOrder() {
+ return mDrawingOrderInParent;
+ }
+
+ /**
+ * Set the drawing order of the view corresponding it this node.
+ *
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param drawingOrderInParent
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setDrawingOrder(int drawingOrderInParent) {
+ enforceNotSealed();
+ mDrawingOrderInParent = drawingOrderInParent;
+ }
+
+ /**
* Gets the collection info if the node is a collection. A collection
* child is always a collection item.
*
@@ -2753,6 +2785,7 @@
parcel.writeInt(mTextSelectionEnd);
parcel.writeInt(mInputType);
parcel.writeInt(mLiveRegion);
+ parcel.writeInt(mDrawingOrderInParent);
if (mExtras != null) {
parcel.writeInt(1);
@@ -2850,6 +2883,7 @@
mTextSelectionEnd = other.mTextSelectionEnd;
mInputType = other.mInputType;
mLiveRegion = other.mLiveRegion;
+ mDrawingOrderInParent = other.mDrawingOrderInParent;
if (other.mExtras != null && !other.mExtras.isEmpty()) {
getExtras().putAll(other.mExtras);
}
@@ -2927,6 +2961,7 @@
mInputType = parcel.readInt();
mLiveRegion = parcel.readInt();
+ mDrawingOrderInParent = parcel.readInt();
if (parcel.readInt() == 1) {
getExtras().putAll(parcel.readBundle());
@@ -2982,6 +3017,7 @@
mBoundsInParent.set(0, 0, 0, 0);
mBoundsInScreen.set(0, 0, 0, 0);
mBooleanProperties = 0;
+ mDrawingOrderInParent = 0;
mPackageName = null;
mClassName = null;
mText = null;
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index 3ff9522..85893b0 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -16,6 +16,7 @@
package android.view.inputmethod;
+import android.annotation.Nullable;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -341,20 +342,26 @@
public Bundle extras;
/**
- * Additional context information that tells what languages are expected by the user.
+ * List of the languages that the user is supposed to switch to no matter what input method
+ * subtype is currently used. This special "hint" can be used mainly for, but not limited to,
+ * multilingual users who want IMEs to switch language context automatically.
*
- * <p><strong>IME authors:</strong> Possible use cases for IME developers would be:</p>
- * <ul>
- * <li>Automatically switching keyboard layout.</li>
- * <li>Changing language model for better typing experience.</li>
- * </ul>
+ * <p>{@code null} means that no special language "hint" is needed.</p>
*
- * <p><strong>Editor authors:</strong> Providing this context information can help IMEs to
- * improve text input experience. For example, chat applications can remember what language is
- * used in the last conversation for each chat session, and put the last used language at the
- * top of {@link #locales}.</p>
+ * <p><strong>Editor authors:</strong> Specify this only when you are confident that the user
+ * will switch to certain languages in this context no matter what input method subtype is
+ * currently selected. Otherwise, keep this {@code null}. Explicit user actions and/or
+ * preferences would be good signals to specify this special "hint", For example, a chat
+ * application may be able to put the last used language at the top of {@link #hintLocales}
+ * based on whom the user is going to talk, by remembering what language is used in the last
+ * conversation. Do not specify {@link android.widget.TextView#getTextLocales()} only because
+ * it is used for text rendering.</p>
+ *
+ * @see android.widget.TextView#setImeHintLocales(LocaleList)
+ * @see android.widget.TextView#getImeHintLocales()
*/
- public LocaleList locales = LocaleList.getEmptyLocaleList();
+ @Nullable
+ public LocaleList hintLocales = null;
/**
* Ensure that the data in this EditorInfo is compatible with an application
@@ -410,7 +417,7 @@
+ " fieldId=" + fieldId
+ " fieldName=" + fieldName);
pw.println(prefix + "extras=" + extras);
- pw.println(prefix + "locales=" + locales);
+ pw.println(prefix + "hintLocales=" + hintLocales);
}
/**
@@ -434,7 +441,11 @@
dest.writeInt(fieldId);
dest.writeString(fieldName);
dest.writeBundle(extras);
- locales.writeToParcel(dest, flags);
+ if (hintLocales != null) {
+ hintLocales.writeToParcel(dest, flags);
+ } else {
+ LocaleList.getEmptyLocaleList().writeToParcel(dest, flags);
+ }
}
/**
@@ -458,7 +469,8 @@
res.fieldId = source.readInt();
res.fieldName = source.readString();
res.extras = source.readBundle();
- res.locales = LocaleList.CREATOR.createFromParcel(source);
+ LocaleList hintLocales = LocaleList.CREATOR.createFromParcel(source);
+ res.hintLocales = hintLocales.isEmpty() ? null : hintLocales;
return res;
}
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 4fc6665..43306d0 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -191,6 +191,8 @@
.InputMethod_Subtype_label, 0))
.setSubtypeIconResId(a.getResourceId(com.android.internal.R.styleable
.InputMethod_Subtype_icon, 0))
+ .setLanguageTag(a.getString(com.android.internal.R.styleable
+ .InputMethod_Subtype_languageTag))
.setSubtypeLocale(a.getString(com.android.internal.R.styleable
.InputMethod_Subtype_imeSubtypeLocale))
.setSubtypeMode(a.getString(com.android.internal.R.styleable
diff --git a/core/java/android/webkit/ServiceWorkerClient.java b/core/java/android/webkit/ServiceWorkerClient.java
new file mode 100644
index 0000000..85de698
--- /dev/null
+++ b/core/java/android/webkit/ServiceWorkerClient.java
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+package android.webkit;
+
+
+public class ServiceWorkerClient {
+
+ /**
+ * Notify the host application of a resource request and allow the
+ * application to return the data. If the return value is null, the
+ * Service Worker will continue to load the resource as usual.
+ * Otherwise, the return response and data will be used.
+ * NOTE: This method is called on a thread other than the UI thread
+ * so clients should exercise caution when accessing private data
+ * or the view system.
+ *
+ * @param request Object containing the details of the request.
+ * @return A {@link android.webkit.WebResourceResponse} containing the
+ * response information or null if the WebView should load the
+ * resource itself.
+ * @see {@link WebViewClient#shouldInterceptRequest()}
+ */
+ public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) {
+ return null;
+ }
+}
+
diff --git a/core/java/android/webkit/ServiceWorkerController.java b/core/java/android/webkit/ServiceWorkerController.java
new file mode 100644
index 0000000..9115558
--- /dev/null
+++ b/core/java/android/webkit/ServiceWorkerController.java
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+package android.webkit;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * Manages Service Workers used by WebView.
+ */
+public abstract class ServiceWorkerController {
+
+ /**
+ * Returns the default ServiceWorkerController instance. At present there is
+ * only one ServiceWorkerController instance for all WebView instances,
+ * however this restriction may be relaxed in the future.
+ *
+ * @return The default ServiceWorkerController instance.
+ */
+ @NonNull
+ public static ServiceWorkerController getInstance() {
+ return WebViewFactory.getProvider().getServiceWorkerController();
+ }
+
+ /**
+ * Gets the settings for all service workers.
+ *
+ * @return The current ServiceWorkerWebSettings
+ */
+ @NonNull
+ public abstract ServiceWorkerWebSettings getServiceWorkerWebSettings();
+
+ /**
+ * Sets the client to capture service worker related callbacks.
+ */
+ public abstract void setServiceWorkerClient(@Nullable ServiceWorkerClient client);
+}
+
diff --git a/core/java/android/webkit/ServiceWorkerWebSettings.java b/core/java/android/webkit/ServiceWorkerWebSettings.java
new file mode 100644
index 0000000..8b104d1c
--- /dev/null
+++ b/core/java/android/webkit/ServiceWorkerWebSettings.java
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+
+package android.webkit;
+
+/**
+ * Manages settings state for all Service Workers. These settings are not tied to
+ * the lifetime of any WebView because service workers can outlive WebView instances.
+ * The settings are similar to {@link WebSettings} but only settings relevant to
+ * Service Workers are supported.
+ */
+// This is an abstract base class: concrete ServiceWorkerControllers must
+// create a class derived from this, and return an instance of it in the
+// ServiceWorkerController.getServiceWorkerWebSettings() method implementation.
+public abstract class ServiceWorkerWebSettings {
+
+ /**
+ * Overrides the way the cache is used, see {@link WebSettings#setCacheMode}.
+ *
+ * @param mode the mode to use
+ */
+ public abstract void setCacheMode(int mode);
+
+ /**
+ * Gets the current setting for overriding the cache mode.
+ *
+ * @return the current setting for overriding the cache mode
+ * @see #setCacheMode
+ */
+ public abstract int getCacheMode();
+
+ /**
+ * Enables or disables content URL access from Service Workers, see
+ * {@link WebSettings#setAllowContentAccess}.
+ */
+ public abstract void setAllowContentAccess(boolean allow);
+
+ /**
+ * Gets whether Service Workers support content URL access.
+ *
+ * @see #setAllowContentAccess
+ */
+ public abstract boolean getAllowContentAccess();
+
+ /**
+ * Enables or disables file access within Service Workers, see
+ * {@link WebSettings#setAllowFileAccess}.
+ */
+ public abstract void setAllowFileAccess(boolean allow);
+
+ /**
+ * Gets whether Service Workers support file access.
+ *
+ * @see #setAllowFileAccess
+ */
+ public abstract boolean getAllowFileAccess();
+
+ /**
+ * Sets whether the Service Workers should not load resources from the network,
+ * see {@link WebSettings#setBlockNetworkLoads}.
+ *
+ * @param flag whether the Service Workers should not load any resources from the
+ * network
+ */
+ public abstract void setBlockNetworkLoads(boolean flag);
+
+ /**
+ * Gets whether Service Workers are prohibited from loading any resources from the network.
+ *
+ * @return true if the Service Workers are not allowed to load any resources from the network
+ * @see #setBlockNetworkLoads
+ */
+ public abstract boolean getBlockNetworkLoads();
+}
+
diff --git a/core/java/android/webkit/TokenBindingService.java b/core/java/android/webkit/TokenBindingService.java
index f11ce51..f7caac7 100644
--- a/core/java/android/webkit/TokenBindingService.java
+++ b/core/java/android/webkit/TokenBindingService.java
@@ -38,6 +38,21 @@
public static final String KEY_ALGORITHM_ECDSAP256 = "ECDSAP256";
/**
+ * Provides the KeyPair information.
+ */
+ public static abstract class TokenBindingKey {
+ /**
+ * The public, private key pair.
+ */
+ public abstract KeyPair getKeyPair();
+
+ /**
+ * The algorithm that is used to generate the key pair.
+ */
+ public abstract String getAlgorithm();
+ }
+
+ /**
* Returns the default TokenBinding service instance. At present there is
* only one token binding service instance for all WebView instances,
* however this restriction may be relaxed in the future.
@@ -59,16 +74,25 @@
/**
* Retrieves the key pair for a given origin from the internal
* TokenBinding key store asynchronously.
- * Will create a key pair if one does not exist.
+ *
+ * The user can provide a list of acceptable algorithms for the retrieved
+ * key pair. If a key pair exists and it is in the list of algorithms, then
+ * the key is returned. If it is not in the list, no key is returned.
+ *
+ * If no key pair exists, WebView chooses an algorithm from the list, in
+ * the order given, to generate a key.
+ *
+ * The user can pass a null if any algorithm is acceptable.
*
* @param origin The origin for the server.
- * @param algorithm The algorithm for generating the token binding key.
+ * @param algorithm The list of algorithms. Can be null. An
+ * IllegalArgumentException is thrown if array is empty.
* @param callback The callback that will be called when key is available.
* Cannot be null.
*/
public abstract void getKey(Uri origin,
- String algorithm,
- ValueCallback<KeyPair> callback);
+ String[] algorithm,
+ ValueCallback<TokenBindingKey> callback);
/**
* Deletes specified key (for use when associated cookie is cleared).
*
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 054eafc..b04b4c0 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -295,15 +295,27 @@
Application initialApplication = AppGlobals.getInitialApplication();
Context webViewContext = null;
- Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "initialApplication.createPackageContext()");
+ Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "PackageManager.getApplicationInfo()");
try {
// Construct a package context to load the Java code into the current app.
// This is done as early as possible since by constructing a package context we
// register the WebView package as a dependency for the current application so that
// when the WebView package is updated this application will be killed.
- webViewContext = initialApplication.createPackageContext(
- sPackageInfo.packageName,
- Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
+ ApplicationInfo applicationInfo =
+ initialApplication.getPackageManager().getApplicationInfo(
+ sPackageInfo.packageName, PackageManager.GET_SHARED_LIBRARY_FILES
+ | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
+ // make sure that we fetch the current provider even if its not installed
+ // for the current user
+ | PackageManager.MATCH_UNINSTALLED_PACKAGES);
+ Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW,
+ "initialApplication.createApplicationContext");
+ try {
+ webViewContext = initialApplication.createApplicationContext(applicationInfo,
+ Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
+ }
} catch (PackageManager.NameNotFoundException e) {
throw new MissingWebViewPackageException(e);
} finally {
diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java
index 2b66a83..8359a10 100644
--- a/core/java/android/webkit/WebViewFactoryProvider.java
+++ b/core/java/android/webkit/WebViewFactoryProvider.java
@@ -111,6 +111,14 @@
TokenBindingService getTokenBindingService();
/**
+ * Gets the ServiceWorkerController instance for this WebView implementation. The
+ * implementation must return the same instance on subsequent calls.
+ *
+ * @return the ServiceWorkerController instance
+ */
+ ServiceWorkerController getServiceWorkerController();
+
+ /**
* Gets the singleton WebIconDatabase instance for this WebView implementation. The
* implementation must return the same instance on subsequent calls.
*
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 6241a4c..6c2c956 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -3203,42 +3203,74 @@
return mContextMenuInfo;
}
- /** @hide */
+ @Override
+ public boolean showContextMenu() {
+ return showContextMenuInternal(0, 0, false);
+ }
+
@Override
public boolean showContextMenu(float x, float y) {
+ return showContextMenuInternal(x, y, true);
+ }
+
+ private boolean showContextMenuInternal(float x, float y, boolean useOffsets) {
final int position = pointToPosition((int)x, (int)y);
if (position != INVALID_POSITION) {
final long id = mAdapter.getItemId(position);
View child = getChildAt(position - mFirstPosition);
if (child != null) {
mContextMenuInfo = createContextMenuInfo(child, position, id);
- return super.showContextMenuForChild(AbsListView.this, x, y);
+ if (useOffsets) {
+ return super.showContextMenuForChild(this, x, y);
+ } else {
+ return super.showContextMenuForChild(this);
+ }
}
}
- return super.showContextMenu(x, y);
+ if (useOffsets) {
+ return super.showContextMenu(x, y);
+ } else {
+ return super.showContextMenu();
+ }
}
@Override
public boolean showContextMenuForChild(View originalView) {
- final int longPressPosition = getPositionForView(originalView);
- if (longPressPosition >= 0) {
- final long longPressId = mAdapter.getItemId(longPressPosition);
- boolean handled = false;
+ return showContextMenuForChildInternal(originalView, 0, 0, false);
+ }
- if (mOnItemLongClickListener != null) {
- handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, originalView,
- longPressPosition, longPressId);
- }
- if (!handled) {
- mContextMenuInfo = createContextMenuInfo(
- getChildAt(longPressPosition - mFirstPosition),
- longPressPosition, longPressId);
+ @Override
+ public boolean showContextMenuForChild(View originalView, float x, float y) {
+ return showContextMenuForChildInternal(originalView,x, y, true);
+ }
+
+ private boolean showContextMenuForChildInternal(View originalView, float x, float y,
+ boolean useOffsets) {
+ final int longPressPosition = getPositionForView(originalView);
+ if (longPressPosition < 0) {
+ return false;
+ }
+
+ final long longPressId = mAdapter.getItemId(longPressPosition);
+ boolean handled = false;
+
+ if (mOnItemLongClickListener != null) {
+ handled = mOnItemLongClickListener.onItemLongClick(this, originalView,
+ longPressPosition, longPressId);
+ }
+
+ if (!handled) {
+ final View child = getChildAt(longPressPosition - mFirstPosition);
+ mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
+
+ if (useOffsets) {
+ handled = super.showContextMenuForChild(originalView, x, y);
+ } else {
handled = super.showContextMenuForChild(originalView);
}
-
- return handled;
}
- return false;
+
+ return handled;
}
@Override
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index df5af25..4355eb3 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -69,6 +69,7 @@
import android.text.style.TextAppearanceSpan;
import android.text.style.URLSpan;
import android.util.DisplayMetrics;
+import android.util.LocaleList;
import android.util.Log;
import android.util.SparseArray;
import android.view.ActionMode;
@@ -5299,6 +5300,7 @@
Bundle extras;
OnEditorActionListener onEditorActionListener;
boolean enterDown;
+ LocaleList imeHintLocales;
}
static class InputMethodState {
diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java
index 9ebbe36..a6ef572 100644
--- a/core/java/android/widget/Gallery.java
+++ b/core/java/android/widget/Gallery.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.annotation.NonNull;
import android.annotation.Widget;
import android.content.Context;
import android.content.res.TypedArray;
@@ -1097,15 +1098,15 @@
}
@Override
- public void onLongPress(MotionEvent e) {
-
+ public void onLongPress(@NonNull MotionEvent e) {
if (mDownTouchPosition < 0) {
return;
}
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- long id = getItemIdAtPosition(mDownTouchPosition);
- dispatchLongPress(mDownTouchView, mDownTouchPosition, id);
+
+ final long id = getItemIdAtPosition(mDownTouchPosition);
+ dispatchLongPress(mDownTouchView, mDownTouchPosition, id, e.getX(), e.getY(), true);
}
// Unused methods from GestureDetector.OnGestureListener below
@@ -1159,29 +1160,47 @@
@Override
public boolean showContextMenuForChild(View originalView) {
+ return showContextMenuForChildInternal(originalView, 0, 0, false);
+ }
+ @Override
+ public boolean showContextMenuForChild(View originalView, float x, float y) {
+ return showContextMenuForChildInternal(originalView, x, y, true);
+ }
+
+ private boolean showContextMenuForChildInternal(View originalView, float x, float y,
+ boolean useOffsets) {
final int longPressPosition = getPositionForView(originalView);
if (longPressPosition < 0) {
return false;
}
final long longPressId = mAdapter.getItemId(longPressPosition);
- return dispatchLongPress(originalView, longPressPosition, longPressId);
+ return dispatchLongPress(originalView, longPressPosition, longPressId, x, y, useOffsets);
}
@Override
public boolean showContextMenu() {
-
+ return showContextMenuInternal(0, 0, false);
+ }
+
+ @Override
+ public boolean showContextMenu(float x, float y) {
+ return showContextMenuInternal(x, y, true);
+ }
+
+ private boolean showContextMenuInternal(float x, float y, boolean useOffsets) {
if (isPressed() && mSelectedPosition >= 0) {
- int index = mSelectedPosition - mFirstPosition;
- View v = getChildAt(index);
- return dispatchLongPress(v, mSelectedPosition, mSelectedRowId);
+ final int index = mSelectedPosition - mFirstPosition;
+ final View v = getChildAt(index);
+ return dispatchLongPress(v, mSelectedPosition, mSelectedRowId, x, y, useOffsets);
}
return false;
}
- private boolean dispatchLongPress(View view, int position, long id) {
+ private boolean dispatchLongPress(View view, int position, long id, float x, float y,
+ boolean useOffsets) {
boolean handled = false;
if (mOnItemLongClickListener != null) {
@@ -1191,7 +1210,12 @@
if (!handled) {
mContextMenuInfo = new AdapterContextMenuInfo(view, position, id);
- handled = super.showContextMenuForChild(this);
+
+ if (useOffsets) {
+ handled = super.showContextMenuForChild(view, x, y);
+ } else {
+ handled = super.showContextMenuForChild(this);
+ }
}
if (handled) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index d46c6f9..c626af6 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -645,6 +645,16 @@
*/
private Editor mEditor;
+ private static final int DEVICE_PROVISIONED_UNKNOWN = 0;
+ private static final int DEVICE_PROVISIONED_NO = 1;
+ private static final int DEVICE_PROVISIONED_YES = 2;
+
+ /**
+ * Some special options such as sharing selected text should only be shown if the device
+ * is provisioned. Only check the provisioned state once for a given view instance.
+ */
+ private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN;
+
/*
* Kick-start the font cache for the zygote process (to pay the cost of
* initializing freetype for our default font only once).
@@ -4992,6 +5002,35 @@
}
/**
+ * Change "hint" locales associated with the text view, which will be reported to an IME with
+ * {@link EditorInfo#hintLocales} when it has focus.
+ *
+ * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to
+ * call {@link InputMethodManager#restartInput(View)}.</p>
+ * @param hintLocales List of the languages that the user is supposed to switch to no matter
+ * what input method subtype is currently used. Set {@code null} to clear the current "hint".
+ * @see #getImeHIntLocales()
+ * @see android.view.inputmethod.EditorInfo#hintLocales
+ */
+ public void setImeHintLocales(@Nullable LocaleList hintLocales) {
+ createEditorIfNeeded();
+ mEditor.createInputContentTypeIfNeeded();
+ mEditor.mInputContentType.imeHintLocales = hintLocales;
+ }
+
+ /**
+ * @return The current languages list "hint". {@code null} when no "hint" is available.
+ * @see #setImeHintLocales(LocaleList)
+ * @see android.view.inputmethod.EditorInfo#hintLocales
+ */
+ @Nullable
+ public LocaleList getImeHintLocales() {
+ if (mEditor == null) { return null; }
+ if (mEditor.mInputContentType == null) { return null; }
+ return mEditor.mInputContentType.imeHintLocales;
+ }
+
+ /**
* Returns the error message that was set to be displayed with
* {@link #setError}, or <code>null</code> if no error was set
* or if it the error was cleared by the widget after user input.
@@ -6411,8 +6450,10 @@
outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
outAttrs.actionId = mEditor.mInputContentType.imeActionId;
outAttrs.extras = mEditor.mInputContentType.extras;
+ outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
} else {
outAttrs.imeOptions = EditorInfo.IME_NULL;
+ outAttrs.hintLocales = null;
}
if (focusSearch(FOCUS_DOWN) != null) {
outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
@@ -6447,9 +6488,6 @@
outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
return ic;
}
- // LocaleList is designed to be immutable. This is theoretically equivalent to copy
- // the snapshot of the current text locales.
- outAttrs.locales = getTextLocales();
}
return null;
}
@@ -9613,7 +9651,17 @@
}
boolean canShare() {
- return canCopy();
+ return canCopy() && isDeviceProvisioned();
+ }
+
+ boolean isDeviceProvisioned() {
+ if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
+ mDeviceProvisionedState = Settings.Global.getInt(
+ mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
+ ? DEVICE_PROVISIONED_YES
+ : DEVICE_PROVISIONED_NO;
+ }
+ return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
}
boolean canPaste() {
diff --git a/core/java/com/android/internal/app/ConfirmUserCreationActivity.java b/core/java/com/android/internal/app/ConfirmUserCreationActivity.java
index df9cf43..53d7793 100644
--- a/core/java/com/android/internal/app/ConfirmUserCreationActivity.java
+++ b/core/java/com/android/internal/app/ConfirmUserCreationActivity.java
@@ -26,6 +26,7 @@
import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.PersistableBundle;
+import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
@@ -91,7 +92,8 @@
}
final String message;
// Check the user restrictions
- boolean cantCreateUser = mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER);
+ boolean cantCreateUser = mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER)
+ || !mUserManager.isAdminUser();
// Check the system state and user count
boolean cantCreateAnyMoreUsers = !mUserManager.canAddMoreUsers();
// Check the account existence
diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl
index 083d6c7..444dbfa 100644
--- a/core/java/com/android/internal/backup/IBackupTransport.aidl
+++ b/core/java/com/android/internal/backup/IBackupTransport.aidl
@@ -133,19 +133,16 @@
*
* @param packageInfo The identity of the application whose data is being backed up.
* This specifically includes the signature list for the package.
- * @param data The data stream that resulted from invoking the application's
+ * @param inFd Descriptor of file with data that resulted from invoking the application's
* BackupService.doBackup() method. This may be a pipe rather than a file on
* persistent media, so it may not be seekable.
- * @param wipeAllFirst When true, <i>all</i> backed-up data for the current device/account
- * will be erased prior to the storage of the data provided here. The purpose of this
- * is to provide a guarantee that no stale data exists in the restore set when the
- * device begins providing backups.
+ * @param flags Some of {@link BackupTransport#FLAG_USER_INITIATED}.
* @return one of {@link BackupConstants#TRANSPORT_OK} (OK so far),
* {@link BackupConstants#TRANSPORT_ERROR} (on network error or other failure), or
* {@link BackupConstants#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has
* become lost due to inactive expiry or some other reason and needs re-initializing)
*/
- int performBackup(in PackageInfo packageInfo, in ParcelFileDescriptor inFd);
+ int performBackup(in PackageInfo packageInfo, in ParcelFileDescriptor inFd, int flags);
/**
* Erase the give application's data from the backup destination. This clears
@@ -237,11 +234,21 @@
// full backup stuff
long requestFullBackupTime();
- int performFullBackup(in PackageInfo targetPackage, in ParcelFileDescriptor socket);
+ int performFullBackup(in PackageInfo targetPackage, in ParcelFileDescriptor socket, int flags);
int checkFullBackupSize(long size);
int sendBackupData(int numBytes);
void cancelFullBackup();
+ /**
+ * Ask the transport whether this app is eligible for backup.
+ *
+ * @param targetPackage The identity of the application.
+ * @param isFullBackup If set, transport should check if app is eligible for full data backup,
+ * otherwise to check if eligible for key-value backup.
+ * @return Whether this app is eligible for backup.
+ */
+ boolean isAppEligibleForBackup(in PackageInfo targetPackage, boolean isFullBackup);
+
// full restore stuff
/**
@@ -289,5 +296,4 @@
* operation will immediately be finished with no further attempts to restore app data.
*/
int abortFullRestore();
-
}
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index 481ab0e..c0dce22 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -47,6 +47,8 @@
sPackageFilt.addDataScheme("package");
sNonDataFilt.addAction(Intent.ACTION_UID_REMOVED);
sNonDataFilt.addAction(Intent.ACTION_USER_STOPPED);
+ sNonDataFilt.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
+ sNonDataFilt.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
sExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
sExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
}
@@ -185,7 +187,13 @@
public void onPackagesUnavailable(String[] packages) {
}
-
+
+ public void onPackagesSuspended(String[] packages) {
+ }
+
+ public void onPackagesUnsuspended(String[] packages) {
+ }
+
public static final int PACKAGE_UNCHANGED = 0;
public static final int PACKAGE_UPDATING = 1;
public static final int PACKAGE_TEMPORARY_CHANGE = 2;
@@ -396,8 +404,16 @@
onPackageDisappeared(pkgList[i], mChangeType);
}
}
+ } else if (Intent.ACTION_PACKAGES_SUSPENDED.equals(action)) {
+ String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ mSomePackagesChanged = true;
+ onPackagesSuspended(pkgList);
+ } else if (Intent.ACTION_PACKAGES_UNSUSPENDED.equals(action)) {
+ String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ mSomePackagesChanged = true;
+ onPackagesUnsuspended(pkgList);
}
-
+
if (mSomePackagesChanged) {
onSomePackagesChanged();
}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.java
new file mode 100644
index 0000000..975021e8
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.java
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+package com.android.internal.inputmethod;
+
+import android.text.TextUtils;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import java.util.Objects;
+
+public class InputMethodSubtypeHandle {
+ private final String mInputMethodId;
+ private final int mSubtypeId;
+
+ public InputMethodSubtypeHandle(InputMethodInfo info, InputMethodSubtype subtype) {
+ mInputMethodId = info.getId();
+ if (subtype != null) {
+ mSubtypeId = subtype.hashCode();
+ } else {
+ mSubtypeId = 0;
+ }
+ }
+
+ public InputMethodSubtypeHandle(String inputMethodId, int subtypeId) {
+ mInputMethodId = inputMethodId;
+ mSubtypeId = subtypeId;
+ }
+
+ public String getInputMethodId() {
+ return mInputMethodId;
+ }
+
+ public int getSubtypeId() {
+ return mSubtypeId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || !(o instanceof InputMethodSubtypeHandle)) {
+ return false;
+ }
+ InputMethodSubtypeHandle other = (InputMethodSubtypeHandle) o;
+ return TextUtils.equals(mInputMethodId, other.getInputMethodId())
+ && mSubtypeId == other.getSubtypeId();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(mInputMethodId) * 31 + mSubtypeId;
+ }
+
+ @Override
+ public String toString() {
+ return "InputMethodSubtypeHandle{mInputMethodId=" + mInputMethodId
+ + ", mSubtypeId=" + mSubtypeId + "}";
+ }
+}
diff --git a/core/java/com/android/internal/os/KernelCpuSpeedReader.java b/core/java/com/android/internal/os/KernelCpuSpeedReader.java
index 5b776ac..3f6ebb9 100644
--- a/core/java/com/android/internal/os/KernelCpuSpeedReader.java
+++ b/core/java/com/android/internal/os/KernelCpuSpeedReader.java
@@ -16,8 +16,11 @@
package com.android.internal.os;
import android.text.TextUtils;
+import android.system.OsConstants;
import android.util.Slog;
+import libcore.io.Libcore;
+
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
@@ -29,7 +32,7 @@
*
* freq time
*
- * where time is measured in 1/100 seconds.
+ * where time is measured in jiffies.
*/
public class KernelCpuSpeedReader {
private static final String TAG = "KernelCpuSpeedReader";
@@ -38,6 +41,9 @@
private final long[] mLastSpeedTimes;
private final long[] mDeltaSpeedTimes;
+ // How long a CPU jiffy is in milliseconds.
+ private final long mJiffyMillis;
+
/**
* @param cpuNumber The cpu (cpu0, cpu1, etc) whose state to read.
*/
@@ -46,6 +52,8 @@
cpuNumber);
mLastSpeedTimes = new long[numSpeedSteps];
mDeltaSpeedTimes = new long[numSpeedSteps];
+ long jiffyHz = Libcore.os.sysconf(OsConstants._SC_CLK_TCK);
+ mJiffyMillis = 1000/jiffyHz;
}
/**
@@ -62,8 +70,7 @@
splitter.setString(line);
Long.parseLong(splitter.next());
- // The proc file reports time in 1/100 sec, so convert to milliseconds.
- long time = Long.parseLong(splitter.next()) * 10;
+ long time = Long.parseLong(splitter.next()) * mJiffyMillis;
if (time < mLastSpeedTimes[speedIndex]) {
// The stats reset when the cpu hotplugged. That means that the time
// we read is offset from 0, so the time is the delta.
diff --git a/core/java/com/android/internal/os/ProcessCpuTracker.java b/core/java/com/android/internal/os/ProcessCpuTracker.java
index bf97f1f..d831902 100644
--- a/core/java/com/android/internal/os/ProcessCpuTracker.java
+++ b/core/java/com/android/internal/os/ProcessCpuTracker.java
@@ -67,10 +67,10 @@
static final int PROCESS_STAT_UTIME = 2;
static final int PROCESS_STAT_STIME = 3;
- /** Stores user time and system time in 100ths of a second. */
+ /** Stores user time and system time in jiffies. */
private final long[] mProcessStatsData = new long[4];
- /** Stores user time and system time in 100ths of a second. Used for
+ /** Stores user time and system time in jiffies. Used for
* public API to retrieve CPU use for a process. Must lock while in use. */
private final long[] mSinglePidStatsData = new long[4];
diff --git a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
index 59a1e4a..5b40bc0 100644
--- a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
+++ b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.util.Log;
import java.util.ArrayList;
@@ -30,6 +31,9 @@
*/
public class DividerSnapAlgorithm {
+ private static final int MIN_FLING_VELOCITY_DP_PER_SECOND = 400;
+ private static final int MIN_DISMISS_VELOCITY_DP_PER_SECOND = 600;
+
/**
* 3 snap targets: left/top has 16:9 ratio (for videos), 1:1, and right/bottom has 16:9 ratio
*/
@@ -46,6 +50,7 @@
private static final int SNAP_ONLY_1_1 = 2;
private final float mMinFlingVelocityPxPerSecond;
+ private final float mMinDismissVelocityPxPerSecond;
private final int mDisplayWidth;
private final int mDisplayHeight;
private final int mDividerSize;
@@ -64,10 +69,12 @@
private final SnapTarget mDismissEndTarget;
private final SnapTarget mMiddleTarget;
- public DividerSnapAlgorithm(Resources res, float minFlingVelocityPxPerSecond,
- int displayWidth, int displayHeight, int dividerSize, boolean isHorizontalDivision,
- Rect insets) {
- mMinFlingVelocityPxPerSecond = minFlingVelocityPxPerSecond;
+ public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
+ boolean isHorizontalDivision, Rect insets) {
+ mMinFlingVelocityPxPerSecond =
+ MIN_FLING_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density;
+ mMinDismissVelocityPxPerSecond =
+ MIN_DISMISS_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density;
mDividerSize = dividerSize;
mDisplayWidth = displayWidth;
mDisplayHeight = displayHeight;
@@ -85,15 +92,24 @@
}
public SnapTarget calculateSnapTarget(int position, float velocity) {
- if (Math.abs(velocity) < mMinFlingVelocityPxPerSecond) {
- return snap(position);
- }
- if (position < mFirstSplitTarget.position && velocity < 0) {
+ return calculateSnapTarget(position, velocity, true /* hardDismiss */);
+ }
+
+ /**
+ * @param position the top/left position of the divider
+ * @param velocity current dragging velocity
+ * @param hardDismiss if set, make it a bit harder to get reach the dismiss targets
+ */
+ public SnapTarget calculateSnapTarget(int position, float velocity, boolean hardDismiss) {
+ if (position < mFirstSplitTarget.position && velocity < -mMinDismissVelocityPxPerSecond) {
return mDismissStartTarget;
}
- if (position > mLastSplitTarget.position && velocity > 0) {
+ if (position > mLastSplitTarget.position && velocity > mMinDismissVelocityPxPerSecond) {
return mDismissEndTarget;
}
+ if (Math.abs(velocity) < mMinFlingVelocityPxPerSecond) {
+ return snap(position, hardDismiss);
+ }
if (velocity < 0) {
return mFirstSplitTarget;
} else {
@@ -102,7 +118,7 @@
}
public SnapTarget calculateNonDismissingSnapTarget(int position) {
- SnapTarget target = snap(position);
+ SnapTarget target = snap(position, false /* hardDismiss */);
if (target == mDismissStartTarget) {
return mFirstSplitTarget;
} else if (target == mDismissEndTarget) {
@@ -146,12 +162,16 @@
return mDismissEndTarget;
}
- private SnapTarget snap(int position) {
+ private SnapTarget snap(int position, boolean hardDismiss) {
int minIndex = -1;
- int minDistance = Integer.MAX_VALUE;
+ float minDistance = Float.MAX_VALUE;
int size = mTargets.size();
for (int i = 0; i < size; i++) {
- int distance = Math.abs(position - mTargets.get(i).position);
+ SnapTarget target = mTargets.get(i);
+ float distance = Math.abs(position - target.position);
+ if (hardDismiss) {
+ distance /= target.distanceMultiplier;
+ }
if (distance < minDistance) {
minIndex = i;
minDistance = distance;
@@ -165,7 +185,7 @@
int dividerMax = isHorizontalDivision
? mDisplayHeight
: mDisplayWidth;
- mTargets.add(new SnapTarget(-mDividerSize, SnapTarget.FLAG_DISMISS_START));
+ mTargets.add(new SnapTarget(-mDividerSize, SnapTarget.FLAG_DISMISS_START, 0.35f));
switch (mSnapMode) {
case SNAP_MODE_16_9:
addRatio16_9Targets(isHorizontalDivision);
@@ -177,7 +197,7 @@
addMiddleTarget(isHorizontalDivision);
break;
}
- mTargets.add(new SnapTarget(dividerMax, SnapTarget.FLAG_DISMISS_END));
+ mTargets.add(new SnapTarget(dividerMax, SnapTarget.FLAG_DISMISS_END, 0.35f));
}
private void addFixedDivisionTargets(boolean isHorizontalDivision) {
@@ -232,9 +252,20 @@
public final int position;
public final int flag;
+ /**
+ * Multiplier used to calculate distance to snap position. The lower this value, the harder
+ * it's to snap on this target
+ */
+ private final float distanceMultiplier;
+
public SnapTarget(int position, int flag) {
+ this(position, flag, 1f);
+ }
+
+ public SnapTarget(int position, int flag, float distanceMultiplier) {
this.position = position;
this.flag = flag;
+ this.distanceMultiplier = distanceMultiplier;
}
}
}
diff --git a/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java b/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java
index c0215a8..0449340 100644
--- a/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java
+++ b/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java
@@ -203,9 +203,8 @@
}
} catch (EOFException eof) {
// Initial state may be empty.
- } finally {
- dataInput.close();
}
+ // We explicitly don't close 'dataInput' because we must not close the backing fd.
return oldMd5Checksum;
}
@@ -219,7 +218,10 @@
dataOutput.writeInt(STATE_VERSION);
dataOutput.write(md5Checksum);
- dataOutput.close();
+
+ // We explicitly don't close 'dataOutput' because we must not close the backing fd.
+ // The FileOutputStream will not close it implicitly.
+
}
private byte[] generateMd5Checksum(byte[] data) throws NoSuchAlgorithmException {
diff --git a/core/java/org/apache/http/conn/ssl/AbstractVerifier.java b/core/java/org/apache/http/conn/ssl/AbstractVerifier.java
index 66a5121..b9349b39 100644
--- a/core/java/org/apache/http/conn/ssl/AbstractVerifier.java
+++ b/core/java/org/apache/http/conn/ssl/AbstractVerifier.java
@@ -208,8 +208,8 @@
}
public static String[] getCNs(X509Certificate cert) {
- DistinguishedNameParser dnParser =
- new DistinguishedNameParser(cert.getSubjectX500Principal());
+ AndroidDistinguishedNameParser dnParser =
+ new AndroidDistinguishedNameParser(cert.getSubjectX500Principal());
List<String> cnList = dnParser.getAllMostSpecificFirst("cn");
if(!cnList.isEmpty()) {
diff --git a/core/java/org/apache/http/conn/ssl/DistinguishedNameParser.java b/core/java/org/apache/http/conn/ssl/AndroidDistinguishedNameParser.java
similarity index 99%
rename from core/java/org/apache/http/conn/ssl/DistinguishedNameParser.java
rename to core/java/org/apache/http/conn/ssl/AndroidDistinguishedNameParser.java
index b2d0e3e..4f0b7269 100644
--- a/core/java/org/apache/http/conn/ssl/DistinguishedNameParser.java
+++ b/core/java/org/apache/http/conn/ssl/AndroidDistinguishedNameParser.java
@@ -29,7 +29,7 @@
* @hide
*/
@Deprecated
-final class DistinguishedNameParser {
+final class AndroidDistinguishedNameParser {
private final String dn;
private final int length;
private int pos;
@@ -42,7 +42,7 @@
/** distinguished name chars */
private char[] chars;
- public DistinguishedNameParser(X500Principal principal) {
+ public AndroidDistinguishedNameParser(X500Principal principal) {
// RFC2253 is used to ensure we get attributes in the reverse
// order of the underlying ASN.1 encoding, so that the most
// significant values of repeated attributes occur first.
diff --git a/core/jni/android/graphics/MinikinUtils.cpp b/core/jni/android/graphics/MinikinUtils.cpp
index 0597d3f..309d35b 100644
--- a/core/jni/android/graphics/MinikinUtils.cpp
+++ b/core/jni/android/graphics/MinikinUtils.cpp
@@ -62,6 +62,15 @@
layout->doLayout(buf, start, count, bufSize, bidiFlags, minikinStyle, minikinPaint);
}
+float MinikinUtils::measureText(const Paint* paint, int bidiFlags, TypefaceImpl* typeface,
+ const uint16_t* buf, size_t start, size_t count, size_t bufSize, float *advances) {
+ FontCollection *font;
+ MinikinPaint minikinPaint;
+ FontStyle minikinStyle = prepareMinikinPaint(&minikinPaint, &font, paint, typeface);
+ return Layout::measureText(buf, start, count, bufSize, bidiFlags, minikinStyle, minikinPaint,
+ font, advances);
+}
+
bool MinikinUtils::hasVariationSelector(TypefaceImpl* typeface, uint32_t codepoint, uint32_t vs) {
const TypefaceImpl* resolvedFace = TypefaceImpl_resolveDefault(typeface);
return resolvedFace->fFontCollection->hasVariationSelector(codepoint, vs);
diff --git a/core/jni/android/graphics/MinikinUtils.h b/core/jni/android/graphics/MinikinUtils.h
index 5bf1eec..9152539 100644
--- a/core/jni/android/graphics/MinikinUtils.h
+++ b/core/jni/android/graphics/MinikinUtils.h
@@ -40,6 +40,9 @@
TypefaceImpl* typeface, const uint16_t* buf, size_t start, size_t count,
size_t bufSize);
+ static float measureText(const Paint* paint, int bidiFlags, TypefaceImpl* typeface,
+ const uint16_t* buf, size_t start, size_t count, size_t bufSize, float *advances);
+
static bool hasVariationSelector(TypefaceImpl* typeface, uint32_t codepoint, uint32_t vs);
static float xOffsetForTextAlign(Paint* paint, const Layout& layout);
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 98f8ce3..a3214eb 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -493,16 +493,16 @@
return 0;
}
}
-
- Layout layout;
- MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, text, start, count,
- contextCount);
- if (advances != NULL) {
- std::unique_ptr<jfloat> advancesArray(new jfloat[count]);
- layout.getAdvances(advancesArray.get());
+ std::unique_ptr<jfloat[]> advancesArray;
+ if (advances) {
+ advancesArray.reset(new jfloat[count]);
+ }
+ const float advance = MinikinUtils::measureText(paint, bidiFlags, typeface, text,
+ start, count, contextCount, advancesArray.get());
+ if (advances) {
env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray.get());
}
- return layout.getAdvance();
+ return advance;
}
static jfloat getTextAdvances___CIIIII_FI(JNIEnv* env, jobject clazz, jlong paintHandle,
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index 24eb961..f9936ae 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -23,6 +23,7 @@
#include <inttypes.h>
#include <android_runtime/AndroidRuntime.h>
+#include <androidfw/DisplayEventDispatcher.h>
#include <utils/Log.h>
#include <utils/Looper.h>
#include <utils/threads.h>
@@ -48,14 +49,12 @@
} gDisplayEventReceiverClassInfo;
-class NativeDisplayEventReceiver : public LooperCallback {
+class NativeDisplayEventReceiver : public DisplayEventDispatcher {
public:
NativeDisplayEventReceiver(JNIEnv* env,
jobject receiverWeak, const sp<MessageQueue>& messageQueue);
- status_t initialize();
void dispose();
- status_t scheduleVsync();
protected:
virtual ~NativeDisplayEventReceiver();
@@ -66,15 +65,14 @@
DisplayEventReceiver mReceiver;
bool mWaitingForVsync;
- virtual int handleEvent(int receiveFd, int events, void* data);
- bool processPendingEvents(nsecs_t* outTimestamp, int32_t* id, uint32_t* outCount);
- void dispatchVsync(nsecs_t timestamp, int32_t id, uint32_t count);
- void dispatchHotplug(nsecs_t timestamp, int32_t id, bool connected);
+ virtual void dispatchVsync(nsecs_t timestamp, int32_t id, uint32_t count);
+ virtual void dispatchHotplug(nsecs_t timestamp, int32_t id, bool connected);
};
NativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env,
jobject receiverWeak, const sp<MessageQueue>& messageQueue) :
+ DisplayEventDispatcher(messageQueue->getLooper()),
mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)),
mMessageQueue(messageQueue), mWaitingForVsync(false) {
ALOGV("receiver %p ~ Initializing display event receiver.", this);
@@ -85,21 +83,6 @@
env->DeleteGlobalRef(mReceiverWeakGlobal);
}
-status_t NativeDisplayEventReceiver::initialize() {
- status_t result = mReceiver.initCheck();
- if (result) {
- ALOGW("Failed to initialize display event receiver, status=%d", result);
- return result;
- }
-
- int rc = mMessageQueue->getLooper()->addFd(mReceiver.getFd(), 0, Looper::EVENT_INPUT,
- this, NULL);
- if (rc < 0) {
- return UNKNOWN_ERROR;
- }
- return OK;
-}
-
void NativeDisplayEventReceiver::dispose() {
ALOGV("receiver %p ~ Disposing display event receiver.", this);
@@ -108,87 +91,6 @@
}
}
-status_t NativeDisplayEventReceiver::scheduleVsync() {
- if (!mWaitingForVsync) {
- ALOGV("receiver %p ~ Scheduling vsync.", this);
-
- // Drain all pending events.
- nsecs_t vsyncTimestamp;
- int32_t vsyncDisplayId;
- uint32_t vsyncCount;
- processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount);
-
- status_t status = mReceiver.requestNextVsync();
- if (status) {
- ALOGW("Failed to request next vsync, status=%d", status);
- return status;
- }
-
- mWaitingForVsync = true;
- }
- return OK;
-}
-
-int NativeDisplayEventReceiver::handleEvent(int receiveFd, int events, void* data) {
- if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
- ALOGE("Display event receiver pipe was closed or an error occurred. "
- "events=0x%x", events);
- return 0; // remove the callback
- }
-
- if (!(events & Looper::EVENT_INPUT)) {
- ALOGW("Received spurious callback for unhandled poll event. "
- "events=0x%x", events);
- return 1; // keep the callback
- }
-
- // Drain all pending events, keep the last vsync.
- nsecs_t vsyncTimestamp;
- int32_t vsyncDisplayId;
- uint32_t vsyncCount;
- if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount)) {
- ALOGV("receiver %p ~ Vsync pulse: timestamp=%" PRId64 ", id=%d, count=%d",
- this, vsyncTimestamp, vsyncDisplayId, vsyncCount);
- mWaitingForVsync = false;
- dispatchVsync(vsyncTimestamp, vsyncDisplayId, vsyncCount);
- }
-
- return 1; // keep the callback
-}
-
-bool NativeDisplayEventReceiver::processPendingEvents(
- nsecs_t* outTimestamp, int32_t* outId, uint32_t* outCount) {
- bool gotVsync = false;
- DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
- ssize_t n;
- while ((n = mReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
- ALOGV("receiver %p ~ Read %d events.", this, int(n));
- for (ssize_t i = 0; i < n; i++) {
- const DisplayEventReceiver::Event& ev = buf[i];
- switch (ev.header.type) {
- case DisplayEventReceiver::DISPLAY_EVENT_VSYNC:
- // Later vsync events will just overwrite the info from earlier
- // ones. That's fine, we only care about the most recent.
- gotVsync = true;
- *outTimestamp = ev.header.timestamp;
- *outId = ev.header.id;
- *outCount = ev.vsync.count;
- break;
- case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:
- dispatchHotplug(ev.header.timestamp, ev.header.id, ev.hotplug.connected);
- break;
- default:
- ALOGW("receiver %p ~ ignoring unknown event type %#x", this, ev.header.type);
- break;
- }
- }
- }
- if (n < 0) {
- ALOGW("Failed to get events from display event receiver, status=%d", status_t(n));
- }
- return gotVsync;
-}
-
void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, int32_t id, uint32_t count) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a65cdb8..a82bfbe 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -356,6 +356,20 @@
<protected-broadcast android:name="android.intent.action.WALLPAPER_CHANGED" />
<protected-broadcast android:name="android.app.action.DEVICE_POLICY_MANAGER_STATE_CHANGED" />
+ <protected-broadcast android:name="android.app.action.CHOOSE_PRIVATE_KEY_ALIAS" />
+ <protected-broadcast android:name="android.app.action.DEVICE_ADMIN_DISABLED" />
+ <protected-broadcast android:name="android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED" />
+ <protected-broadcast android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+ <protected-broadcast android:name="android.app.action.LOCK_TASK_ENTERING" />
+ <protected-broadcast android:name="android.app.action.LOCK_TASK_EXITING" />
+ <protected-broadcast android:name="android.app.action.NOTIFY_PENDING_SYSTEM_UPDATE" />
+ <protected-broadcast android:name="android.app.action.ACTION_PASSWORD_CHANGED" />
+ <protected-broadcast android:name="android.app.action.ACTION_PASSWORD_EXPIRING" />
+ <protected-broadcast android:name="android.app.action.ACTION_PASSWORD_FAILED" />
+ <protected-broadcast android:name="android.app.action.ACTION_PASSWORD_SUCCEEDED" />
+ <protected-broadcast android:name="com.android.server.ACTION_EXPIRED_PASSWORD_NOTIFICATION" />
+ <protected-broadcast android:name="android.intent.action.MANAGED_PROFILE_ADDED" />
+
<protected-broadcast android:name="android.bluetooth.adapter.action.BLE_STATE_CHANGED" />
<protected-broadcast android:name="android.content.jobscheduler.JOB_DELAY_EXPIRED" />
<protected-broadcast android:name="android.content.syncmanager.SYNC_ALARM" />
diff --git a/core/res/res/drawable/ic_corp_badge_no_background.xml b/core/res/res/drawable/ic_corp_badge_no_background.xml
new file mode 100644
index 0000000..b1bddfc
--- /dev/null
+++ b/core/res/res/drawable/ic_corp_badge_no_background.xml
@@ -0,0 +1,30 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M20.801,5.981L17.13,5.98l0.001,-1.471l-2.053,-2.055L8.969,2.453L6.915,4.506L6.914,5.977L3.203,5.976c-1.216,0.0 -2.189,0.983 -2.189,2.199L1.0,12.406c0.0,1.216 0.983,2.2 2.199,2.2L10.0,14.608l0.0,-1.644l0.291,0.0l3.351,0.0l0.291,0.0l0.0,1.645l6.863,0.002c1.216,0.0 2.2,-0.983 2.2,-2.199L23.0,8.181C23.0,6.965 22.017,5.981 20.801,5.981zM15.076,5.979L8.968,5.978l0.001,-1.471l6.108,0.001L15.076,5.979z"
+ android:fillColor="#FF5722"/>
+ <path
+ android:pathData="M13.911,16.646L9.978,16.646L9.978,15.48L1.673,15.48l0.0,4.105c0.0,1.216 0.959,2.2 2.175,2.2l16.13,0.004c1.216,0.0 2.203,-0.983 2.203,-2.199l0.0,-4.11l-8.27,0.0L13.910999,16.646z"
+ android:fillColor="#FF5722"/>
+ <path
+ android:pathData="M23.657,6.55 h4.72 v1.137 h-4.72z"
+ android:fillColor="#00000000"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_notification_alert.xml b/core/res/res/drawable/ic_notification_alert.xml
index d17dfc1..c8514ac 100644
--- a/core/res/res/drawable/ic_notification_alert.xml
+++ b/core/res/res/drawable/ic_notification_alert.xml
@@ -20,14 +20,14 @@
android:viewportHeight="24.0">
<path
android:pathData="M18.4,2.2L17.0,3.6c2.0,1.4 3.3,3.7 3.5,6.4l2.0,0.0C22.3,6.8 20.8,4.0 18.4,2.2z"
- android:fillColor="#231F20"/>
+ android:fillColor="#FFFFFFFF"/>
<path
android:pathData="M7.1,3.6L5.7,2.2C3.3,4.0 1.7,6.8 1.5,10.0l2.0,0.0C3.7,7.3 5.0,5.0 7.1,3.6z"
- android:fillColor="#231F20"/>
+ android:fillColor="#FFFFFFFF"/>
<path
android:pathData="M18.5,10.5c0.0,-3.1 -2.1,-5.6 -5.0,-6.3L13.5,3.5C13.5,2.7 12.8,2.0 12.0,2.0s-1.5,0.7 -1.5,1.5l0.0,0.7c-2.9,0.7 -5.0,3.2 -5.0,6.3L5.5,16.0l-2.0,2.0l0.0,1.0l17.0,0.0l0.0,-1.0l-2.0,-2.0L18.5,10.5zM13.0,16.5l-2.0,0.0l0.0,-2.0l2.0,0.0L13.0,16.5zM13.0,12.5l-2.0,0.0l0.0,-6.0l2.0,0.0L13.0,12.5z"
- android:fillColor="#231F20"/>
+ android:fillColor="#FFFFFFFF"/>
<path
android:pathData="M12.0,22.0c1.1,0.0 2.0,-0.9 2.0,-2.0L10.0,20.0C10.0,21.1 10.9,22.0 12.0,22.0z"
- android:fillColor="#231F20"/>
+ android:fillColor="#FFFFFFFF"/>
</vector>
diff --git a/core/res/res/drawable/ic_notification_block.xml b/core/res/res/drawable/ic_notification_block.xml
index 27690740..572e97b 100644
--- a/core/res/res/drawable/ic_notification_block.xml
+++ b/core/res/res/drawable/ic_notification_block.xml
@@ -20,6 +20,6 @@
android:viewportHeight="24.0">
<path
- android:fillColor="#FF000000"
+ android:fillColor="#FFFFFFFF"
android:pathData="M12.0,2.0C6.48,2.0 2.0,6.48 2.0,12.0s4.48,10.0 10.0,10.0 10.0,-4.48 10.0,-10.0S17.52,2.0 12.0,2.0zM4.0,12.0c0.0,-4.42 3.58,-8.0 8.0,-8.0 1.85,0.0 3.5,0.63 4.9,1.69L5.69,16.9C4.63,15.55 4.0,13.85 4.0,12.0zm8.0,8.0c-1.85,0.0 -3.55,-0.63 -4.9,-1.69L18.31,7.1C19.37,8.45 20.0,10.15 20.0,12.0c0.0,4.42 -3.58,8.0 -8.0,8.0z"/>
</vector>
diff --git a/core/res/res/layout-sw600dp/date_picker_dialog.xml b/core/res/res/layout-sw600dp/date_picker_dialog.xml
index f9b247f..f18485f 100644
--- a/core/res/res/layout-sw600dp/date_picker_dialog.xml
+++ b/core/res/res/layout-sw600dp/date_picker_dialog.xml
@@ -1,20 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
-**
-** Copyright 2007, 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.
-*/
+ Copyright (C) 2007 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.
-->
<DatePicker xmlns:android="http://schemas.android.com/apk/res/android"
@@ -23,5 +21,4 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:spinnersShown="true"
- android:calendarViewShown="true"
- />
+ android:calendarViewShown="true" />
diff --git a/core/res/res/layout/date_picker_dialog.xml b/core/res/res/layout/date_picker_dialog.xml
index db8f311..64ac1b9 100644
--- a/core/res/res/layout/date_picker_dialog.xml
+++ b/core/res/res/layout/date_picker_dialog.xml
@@ -1,20 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
-**
-** Copyright 2007, 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.
-*/
+ Copyright (C) 2007 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.
-->
<DatePicker xmlns:android="http://schemas.android.com/apk/res/android"
@@ -23,5 +21,4 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:spinnersShown="true"
- android:calendarViewShown="false"
- />
+ android:calendarViewShown="false" />
diff --git a/core/res/res/layout/date_picker_header_material.xml b/core/res/res/layout/date_picker_header_material.xml
index 821b588..cfc6d0d 100644
--- a/core/res/res/layout/date_picker_header_material.xml
+++ b/core/res/res/layout/date_picker_header_material.xml
@@ -19,35 +19,46 @@
android:id="@+id/date_picker_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingStart="?attr/dialogPreferredPadding"
- android:paddingEnd="?attr/dialogPreferredPadding"
- android:paddingTop="16dp"
- android:paddingBottom="18dp"
- android:orientation="vertical"
- android:clipToPadding="false"
- android:clipChildren="false">
+ android:orientation="vertical">
- <TextView
- android:id="@+id/date_picker_header_year"
- android:layout_width="wrap_content"
+ <ViewStub
+ android:id="@id/topPanel"
+ android:layout="@layout/alert_dialog_title_material"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:focusable="true"
- android:layout_marginStart="-8dp"
- android:layout_marginEnd="-8dp"
- android:layout_marginTop="-8dp"
- android:layout_marginBottom="-8dp"
- android:padding="8dp"
- android:background="?attr/selectableItemBackground"
- android:textAppearance="@style/TextAppearance.Material.DatePicker.YearLabel"
- android:nextFocusForward="@+id/prev" />
+ android:paddingStart="?attr/dialogPreferredPadding"
+ android:paddingEnd="?attr/dialogPreferredPadding"
+ android:paddingTop="16dp"
+ android:paddingBottom="18dp"
+ android:orientation="vertical"
+ android:clipToPadding="false"
+ android:clipChildren="false">
- <TextView
- android:id="@+id/date_picker_header_date"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.Material.DatePicker.DateLabel"
- android:gravity="start"
- android:maxLines="2"
- android:ellipsize="none" />
+ <TextView
+ android:id="@+id/date_picker_header_year"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:focusable="true"
+ android:layout_marginStart="-8dp"
+ android:layout_marginEnd="-8dp"
+ android:layout_marginTop="-8dp"
+ android:layout_marginBottom="-8dp"
+ android:padding="8dp"
+ android:background="?attr/selectableItemBackground"
+ android:textAppearance="@style/TextAppearance.Material.DatePicker.YearLabel"
+ android:nextFocusForward="@+id/prev" />
+ <TextView
+ android:id="@+id/date_picker_header_date"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.Material.DatePicker.DateLabel"
+ android:gravity="start"
+ android:maxLines="2"
+ android:ellipsize="none" />
+ </LinearLayout>
</LinearLayout>
diff --git a/core/res/res/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml
index 2a89faa..2a4aa967 100644
--- a/core/res/res/layout/notification_material_action_list.xml
+++ b/core/res/res/layout/notification_material_action_list.xml
@@ -26,6 +26,7 @@
android:layout_height="56dp"
android:paddingEnd="4dp"
android:orientation="horizontal"
+ android:gravity="center_vertical"
android:visibility="gone"
android:background="#ffeeeeee"
>
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index e45f52b..163db30 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -127,5 +127,15 @@
android:paddingTop="1dp"
android:visibility="gone"
/>
+ <ImageView android:id="@+id/profile_badge"
+ android:layout_width="@dimen/notification_badge_size"
+ android:layout_height="@dimen/notification_badge_size"
+ android:layout_gravity="center"
+ android:layout_marginStart="4dp"
+ android:paddingTop="1dp"
+ android:scaleType="fitCenter"
+ android:visibility="gone"
+ android:contentDescription="@string/notification_work_profile_content_description"
+ />
</NotificationHeaderView>
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index fdbbbd6..a37bfa9 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -34,7 +34,7 @@
android:orientation="vertical"
>
<include layout="@layout/notification_template_part_line1" />
- <include layout="@layout/notification_template_part_line3" />
+ <include layout="@layout/notification_template_text" />
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
diff --git a/core/res/res/layout/notification_template_material_big_base.xml b/core/res/res/layout/notification_template_material_big_base.xml
index dfd72c0..f302087 100644
--- a/core/res/res/layout/notification_template_material_big_base.xml
+++ b/core/res/res/layout/notification_template_material_big_base.xml
@@ -42,7 +42,7 @@
android:orientation="vertical"
>
<include layout="@layout/notification_template_part_line1" />
- <include layout="@layout/notification_template_part_line3" />
+ <include layout="@layout/notification_template_text" />
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
diff --git a/core/res/res/layout/notification_template_material_big_media.xml b/core/res/res/layout/notification_template_material_big_media.xml
index c3db7c5..e8ff186 100644
--- a/core/res/res/layout/notification_template_material_big_media.xml
+++ b/core/res/res/layout/notification_template_material_big_media.xml
@@ -40,7 +40,7 @@
android:orientation="vertical"
>
<include layout="@layout/notification_template_part_line1" />
- <include layout="@layout/notification_template_part_line3" />
+ <include layout="@layout/notification_template_text" />
</LinearLayout>
<LinearLayout
android:id="@+id/media_actions"
diff --git a/core/res/res/layout/notification_template_material_big_picture.xml b/core/res/res/layout/notification_template_material_big_picture.xml
index 5c07b9b..50aec6b 100644
--- a/core/res/res/layout/notification_template_material_big_picture.xml
+++ b/core/res/res/layout/notification_template_material_big_picture.xml
@@ -40,7 +40,7 @@
android:orientation="vertical">
<include layout="@layout/notification_template_part_line1"/>
<include layout="@layout/notification_template_progress"/>
- <include layout="@layout/notification_template_part_line3"/>
+ <include layout="@layout/notification_template_text"/>
</LinearLayout>
<ImageView
android:id="@+id/big_picture"
diff --git a/core/res/res/layout/notification_template_material_big_text.xml b/core/res/res/layout/notification_template_material_big_text.xml
index ebaffc3..9a4b28c 100644
--- a/core/res/res/layout/notification_template_material_big_text.xml
+++ b/core/res/res/layout/notification_template_material_big_text.xml
@@ -36,33 +36,17 @@
>
<include layout="@layout/notification_template_part_line1" />
<include layout="@layout/notification_template_progress" />
- <LinearLayout
+ <com.android.internal.widget.ImageFloatingTextView android:id="@+id/big_text"
android:layout_width="match_parent"
android:layout_height="0dp"
- android:paddingBottom="@dimen/notification_content_margin_bottom"
- android:orientation="horizontal"
- android:gravity="top"
- android:layout_weight="1"
android:layout_marginTop="1.5dp"
- >
- <com.android.internal.widget.ImageFloatingTextView android:id="@+id/big_text"
- android:textAppearance="@style/TextAppearance.Material.Notification"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:singleLine="false"
- android:visibility="gone"
- />
- <ImageView android:id="@+id/profile_badge_large_template"
- android:layout_width="@dimen/notification_badge_size"
- android:layout_height="@dimen/notification_badge_size"
- android:layout_weight="0"
- android:layout_marginStart="4dp"
- android:scaleType="fitCenter"
- android:visibility="gone"
- android:contentDescription="@string/notification_work_profile_content_description"
- />
- </LinearLayout>
+ android:paddingBottom="@dimen/notification_content_margin_bottom"
+ android:textAppearance="@style/TextAppearance.Material.Notification"
+ android:singleLine="false"
+ android:layout_weight="1"
+ android:gravity="top"
+ android:visibility="gone"
+ />
<ViewStub android:layout="@layout/notification_material_reply_text"
android:id="@+id/notification_material_reply_container"
android:layout_width="match_parent"
diff --git a/core/res/res/layout/notification_template_material_inbox.xml b/core/res/res/layout/notification_template_material_inbox.xml
index 14bf899..86902db 100644
--- a/core/res/res/layout/notification_template_material_inbox.xml
+++ b/core/res/res/layout/notification_template_material_inbox.xml
@@ -41,34 +41,15 @@
android:layout_width="match_parent"
android:layout_height="15dp"
android:layout_marginTop="4dp"/>
-
- <!-- We can't have another vertical linear layout here with weight != 0 so this forces us to
- put the badge on the first line. -->
- <LinearLayout
+ <TextView android:id="@+id/inbox_text0"
+ android:textAppearance="@style/TextAppearance.Material.Notification"
android:layout_width="match_parent"
- android:layout_weight="1"
android:layout_height="0dp"
- android:orientation="horizontal"
- >
- <TextView android:id="@+id/inbox_text0"
- android:textAppearance="@style/TextAppearance.Material.Notification"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:ellipsize="end"
- android:visibility="gone"
- android:layout_weight="1"
- />
- <ImageView android:id="@+id/profile_badge_large_template"
- android:layout_width="@dimen/notification_badge_size"
- android:layout_height="@dimen/notification_badge_size"
- android:layout_weight="0"
- android:layout_marginStart="4dp"
- android:scaleType="fitCenter"
- android:visibility="gone"
- android:contentDescription="@string/notification_work_profile_content_description"
- />
- </LinearLayout>
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:visibility="gone"
+ android:layout_weight="1"
+ />
<TextView android:id="@+id/inbox_text1"
android:textAppearance="@style/TextAppearance.Material.Notification"
android:layout_width="match_parent"
diff --git a/core/res/res/layout/notification_template_material_media.xml b/core/res/res/layout/notification_template_material_media.xml
index f0ced5f..9fcd356 100644
--- a/core/res/res/layout/notification_template_material_media.xml
+++ b/core/res/res/layout/notification_template_material_media.xml
@@ -48,7 +48,7 @@
>
<include layout="@layout/notification_template_part_line1"/>
<include layout="@layout/notification_template_progress"/>
- <include layout="@layout/notification_template_part_line3"/>
+ <include layout="@layout/notification_template_text"/>
</LinearLayout>
<LinearLayout
android:id="@+id/media_actions"
diff --git a/core/res/res/layout/notification_template_part_line3.xml b/core/res/res/layout/notification_template_part_line3.xml
deleted file mode 100644
index dc47a48..0000000
--- a/core/res/res/layout/notification_template_part_line3.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2014 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/line3"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:gravity="top"
- >
- <TextView android:id="@+id/text"
- android:textAppearance="@style/TextAppearance.Material.Notification"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_gravity="top"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal"
- android:layout_marginTop="1.5dp"
- />
- <ImageView android:id="@+id/profile_badge_line3"
- android:layout_width="@dimen/notification_badge_size"
- android:layout_height="@dimen/notification_badge_size"
- android:layout_gravity="center"
- android:layout_weight="0"
- android:layout_marginStart="4dp"
- android:scaleType="fitCenter"
- android:visibility="gone"
- android:contentDescription="@string/notification_work_profile_content_description"
- />
-</LinearLayout>
diff --git a/core/res/res/layout/notification_template_text.xml b/core/res/res/layout/notification_template_text.xml
new file mode 100644
index 0000000..38470cd
--- /dev/null
+++ b/core/res/res/layout/notification_template_text.xml
@@ -0,0 +1,28 @@
+<?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
+ -->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:layout_marginTop="1.5dp"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
+ android:gravity="top"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.Material.Notification"
+ />
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index e0f9eca..80c6060 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -7512,6 +7512,10 @@
<!-- Flag indicating whether this voice interaction service is capable of being launched
from the keyguard. -->
<attr name="supportsLaunchVoiceAssistFromKeyguard" format="boolean" />
+ <!-- Flag indicating whether this voice interaction service can handle local voice
+ interaction requests from an Activity. This flag is new in
+ {@link android.os.Build.VERSION_CODES#N} and not used in previous versions. -->
+ <attr name="supportsLocalInteraction" format="boolean" />
</declare-styleable>
<!-- Use <code>voice-enrollment-application</code>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index b2482cd..f92e7f0 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -275,7 +275,7 @@
<dimen name="notification_large_icon_circle_padding">11dp</dimen>
<!-- Size of the profile badge for notifications -->
- <dimen name="notification_badge_size">16dp</dimen>
+ <dimen name="notification_badge_size">12dp</dimen>
<!-- Keyguard dimensions -->
<!-- TEMP -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index c883b1f8..57132ea 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2690,6 +2690,7 @@
<public type="attr" name="tickMarkTintMode" />
<public type="attr" name="canPerformGestures" />
<public type="attr" name="externalService" />
+ <public type="attr" name="supportsLocalInteraction" />
<public type="style" name="Theme.Material.Light.DialogWhenLarge.DarkActionBar" />
<public type="style" name="Widget.Material.SeekBar.Discrete" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index eebdd613..d7f7b10 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -492,17 +492,17 @@
current device state, to send as an e-mail message. It will take a little
time from starting the bug report until it is ready to be sent; please be
patient.</string>
- <!-- Title in the bugreport dialog for the interactive workflow. [CHAR LIMIT=20] -->
+ <!-- Title in the bugreport dialog for the interactive workflow. Should fit in one line. [CHAR LIMIT=30] -->
<string name="bugreport_option_interactive_title">Interactive report</string>
<!-- Summary in the bugreport dialog for the interactive workflow. [CHAR LIMIT=NONE] -->
<string name="bugreport_option_interactive_summary">Use this under most circumstances.
It allows you to track progress of the report and enter more details about the problem.
It might omit some less-used sections that take a long time to report.</string>
- <!-- Title in the bugreport dialog for the full workflow. [CHAR LIMIT=20] -->
+ <!-- Title in the bugreport dialog for the full workflow. Should fit in one line. [CHAR LIMIT=30] -->
<string name="bugreport_option_full_title">Full report</string>
- <!-- Summary in the bugreport dialog for the full workflow. [CHAR LIMIT=20] -->
- <string name="bugreport_option_full_summary">Use this option for minimal interference when
- your device is unresponsive or too slow, or when you need all sections.
+ <!-- Summary in the bugreport dialog for the full workflow. [CHAR LIMIT=NONE] -->
+ <string name="bugreport_option_full_summary">Use this option for minimal system interference when
+ your device is unresponsive or too slow, or when you need all report sections.
Does not take a screenshot or allow you to enter more details.</string>
<!-- Toast message informing user in how many seconds a bugreport screenshot will be taken -->
<plurals name="bugreport_countdown">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9023dd4..a5d0020 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -86,7 +86,6 @@
<java-symbol type="id" name="left_icon" />
<java-symbol type="id" name="leftSpacer" />
<java-symbol type="id" name="line1" />
- <java-symbol type="id" name="line3" />
<java-symbol type="id" name="list_footer" />
<java-symbol type="id" name="list_item" />
<java-symbol type="id" name="listContainer" />
@@ -209,8 +208,7 @@
<java-symbol type="id" name="pin_confirm_text" />
<java-symbol type="id" name="pin_error_message" />
<java-symbol type="id" name="timePickerLayout" />
- <java-symbol type="id" name="profile_badge_large_template" />
- <java-symbol type="id" name="profile_badge_line3" />
+ <java-symbol type="id" name="profile_badge" />
<java-symbol type="id" name="transitionPosition" />
<java-symbol type="id" name="selection_start_handle" />
<java-symbol type="id" name="selection_end_handle" />
@@ -1267,6 +1265,7 @@
<java-symbol type="drawable" name="ic_corp_badge" />
<java-symbol type="drawable" name="ic_corp_badge_off" />
<java-symbol type="drawable" name="ic_corp_icon_badge" />
+ <java-symbol type="drawable" name="ic_corp_badge_no_background" />
<java-symbol type="drawable" name="ic_corp_icon" />
<java-symbol type="drawable" name="ic_corp_statusbar_icon" />
<java-symbol type="drawable" name="emulator_circular_window_overlay" />
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 77d2ada..5a08887 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -78,10 +78,10 @@
<shortcode country="cz" premium="9\\d{6,7}" free="116\\d{3}" />
<!-- Germany: 4-5 digits plus 1232xxx (premium codes from http://www.vodafone.de/infofaxe/537.pdf and http://premiumdienste.eplus.de/pdf/kodex.pdf), plus EU. To keep the premium regex from being too large, it only includes payment processors that have been used by SMS malware, with the regular pattern matching the other premium short codes. -->
- <shortcode country="de" pattern="\\d{4,5}|1232\\d{3}" premium="11(?:111|833)|1232(?:013|021|060|075|286|358)|118(?:44|80|86)|20[25]00|220(?:21|22|88|99)|221(?:14|21)|223(?:44|53|77)|224[13]0|225(?:20|59|90)|226(?:06|10|20|26|30|40|56|70)|227(?:07|33|39|66|76|78|79|88|99)|228(?:08|11|66|77)|23300|30030|3[12347]000|330(?:33|55|66)|33(?:233|331|366|533)|34(?:34|567)|37000|40(?:040|123|444|[3568]00)|41(?:010|414)|44(?:000|044|344|44[24]|544)|50005|50100|50123|50555|51000|52(?:255|783)|54(?:100|2542)|55(?:077|[24]00|222|333|55|[12369]55)|56(?:789|886)|60800|6[13]000|66(?:[12348]66|566|766|777|88|999)|68888|70(?:07|123|777)|76766|77(?:007|070|222|444|[567]77)|80(?:008|123|888)|82(?:002|[378]00|323|444|472|474|488|727)|83(?:005|[169]00|333|830)|84(?:141|300|32[34]|343|488|499|777|888)|85888|86(?:188|566|640|644|650|677|868|888)|870[24]9|871(?:23|[49]9)|872(?:1[0-8]|49|99)|87499|875(?:49|55|99)|876(?:0[1367]|1[1245678]|54|99)|877(?:00|99)|878(?:15|25|3[567]|8[12])|87999|880(?:08|44|55|77|99)|88688|888(?:03|10|8|89)|8899|90(?:009|999)|99999" free="116\\d{3}|81214|81215|47529" />
+ <shortcode country="de" pattern="\\d{4,5}|1232\\d{3}" premium="11(?:111|833)|1232(?:013|021|060|075|286|358)|118(?:44|80|86)|20[25]00|220(?:21|22|88|99)|221(?:14|21)|223(?:44|53|77)|224[13]0|225(?:20|59|90)|226(?:06|10|20|26|30|40|56|70)|227(?:07|33|39|66|76|78|79|88|99)|228(?:08|11|66|77)|23300|30030|3[12347]000|330(?:33|55|66)|33(?:233|331|366|533)|34(?:34|567)|37000|40(?:040|123|444|[3568]00)|41(?:010|414)|44(?:000|044|344|44[24]|544)|50005|50100|50123|50555|51000|52(?:255|783)|54(?:100|2542)|55(?:077|[24]00|222|333|55|[12369]55)|56(?:789|886)|60800|6[13]000|66(?:[12348]66|566|766|777|88|999)|68888|70(?:07|123|777)|76766|77(?:007|070|222|444|[567]77)|80(?:008|123|888)|82(?:002|[378]00|323|444|472|474|488|727)|83(?:005|[169]00|333|830)|84(?:141|300|32[34]|343|488|499|777|888)|85888|86(?:188|566|640|644|650|677|868|888)|870[24]9|871(?:23|[49]9)|872(?:1[0-8]|49|99)|87499|875(?:49|55|99)|876(?:0[1367]|1[1245678]|54|99)|877(?:00|99)|878(?:15|25|3[567]|8[12])|87999|880(?:08|44|55|77|99)|88688|888(?:03|10|8|89)|8899|90(?:009|999)|99999" free="116\\d{3}|81214|81215|47529|70296" />
<!-- Denmark: see http://iprs.webspacecommerce.com/Denmark-Premium-Rate-Numbers -->
- <shortcode country="dk" pattern="\\d{4,5}" premium="1\\d{3}" free="116\\d{3}" />
+ <shortcode country="dk" pattern="\\d{4,5}" premium="1\\d{3}" free="116\\d{3}|4665" />
<!-- Estonia: short codes 3-5 digits starting with 1, plus premium 7 digit numbers starting with 90, plus EU.
http://www.tja.ee/public/documents/Elektrooniline_side/Oigusaktid/ENG/Estonian_Numbering_Plan_annex_06_09_2010.mht -->
@@ -102,7 +102,7 @@
<!-- United Kingdom (Great Britain): 4-6 digits, common codes [5-8]xxxx, plus EU:
http://www.short-codes.com/media/Co-regulatoryCodeofPracticeforcommonshortcodes170206.pdf,
visual voicemail code for EE: 887 -->
- <shortcode country="gb" pattern="\\d{4,6}" premium="[5-8]\\d{4}" free="116\\d{3}|887" />
+ <shortcode country="gb" pattern="\\d{4,6}" premium="[5-8]\\d{4}" free="116\\d{3}|887|83669|34664|40406" />
<!-- Georgia: 4 digits, known premium codes listed -->
<shortcode country="ge" pattern="\\d{4}" premium="801[234]|888[239]" />
@@ -114,6 +114,9 @@
http://clients.txtnation.com/entries/209633-hungary-premium-sms-short-code-regulations -->
<shortcode country="hu" pattern="[01](?:\\d{3}|\\d{9})" premium="0691227910|1784" free="116\\d{3}" />
+ <!-- Indonesia -->
+ <shortcode country="id" free="99477|6006|46645" />
+
<!-- Ireland: 5 digits, 5xxxx (50xxx=free, 5[12]xxx=standard), plus EU:
http://www.comreg.ie/_fileupload/publications/ComReg1117.pdf -->
<shortcode country="ie" pattern="\\d{5}" premium="5[3-9]\\d{3}" free="50\\d{3}|116\\d{3}" standard="5[12]\\d{3}" />
@@ -123,7 +126,7 @@
<!-- Italy: 5 digits (premium=4xxxx), plus EU:
http://clients.txtnation.com/attachments/token/di5kfblvubttvlw/?name=Italy_CASP_EN.pdf -->
- <shortcode country="it" pattern="\\d{5}" premium="4\\d{4}" free="116\\d{3}" />
+ <shortcode country="it" pattern="\\d{5}" premium="4\\d{4}" free="116\\d{3}|4112503" />
<!-- Japan: 8083 used by SOFTBANK_DCB_2 -->
<shortcode country="jp" free="8083" />
@@ -137,6 +140,9 @@
<!-- Kazakhstan: 4 digits, known premium codes listed: http://smscoin.net/info/pricing-kazakhstan/ -->
<shortcode country="kz" pattern="\\d{4}" premium="335[02]|4161|444[469]|77[2359]0|8444|919[3-5]|968[2-5]" />
+ <!-- Kuwait -->
+ <shortcode country="kw" free="1378|50420|94006" />
+
<!-- Lithuania: 3-5 digits, known premium codes listed, plus EU -->
<shortcode country="lt" pattern="\\d{3,5}" premium="13[89]1|1394|16[34]5" free="116\\d{3}" />
@@ -148,13 +154,13 @@
<shortcode country="lv" pattern="\\d{4}" premium="18(?:19|63|7[1-4])" free="116\\d{3}" />
<!-- Mexico: 4-5 digits (not confirmed), known premium codes listed -->
- <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" />
+ <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="46645" />
<!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf -->
- <shortcode country="my" pattern="\\d{5}" premium="32298|33776" />
+ <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099" />
<!-- The Netherlands, 4 digits, known premium codes listed, plus EU -->
- <shortcode country="nl" pattern="\\d{4}" premium="4466|5040" free="116\\d{3}" />
+ <shortcode country="nl" pattern="\\d{4}" premium="4466|5040" free="116\\d{3}|2223" />
<!-- Norway: 4-5 digits (not confirmed), known premium codes listed -->
<shortcode country="no" pattern="\\d{4,5}" premium="2201|222[67]" />
@@ -163,10 +169,10 @@
<shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" />
<!-- Philippines -->
- <shortcode country="ph" free="2147" />
+ <shortcode country="ph" free="2147|5495|5496" />
<!-- Poland: 4-5 digits (not confirmed), known premium codes listed, plus EU -->
- <shortcode country="pl" pattern="\\d{4,5}" premium="74240|79(?:10|866)|92525" free="116\\d{3}|8012" />
+ <shortcode country="pl" pattern="\\d{4,5}" premium="74240|79(?:10|866)|92525" free="116\\d{3}|8012|80921" />
<!-- Portugal: 5 digits, plus EU:
http://clients.txtnation.com/entries/158326-portugal-premium-sms-short-code-regulations -->
@@ -210,4 +216,7 @@
visual voicemail code for T-Mobile: 122 -->
<shortcode country="us" pattern="\\d{5,6}" premium="20433|21(?:344|472)|22715|23(?:333|847)|24(?:15|28)0|25209|27(?:449|606|663)|28498|305(?:00|83)|32(?:340|941)|33(?:166|786|849)|34746|35(?:182|564)|37975|38(?:135|146|254)|41(?:366|463)|42335|43(?:355|500)|44(?:578|711|811)|45814|46(?:157|173|327|654)|46666|47553|48(?:221|277|669)|50(?:844|920)|51(?:062|368)|52944|54(?:723|892)|55928|56483|57370|59(?:182|187|252|342)|60339|61(?:266|982)|62478|64(?:219|898)|65(?:108|500)|69(?:208|388)|70877|71851|72(?:078|087|465)|73(?:288|588|882|909|997)|74(?:034|332|815)|76426|79213|81946|83177|84(?:103|685)|85797|86(?:234|236|666)|89616|90(?:715|842|938)|91(?:362|958)|94719|95297|96(?:040|666|835|969)|97(?:142|294|688)|99(?:689|796|807)" free="122|87902" />
+ <!-- Vietnam -->
+ <shortcode country="vn" free="5001" />
+
</shortcodes>
diff --git a/core/tests/benchmarks/src/android/text/SpannableStringBuilderBenchmark.java b/core/tests/benchmarks/src/android/text/SpannableStringBuilderBenchmark.java
new file mode 100644
index 0000000..23f8810
--- /dev/null
+++ b/core/tests/benchmarks/src/android/text/SpannableStringBuilderBenchmark.java
@@ -0,0 +1,138 @@
+/*
+ * 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.
+ */
+
+package android.text;
+
+import com.google.caliper.AfterExperiment;
+import com.google.caliper.BeforeExperiment;
+import com.google.caliper.Benchmark;
+import com.google.caliper.Param;
+
+public class SpannableStringBuilderBenchmark {
+
+ @Param({"android.text.style.ImageSpan",
+ "android.text.style.ParagraphStyle",
+ "android.text.style.CharacterStyle",
+ "java.lang.Object"})
+ private String paramType;
+
+ @Param({"1", "4", "16"})
+ private String paramStringMult;
+
+ private Class clazz;
+ private SpannableStringBuilder builder;
+
+ @BeforeExperiment
+ protected void setUp() throws Exception {
+ clazz = Class.forName(paramType);
+ int strSize = Integer.valueOf(paramStringMult);
+ StringBuilder strBuilder = new StringBuilder();
+ for (int i = 0; i < strSize; i++) {
+ strBuilder.append(TEST_STRING);
+ }
+ builder = new SpannableStringBuilder(Html.fromHtml(strBuilder.toString()));
+ }
+
+ @AfterExperiment
+ protected void tearDown() {
+ builder.clear();
+ builder = null;
+ }
+
+ @Benchmark
+ public void timeGetSpans(int reps) throws Exception {
+ for (int i = 0; i < reps; i++) {
+ builder.getSpans(0, builder.length(), clazz);
+ }
+ }
+
+ //contains 0 ImageSpans, 2 ParagraphSpans, 53 CharacterStyleSpans
+ public static String TEST_STRING =
+ "<p><span><a href=\"http://android.com\">some link</a></span></p>\n" +
+ "<h1 style=\"margin: 0.0px 0.0px 10.0px 0.0px; line-height: 64.0px; font: 62.0px " +
+ "'Helvetica Neue Light'; color: #000000; \"><span>some title</span></h1>\n" +
+ "<p><span>by <a href=\"http://android.com\"><span>some name</span></a>\n" +
+ " <a href=\"https://android.com\"><span>some text</span></a></span></p>\n" +
+ "<p><span>some date</span></p>\n" +
+ "<table cellspacing=\"0\" cellpadding=\"0\">\n" +
+ " <tbody><tr><td valign=\"bottom\">\n" +
+ " <p><span><blockquote>a paragraph</blockquote></span><br></p>\n" +
+ " </tbody></tr></td>\n" +
+ "</table>\n" +
+ "<h2 style=\"margin: 0.0px 0.0px 0.0px 0.0px; line-height: 38.0px; font: 26.0px " +
+ "'Helvetica Neue Light'; color: #262626; -webkit-text-stroke: #262626\">" +
+ "<span>some header two</span></h2>\n" +
+ "<p><span>Lorem ipsum dolor concludaturque. </span></p>\n" +
+ "<p><span></span><br></p>\n" +
+ "<p><span>Vix te doctus</span></p>\n" +
+ "<p><span><b>Error mel</b></span><span>, est ei. <a href=\"http://andorid.com\">" +
+ "<span>asda</span></a> ullamcorper eam.</span></p>\n" +
+ "<p><span>adversarium <a href=\"http://android.com\"><span>efficiantur</span></a>, " +
+ "mea te.</span></p>\n" +
+ "<p><span></span><br></p>\n" +
+ "<h1>Testing display of HTML elements</h1>\n" +
+ "<h2>2nd level heading</h2>\n" +
+ "<p>test paragraph.</p>\n" +
+ "<h3>3rd level heading</h3>\n" +
+ "<p>test paragraph.</p>\n" +
+ "<h4>4th level heading</h4>\n" +
+ "<p>test paragraph.</p>\n" +
+ "<h5>5th level heading</h5>\n" +
+ "<p>test paragraph.</p>\n" +
+ "<h6>6th level heading</h6>\n" +
+ "<p>test paragraph.</p>\n" +
+ "<h2>level elements</h2>\n" +
+ "<p>a normap paragraph(<code>p</code> element).\n" +
+ " with some <strong>strong</strong>.</p>\n" +
+ "<div>This is a <code>div</code> element. </div>\n" +
+ "<blockquote><p>This is a block quotation with some <em>style</em></p></blockquote>\n" +
+ "<address>an address element</address>\n" +
+ "<h2>Text-level markup</h2>\n" +
+ "<ul>\n" +
+ " <li> <abbr title=\"Cascading Style Sheets\">CSS</abbr> (an abbreviation;\n" +
+ " <code>abbr</code> markup used)\n" +
+ " <li> <acronym title=\"radio detecting and ranging\">radar</acronym>\n" +
+ " <li> <b>bolded</b>\n" +
+ " <li> <big>big thing</big>\n" +
+ " <li> <font size=6>large size</font>\n" +
+ " <li> <font face=Courier>Courier font</font>\n" +
+ " <li> <font color=red>red text</font>\n" +
+ " <li> <cite>Origin of Species</cite>\n" +
+ " <li> <code>a[i] = b[i] + c[i);</code>\n" +
+ " <li> some <del>deleted</del> text\n" +
+ " <li> an <dfn>octet</dfn> is an\n" +
+ " <li> this is <em>very</em> simple\n" +
+ " <li> <i lang=\"la\">Homo sapiens</i>\n" +
+ " <li> some <ins>inserted</ins> text\n" +
+ " <li> type <kbd>yes</kbd> when\n" +
+ " <li> <q>Hello!</q>\n" +
+ " <li> <q>She said <q>Hello!</q></q>\n" +
+ " <li> <samp>ccc</samp>\n" +
+ " <li> <small>important</small>\n" +
+ " <li> <strike>overstruck</strike>\n" +
+ " <li> <strong>this is highlighted text</strong>\n" +
+ " <li> <code>sub</code> and\n" +
+ " <code>sup</code> x<sub>1</sub> and H<sub>2</sub>O\n" +
+ " M<sup>lle</sup>, 1<sup>st</sup>, e<sup>x</sup>, sin<sup>2</sup> <i>x</i>,\n" +
+ " e<sup>x<sup>2</sup></sup> and f(x)<sup>g(x)<sup>a+b+c</sup></sup>\n" +
+ " (where 2 and a+b+c should appear as exponents of exponents).\n" +
+ " <li> <tt>text in monospace font</tt>\n" +
+ " <li> <u>underlined</u> text\n" +
+ " <li> <code>cat</code> <var>filename</var> displays the\n" +
+ " the <var>filename</var>.\n" +
+ "</ul>\n";
+
+}
diff --git a/core/tests/benchmarks/src/android/text/SpannableStringInternalCopyBenchmark.java b/core/tests/benchmarks/src/android/text/SpannableStringInternalCopyBenchmark.java
new file mode 100644
index 0000000..dc5fed0
--- /dev/null
+++ b/core/tests/benchmarks/src/android/text/SpannableStringInternalCopyBenchmark.java
@@ -0,0 +1,61 @@
+/*
+ * 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 android.text;
+
+import com.google.caliper.AfterExperiment;
+import com.google.caliper.BeforeExperiment;
+import com.google.caliper.Benchmark;
+import com.google.caliper.Param;
+
+public class SpannableStringInternalCopyBenchmark {
+
+ @Param({"1", "4", "16"})
+ private String paramStringMult;
+
+ private SpannedString spanned;
+
+ @BeforeExperiment
+ protected void setUp() throws Exception {
+ int strSize = Integer.valueOf(paramStringMult);
+ StringBuilder strBuilder = new StringBuilder();
+ for (int i = 0; i < strSize; i++) {
+ strBuilder.append(SpannableStringBuilderBenchmark.TEST_STRING);
+ }
+ Spanned source = Html.fromHtml(strBuilder.toString());
+ spanned = new SpannedString(source);
+ }
+
+ @AfterExperiment
+ protected void tearDown() {
+ spanned = null;
+ }
+
+ @Benchmark
+ public void timeCopyConstructor(int reps) throws Exception {
+ for (int i = 0; i < reps; i++) {
+ new SpannedString(spanned);
+ }
+ }
+
+ @Benchmark
+ public void timeSubsequence(int reps) throws Exception {
+ for (int i = 0; i < reps; i++) {
+ spanned.subSequence(1, spanned.length()-1);
+ }
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index e54723e..edbfef9 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -48,6 +48,9 @@
import android.test.suitebuilder.annotation.SmallTest;
import android.view.KeyEvent;
+import static org.hamcrest.Matchers.anyOf;
+import static org.hamcrest.Matchers.is;
+
/**
* Tests the TextView widget from an Activity
*/
@@ -84,6 +87,43 @@
}
@SmallTest
+ public void testPositionCursorAtTextAtIndex_arabic() throws Exception {
+ // Arabic text. The expected cursorable boundary is
+ // | \u0623 \u064F | \u067A | \u0633 \u0652 |
+ final String text = "\u0623\u064F\u067A\u0633\u0652";
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(replaceText(text));
+
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(0));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(1));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(anyOf(is(0), is(2))));
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(2));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(2));
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(3));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(3));
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(4));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(anyOf(is(3), is(5))));
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(5));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(5));
+ }
+
+ @SmallTest
+ public void testPositionCursorAtTextAtIndex_devanagari() throws Exception {
+ // Devanagari text. The expected cursorable boundary is | \u0915 \u093E |
+ final String text = "\u0915\u093E";
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(replaceText(text));
+
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(0));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(1));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(anyOf(is(0), is(2))));
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(2));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(2));
+ }
+
+ @SmallTest
public void testLongPressToSelect() throws Exception {
final String helloWorld = "Hello Kirk!";
onView(withId(R.id.textview)).perform(click());
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 731bf42..961d0eb 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -341,15 +341,9 @@
<family lang="ko">
<font weight="400" style="normal" index="1">NotoSansCJK-Regular.ttc</font>
</family>
- <family>
- <font weight="400" style="normal">NanumGothic.ttf</font>
- </family>
<family lang="und-Zsye">
<font weight="400" style="normal">NotoColorEmoji.ttf</font>
</family>
- <family>
- <font weight="400" style="normal">DroidSansFallback.ttf</font>
- </family>
<!--
Tai Le and Mongolian are intentionally kept last, to make sure they don't override
the East Asian punctuation for Chinese.
diff --git a/include/androidfw/DisplayEventDispatcher.h b/include/androidfw/DisplayEventDispatcher.h
new file mode 100644
index 0000000..3ade215
--- /dev/null
+++ b/include/androidfw/DisplayEventDispatcher.h
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+#include <gui/DisplayEventReceiver.h>
+#include <utils/Log.h>
+#include <utils/Looper.h>
+
+namespace android {
+
+class DisplayEventDispatcher : public LooperCallback {
+public:
+ DisplayEventDispatcher(const sp<Looper>& looper);
+
+ status_t initialize();
+ void dispose();
+ status_t scheduleVsync();
+
+protected:
+ virtual ~DisplayEventDispatcher() = default;
+
+private:
+ sp<Looper> mLooper;
+ DisplayEventReceiver mReceiver;
+ bool mWaitingForVsync;
+
+ virtual void dispatchVsync(nsecs_t timestamp, int32_t id, uint32_t count) = 0;
+ virtual void dispatchHotplug(nsecs_t timestamp, int32_t id, bool connected) = 0;
+
+ virtual int handleEvent(int receiveFd, int events, void* data);
+ bool processPendingEvents(nsecs_t* outTimestamp, int32_t* id, uint32_t* outCount);
+};
+}
diff --git a/include/androidfw/LocaleData.h b/include/androidfw/LocaleData.h
new file mode 100644
index 0000000..add0ab5
--- /dev/null
+++ b/include/androidfw/LocaleData.h
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+#ifndef _LIBS_UTILS_LOCALE_DATA_H
+#define _LIBS_UTILS_LOCALE_DATA_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+namespace android {
+
+int localeDataCompareRegions(
+ const char* left_region, const char* right_region,
+ const char* requested_language, const char* requested_script,
+ const char* requested_region);
+
+void localeDataComputeScript(char out[4], const char* language, const char* region);
+
+} // namespace android
+
+#endif // _LIBS_UTILS_LOCALE_DATA_H
diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h
index 428a2b8..d8801b8 100644
--- a/include/androidfw/ResourceTypes.h
+++ b/include/androidfw/ResourceTypes.h
@@ -21,6 +21,7 @@
#define _LIBS_UTILS_RESOURCE_TYPES_H
#include <androidfw/Asset.h>
+#include <androidfw/LocaleData.h>
#include <utils/ByteOrder.h>
#include <utils/Errors.h>
#include <utils/String16.h>
@@ -1128,7 +1129,7 @@
// the locale field.
char localeScript[4];
- // A single BCP-47 variant subtag. Will vary in length between 5 and 8
+ // A single BCP-47 variant subtag. Will vary in length between 4 and 8
// chars. Interpreted in conjunction with the locale field.
char localeVariant[8];
@@ -1150,6 +1151,10 @@
uint32_t screenConfig2;
};
+ // If true, it means that the script of the locale was explicitly provided.
+ // If false, it means that the script was automatically computed.
+ bool localeScriptWasProvided;
+
void copyFromDeviceNoSwap(const ResTable_config& o);
void copyFromDtoH(const ResTable_config& o);
@@ -1228,10 +1233,15 @@
inline void clearLocale() {
locale = 0;
+ localeScriptWasProvided = false;
memset(localeScript, 0, sizeof(localeScript));
memset(localeVariant, 0, sizeof(localeVariant));
}
+ inline void computeScript() {
+ localeDataComputeScript(localeScript, language, country);
+ }
+
// Get the 2 or 3 letter language code of this configuration. Trailing
// bytes are set to '\0'.
size_t unpackLanguage(char language[4]) const;
@@ -1255,6 +1265,12 @@
// and 0 if they're equally specific.
int isLocaleMoreSpecificThan(const ResTable_config &o) const;
+ // Return true if 'this' is a better locale match than 'o' for the
+ // 'requested' configuration. Similar to isBetterThan(), this assumes that
+ // match() has already been used to remove any configurations that don't
+ // match the requested configuration at all.
+ bool isLocaleBetterThan(const ResTable_config& o, const ResTable_config* requested) const;
+
String8 toString() const;
};
diff --git a/libs/androidfw/Android.mk b/libs/androidfw/Android.mk
index f682fb8..6bbfcd2 100644
--- a/libs/androidfw/Android.mk
+++ b/libs/androidfw/Android.mk
@@ -21,6 +21,7 @@
Asset.cpp \
AssetDir.cpp \
AssetManager.cpp \
+ LocaleData.cpp \
misc.cpp \
ObbFile.cpp \
ResourceTypes.cpp \
@@ -33,7 +34,8 @@
$(commonSources) \
BackupData.cpp \
BackupHelpers.cpp \
- CursorWindow.cpp
+ CursorWindow.cpp \
+ DisplayEventDispatcher.cpp
hostSources := $(commonSources)
@@ -65,6 +67,7 @@
libbinder \
liblog \
libcutils \
+ libgui \
libutils \
libz
diff --git a/libs/androidfw/DisplayEventDispatcher.cpp b/libs/androidfw/DisplayEventDispatcher.cpp
new file mode 100644
index 0000000..b8ef9ea4
--- /dev/null
+++ b/libs/androidfw/DisplayEventDispatcher.cpp
@@ -0,0 +1,147 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "DisplayEventDispatcher"
+
+#include <cinttypes>
+#include <cstdint>
+
+#include <androidfw/DisplayEventDispatcher.h>
+#include <gui/DisplayEventReceiver.h>
+#include <utils/Log.h>
+#include <utils/Looper.h>
+
+#include <utils/Timers.h>
+
+namespace android {
+
+// Number of events to read at a time from the DisplayEventDispatcher pipe.
+// The value should be large enough that we can quickly drain the pipe
+// using just a few large reads.
+static const size_t EVENT_BUFFER_SIZE = 100;
+
+DisplayEventDispatcher::DisplayEventDispatcher(const sp<Looper>& looper) :
+ mLooper(looper), mWaitingForVsync(false) {
+ ALOGV("dispatcher %p ~ Initializing display event dispatcher.", this);
+}
+
+status_t DisplayEventDispatcher::initialize() {
+ status_t result = mReceiver.initCheck();
+ if (result) {
+ ALOGW("Failed to initialize display event receiver, status=%d", result);
+ return result;
+ }
+
+ int rc = mLooper->addFd(mReceiver.getFd(), 0, Looper::EVENT_INPUT,
+ this, NULL);
+ if (rc < 0) {
+ return UNKNOWN_ERROR;
+ }
+ return OK;
+}
+
+void DisplayEventDispatcher::dispose() {
+ ALOGV("dispatcher %p ~ Disposing display event dispatcher.", this);
+
+ if (!mReceiver.initCheck()) {
+ mLooper->removeFd(mReceiver.getFd());
+ }
+}
+
+status_t DisplayEventDispatcher::scheduleVsync() {
+ if (!mWaitingForVsync) {
+ ALOGV("dispatcher %p ~ Scheduling vsync.", this);
+
+ // Drain all pending events.
+ nsecs_t vsyncTimestamp;
+ int32_t vsyncDisplayId;
+ uint32_t vsyncCount;
+ if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount)) {
+ ALOGE("dispatcher %p ~ last event processed while scheduling was for %" PRId64 "",
+ this, ns2ms(static_cast<nsecs_t>(vsyncTimestamp)));
+ }
+
+ status_t status = mReceiver.requestNextVsync();
+ if (status) {
+ ALOGW("Failed to request next vsync, status=%d", status);
+ return status;
+ }
+
+ mWaitingForVsync = true;
+ }
+ return OK;
+}
+
+int DisplayEventDispatcher::handleEvent(int, int events, void*) {
+ if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
+ ALOGE("Display event receiver pipe was closed or an error occurred. "
+ "events=0x%x", events);
+ return 0; // remove the callback
+ }
+
+ if (!(events & Looper::EVENT_INPUT)) {
+ ALOGW("Received spurious callback for unhandled poll event. "
+ "events=0x%x", events);
+ return 1; // keep the callback
+ }
+
+ // Drain all pending events, keep the last vsync.
+ nsecs_t vsyncTimestamp;
+ int32_t vsyncDisplayId;
+ uint32_t vsyncCount;
+ if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount)) {
+ ALOGV("dispatcher %p ~ Vsync pulse: timestamp=%" PRId64 ", id=%d, count=%d",
+ this, ns2ms(vsyncTimestamp), vsyncDisplayId, vsyncCount);
+ mWaitingForVsync = false;
+ dispatchVsync(vsyncTimestamp, vsyncDisplayId, vsyncCount);
+ }
+
+ return 1; // keep the callback
+}
+
+bool DisplayEventDispatcher::processPendingEvents(
+ nsecs_t* outTimestamp, int32_t* outId, uint32_t* outCount) {
+ bool gotVsync = false;
+ DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
+ ssize_t n;
+ while ((n = mReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
+ ALOGV("dispatcher %p ~ Read %d events.", this, int(n));
+ for (ssize_t i = 0; i < n; i++) {
+ const DisplayEventReceiver::Event& ev = buf[i];
+ switch (ev.header.type) {
+ case DisplayEventReceiver::DISPLAY_EVENT_VSYNC:
+ // Later vsync events will just overwrite the info from earlier
+ // ones. That's fine, we only care about the most recent.
+ gotVsync = true;
+ *outTimestamp = ev.header.timestamp;
+ *outId = ev.header.id;
+ *outCount = ev.vsync.count;
+ break;
+ case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:
+ dispatchHotplug(ev.header.timestamp, ev.header.id, ev.hotplug.connected);
+ break;
+ default:
+ ALOGW("dispatcher %p ~ ignoring unknown event type %#x", this, ev.header.type);
+ break;
+ }
+ }
+ }
+ if (n < 0) {
+ ALOGW("Failed to get events from display event dispatcher, status=%d", status_t(n));
+ }
+ return gotVsync;
+}
+}
diff --git a/libs/androidfw/LocaleData.cpp b/libs/androidfw/LocaleData.cpp
new file mode 100644
index 0000000..c0c3ab8
--- /dev/null
+++ b/libs/androidfw/LocaleData.cpp
@@ -0,0 +1,201 @@
+/*
+ * 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 <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+
+#include <androidfw/LocaleData.h>
+
+namespace android {
+
+#include "LocaleDataTables.cpp"
+
+inline uint32_t packLocale(const char* language, const char* region) {
+ return (((uint8_t) language[0]) << 24u) | (((uint8_t) language[1]) << 16u) |
+ (((uint8_t) region[0]) << 8u) | ((uint8_t) region[1]);
+}
+
+inline uint32_t dropRegion(uint32_t packed_locale) {
+ return packed_locale & 0xFFFF0000lu;
+}
+
+inline bool hasRegion(uint32_t packed_locale) {
+ return (packed_locale & 0x0000FFFFlu) != 0;
+}
+
+const size_t SCRIPT_LENGTH = 4;
+const size_t SCRIPT_PARENTS_COUNT = sizeof(SCRIPT_PARENTS)/sizeof(SCRIPT_PARENTS[0]);
+const uint32_t PACKED_ROOT = 0; // to represent the root locale
+
+uint32_t findParent(uint32_t packed_locale, const char* script) {
+ if (hasRegion(packed_locale)) {
+ for (size_t i = 0; i < SCRIPT_PARENTS_COUNT; i++) {
+ if (memcmp(script, SCRIPT_PARENTS[i].script, SCRIPT_LENGTH) == 0) {
+ auto map = SCRIPT_PARENTS[i].map;
+ auto lookup_result = map->find(packed_locale);
+ if (lookup_result != map->end()) {
+ return lookup_result->second;
+ }
+ break;
+ }
+ }
+ return dropRegion(packed_locale);
+ }
+ return PACKED_ROOT;
+}
+
+// Find the ancestors of a locale, and fill 'out' with it (assumes out has enough
+// space). If any of the members of stop_list was seen, write it in the
+// output but stop afterwards.
+//
+// This also outputs the index of the last written ancestor in the stop_list
+// to stop_list_index, which will be -1 if it is not found in the stop_list.
+//
+// Returns the number of ancestors written in the output, which is always
+// at least one.
+size_t findAncestors(uint32_t* out, ssize_t* stop_list_index,
+ uint32_t packed_locale, const char* script,
+ const uint32_t* stop_list, size_t stop_set_length) {
+ uint32_t ancestor = packed_locale;
+ size_t count = 0;
+ do {
+ out[count++] = ancestor;
+ for (size_t i = 0; i < stop_set_length; i++) {
+ if (stop_list[i] == ancestor) {
+ *stop_list_index = (ssize_t) i;
+ return count;
+ }
+ }
+ ancestor = findParent(ancestor, script);
+ } while (ancestor != PACKED_ROOT);
+ *stop_list_index = (ssize_t) -1;
+ return count;
+}
+
+size_t findDistance(uint32_t supported,
+ const char* script,
+ const uint32_t* request_ancestors,
+ size_t request_ancestors_count) {
+ uint32_t supported_ancestors[MAX_PARENT_DEPTH+1];
+ ssize_t request_ancestors_index;
+ const size_t supported_ancestor_count = findAncestors(
+ supported_ancestors, &request_ancestors_index,
+ supported, script,
+ request_ancestors, request_ancestors_count);
+ // Since both locales share the same root, there will always be a shared
+ // ancestor, so the distance in the parent tree is the sum of the distance
+ // of 'supported' to the lowest common ancestor (number of ancestors
+ // written for 'supported' minus 1) plus the distance of 'request' to the
+ // lowest common ancestor (the index of the ancestor in request_ancestors).
+ return supported_ancestor_count + request_ancestors_index - 1;
+}
+
+inline bool isRepresentative(uint32_t language_and_region, const char* script) {
+ const uint64_t packed_locale = (
+ (((uint64_t) language_and_region) << 32u) |
+ (((uint64_t) script[0]) << 24u) |
+ (((uint64_t) script[1]) << 16u) |
+ (((uint64_t) script[2]) << 8u) |
+ ((uint64_t) script[3]));
+
+ return (REPRESENTATIVE_LOCALES.count(packed_locale) != 0);
+}
+
+int localeDataCompareRegions(
+ const char* left_region, const char* right_region,
+ const char* requested_language, const char* requested_script,
+ const char* requested_region) {
+
+ if (left_region[0] == right_region[0] && left_region[1] == right_region[1]) {
+ return 0;
+ }
+ const uint32_t left = packLocale(requested_language, left_region);
+ const uint32_t right = packLocale(requested_language, right_region);
+ const uint32_t request = packLocale(requested_language, requested_region);
+
+ uint32_t request_ancestors[MAX_PARENT_DEPTH+1];
+ ssize_t left_right_index;
+ // Find the parents of the request, but stop as soon as we saw left or right
+ const uint32_t left_and_right[] = {left, right};
+ const size_t ancestor_count = findAncestors(
+ request_ancestors, &left_right_index,
+ request, requested_script,
+ left_and_right, sizeof(left_and_right)/sizeof(left_and_right[0]));
+ if (left_right_index == 0) { // We saw left earlier
+ return 1;
+ }
+ if (left_right_index == 1) { // We saw right earlier
+ return -1;
+ }
+
+ // If we are here, neither left nor right are an ancestor of the
+ // request. This means that all the ancestors have been computed and
+ // the last ancestor is just the language by itself. We will use the
+ // distance in the parent tree for determining the better match.
+ const size_t left_distance = findDistance(
+ left, requested_script, request_ancestors, ancestor_count);
+ const size_t right_distance = findDistance(
+ right, requested_script, request_ancestors, ancestor_count);
+ if (left_distance != right_distance) {
+ return (int) right_distance - (int) left_distance; // smaller distance is better
+ }
+
+ // If we are here, left and right are equidistant from the request. We will
+ // try and see if any of them is a representative locale.
+ const bool left_is_representative = isRepresentative(left, requested_script);
+ const bool right_is_representative = isRepresentative(right, requested_script);
+ if (left_is_representative != right_is_representative) {
+ return (int) left_is_representative - (int) right_is_representative;
+ }
+
+ // We have no way of figuring out which locale is a better match. For
+ // the sake of stability, we consider the locale with the lower region
+ // code (in dictionary order) better, with two-letter codes before
+ // three-digit codes (since two-letter codes are more specific).
+ return (int64_t) right - (int64_t) left;
+}
+
+void localeDataComputeScript(char out[4], const char* language, const char* region) {
+ if (language[0] == '\0') {
+ memset(out, '\0', SCRIPT_LENGTH);
+ return;
+ }
+ uint32_t lookup_key = packLocale(language, region);
+ auto lookup_result = LIKELY_SCRIPTS.find(lookup_key);
+ if (lookup_result == LIKELY_SCRIPTS.end()) {
+ // We couldn't find the locale. Let's try without the region
+ if (region[0] != '\0') {
+ lookup_key = dropRegion(lookup_key);
+ lookup_result = LIKELY_SCRIPTS.find(lookup_key);
+ if (lookup_result != LIKELY_SCRIPTS.end()) {
+ memcpy(out, SCRIPT_CODES[lookup_result->second], SCRIPT_LENGTH);
+ return;
+ }
+ }
+ // We don't know anything about the locale
+ memset(out, '\0', SCRIPT_LENGTH);
+ return;
+ } else {
+ // We found the locale.
+ memcpy(out, SCRIPT_CODES[lookup_result->second], SCRIPT_LENGTH);
+ }
+}
+
+} // namespace android
diff --git a/libs/androidfw/LocaleDataTables.cpp b/libs/androidfw/LocaleDataTables.cpp
new file mode 100644
index 0000000..1ac5085
--- /dev/null
+++ b/libs/androidfw/LocaleDataTables.cpp
@@ -0,0 +1,1701 @@
+// Auto-generated by frameworks/base/tools/localedata/extract_icu_data.py
+
+const char SCRIPT_CODES[][4] = {
+ /* 0 */ {'A', 'h', 'o', 'm'},
+ /* 1 */ {'A', 'r', 'a', 'b'},
+ /* 2 */ {'A', 'r', 'm', 'i'},
+ /* 3 */ {'A', 'r', 'm', 'n'},
+ /* 4 */ {'A', 'v', 's', 't'},
+ /* 5 */ {'B', 'a', 'm', 'u'},
+ /* 6 */ {'B', 'a', 's', 's'},
+ /* 7 */ {'B', 'e', 'n', 'g'},
+ /* 8 */ {'B', 'r', 'a', 'h'},
+ /* 9 */ {'C', 'a', 'n', 's'},
+ /* 10 */ {'C', 'a', 'r', 'i'},
+ /* 11 */ {'C', 'h', 'a', 'm'},
+ /* 12 */ {'C', 'h', 'e', 'r'},
+ /* 13 */ {'C', 'o', 'p', 't'},
+ /* 14 */ {'C', 'p', 'r', 't'},
+ /* 15 */ {'C', 'y', 'r', 'l'},
+ /* 16 */ {'D', 'e', 'v', 'a'},
+ /* 17 */ {'E', 'g', 'y', 'p'},
+ /* 18 */ {'E', 't', 'h', 'i'},
+ /* 19 */ {'G', 'e', 'o', 'r'},
+ /* 20 */ {'G', 'o', 't', 'h'},
+ /* 21 */ {'G', 'r', 'e', 'k'},
+ /* 22 */ {'G', 'u', 'j', 'r'},
+ /* 23 */ {'G', 'u', 'r', 'u'},
+ /* 24 */ {'H', 'a', 'n', 's'},
+ /* 25 */ {'H', 'a', 'n', 't'},
+ /* 26 */ {'H', 'a', 't', 'r'},
+ /* 27 */ {'H', 'e', 'b', 'r'},
+ /* 28 */ {'H', 'l', 'u', 'w'},
+ /* 29 */ {'H', 'm', 'n', 'g'},
+ /* 30 */ {'I', 't', 'a', 'l'},
+ /* 31 */ {'J', 'p', 'a', 'n'},
+ /* 32 */ {'K', 'a', 'l', 'i'},
+ /* 33 */ {'K', 'a', 'n', 'a'},
+ /* 34 */ {'K', 'h', 'a', 'r'},
+ /* 35 */ {'K', 'h', 'm', 'r'},
+ /* 36 */ {'K', 'n', 'd', 'a'},
+ /* 37 */ {'K', 'o', 'r', 'e'},
+ /* 38 */ {'K', 't', 'h', 'i'},
+ /* 39 */ {'L', 'a', 'n', 'a'},
+ /* 40 */ {'L', 'a', 'o', 'o'},
+ /* 41 */ {'L', 'a', 't', 'n'},
+ /* 42 */ {'L', 'e', 'p', 'c'},
+ /* 43 */ {'L', 'i', 'n', 'a'},
+ /* 44 */ {'L', 'i', 's', 'u'},
+ /* 45 */ {'L', 'y', 'c', 'i'},
+ /* 46 */ {'L', 'y', 'd', 'i'},
+ /* 47 */ {'M', 'a', 'n', 'd'},
+ /* 48 */ {'M', 'a', 'n', 'i'},
+ /* 49 */ {'M', 'e', 'r', 'c'},
+ /* 50 */ {'M', 'l', 'y', 'm'},
+ /* 51 */ {'M', 'o', 'n', 'g'},
+ /* 52 */ {'M', 'r', 'o', 'o'},
+ /* 53 */ {'M', 'y', 'm', 'r'},
+ /* 54 */ {'N', 'a', 'r', 'b'},
+ /* 55 */ {'N', 'k', 'o', 'o'},
+ /* 56 */ {'O', 'g', 'a', 'm'},
+ /* 57 */ {'O', 'r', 'k', 'h'},
+ /* 58 */ {'O', 'r', 'y', 'a'},
+ /* 59 */ {'P', 'a', 'u', 'c'},
+ /* 60 */ {'P', 'h', 'l', 'i'},
+ /* 61 */ {'P', 'h', 'n', 'x'},
+ /* 62 */ {'P', 'l', 'r', 'd'},
+ /* 63 */ {'P', 'r', 't', 'i'},
+ /* 64 */ {'R', 'u', 'n', 'r'},
+ /* 65 */ {'S', 'a', 'm', 'r'},
+ /* 66 */ {'S', 'a', 'r', 'b'},
+ /* 67 */ {'S', 'a', 'u', 'r'},
+ /* 68 */ {'S', 'g', 'n', 'w'},
+ /* 69 */ {'S', 'i', 'n', 'h'},
+ /* 70 */ {'S', 'o', 'r', 'a'},
+ /* 71 */ {'S', 'y', 'r', 'c'},
+ /* 72 */ {'T', 'a', 'l', 'e'},
+ /* 73 */ {'T', 'a', 'l', 'u'},
+ /* 74 */ {'T', 'a', 'm', 'l'},
+ /* 75 */ {'T', 'a', 'v', 't'},
+ /* 76 */ {'T', 'e', 'l', 'u'},
+ /* 77 */ {'T', 'f', 'n', 'g'},
+ /* 78 */ {'T', 'h', 'a', 'a'},
+ /* 79 */ {'T', 'h', 'a', 'i'},
+ /* 80 */ {'T', 'i', 'b', 't'},
+ /* 81 */ {'U', 'g', 'a', 'r'},
+ /* 82 */ {'V', 'a', 'i', 'i'},
+ /* 83 */ {'X', 'p', 'e', 'o'},
+ /* 84 */ {'X', 's', 'u', 'x'},
+ /* 85 */ {'Y', 'i', 'i', 'i'},
+ /* 86 */ {'~', '~', '~', 'A'},
+ /* 87 */ {'~', '~', '~', 'B'},
+};
+
+
+const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({
+ {0x61610000u, 41u}, // aa -> Latn
+ {0x61620000u, 15u}, // ab -> Cyrl
+ {0xC4200000u, 41u}, // abr -> Latn
+ {0x90400000u, 41u}, // ace -> Latn
+ {0x9C400000u, 41u}, // ach -> Latn
+ {0x80600000u, 41u}, // ada -> Latn
+ {0xE0600000u, 15u}, // ady -> Cyrl
+ {0x61650000u, 4u}, // ae -> Avst
+ {0x84800000u, 1u}, // aeb -> Arab
+ {0x61660000u, 41u}, // af -> Latn
+ {0xC0C00000u, 41u}, // agq -> Latn
+ {0xB8E00000u, 0u}, // aho -> Ahom
+ {0x616B0000u, 41u}, // ak -> Latn
+ {0xA9400000u, 84u}, // akk -> Xsux
+ {0xB5600000u, 41u}, // aln -> Latn
+ {0xCD600000u, 15u}, // alt -> Cyrl
+ {0x616D0000u, 18u}, // am -> Ethi
+ {0xB9800000u, 41u}, // amo -> Latn
+ {0xE5C00000u, 41u}, // aoz -> Latn
+ {0x61720000u, 1u}, // ar -> Arab
+ {0x61725842u, 87u}, // ar-XB -> ~~~B
+ {0x8A200000u, 2u}, // arc -> Armi
+ {0xB6200000u, 41u}, // arn -> Latn
+ {0xBA200000u, 41u}, // aro -> Latn
+ {0xC2200000u, 1u}, // arq -> Arab
+ {0xE2200000u, 1u}, // ary -> Arab
+ {0xE6200000u, 1u}, // arz -> Arab
+ {0x61730000u, 7u}, // as -> Beng
+ {0x82400000u, 41u}, // asa -> Latn
+ {0x92400000u, 68u}, // ase -> Sgnw
+ {0xCE400000u, 41u}, // ast -> Latn
+ {0xA6600000u, 41u}, // atj -> Latn
+ {0x61760000u, 15u}, // av -> Cyrl
+ {0x82C00000u, 16u}, // awa -> Deva
+ {0x61790000u, 41u}, // ay -> Latn
+ {0x617A0000u, 41u}, // az -> Latn
+ {0x617A4951u, 1u}, // az-IQ -> Arab
+ {0x617A4952u, 1u}, // az-IR -> Arab
+ {0x617A5255u, 15u}, // az-RU -> Cyrl
+ {0x62610000u, 15u}, // ba -> Cyrl
+ {0xAC010000u, 1u}, // bal -> Arab
+ {0xB4010000u, 41u}, // ban -> Latn
+ {0xBC010000u, 16u}, // bap -> Deva
+ {0xC4010000u, 41u}, // bar -> Latn
+ {0xC8010000u, 41u}, // bas -> Latn
+ {0xDC010000u, 5u}, // bax -> Bamu
+ {0x88210000u, 41u}, // bbc -> Latn
+ {0xA4210000u, 41u}, // bbj -> Latn
+ {0xA0410000u, 41u}, // bci -> Latn
+ {0x62650000u, 15u}, // be -> Cyrl
+ {0xA4810000u, 1u}, // bej -> Arab
+ {0xB0810000u, 41u}, // bem -> Latn
+ {0xD8810000u, 41u}, // bew -> Latn
+ {0xE4810000u, 41u}, // bez -> Latn
+ {0x8CA10000u, 41u}, // bfd -> Latn
+ {0xC0A10000u, 74u}, // bfq -> Taml
+ {0xCCA10000u, 1u}, // bft -> Arab
+ {0xE0A10000u, 16u}, // bfy -> Deva
+ {0x62670000u, 15u}, // bg -> Cyrl
+ {0x88C10000u, 16u}, // bgc -> Deva
+ {0xB4C10000u, 1u}, // bgn -> Arab
+ {0xDCC10000u, 21u}, // bgx -> Grek
+ {0x62680000u, 38u}, // bh -> Kthi
+ {0x84E10000u, 16u}, // bhb -> Deva
+ {0xA0E10000u, 16u}, // bhi -> Deva
+ {0xA8E10000u, 41u}, // bhk -> Latn
+ {0xB8E10000u, 16u}, // bho -> Deva
+ {0x62690000u, 41u}, // bi -> Latn
+ {0xA9010000u, 41u}, // bik -> Latn
+ {0xB5010000u, 41u}, // bin -> Latn
+ {0xA5210000u, 16u}, // bjj -> Deva
+ {0xB5210000u, 41u}, // bjn -> Latn
+ {0xB1410000u, 41u}, // bkm -> Latn
+ {0xD1410000u, 41u}, // bku -> Latn
+ {0xCD610000u, 75u}, // blt -> Tavt
+ {0x626D0000u, 41u}, // bm -> Latn
+ {0xC1810000u, 41u}, // bmq -> Latn
+ {0x626E0000u, 7u}, // bn -> Beng
+ {0x626F0000u, 80u}, // bo -> Tibt
+ {0xE1E10000u, 7u}, // bpy -> Beng
+ {0xA2010000u, 1u}, // bqi -> Arab
+ {0xD6010000u, 41u}, // bqv -> Latn
+ {0x62720000u, 41u}, // br -> Latn
+ {0x82210000u, 16u}, // bra -> Deva
+ {0x9E210000u, 1u}, // brh -> Arab
+ {0xDE210000u, 16u}, // brx -> Deva
+ {0x62730000u, 41u}, // bs -> Latn
+ {0xC2410000u, 6u}, // bsq -> Bass
+ {0xCA410000u, 41u}, // bss -> Latn
+ {0xBA610000u, 41u}, // bto -> Latn
+ {0xD6610000u, 16u}, // btv -> Deva
+ {0x82810000u, 15u}, // bua -> Cyrl
+ {0x8A810000u, 41u}, // buc -> Latn
+ {0x9A810000u, 41u}, // bug -> Latn
+ {0xB2810000u, 41u}, // bum -> Latn
+ {0x86A10000u, 41u}, // bvb -> Latn
+ {0xB7010000u, 18u}, // byn -> Ethi
+ {0xD7010000u, 41u}, // byv -> Latn
+ {0x93210000u, 41u}, // bze -> Latn
+ {0x63610000u, 41u}, // ca -> Latn
+ {0x9C420000u, 41u}, // cch -> Latn
+ {0xBC420000u, 7u}, // ccp -> Beng
+ {0x63650000u, 15u}, // ce -> Cyrl
+ {0x84820000u, 41u}, // ceb -> Latn
+ {0x98C20000u, 41u}, // cgg -> Latn
+ {0x63680000u, 41u}, // ch -> Latn
+ {0xA8E20000u, 41u}, // chk -> Latn
+ {0xB0E20000u, 15u}, // chm -> Cyrl
+ {0xB8E20000u, 41u}, // cho -> Latn
+ {0xBCE20000u, 41u}, // chp -> Latn
+ {0xC4E20000u, 12u}, // chr -> Cher
+ {0x81220000u, 1u}, // cja -> Arab
+ {0xB1220000u, 11u}, // cjm -> Cham
+ {0x85420000u, 1u}, // ckb -> Arab
+ {0x636F0000u, 41u}, // co -> Latn
+ {0xBDC20000u, 13u}, // cop -> Copt
+ {0xC9E20000u, 41u}, // cps -> Latn
+ {0x63720000u, 9u}, // cr -> Cans
+ {0xA6220000u, 9u}, // crj -> Cans
+ {0xAA220000u, 9u}, // crk -> Cans
+ {0xAE220000u, 9u}, // crl -> Cans
+ {0xB2220000u, 9u}, // crm -> Cans
+ {0xCA220000u, 41u}, // crs -> Latn
+ {0x63730000u, 41u}, // cs -> Latn
+ {0x86420000u, 41u}, // csb -> Latn
+ {0xDA420000u, 9u}, // csw -> Cans
+ {0x8E620000u, 59u}, // ctd -> Pauc
+ {0x63750000u, 15u}, // cu -> Cyrl
+ {0x63760000u, 15u}, // cv -> Cyrl
+ {0x63790000u, 41u}, // cy -> Latn
+ {0x64610000u, 41u}, // da -> Latn
+ {0xA8030000u, 41u}, // dak -> Latn
+ {0xC4030000u, 15u}, // dar -> Cyrl
+ {0xD4030000u, 41u}, // dav -> Latn
+ {0x88430000u, 1u}, // dcc -> Arab
+ {0x64650000u, 41u}, // de -> Latn
+ {0xB4830000u, 41u}, // den -> Latn
+ {0xC4C30000u, 41u}, // dgr -> Latn
+ {0x91230000u, 41u}, // dje -> Latn
+ {0xA5A30000u, 41u}, // dnj -> Latn
+ {0xA1C30000u, 1u}, // doi -> Arab
+ {0x86430000u, 41u}, // dsb -> Latn
+ {0xB2630000u, 41u}, // dtm -> Latn
+ {0xBE630000u, 41u}, // dtp -> Latn
+ {0x82830000u, 41u}, // dua -> Latn
+ {0x64760000u, 78u}, // dv -> Thaa
+ {0xBB030000u, 41u}, // dyo -> Latn
+ {0xD3030000u, 41u}, // dyu -> Latn
+ {0x647A0000u, 80u}, // dz -> Tibt
+ {0xD0240000u, 41u}, // ebu -> Latn
+ {0x65650000u, 41u}, // ee -> Latn
+ {0xA0A40000u, 41u}, // efi -> Latn
+ {0xACC40000u, 41u}, // egl -> Latn
+ {0xE0C40000u, 17u}, // egy -> Egyp
+ {0xE1440000u, 32u}, // eky -> Kali
+ {0x656C0000u, 21u}, // el -> Grek
+ {0x656E0000u, 41u}, // en -> Latn
+ {0x656E5841u, 86u}, // en-XA -> ~~~A
+ {0x656F0000u, 41u}, // eo -> Latn
+ {0x65730000u, 41u}, // es -> Latn
+ {0xD2440000u, 41u}, // esu -> Latn
+ {0x65740000u, 41u}, // et -> Latn
+ {0xCE640000u, 30u}, // ett -> Ital
+ {0x65750000u, 41u}, // eu -> Latn
+ {0xBAC40000u, 41u}, // ewo -> Latn
+ {0xCEE40000u, 41u}, // ext -> Latn
+ {0x66610000u, 1u}, // fa -> Arab
+ {0xB4050000u, 41u}, // fan -> Latn
+ {0x66660000u, 41u}, // ff -> Latn
+ {0xB0A50000u, 41u}, // ffm -> Latn
+ {0x66690000u, 41u}, // fi -> Latn
+ {0x81050000u, 1u}, // fia -> Arab
+ {0xAD050000u, 41u}, // fil -> Latn
+ {0xCD050000u, 41u}, // fit -> Latn
+ {0x666A0000u, 41u}, // fj -> Latn
+ {0x666F0000u, 41u}, // fo -> Latn
+ {0xB5C50000u, 41u}, // fon -> Latn
+ {0x66720000u, 41u}, // fr -> Latn
+ {0x8A250000u, 41u}, // frc -> Latn
+ {0xBE250000u, 41u}, // frp -> Latn
+ {0xC6250000u, 41u}, // frr -> Latn
+ {0xCA250000u, 41u}, // frs -> Latn
+ {0x8E850000u, 41u}, // fud -> Latn
+ {0xC2850000u, 41u}, // fuq -> Latn
+ {0xC6850000u, 41u}, // fur -> Latn
+ {0xD6850000u, 41u}, // fuv -> Latn
+ {0xC6A50000u, 41u}, // fvr -> Latn
+ {0x66790000u, 41u}, // fy -> Latn
+ {0x67610000u, 41u}, // ga -> Latn
+ {0x80060000u, 41u}, // gaa -> Latn
+ {0x98060000u, 41u}, // gag -> Latn
+ {0xB4060000u, 24u}, // gan -> Hans
+ {0xE0060000u, 41u}, // gay -> Latn
+ {0xB0260000u, 16u}, // gbm -> Deva
+ {0xE4260000u, 1u}, // gbz -> Arab
+ {0xC4460000u, 41u}, // gcr -> Latn
+ {0x67640000u, 41u}, // gd -> Latn
+ {0xE4860000u, 18u}, // gez -> Ethi
+ {0xB4C60000u, 16u}, // ggn -> Deva
+ {0xAD060000u, 41u}, // gil -> Latn
+ {0xA9260000u, 1u}, // gjk -> Arab
+ {0xD1260000u, 1u}, // gju -> Arab
+ {0x676C0000u, 41u}, // gl -> Latn
+ {0xA9660000u, 1u}, // glk -> Arab
+ {0x676E0000u, 41u}, // gn -> Latn
+ {0xB1C60000u, 16u}, // gom -> Deva
+ {0xB5C60000u, 76u}, // gon -> Telu
+ {0xC5C60000u, 41u}, // gor -> Latn
+ {0xC9C60000u, 41u}, // gos -> Latn
+ {0xCDC60000u, 20u}, // got -> Goth
+ {0x8A260000u, 14u}, // grc -> Cprt
+ {0xCE260000u, 7u}, // grt -> Beng
+ {0xDA460000u, 41u}, // gsw -> Latn
+ {0x67750000u, 22u}, // gu -> Gujr
+ {0x86860000u, 41u}, // gub -> Latn
+ {0x8A860000u, 41u}, // guc -> Latn
+ {0xC6860000u, 41u}, // gur -> Latn
+ {0xE6860000u, 41u}, // guz -> Latn
+ {0x67760000u, 41u}, // gv -> Latn
+ {0xC6A60000u, 16u}, // gvr -> Deva
+ {0xA2C60000u, 41u}, // gwi -> Latn
+ {0x68610000u, 41u}, // ha -> Latn
+ {0x6861434Du, 1u}, // ha-CM -> Arab
+ {0x68615344u, 1u}, // ha-SD -> Arab
+ {0xA8070000u, 24u}, // hak -> Hans
+ {0xD8070000u, 41u}, // haw -> Latn
+ {0xE4070000u, 1u}, // haz -> Arab
+ {0x68650000u, 27u}, // he -> Hebr
+ {0x68690000u, 16u}, // hi -> Deva
+ {0x95070000u, 41u}, // hif -> Latn
+ {0xAD070000u, 41u}, // hil -> Latn
+ {0xD1670000u, 28u}, // hlu -> Hluw
+ {0x8D870000u, 62u}, // hmd -> Plrd
+ {0x8DA70000u, 1u}, // hnd -> Arab
+ {0x91A70000u, 16u}, // hne -> Deva
+ {0xA5A70000u, 29u}, // hnj -> Hmng
+ {0xB5A70000u, 41u}, // hnn -> Latn
+ {0xB9A70000u, 1u}, // hno -> Arab
+ {0x686F0000u, 41u}, // ho -> Latn
+ {0x89C70000u, 16u}, // hoc -> Deva
+ {0xA5C70000u, 16u}, // hoj -> Deva
+ {0x68720000u, 41u}, // hr -> Latn
+ {0x86470000u, 41u}, // hsb -> Latn
+ {0xB6470000u, 24u}, // hsn -> Hans
+ {0x68740000u, 41u}, // ht -> Latn
+ {0x68750000u, 41u}, // hu -> Latn
+ {0x68790000u, 3u}, // hy -> Armn
+ {0x687A0000u, 41u}, // hz -> Latn
+ {0x69610000u, 41u}, // ia -> Latn
+ {0x80280000u, 41u}, // iba -> Latn
+ {0x84280000u, 41u}, // ibb -> Latn
+ {0x69640000u, 41u}, // id -> Latn
+ {0x69670000u, 41u}, // ig -> Latn
+ {0x69690000u, 85u}, // ii -> Yiii
+ {0x696B0000u, 41u}, // ik -> Latn
+ {0xCD480000u, 41u}, // ikt -> Latn
+ {0xB9680000u, 41u}, // ilo -> Latn
+ {0x696E0000u, 41u}, // in -> Latn
+ {0x9DA80000u, 15u}, // inh -> Cyrl
+ {0x69730000u, 41u}, // is -> Latn
+ {0x69740000u, 41u}, // it -> Latn
+ {0x69750000u, 9u}, // iu -> Cans
+ {0x69770000u, 27u}, // iw -> Hebr
+ {0x9F280000u, 41u}, // izh -> Latn
+ {0x6A610000u, 31u}, // ja -> Jpan
+ {0xB0090000u, 41u}, // jam -> Latn
+ {0xB8C90000u, 41u}, // jgo -> Latn
+ {0x6A690000u, 27u}, // ji -> Hebr
+ {0x89890000u, 41u}, // jmc -> Latn
+ {0xAD890000u, 16u}, // jml -> Deva
+ {0xCE890000u, 41u}, // jut -> Latn
+ {0x6A760000u, 41u}, // jv -> Latn
+ {0x6A770000u, 41u}, // jw -> Latn
+ {0x6B610000u, 19u}, // ka -> Geor
+ {0x800A0000u, 15u}, // kaa -> Cyrl
+ {0x840A0000u, 41u}, // kab -> Latn
+ {0x880A0000u, 41u}, // kac -> Latn
+ {0xA40A0000u, 41u}, // kaj -> Latn
+ {0xB00A0000u, 41u}, // kam -> Latn
+ {0xB80A0000u, 41u}, // kao -> Latn
+ {0x8C2A0000u, 15u}, // kbd -> Cyrl
+ {0x984A0000u, 41u}, // kcg -> Latn
+ {0xA84A0000u, 41u}, // kck -> Latn
+ {0x906A0000u, 41u}, // kde -> Latn
+ {0xCC6A0000u, 79u}, // kdt -> Thai
+ {0x808A0000u, 41u}, // kea -> Latn
+ {0xB48A0000u, 41u}, // ken -> Latn
+ {0xB8AA0000u, 41u}, // kfo -> Latn
+ {0xC4AA0000u, 16u}, // kfr -> Deva
+ {0xE0AA0000u, 16u}, // kfy -> Deva
+ {0x6B670000u, 41u}, // kg -> Latn
+ {0x90CA0000u, 41u}, // kge -> Latn
+ {0xBCCA0000u, 41u}, // kgp -> Latn
+ {0x80EA0000u, 41u}, // kha -> Latn
+ {0x84EA0000u, 73u}, // khb -> Talu
+ {0xB4EA0000u, 16u}, // khn -> Deva
+ {0xC0EA0000u, 41u}, // khq -> Latn
+ {0xCCEA0000u, 53u}, // kht -> Mymr
+ {0xD8EA0000u, 1u}, // khw -> Arab
+ {0x6B690000u, 41u}, // ki -> Latn
+ {0xD10A0000u, 41u}, // kiu -> Latn
+ {0x6B6A0000u, 41u}, // kj -> Latn
+ {0x992A0000u, 40u}, // kjg -> Laoo
+ {0x6B6B0000u, 15u}, // kk -> Cyrl
+ {0x6B6B4146u, 1u}, // kk-AF -> Arab
+ {0x6B6B434Eu, 1u}, // kk-CN -> Arab
+ {0x6B6B4952u, 1u}, // kk-IR -> Arab
+ {0x6B6B4D4Eu, 1u}, // kk-MN -> Arab
+ {0xA54A0000u, 41u}, // kkj -> Latn
+ {0x6B6C0000u, 41u}, // kl -> Latn
+ {0xB56A0000u, 41u}, // kln -> Latn
+ {0x6B6D0000u, 35u}, // km -> Khmr
+ {0x858A0000u, 41u}, // kmb -> Latn
+ {0x6B6E0000u, 36u}, // kn -> Knda
+ {0x6B6F0000u, 37u}, // ko -> Kore
+ {0xA1CA0000u, 15u}, // koi -> Cyrl
+ {0xA9CA0000u, 16u}, // kok -> Deva
+ {0xC9CA0000u, 41u}, // kos -> Latn
+ {0x91EA0000u, 41u}, // kpe -> Latn
+ {0x8A2A0000u, 15u}, // krc -> Cyrl
+ {0xA22A0000u, 41u}, // kri -> Latn
+ {0xA62A0000u, 41u}, // krj -> Latn
+ {0xAE2A0000u, 41u}, // krl -> Latn
+ {0xD22A0000u, 16u}, // kru -> Deva
+ {0x6B730000u, 1u}, // ks -> Arab
+ {0x864A0000u, 41u}, // ksb -> Latn
+ {0x964A0000u, 41u}, // ksf -> Latn
+ {0x9E4A0000u, 41u}, // ksh -> Latn
+ {0x6B750000u, 41u}, // ku -> Latn
+ {0x6B754952u, 1u}, // ku-IR -> Arab
+ {0x6B754C42u, 1u}, // ku-LB -> Arab
+ {0xB28A0000u, 15u}, // kum -> Cyrl
+ {0x6B760000u, 15u}, // kv -> Cyrl
+ {0xC6AA0000u, 41u}, // kvr -> Latn
+ {0xDEAA0000u, 1u}, // kvx -> Arab
+ {0x6B770000u, 41u}, // kw -> Latn
+ {0xB2EA0000u, 79u}, // kxm -> Thai
+ {0xBEEA0000u, 1u}, // kxp -> Arab
+ {0x6B790000u, 15u}, // ky -> Cyrl
+ {0x6B79434Eu, 1u}, // ky-CN -> Arab
+ {0x6B795452u, 41u}, // ky-TR -> Latn
+ {0x6C610000u, 41u}, // la -> Latn
+ {0x840B0000u, 43u}, // lab -> Lina
+ {0x8C0B0000u, 27u}, // lad -> Hebr
+ {0x980B0000u, 41u}, // lag -> Latn
+ {0x9C0B0000u, 1u}, // lah -> Arab
+ {0xA40B0000u, 41u}, // laj -> Latn
+ {0x6C620000u, 41u}, // lb -> Latn
+ {0x902B0000u, 15u}, // lbe -> Cyrl
+ {0xD82B0000u, 41u}, // lbw -> Latn
+ {0xBC4B0000u, 79u}, // lcp -> Thai
+ {0xBC8B0000u, 42u}, // lep -> Lepc
+ {0xE48B0000u, 15u}, // lez -> Cyrl
+ {0x6C670000u, 41u}, // lg -> Latn
+ {0x6C690000u, 41u}, // li -> Latn
+ {0x950B0000u, 16u}, // lif -> Deva
+ {0xA50B0000u, 41u}, // lij -> Latn
+ {0xC90B0000u, 44u}, // lis -> Lisu
+ {0xBD2B0000u, 41u}, // ljp -> Latn
+ {0xA14B0000u, 1u}, // lki -> Arab
+ {0xCD4B0000u, 41u}, // lkt -> Latn
+ {0xB58B0000u, 76u}, // lmn -> Telu
+ {0xB98B0000u, 41u}, // lmo -> Latn
+ {0x6C6E0000u, 41u}, // ln -> Latn
+ {0x6C6F0000u, 40u}, // lo -> Laoo
+ {0xADCB0000u, 41u}, // lol -> Latn
+ {0xE5CB0000u, 41u}, // loz -> Latn
+ {0x8A2B0000u, 1u}, // lrc -> Arab
+ {0x6C740000u, 41u}, // lt -> Latn
+ {0x9A6B0000u, 41u}, // ltg -> Latn
+ {0x6C750000u, 41u}, // lu -> Latn
+ {0x828B0000u, 41u}, // lua -> Latn
+ {0xBA8B0000u, 41u}, // luo -> Latn
+ {0xE28B0000u, 41u}, // luy -> Latn
+ {0xE68B0000u, 1u}, // luz -> Arab
+ {0x6C760000u, 41u}, // lv -> Latn
+ {0xAECB0000u, 79u}, // lwl -> Thai
+ {0x9F2B0000u, 24u}, // lzh -> Hans
+ {0xE72B0000u, 41u}, // lzz -> Latn
+ {0x8C0C0000u, 41u}, // mad -> Latn
+ {0x940C0000u, 41u}, // maf -> Latn
+ {0x980C0000u, 16u}, // mag -> Deva
+ {0xA00C0000u, 16u}, // mai -> Deva
+ {0xA80C0000u, 41u}, // mak -> Latn
+ {0xB40C0000u, 41u}, // man -> Latn
+ {0xB40C474Eu, 55u}, // man-GN -> Nkoo
+ {0xC80C0000u, 41u}, // mas -> Latn
+ {0xE40C0000u, 41u}, // maz -> Latn
+ {0x946C0000u, 15u}, // mdf -> Cyrl
+ {0x9C6C0000u, 41u}, // mdh -> Latn
+ {0xC46C0000u, 41u}, // mdr -> Latn
+ {0xB48C0000u, 41u}, // men -> Latn
+ {0xC48C0000u, 41u}, // mer -> Latn
+ {0x80AC0000u, 1u}, // mfa -> Arab
+ {0x90AC0000u, 41u}, // mfe -> Latn
+ {0x6D670000u, 41u}, // mg -> Latn
+ {0x9CCC0000u, 41u}, // mgh -> Latn
+ {0xB8CC0000u, 41u}, // mgo -> Latn
+ {0xBCCC0000u, 16u}, // mgp -> Deva
+ {0xE0CC0000u, 41u}, // mgy -> Latn
+ {0x6D680000u, 41u}, // mh -> Latn
+ {0x6D690000u, 41u}, // mi -> Latn
+ {0xB50C0000u, 41u}, // min -> Latn
+ {0xC90C0000u, 26u}, // mis -> Hatr
+ {0x6D6B0000u, 15u}, // mk -> Cyrl
+ {0x6D6C0000u, 50u}, // ml -> Mlym
+ {0xC96C0000u, 41u}, // mls -> Latn
+ {0x6D6E0000u, 15u}, // mn -> Cyrl
+ {0x6D6E434Eu, 51u}, // mn-CN -> Mong
+ {0xA1AC0000u, 7u}, // mni -> Beng
+ {0xD9AC0000u, 53u}, // mnw -> Mymr
+ {0x91CC0000u, 41u}, // moe -> Latn
+ {0x9DCC0000u, 41u}, // moh -> Latn
+ {0xC9CC0000u, 41u}, // mos -> Latn
+ {0x6D720000u, 16u}, // mr -> Deva
+ {0x8E2C0000u, 16u}, // mrd -> Deva
+ {0xA62C0000u, 15u}, // mrj -> Cyrl
+ {0xD22C0000u, 52u}, // mru -> Mroo
+ {0x6D730000u, 41u}, // ms -> Latn
+ {0x6D734343u, 1u}, // ms-CC -> Arab
+ {0x6D734944u, 1u}, // ms-ID -> Arab
+ {0x6D740000u, 41u}, // mt -> Latn
+ {0xC66C0000u, 16u}, // mtr -> Deva
+ {0x828C0000u, 41u}, // mua -> Latn
+ {0xCA8C0000u, 41u}, // mus -> Latn
+ {0xE2AC0000u, 1u}, // mvy -> Arab
+ {0xAACC0000u, 41u}, // mwk -> Latn
+ {0xC6CC0000u, 16u}, // mwr -> Deva
+ {0xD6CC0000u, 41u}, // mwv -> Latn
+ {0x8AEC0000u, 41u}, // mxc -> Latn
+ {0x6D790000u, 53u}, // my -> Mymr
+ {0xD70C0000u, 15u}, // myv -> Cyrl
+ {0xDF0C0000u, 41u}, // myx -> Latn
+ {0xE70C0000u, 47u}, // myz -> Mand
+ {0xB72C0000u, 1u}, // mzn -> Arab
+ {0x6E610000u, 41u}, // na -> Latn
+ {0xB40D0000u, 24u}, // nan -> Hans
+ {0xBC0D0000u, 41u}, // nap -> Latn
+ {0xC00D0000u, 41u}, // naq -> Latn
+ {0x6E620000u, 41u}, // nb -> Latn
+ {0x9C4D0000u, 41u}, // nch -> Latn
+ {0x6E640000u, 41u}, // nd -> Latn
+ {0x886D0000u, 41u}, // ndc -> Latn
+ {0xC86D0000u, 41u}, // nds -> Latn
+ {0x6E650000u, 16u}, // ne -> Deva
+ {0xD88D0000u, 16u}, // new -> Deva
+ {0x6E670000u, 41u}, // ng -> Latn
+ {0xACCD0000u, 41u}, // ngl -> Latn
+ {0x90ED0000u, 41u}, // nhe -> Latn
+ {0xD8ED0000u, 41u}, // nhw -> Latn
+ {0xA50D0000u, 41u}, // nij -> Latn
+ {0xD10D0000u, 41u}, // niu -> Latn
+ {0xB92D0000u, 41u}, // njo -> Latn
+ {0x6E6C0000u, 41u}, // nl -> Latn
+ {0x998D0000u, 41u}, // nmg -> Latn
+ {0x6E6E0000u, 41u}, // nn -> Latn
+ {0x9DAD0000u, 41u}, // nnh -> Latn
+ {0x6E6F0000u, 41u}, // no -> Latn
+ {0x8DCD0000u, 39u}, // nod -> Lana
+ {0x91CD0000u, 16u}, // noe -> Deva
+ {0xB5CD0000u, 64u}, // non -> Runr
+ {0xBA0D0000u, 55u}, // nqo -> Nkoo
+ {0x6E720000u, 41u}, // nr -> Latn
+ {0xAA4D0000u, 9u}, // nsk -> Cans
+ {0xBA4D0000u, 41u}, // nso -> Latn
+ {0xCA8D0000u, 41u}, // nus -> Latn
+ {0x6E760000u, 41u}, // nv -> Latn
+ {0xC2ED0000u, 41u}, // nxq -> Latn
+ {0x6E790000u, 41u}, // ny -> Latn
+ {0xB30D0000u, 41u}, // nym -> Latn
+ {0xB70D0000u, 41u}, // nyn -> Latn
+ {0xA32D0000u, 41u}, // nzi -> Latn
+ {0x6F630000u, 41u}, // oc -> Latn
+ {0x6F6D0000u, 41u}, // om -> Latn
+ {0x6F720000u, 58u}, // or -> Orya
+ {0x6F730000u, 15u}, // os -> Cyrl
+ {0xAA6E0000u, 57u}, // otk -> Orkh
+ {0x70610000u, 23u}, // pa -> Guru
+ {0x7061504Bu, 1u}, // pa-PK -> Arab
+ {0x980F0000u, 41u}, // pag -> Latn
+ {0xAC0F0000u, 60u}, // pal -> Phli
+ {0xB00F0000u, 41u}, // pam -> Latn
+ {0xBC0F0000u, 41u}, // pap -> Latn
+ {0xD00F0000u, 41u}, // pau -> Latn
+ {0x8C4F0000u, 41u}, // pcd -> Latn
+ {0xB04F0000u, 41u}, // pcm -> Latn
+ {0x886F0000u, 41u}, // pdc -> Latn
+ {0xCC6F0000u, 41u}, // pdt -> Latn
+ {0xB88F0000u, 83u}, // peo -> Xpeo
+ {0xACAF0000u, 41u}, // pfl -> Latn
+ {0xB4EF0000u, 61u}, // phn -> Phnx
+ {0x814F0000u, 8u}, // pka -> Brah
+ {0xB94F0000u, 41u}, // pko -> Latn
+ {0x706C0000u, 41u}, // pl -> Latn
+ {0xC98F0000u, 41u}, // pms -> Latn
+ {0xCDAF0000u, 21u}, // pnt -> Grek
+ {0xB5CF0000u, 41u}, // pon -> Latn
+ {0x822F0000u, 34u}, // pra -> Khar
+ {0x8E2F0000u, 1u}, // prd -> Arab
+ {0x9A2F0000u, 41u}, // prg -> Latn
+ {0x70730000u, 1u}, // ps -> Arab
+ {0x70740000u, 41u}, // pt -> Latn
+ {0xD28F0000u, 41u}, // puu -> Latn
+ {0x71750000u, 41u}, // qu -> Latn
+ {0x8A900000u, 41u}, // quc -> Latn
+ {0x9A900000u, 41u}, // qug -> Latn
+ {0xA4110000u, 16u}, // raj -> Deva
+ {0x94510000u, 41u}, // rcf -> Latn
+ {0xA4910000u, 41u}, // rej -> Latn
+ {0xB4D10000u, 41u}, // rgn -> Latn
+ {0x81110000u, 41u}, // ria -> Latn
+ {0x95110000u, 77u}, // rif -> Tfng
+ {0x95114E4Cu, 41u}, // rif-NL -> Latn
+ {0xC9310000u, 16u}, // rjs -> Deva
+ {0xCD510000u, 7u}, // rkt -> Beng
+ {0x726D0000u, 41u}, // rm -> Latn
+ {0x95910000u, 41u}, // rmf -> Latn
+ {0xB9910000u, 41u}, // rmo -> Latn
+ {0xCD910000u, 1u}, // rmt -> Arab
+ {0xD1910000u, 41u}, // rmu -> Latn
+ {0x726E0000u, 41u}, // rn -> Latn
+ {0x99B10000u, 41u}, // rng -> Latn
+ {0x726F0000u, 41u}, // ro -> Latn
+ {0x85D10000u, 41u}, // rob -> Latn
+ {0x95D10000u, 41u}, // rof -> Latn
+ {0xB2710000u, 41u}, // rtm -> Latn
+ {0x72750000u, 15u}, // ru -> Cyrl
+ {0x92910000u, 15u}, // rue -> Cyrl
+ {0x9A910000u, 41u}, // rug -> Latn
+ {0x72770000u, 41u}, // rw -> Latn
+ {0xAAD10000u, 41u}, // rwk -> Latn
+ {0xD3110000u, 33u}, // ryu -> Kana
+ {0x73610000u, 16u}, // sa -> Deva
+ {0x94120000u, 41u}, // saf -> Latn
+ {0x9C120000u, 15u}, // sah -> Cyrl
+ {0xC0120000u, 41u}, // saq -> Latn
+ {0xC8120000u, 41u}, // sas -> Latn
+ {0xCC120000u, 41u}, // sat -> Latn
+ {0xE4120000u, 67u}, // saz -> Saur
+ {0xBC320000u, 41u}, // sbp -> Latn
+ {0x73630000u, 41u}, // sc -> Latn
+ {0xA8520000u, 16u}, // sck -> Deva
+ {0xB4520000u, 41u}, // scn -> Latn
+ {0xB8520000u, 41u}, // sco -> Latn
+ {0xC8520000u, 41u}, // scs -> Latn
+ {0x73640000u, 1u}, // sd -> Arab
+ {0x88720000u, 41u}, // sdc -> Latn
+ {0x9C720000u, 1u}, // sdh -> Arab
+ {0x73650000u, 41u}, // se -> Latn
+ {0x94920000u, 41u}, // sef -> Latn
+ {0x9C920000u, 41u}, // seh -> Latn
+ {0xA0920000u, 41u}, // sei -> Latn
+ {0xC8920000u, 41u}, // ses -> Latn
+ {0x73670000u, 41u}, // sg -> Latn
+ {0x80D20000u, 56u}, // sga -> Ogam
+ {0xC8D20000u, 41u}, // sgs -> Latn
+ {0x73680000u, 41u}, // sh -> Latn
+ {0xA0F20000u, 77u}, // shi -> Tfng
+ {0xB4F20000u, 53u}, // shn -> Mymr
+ {0x73690000u, 69u}, // si -> Sinh
+ {0x8D120000u, 41u}, // sid -> Latn
+ {0x736B0000u, 41u}, // sk -> Latn
+ {0xC5520000u, 1u}, // skr -> Arab
+ {0x736C0000u, 41u}, // sl -> Latn
+ {0xA1720000u, 41u}, // sli -> Latn
+ {0xE1720000u, 41u}, // sly -> Latn
+ {0x736D0000u, 41u}, // sm -> Latn
+ {0x81920000u, 41u}, // sma -> Latn
+ {0xA5920000u, 41u}, // smj -> Latn
+ {0xB5920000u, 41u}, // smn -> Latn
+ {0xBD920000u, 65u}, // smp -> Samr
+ {0xC9920000u, 41u}, // sms -> Latn
+ {0x736E0000u, 41u}, // sn -> Latn
+ {0xA9B20000u, 41u}, // snk -> Latn
+ {0x736F0000u, 41u}, // so -> Latn
+ {0xD1D20000u, 79u}, // sou -> Thai
+ {0x73710000u, 41u}, // sq -> Latn
+ {0x73720000u, 15u}, // sr -> Cyrl
+ {0x73724D45u, 41u}, // sr-ME -> Latn
+ {0x7372524Fu, 41u}, // sr-RO -> Latn
+ {0x73725255u, 41u}, // sr-RU -> Latn
+ {0x73725452u, 41u}, // sr-TR -> Latn
+ {0x86320000u, 70u}, // srb -> Sora
+ {0xB6320000u, 41u}, // srn -> Latn
+ {0xC6320000u, 41u}, // srr -> Latn
+ {0xDE320000u, 16u}, // srx -> Deva
+ {0x73730000u, 41u}, // ss -> Latn
+ {0xE2520000u, 41u}, // ssy -> Latn
+ {0x73740000u, 41u}, // st -> Latn
+ {0xC2720000u, 41u}, // stq -> Latn
+ {0x73750000u, 41u}, // su -> Latn
+ {0xAA920000u, 41u}, // suk -> Latn
+ {0xCA920000u, 41u}, // sus -> Latn
+ {0x73760000u, 41u}, // sv -> Latn
+ {0x73770000u, 41u}, // sw -> Latn
+ {0x86D20000u, 1u}, // swb -> Arab
+ {0x8AD20000u, 41u}, // swc -> Latn
+ {0x9AD20000u, 41u}, // swg -> Latn
+ {0xD6D20000u, 16u}, // swv -> Deva
+ {0xB6F20000u, 41u}, // sxn -> Latn
+ {0xAF120000u, 7u}, // syl -> Beng
+ {0xC7120000u, 71u}, // syr -> Syrc
+ {0xAF320000u, 41u}, // szl -> Latn
+ {0x74610000u, 74u}, // ta -> Taml
+ {0xA4130000u, 16u}, // taj -> Deva
+ {0xD8330000u, 41u}, // tbw -> Latn
+ {0xE0530000u, 36u}, // tcy -> Knda
+ {0x8C730000u, 72u}, // tdd -> Tale
+ {0x98730000u, 16u}, // tdg -> Deva
+ {0x9C730000u, 16u}, // tdh -> Deva
+ {0x74650000u, 76u}, // te -> Telu
+ {0xB0930000u, 41u}, // tem -> Latn
+ {0xB8930000u, 41u}, // teo -> Latn
+ {0xCC930000u, 41u}, // tet -> Latn
+ {0x74670000u, 15u}, // tg -> Cyrl
+ {0x7467504Bu, 1u}, // tg-PK -> Arab
+ {0x74680000u, 79u}, // th -> Thai
+ {0xACF30000u, 16u}, // thl -> Deva
+ {0xC0F30000u, 16u}, // thq -> Deva
+ {0xC4F30000u, 16u}, // thr -> Deva
+ {0x74690000u, 18u}, // ti -> Ethi
+ {0x99130000u, 18u}, // tig -> Ethi
+ {0xD5130000u, 41u}, // tiv -> Latn
+ {0x746B0000u, 41u}, // tk -> Latn
+ {0xAD530000u, 41u}, // tkl -> Latn
+ {0xC5530000u, 41u}, // tkr -> Latn
+ {0xCD530000u, 16u}, // tkt -> Deva
+ {0x746C0000u, 41u}, // tl -> Latn
+ {0xE1730000u, 41u}, // tly -> Latn
+ {0x9D930000u, 41u}, // tmh -> Latn
+ {0x746E0000u, 41u}, // tn -> Latn
+ {0x746F0000u, 41u}, // to -> Latn
+ {0x99D30000u, 41u}, // tog -> Latn
+ {0xA1F30000u, 41u}, // tpi -> Latn
+ {0x74720000u, 41u}, // tr -> Latn
+ {0xD2330000u, 41u}, // tru -> Latn
+ {0xD6330000u, 41u}, // trv -> Latn
+ {0x74730000u, 41u}, // ts -> Latn
+ {0x8E530000u, 21u}, // tsd -> Grek
+ {0x96530000u, 16u}, // tsf -> Deva
+ {0x9A530000u, 41u}, // tsg -> Latn
+ {0xA6530000u, 80u}, // tsj -> Tibt
+ {0x74740000u, 15u}, // tt -> Cyrl
+ {0xA6730000u, 41u}, // ttj -> Latn
+ {0xCA730000u, 79u}, // tts -> Thai
+ {0xCE730000u, 41u}, // ttt -> Latn
+ {0xB2930000u, 41u}, // tum -> Latn
+ {0xAEB30000u, 41u}, // tvl -> Latn
+ {0xC2D30000u, 41u}, // twq -> Latn
+ {0x74790000u, 41u}, // ty -> Latn
+ {0xD7130000u, 15u}, // tyv -> Cyrl
+ {0xB3330000u, 41u}, // tzm -> Latn
+ {0xB0740000u, 15u}, // udm -> Cyrl
+ {0x75670000u, 1u}, // ug -> Arab
+ {0x75674B5Au, 15u}, // ug-KZ -> Cyrl
+ {0x75674D4Eu, 15u}, // ug-MN -> Cyrl
+ {0x80D40000u, 81u}, // uga -> Ugar
+ {0x756B0000u, 15u}, // uk -> Cyrl
+ {0xA1740000u, 41u}, // uli -> Latn
+ {0x85940000u, 41u}, // umb -> Latn
+ {0xC5B40000u, 7u}, // unr -> Beng
+ {0xC5B44E50u, 16u}, // unr-NP -> Deva
+ {0xDDB40000u, 7u}, // unx -> Beng
+ {0x75720000u, 1u}, // ur -> Arab
+ {0x757A0000u, 41u}, // uz -> Latn
+ {0x757A4146u, 1u}, // uz-AF -> Arab
+ {0x757A434Eu, 15u}, // uz-CN -> Cyrl
+ {0xA0150000u, 82u}, // vai -> Vaii
+ {0x76650000u, 41u}, // ve -> Latn
+ {0x88950000u, 41u}, // vec -> Latn
+ {0xBC950000u, 41u}, // vep -> Latn
+ {0x76690000u, 41u}, // vi -> Latn
+ {0x89150000u, 41u}, // vic -> Latn
+ {0xC9750000u, 41u}, // vls -> Latn
+ {0x95950000u, 41u}, // vmf -> Latn
+ {0xD9950000u, 41u}, // vmw -> Latn
+ {0x766F0000u, 41u}, // vo -> Latn
+ {0xCDD50000u, 41u}, // vot -> Latn
+ {0xBA350000u, 41u}, // vro -> Latn
+ {0xB6950000u, 41u}, // vun -> Latn
+ {0x77610000u, 41u}, // wa -> Latn
+ {0x90160000u, 41u}, // wae -> Latn
+ {0xAC160000u, 18u}, // wal -> Ethi
+ {0xC4160000u, 41u}, // war -> Latn
+ {0xBC360000u, 41u}, // wbp -> Latn
+ {0xC0360000u, 76u}, // wbq -> Telu
+ {0xC4360000u, 16u}, // wbr -> Deva
+ {0xC9760000u, 41u}, // wls -> Latn
+ {0xA1B60000u, 1u}, // wni -> Arab
+ {0x776F0000u, 41u}, // wo -> Latn
+ {0xB2760000u, 16u}, // wtm -> Deva
+ {0xD2960000u, 24u}, // wuu -> Hans
+ {0xD4170000u, 41u}, // xav -> Latn
+ {0xC4570000u, 10u}, // xcr -> Cari
+ {0x78680000u, 41u}, // xh -> Latn
+ {0x89770000u, 45u}, // xlc -> Lyci
+ {0x8D770000u, 46u}, // xld -> Lydi
+ {0x95970000u, 19u}, // xmf -> Geor
+ {0xB5970000u, 48u}, // xmn -> Mani
+ {0xC5970000u, 49u}, // xmr -> Merc
+ {0x81B70000u, 54u}, // xna -> Narb
+ {0xC5B70000u, 16u}, // xnr -> Deva
+ {0x99D70000u, 41u}, // xog -> Latn
+ {0xC5F70000u, 63u}, // xpr -> Prti
+ {0x82570000u, 66u}, // xsa -> Sarb
+ {0xC6570000u, 16u}, // xsr -> Deva
+ {0xB8180000u, 41u}, // yao -> Latn
+ {0xBC180000u, 41u}, // yap -> Latn
+ {0xD4180000u, 41u}, // yav -> Latn
+ {0x84380000u, 41u}, // ybb -> Latn
+ {0x79690000u, 27u}, // yi -> Hebr
+ {0x796F0000u, 41u}, // yo -> Latn
+ {0xAE380000u, 41u}, // yrl -> Latn
+ {0x82980000u, 41u}, // yua -> Latn
+ {0x7A610000u, 41u}, // za -> Latn
+ {0x98190000u, 41u}, // zag -> Latn
+ {0xA4790000u, 1u}, // zdj -> Arab
+ {0x80990000u, 41u}, // zea -> Latn
+ {0x9CD90000u, 77u}, // zgh -> Tfng
+ {0x7A680000u, 24u}, // zh -> Hans
+ {0x7A684155u, 25u}, // zh-AU -> Hant
+ {0x7A68424Eu, 25u}, // zh-BN -> Hant
+ {0x7A684742u, 25u}, // zh-GB -> Hant
+ {0x7A684746u, 25u}, // zh-GF -> Hant
+ {0x7A68484Bu, 25u}, // zh-HK -> Hant
+ {0x7A684944u, 25u}, // zh-ID -> Hant
+ {0x7A684D4Fu, 25u}, // zh-MO -> Hant
+ {0x7A684D59u, 25u}, // zh-MY -> Hant
+ {0x7A685041u, 25u}, // zh-PA -> Hant
+ {0x7A685046u, 25u}, // zh-PF -> Hant
+ {0x7A685048u, 25u}, // zh-PH -> Hant
+ {0x7A685352u, 25u}, // zh-SR -> Hant
+ {0x7A685448u, 25u}, // zh-TH -> Hant
+ {0x7A685457u, 25u}, // zh-TW -> Hant
+ {0x7A685553u, 25u}, // zh-US -> Hant
+ {0x7A68564Eu, 25u}, // zh-VN -> Hant
+ {0xA1990000u, 41u}, // zmi -> Latn
+ {0x7A750000u, 41u}, // zu -> Latn
+ {0x83390000u, 41u}, // zza -> Latn
+});
+
+std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({
+ 0x616145544C61746Ellu, // aa_Latn_ET
+ 0x616247454379726Cllu, // ab_Cyrl_GE
+ 0xC42047484C61746Ellu, // abr_Latn_GH
+ 0x904049444C61746Ellu, // ace_Latn_ID
+ 0x9C4055474C61746Ellu, // ach_Latn_UG
+ 0x806047484C61746Ellu, // ada_Latn_GH
+ 0xE06052554379726Cllu, // ady_Cyrl_RU
+ 0x6165495241767374llu, // ae_Avst_IR
+ 0x8480544E41726162llu, // aeb_Arab_TN
+ 0x61665A414C61746Ellu, // af_Latn_ZA
+ 0xC0C0434D4C61746Ellu, // agq_Latn_CM
+ 0xB8E0494E41686F6Dllu, // aho_Ahom_IN
+ 0x616B47484C61746Ellu, // ak_Latn_GH
+ 0xA940495158737578llu, // akk_Xsux_IQ
+ 0xB560584B4C61746Ellu, // aln_Latn_XK
+ 0xCD6052554379726Cllu, // alt_Cyrl_RU
+ 0x616D455445746869llu, // am_Ethi_ET
+ 0xB9804E474C61746Ellu, // amo_Latn_NG
+ 0xE5C049444C61746Ellu, // aoz_Latn_ID
+ 0x6172454741726162llu, // ar_Arab_EG
+ 0x8A20495241726D69llu, // arc_Armi_IR
+ 0x8A204A4F4E626174llu, // arc_Nbat_JO
+ 0x8A20535950616C6Dllu, // arc_Palm_SY
+ 0xB620434C4C61746Ellu, // arn_Latn_CL
+ 0xBA20424F4C61746Ellu, // aro_Latn_BO
+ 0xC220445A41726162llu, // arq_Arab_DZ
+ 0xE2204D4141726162llu, // ary_Arab_MA
+ 0xE620454741726162llu, // arz_Arab_EG
+ 0x6173494E42656E67llu, // as_Beng_IN
+ 0x8240545A4C61746Ellu, // asa_Latn_TZ
+ 0x9240555353676E77llu, // ase_Sgnw_US
+ 0xCE4045534C61746Ellu, // ast_Latn_ES
+ 0xA66043414C61746Ellu, // atj_Latn_CA
+ 0x617652554379726Cllu, // av_Cyrl_RU
+ 0x82C0494E44657661llu, // awa_Deva_IN
+ 0x6179424F4C61746Ellu, // ay_Latn_BO
+ 0x617A495241726162llu, // az_Arab_IR
+ 0x617A415A4C61746Ellu, // az_Latn_AZ
+ 0x626152554379726Cllu, // ba_Cyrl_RU
+ 0xAC01504B41726162llu, // bal_Arab_PK
+ 0xB40149444C61746Ellu, // ban_Latn_ID
+ 0xBC014E5044657661llu, // bap_Deva_NP
+ 0xC40141544C61746Ellu, // bar_Latn_AT
+ 0xC801434D4C61746Ellu, // bas_Latn_CM
+ 0xDC01434D42616D75llu, // bax_Bamu_CM
+ 0x882149444C61746Ellu, // bbc_Latn_ID
+ 0xA421434D4C61746Ellu, // bbj_Latn_CM
+ 0xA04143494C61746Ellu, // bci_Latn_CI
+ 0x626542594379726Cllu, // be_Cyrl_BY
+ 0xA481534441726162llu, // bej_Arab_SD
+ 0xB0815A4D4C61746Ellu, // bem_Latn_ZM
+ 0xD88149444C61746Ellu, // bew_Latn_ID
+ 0xE481545A4C61746Ellu, // bez_Latn_TZ
+ 0x8CA1434D4C61746Ellu, // bfd_Latn_CM
+ 0xC0A1494E54616D6Cllu, // bfq_Taml_IN
+ 0xCCA1504B41726162llu, // bft_Arab_PK
+ 0xE0A1494E44657661llu, // bfy_Deva_IN
+ 0x626742474379726Cllu, // bg_Cyrl_BG
+ 0x88C1494E44657661llu, // bgc_Deva_IN
+ 0xB4C1504B41726162llu, // bgn_Arab_PK
+ 0xDCC154524772656Bllu, // bgx_Grek_TR
+ 0x6268494E4B746869llu, // bh_Kthi_IN
+ 0x84E1494E44657661llu, // bhb_Deva_IN
+ 0xA0E1494E44657661llu, // bhi_Deva_IN
+ 0xA8E150484C61746Ellu, // bhk_Latn_PH
+ 0xB8E1494E44657661llu, // bho_Deva_IN
+ 0x626956554C61746Ellu, // bi_Latn_VU
+ 0xA90150484C61746Ellu, // bik_Latn_PH
+ 0xB5014E474C61746Ellu, // bin_Latn_NG
+ 0xA521494E44657661llu, // bjj_Deva_IN
+ 0xB52149444C61746Ellu, // bjn_Latn_ID
+ 0xB141434D4C61746Ellu, // bkm_Latn_CM
+ 0xD14150484C61746Ellu, // bku_Latn_PH
+ 0xCD61564E54617674llu, // blt_Tavt_VN
+ 0x626D4D4C4C61746Ellu, // bm_Latn_ML
+ 0xC1814D4C4C61746Ellu, // bmq_Latn_ML
+ 0x626E424442656E67llu, // bn_Beng_BD
+ 0x626F434E54696274llu, // bo_Tibt_CN
+ 0xE1E1494E42656E67llu, // bpy_Beng_IN
+ 0xA201495241726162llu, // bqi_Arab_IR
+ 0xD60143494C61746Ellu, // bqv_Latn_CI
+ 0x627246524C61746Ellu, // br_Latn_FR
+ 0x8221494E44657661llu, // bra_Deva_IN
+ 0x9E21504B41726162llu, // brh_Arab_PK
+ 0xDE21494E44657661llu, // brx_Deva_IN
+ 0x627342414C61746Ellu, // bs_Latn_BA
+ 0xC2414C5242617373llu, // bsq_Bass_LR
+ 0xCA41434D4C61746Ellu, // bss_Latn_CM
+ 0xBA6150484C61746Ellu, // bto_Latn_PH
+ 0xD661504B44657661llu, // btv_Deva_PK
+ 0x828152554379726Cllu, // bua_Cyrl_RU
+ 0x8A8159544C61746Ellu, // buc_Latn_YT
+ 0x9A8149444C61746Ellu, // bug_Latn_ID
+ 0xB281434D4C61746Ellu, // bum_Latn_CM
+ 0x86A147514C61746Ellu, // bvb_Latn_GQ
+ 0xB701455245746869llu, // byn_Ethi_ER
+ 0xD701434D4C61746Ellu, // byv_Latn_CM
+ 0x93214D4C4C61746Ellu, // bze_Latn_ML
+ 0x636145534C61746Ellu, // ca_Latn_ES
+ 0x9C424E474C61746Ellu, // cch_Latn_NG
+ 0xBC42494E42656E67llu, // ccp_Beng_IN
+ 0xBC42424443616B6Dllu, // ccp_Cakm_BD
+ 0x636552554379726Cllu, // ce_Cyrl_RU
+ 0x848250484C61746Ellu, // ceb_Latn_PH
+ 0x98C255474C61746Ellu, // cgg_Latn_UG
+ 0x636847554C61746Ellu, // ch_Latn_GU
+ 0xA8E2464D4C61746Ellu, // chk_Latn_FM
+ 0xB0E252554379726Cllu, // chm_Cyrl_RU
+ 0xB8E255534C61746Ellu, // cho_Latn_US
+ 0xBCE243414C61746Ellu, // chp_Latn_CA
+ 0xC4E2555343686572llu, // chr_Cher_US
+ 0x81224B4841726162llu, // cja_Arab_KH
+ 0xB122564E4368616Dllu, // cjm_Cham_VN
+ 0x8542495141726162llu, // ckb_Arab_IQ
+ 0x636F46524C61746Ellu, // co_Latn_FR
+ 0xBDC24547436F7074llu, // cop_Copt_EG
+ 0xC9E250484C61746Ellu, // cps_Latn_PH
+ 0x6372434143616E73llu, // cr_Cans_CA
+ 0xA622434143616E73llu, // crj_Cans_CA
+ 0xAA22434143616E73llu, // crk_Cans_CA
+ 0xAE22434143616E73llu, // crl_Cans_CA
+ 0xB222434143616E73llu, // crm_Cans_CA
+ 0xCA2253434C61746Ellu, // crs_Latn_SC
+ 0x6373435A4C61746Ellu, // cs_Latn_CZ
+ 0x8642504C4C61746Ellu, // csb_Latn_PL
+ 0xDA42434143616E73llu, // csw_Cans_CA
+ 0x8E624D4D50617563llu, // ctd_Pauc_MM
+ 0x637552554379726Cllu, // cu_Cyrl_RU
+ 0x63754247476C6167llu, // cu_Glag_BG
+ 0x637652554379726Cllu, // cv_Cyrl_RU
+ 0x637947424C61746Ellu, // cy_Latn_GB
+ 0x6461444B4C61746Ellu, // da_Latn_DK
+ 0xA80355534C61746Ellu, // dak_Latn_US
+ 0xC40352554379726Cllu, // dar_Cyrl_RU
+ 0xD4034B454C61746Ellu, // dav_Latn_KE
+ 0x8843494E41726162llu, // dcc_Arab_IN
+ 0x646544454C61746Ellu, // de_Latn_DE
+ 0xB48343414C61746Ellu, // den_Latn_CA
+ 0xC4C343414C61746Ellu, // dgr_Latn_CA
+ 0x91234E454C61746Ellu, // dje_Latn_NE
+ 0xA5A343494C61746Ellu, // dnj_Latn_CI
+ 0xA1C3494E41726162llu, // doi_Arab_IN
+ 0x864344454C61746Ellu, // dsb_Latn_DE
+ 0xB2634D4C4C61746Ellu, // dtm_Latn_ML
+ 0xBE634D594C61746Ellu, // dtp_Latn_MY
+ 0x8283434D4C61746Ellu, // dua_Latn_CM
+ 0x64764D5654686161llu, // dv_Thaa_MV
+ 0xBB03534E4C61746Ellu, // dyo_Latn_SN
+ 0xD30342464C61746Ellu, // dyu_Latn_BF
+ 0x647A425454696274llu, // dz_Tibt_BT
+ 0xD0244B454C61746Ellu, // ebu_Latn_KE
+ 0x656547484C61746Ellu, // ee_Latn_GH
+ 0xA0A44E474C61746Ellu, // efi_Latn_NG
+ 0xACC449544C61746Ellu, // egl_Latn_IT
+ 0xE0C4454745677970llu, // egy_Egyp_EG
+ 0xE1444D4D4B616C69llu, // eky_Kali_MM
+ 0x656C47524772656Bllu, // el_Grek_GR
+ 0x656E47424C61746Ellu, // en_Latn_GB
+ 0x656E55534C61746Ellu, // en_Latn_US
+ 0x656E474253686177llu, // en_Shaw_GB
+ 0x657345534C61746Ellu, // es_Latn_ES
+ 0x65734D584C61746Ellu, // es_Latn_MX
+ 0x657355534C61746Ellu, // es_Latn_US
+ 0xD24455534C61746Ellu, // esu_Latn_US
+ 0x657445454C61746Ellu, // et_Latn_EE
+ 0xCE6449544974616Cllu, // ett_Ital_IT
+ 0x657545534C61746Ellu, // eu_Latn_ES
+ 0xBAC4434D4C61746Ellu, // ewo_Latn_CM
+ 0xCEE445534C61746Ellu, // ext_Latn_ES
+ 0x6661495241726162llu, // fa_Arab_IR
+ 0xB40547514C61746Ellu, // fan_Latn_GQ
+ 0x6666534E4C61746Ellu, // ff_Latn_SN
+ 0xB0A54D4C4C61746Ellu, // ffm_Latn_ML
+ 0x666946494C61746Ellu, // fi_Latn_FI
+ 0x8105534441726162llu, // fia_Arab_SD
+ 0xAD0550484C61746Ellu, // fil_Latn_PH
+ 0xCD0553454C61746Ellu, // fit_Latn_SE
+ 0x666A464A4C61746Ellu, // fj_Latn_FJ
+ 0x666F464F4C61746Ellu, // fo_Latn_FO
+ 0xB5C5424A4C61746Ellu, // fon_Latn_BJ
+ 0x667246524C61746Ellu, // fr_Latn_FR
+ 0x8A2555534C61746Ellu, // frc_Latn_US
+ 0xBE2546524C61746Ellu, // frp_Latn_FR
+ 0xC62544454C61746Ellu, // frr_Latn_DE
+ 0xCA2544454C61746Ellu, // frs_Latn_DE
+ 0x8E8557464C61746Ellu, // fud_Latn_WF
+ 0xC2854E454C61746Ellu, // fuq_Latn_NE
+ 0xC68549544C61746Ellu, // fur_Latn_IT
+ 0xD6854E474C61746Ellu, // fuv_Latn_NG
+ 0xC6A553444C61746Ellu, // fvr_Latn_SD
+ 0x66794E4C4C61746Ellu, // fy_Latn_NL
+ 0x676149454C61746Ellu, // ga_Latn_IE
+ 0x800647484C61746Ellu, // gaa_Latn_GH
+ 0x98064D444C61746Ellu, // gag_Latn_MD
+ 0xB406434E48616E73llu, // gan_Hans_CN
+ 0xE00649444C61746Ellu, // gay_Latn_ID
+ 0xB026494E44657661llu, // gbm_Deva_IN
+ 0xE426495241726162llu, // gbz_Arab_IR
+ 0xC44647464C61746Ellu, // gcr_Latn_GF
+ 0x676447424C61746Ellu, // gd_Latn_GB
+ 0xE486455445746869llu, // gez_Ethi_ET
+ 0xB4C64E5044657661llu, // ggn_Deva_NP
+ 0xAD064B494C61746Ellu, // gil_Latn_KI
+ 0xA926504B41726162llu, // gjk_Arab_PK
+ 0xD126504B41726162llu, // gju_Arab_PK
+ 0x676C45534C61746Ellu, // gl_Latn_ES
+ 0xA966495241726162llu, // glk_Arab_IR
+ 0x676E50594C61746Ellu, // gn_Latn_PY
+ 0xB1C6494E44657661llu, // gom_Deva_IN
+ 0xB5C6494E54656C75llu, // gon_Telu_IN
+ 0xC5C649444C61746Ellu, // gor_Latn_ID
+ 0xC9C64E4C4C61746Ellu, // gos_Latn_NL
+ 0xCDC65541476F7468llu, // got_Goth_UA
+ 0x8A26435943707274llu, // grc_Cprt_CY
+ 0x8A2647524C696E62llu, // grc_Linb_GR
+ 0xCE26494E42656E67llu, // grt_Beng_IN
+ 0xDA4643484C61746Ellu, // gsw_Latn_CH
+ 0x6775494E47756A72llu, // gu_Gujr_IN
+ 0x868642524C61746Ellu, // gub_Latn_BR
+ 0x8A86434F4C61746Ellu, // guc_Latn_CO
+ 0xC68647484C61746Ellu, // gur_Latn_GH
+ 0xE6864B454C61746Ellu, // guz_Latn_KE
+ 0x6776494D4C61746Ellu, // gv_Latn_IM
+ 0xC6A64E5044657661llu, // gvr_Deva_NP
+ 0xA2C643414C61746Ellu, // gwi_Latn_CA
+ 0x68614E474C61746Ellu, // ha_Latn_NG
+ 0xA807434E48616E73llu, // hak_Hans_CN
+ 0xD80755534C61746Ellu, // haw_Latn_US
+ 0xE407414641726162llu, // haz_Arab_AF
+ 0x6865494C48656272llu, // he_Hebr_IL
+ 0x6869494E44657661llu, // hi_Deva_IN
+ 0x9507464A4C61746Ellu, // hif_Latn_FJ
+ 0xAD0750484C61746Ellu, // hil_Latn_PH
+ 0xD1675452486C7577llu, // hlu_Hluw_TR
+ 0x8D87434E506C7264llu, // hmd_Plrd_CN
+ 0x8DA7504B41726162llu, // hnd_Arab_PK
+ 0x91A7494E44657661llu, // hne_Deva_IN
+ 0xA5A74C41486D6E67llu, // hnj_Hmng_LA
+ 0xB5A750484C61746Ellu, // hnn_Latn_PH
+ 0xB9A7504B41726162llu, // hno_Arab_PK
+ 0x686F50474C61746Ellu, // ho_Latn_PG
+ 0x89C7494E44657661llu, // hoc_Deva_IN
+ 0xA5C7494E44657661llu, // hoj_Deva_IN
+ 0x687248524C61746Ellu, // hr_Latn_HR
+ 0x864744454C61746Ellu, // hsb_Latn_DE
+ 0xB647434E48616E73llu, // hsn_Hans_CN
+ 0x687448544C61746Ellu, // ht_Latn_HT
+ 0x687548554C61746Ellu, // hu_Latn_HU
+ 0x6879414D41726D6Ellu, // hy_Armn_AM
+ 0x687A4E414C61746Ellu, // hz_Latn_NA
+ 0x696146524C61746Ellu, // ia_Latn_FR
+ 0x80284D594C61746Ellu, // iba_Latn_MY
+ 0x84284E474C61746Ellu, // ibb_Latn_NG
+ 0x696449444C61746Ellu, // id_Latn_ID
+ 0x69674E474C61746Ellu, // ig_Latn_NG
+ 0x6969434E59696969llu, // ii_Yiii_CN
+ 0x696B55534C61746Ellu, // ik_Latn_US
+ 0xCD4843414C61746Ellu, // ikt_Latn_CA
+ 0xB96850484C61746Ellu, // ilo_Latn_PH
+ 0x696E49444C61746Ellu, // in_Latn_ID
+ 0x9DA852554379726Cllu, // inh_Cyrl_RU
+ 0x697349534C61746Ellu, // is_Latn_IS
+ 0x697449544C61746Ellu, // it_Latn_IT
+ 0x6975434143616E73llu, // iu_Cans_CA
+ 0x6977494C48656272llu, // iw_Hebr_IL
+ 0x9F2852554C61746Ellu, // izh_Latn_RU
+ 0x6A614A504A70616Ellu, // ja_Jpan_JP
+ 0xB0094A4D4C61746Ellu, // jam_Latn_JM
+ 0xB8C9434D4C61746Ellu, // jgo_Latn_CM
+ 0x6A69554148656272llu, // ji_Hebr_UA
+ 0x8989545A4C61746Ellu, // jmc_Latn_TZ
+ 0xAD894E5044657661llu, // jml_Deva_NP
+ 0xCE89444B4C61746Ellu, // jut_Latn_DK
+ 0x6A7649444C61746Ellu, // jv_Latn_ID
+ 0x6A7749444C61746Ellu, // jw_Latn_ID
+ 0x6B61474547656F72llu, // ka_Geor_GE
+ 0x800A555A4379726Cllu, // kaa_Cyrl_UZ
+ 0x840A445A4C61746Ellu, // kab_Latn_DZ
+ 0x880A4D4D4C61746Ellu, // kac_Latn_MM
+ 0xA40A4E474C61746Ellu, // kaj_Latn_NG
+ 0xB00A4B454C61746Ellu, // kam_Latn_KE
+ 0xB80A4D4C4C61746Ellu, // kao_Latn_ML
+ 0x8C2A52554379726Cllu, // kbd_Cyrl_RU
+ 0x984A4E474C61746Ellu, // kcg_Latn_NG
+ 0xA84A5A574C61746Ellu, // kck_Latn_ZW
+ 0x906A545A4C61746Ellu, // kde_Latn_TZ
+ 0xCC6A544854686169llu, // kdt_Thai_TH
+ 0x808A43564C61746Ellu, // kea_Latn_CV
+ 0xB48A434D4C61746Ellu, // ken_Latn_CM
+ 0xB8AA43494C61746Ellu, // kfo_Latn_CI
+ 0xC4AA494E44657661llu, // kfr_Deva_IN
+ 0xE0AA494E44657661llu, // kfy_Deva_IN
+ 0x6B6743444C61746Ellu, // kg_Latn_CD
+ 0x90CA49444C61746Ellu, // kge_Latn_ID
+ 0xBCCA42524C61746Ellu, // kgp_Latn_BR
+ 0x80EA494E4C61746Ellu, // kha_Latn_IN
+ 0x84EA434E54616C75llu, // khb_Talu_CN
+ 0xB4EA494E44657661llu, // khn_Deva_IN
+ 0xC0EA4D4C4C61746Ellu, // khq_Latn_ML
+ 0xCCEA494E4D796D72llu, // kht_Mymr_IN
+ 0xD8EA504B41726162llu, // khw_Arab_PK
+ 0x6B694B454C61746Ellu, // ki_Latn_KE
+ 0xD10A54524C61746Ellu, // kiu_Latn_TR
+ 0x6B6A4E414C61746Ellu, // kj_Latn_NA
+ 0x992A4C414C616F6Fllu, // kjg_Laoo_LA
+ 0x6B6B434E41726162llu, // kk_Arab_CN
+ 0x6B6B4B5A4379726Cllu, // kk_Cyrl_KZ
+ 0xA54A434D4C61746Ellu, // kkj_Latn_CM
+ 0x6B6C474C4C61746Ellu, // kl_Latn_GL
+ 0xB56A4B454C61746Ellu, // kln_Latn_KE
+ 0x6B6D4B484B686D72llu, // km_Khmr_KH
+ 0x858A414F4C61746Ellu, // kmb_Latn_AO
+ 0x6B6E494E4B6E6461llu, // kn_Knda_IN
+ 0x6B6F4B524B6F7265llu, // ko_Kore_KR
+ 0xA1CA52554379726Cllu, // koi_Cyrl_RU
+ 0xA9CA494E44657661llu, // kok_Deva_IN
+ 0xC9CA464D4C61746Ellu, // kos_Latn_FM
+ 0x91EA4C524C61746Ellu, // kpe_Latn_LR
+ 0x8A2A52554379726Cllu, // krc_Cyrl_RU
+ 0xA22A534C4C61746Ellu, // kri_Latn_SL
+ 0xA62A50484C61746Ellu, // krj_Latn_PH
+ 0xAE2A52554C61746Ellu, // krl_Latn_RU
+ 0xD22A494E44657661llu, // kru_Deva_IN
+ 0x6B73494E41726162llu, // ks_Arab_IN
+ 0x864A545A4C61746Ellu, // ksb_Latn_TZ
+ 0x964A434D4C61746Ellu, // ksf_Latn_CM
+ 0x9E4A44454C61746Ellu, // ksh_Latn_DE
+ 0x6B75495141726162llu, // ku_Arab_IQ
+ 0x6B7554524C61746Ellu, // ku_Latn_TR
+ 0xB28A52554379726Cllu, // kum_Cyrl_RU
+ 0x6B7652554379726Cllu, // kv_Cyrl_RU
+ 0xC6AA49444C61746Ellu, // kvr_Latn_ID
+ 0xDEAA504B41726162llu, // kvx_Arab_PK
+ 0x6B7747424C61746Ellu, // kw_Latn_GB
+ 0xB2EA544854686169llu, // kxm_Thai_TH
+ 0xBEEA504B41726162llu, // kxp_Arab_PK
+ 0x6B79434E41726162llu, // ky_Arab_CN
+ 0x6B794B474379726Cllu, // ky_Cyrl_KG
+ 0x6B7954524C61746Ellu, // ky_Latn_TR
+ 0x6C6156414C61746Ellu, // la_Latn_VA
+ 0x840B47524C696E61llu, // lab_Lina_GR
+ 0x8C0B494C48656272llu, // lad_Hebr_IL
+ 0x980B545A4C61746Ellu, // lag_Latn_TZ
+ 0x9C0B504B41726162llu, // lah_Arab_PK
+ 0xA40B55474C61746Ellu, // laj_Latn_UG
+ 0x6C624C554C61746Ellu, // lb_Latn_LU
+ 0x902B52554379726Cllu, // lbe_Cyrl_RU
+ 0xD82B49444C61746Ellu, // lbw_Latn_ID
+ 0xBC4B434E54686169llu, // lcp_Thai_CN
+ 0xBC8B494E4C657063llu, // lep_Lepc_IN
+ 0xE48B52554379726Cllu, // lez_Cyrl_RU
+ 0x6C6755474C61746Ellu, // lg_Latn_UG
+ 0x6C694E4C4C61746Ellu, // li_Latn_NL
+ 0x950B4E5044657661llu, // lif_Deva_NP
+ 0x950B494E4C696D62llu, // lif_Limb_IN
+ 0xA50B49544C61746Ellu, // lij_Latn_IT
+ 0xC90B434E4C697375llu, // lis_Lisu_CN
+ 0xBD2B49444C61746Ellu, // ljp_Latn_ID
+ 0xA14B495241726162llu, // lki_Arab_IR
+ 0xCD4B55534C61746Ellu, // lkt_Latn_US
+ 0xB58B494E54656C75llu, // lmn_Telu_IN
+ 0xB98B49544C61746Ellu, // lmo_Latn_IT
+ 0x6C6E43444C61746Ellu, // ln_Latn_CD
+ 0x6C6F4C414C616F6Fllu, // lo_Laoo_LA
+ 0xADCB43444C61746Ellu, // lol_Latn_CD
+ 0xE5CB5A4D4C61746Ellu, // loz_Latn_ZM
+ 0x8A2B495241726162llu, // lrc_Arab_IR
+ 0x6C744C544C61746Ellu, // lt_Latn_LT
+ 0x9A6B4C564C61746Ellu, // ltg_Latn_LV
+ 0x6C7543444C61746Ellu, // lu_Latn_CD
+ 0x828B43444C61746Ellu, // lua_Latn_CD
+ 0xBA8B4B454C61746Ellu, // luo_Latn_KE
+ 0xE28B4B454C61746Ellu, // luy_Latn_KE
+ 0xE68B495241726162llu, // luz_Arab_IR
+ 0x6C764C564C61746Ellu, // lv_Latn_LV
+ 0xAECB544854686169llu, // lwl_Thai_TH
+ 0x9F2B434E48616E73llu, // lzh_Hans_CN
+ 0xE72B54524C61746Ellu, // lzz_Latn_TR
+ 0x8C0C49444C61746Ellu, // mad_Latn_ID
+ 0x940C434D4C61746Ellu, // maf_Latn_CM
+ 0x980C494E44657661llu, // mag_Deva_IN
+ 0xA00C494E44657661llu, // mai_Deva_IN
+ 0xA80C49444C61746Ellu, // mak_Latn_ID
+ 0xB40C474D4C61746Ellu, // man_Latn_GM
+ 0xB40C474E4E6B6F6Fllu, // man_Nkoo_GN
+ 0xC80C4B454C61746Ellu, // mas_Latn_KE
+ 0xE40C4D584C61746Ellu, // maz_Latn_MX
+ 0x946C52554379726Cllu, // mdf_Cyrl_RU
+ 0x9C6C50484C61746Ellu, // mdh_Latn_PH
+ 0xC46C49444C61746Ellu, // mdr_Latn_ID
+ 0xB48C534C4C61746Ellu, // men_Latn_SL
+ 0xC48C4B454C61746Ellu, // mer_Latn_KE
+ 0x80AC544841726162llu, // mfa_Arab_TH
+ 0x90AC4D554C61746Ellu, // mfe_Latn_MU
+ 0x6D674D474C61746Ellu, // mg_Latn_MG
+ 0x9CCC4D5A4C61746Ellu, // mgh_Latn_MZ
+ 0xB8CC434D4C61746Ellu, // mgo_Latn_CM
+ 0xBCCC4E5044657661llu, // mgp_Deva_NP
+ 0xE0CC545A4C61746Ellu, // mgy_Latn_TZ
+ 0x6D684D484C61746Ellu, // mh_Latn_MH
+ 0x6D694E5A4C61746Ellu, // mi_Latn_NZ
+ 0xB50C49444C61746Ellu, // min_Latn_ID
+ 0xC90C495148617472llu, // mis_Hatr_IQ
+ 0x6D6B4D4B4379726Cllu, // mk_Cyrl_MK
+ 0x6D6C494E4D6C796Dllu, // ml_Mlym_IN
+ 0xC96C53444C61746Ellu, // mls_Latn_SD
+ 0x6D6E4D4E4379726Cllu, // mn_Cyrl_MN
+ 0x6D6E434E4D6F6E67llu, // mn_Mong_CN
+ 0xA1AC494E42656E67llu, // mni_Beng_IN
+ 0xD9AC4D4D4D796D72llu, // mnw_Mymr_MM
+ 0x91CC43414C61746Ellu, // moe_Latn_CA
+ 0x9DCC43414C61746Ellu, // moh_Latn_CA
+ 0xC9CC42464C61746Ellu, // mos_Latn_BF
+ 0x6D72494E44657661llu, // mr_Deva_IN
+ 0x8E2C4E5044657661llu, // mrd_Deva_NP
+ 0xA62C52554379726Cllu, // mrj_Cyrl_RU
+ 0xD22C42444D726F6Fllu, // mru_Mroo_BD
+ 0x6D734D594C61746Ellu, // ms_Latn_MY
+ 0x6D744D544C61746Ellu, // mt_Latn_MT
+ 0xC66C494E44657661llu, // mtr_Deva_IN
+ 0x828C434D4C61746Ellu, // mua_Latn_CM
+ 0xCA8C55534C61746Ellu, // mus_Latn_US
+ 0xE2AC504B41726162llu, // mvy_Arab_PK
+ 0xAACC4D4C4C61746Ellu, // mwk_Latn_ML
+ 0xC6CC494E44657661llu, // mwr_Deva_IN
+ 0xD6CC49444C61746Ellu, // mwv_Latn_ID
+ 0x8AEC5A574C61746Ellu, // mxc_Latn_ZW
+ 0x6D794D4D4D796D72llu, // my_Mymr_MM
+ 0xD70C52554379726Cllu, // myv_Cyrl_RU
+ 0xDF0C55474C61746Ellu, // myx_Latn_UG
+ 0xE70C49524D616E64llu, // myz_Mand_IR
+ 0xB72C495241726162llu, // mzn_Arab_IR
+ 0x6E614E524C61746Ellu, // na_Latn_NR
+ 0xB40D434E48616E73llu, // nan_Hans_CN
+ 0xBC0D49544C61746Ellu, // nap_Latn_IT
+ 0xC00D4E414C61746Ellu, // naq_Latn_NA
+ 0x6E624E4F4C61746Ellu, // nb_Latn_NO
+ 0x9C4D4D584C61746Ellu, // nch_Latn_MX
+ 0x6E645A574C61746Ellu, // nd_Latn_ZW
+ 0x886D4D5A4C61746Ellu, // ndc_Latn_MZ
+ 0xC86D44454C61746Ellu, // nds_Latn_DE
+ 0x6E654E5044657661llu, // ne_Deva_NP
+ 0xD88D4E5044657661llu, // new_Deva_NP
+ 0x6E674E414C61746Ellu, // ng_Latn_NA
+ 0xACCD4D5A4C61746Ellu, // ngl_Latn_MZ
+ 0x90ED4D584C61746Ellu, // nhe_Latn_MX
+ 0xD8ED4D584C61746Ellu, // nhw_Latn_MX
+ 0xA50D49444C61746Ellu, // nij_Latn_ID
+ 0xD10D4E554C61746Ellu, // niu_Latn_NU
+ 0xB92D494E4C61746Ellu, // njo_Latn_IN
+ 0x6E6C4E4C4C61746Ellu, // nl_Latn_NL
+ 0x998D434D4C61746Ellu, // nmg_Latn_CM
+ 0x6E6E4E4F4C61746Ellu, // nn_Latn_NO
+ 0x9DAD434D4C61746Ellu, // nnh_Latn_CM
+ 0x6E6F4E4F4C61746Ellu, // no_Latn_NO
+ 0x8DCD54484C616E61llu, // nod_Lana_TH
+ 0x91CD494E44657661llu, // noe_Deva_IN
+ 0xB5CD534552756E72llu, // non_Runr_SE
+ 0xBA0D474E4E6B6F6Fllu, // nqo_Nkoo_GN
+ 0x6E725A414C61746Ellu, // nr_Latn_ZA
+ 0xAA4D434143616E73llu, // nsk_Cans_CA
+ 0xBA4D5A414C61746Ellu, // nso_Latn_ZA
+ 0xCA8D53534C61746Ellu, // nus_Latn_SS
+ 0x6E7655534C61746Ellu, // nv_Latn_US
+ 0xC2ED434E4C61746Ellu, // nxq_Latn_CN
+ 0x6E794D574C61746Ellu, // ny_Latn_MW
+ 0xB30D545A4C61746Ellu, // nym_Latn_TZ
+ 0xB70D55474C61746Ellu, // nyn_Latn_UG
+ 0xA32D47484C61746Ellu, // nzi_Latn_GH
+ 0x6F6346524C61746Ellu, // oc_Latn_FR
+ 0x6F6D45544C61746Ellu, // om_Latn_ET
+ 0x6F72494E4F727961llu, // or_Orya_IN
+ 0x6F7347454379726Cllu, // os_Cyrl_GE
+ 0xAA6E4D4E4F726B68llu, // otk_Orkh_MN
+ 0x7061504B41726162llu, // pa_Arab_PK
+ 0x7061494E47757275llu, // pa_Guru_IN
+ 0x980F50484C61746Ellu, // pag_Latn_PH
+ 0xAC0F495250686C69llu, // pal_Phli_IR
+ 0xAC0F434E50686C70llu, // pal_Phlp_CN
+ 0xB00F50484C61746Ellu, // pam_Latn_PH
+ 0xBC0F41574C61746Ellu, // pap_Latn_AW
+ 0xD00F50574C61746Ellu, // pau_Latn_PW
+ 0x8C4F46524C61746Ellu, // pcd_Latn_FR
+ 0xB04F4E474C61746Ellu, // pcm_Latn_NG
+ 0x886F55534C61746Ellu, // pdc_Latn_US
+ 0xCC6F43414C61746Ellu, // pdt_Latn_CA
+ 0xB88F49525870656Fllu, // peo_Xpeo_IR
+ 0xACAF44454C61746Ellu, // pfl_Latn_DE
+ 0xB4EF4C4250686E78llu, // phn_Phnx_LB
+ 0x814F494E42726168llu, // pka_Brah_IN
+ 0xB94F4B454C61746Ellu, // pko_Latn_KE
+ 0x706C504C4C61746Ellu, // pl_Latn_PL
+ 0xC98F49544C61746Ellu, // pms_Latn_IT
+ 0xCDAF47524772656Bllu, // pnt_Grek_GR
+ 0xB5CF464D4C61746Ellu, // pon_Latn_FM
+ 0x822F504B4B686172llu, // pra_Khar_PK
+ 0x8E2F495241726162llu, // prd_Arab_IR
+ 0x7073414641726162llu, // ps_Arab_AF
+ 0x707442524C61746Ellu, // pt_Latn_BR
+ 0xD28F47414C61746Ellu, // puu_Latn_GA
+ 0x717550454C61746Ellu, // qu_Latn_PE
+ 0x8A9047544C61746Ellu, // quc_Latn_GT
+ 0x9A9045434C61746Ellu, // qug_Latn_EC
+ 0xA411494E44657661llu, // raj_Deva_IN
+ 0x945152454C61746Ellu, // rcf_Latn_RE
+ 0xA49149444C61746Ellu, // rej_Latn_ID
+ 0xB4D149544C61746Ellu, // rgn_Latn_IT
+ 0x8111494E4C61746Ellu, // ria_Latn_IN
+ 0x95114D4154666E67llu, // rif_Tfng_MA
+ 0xC9314E5044657661llu, // rjs_Deva_NP
+ 0xCD51424442656E67llu, // rkt_Beng_BD
+ 0x726D43484C61746Ellu, // rm_Latn_CH
+ 0x959146494C61746Ellu, // rmf_Latn_FI
+ 0xB99143484C61746Ellu, // rmo_Latn_CH
+ 0xCD91495241726162llu, // rmt_Arab_IR
+ 0xD19153454C61746Ellu, // rmu_Latn_SE
+ 0x726E42494C61746Ellu, // rn_Latn_BI
+ 0x99B14D5A4C61746Ellu, // rng_Latn_MZ
+ 0x726F524F4C61746Ellu, // ro_Latn_RO
+ 0x85D149444C61746Ellu, // rob_Latn_ID
+ 0x95D1545A4C61746Ellu, // rof_Latn_TZ
+ 0xB271464A4C61746Ellu, // rtm_Latn_FJ
+ 0x727552554379726Cllu, // ru_Cyrl_RU
+ 0x929155414379726Cllu, // rue_Cyrl_UA
+ 0x9A9153424C61746Ellu, // rug_Latn_SB
+ 0x727752574C61746Ellu, // rw_Latn_RW
+ 0xAAD1545A4C61746Ellu, // rwk_Latn_TZ
+ 0xD3114A504B616E61llu, // ryu_Kana_JP
+ 0x7361494E44657661llu, // sa_Deva_IN
+ 0x941247484C61746Ellu, // saf_Latn_GH
+ 0x9C1252554379726Cllu, // sah_Cyrl_RU
+ 0xC0124B454C61746Ellu, // saq_Latn_KE
+ 0xC81249444C61746Ellu, // sas_Latn_ID
+ 0xCC12494E4C61746Ellu, // sat_Latn_IN
+ 0xE412494E53617572llu, // saz_Saur_IN
+ 0xBC32545A4C61746Ellu, // sbp_Latn_TZ
+ 0x736349544C61746Ellu, // sc_Latn_IT
+ 0xA852494E44657661llu, // sck_Deva_IN
+ 0xB45249544C61746Ellu, // scn_Latn_IT
+ 0xB85247424C61746Ellu, // sco_Latn_GB
+ 0xC85243414C61746Ellu, // scs_Latn_CA
+ 0x7364504B41726162llu, // sd_Arab_PK
+ 0x7364494E44657661llu, // sd_Deva_IN
+ 0x7364494E4B686F6Allu, // sd_Khoj_IN
+ 0x7364494E53696E64llu, // sd_Sind_IN
+ 0x887249544C61746Ellu, // sdc_Latn_IT
+ 0x9C72495241726162llu, // sdh_Arab_IR
+ 0x73654E4F4C61746Ellu, // se_Latn_NO
+ 0x949243494C61746Ellu, // sef_Latn_CI
+ 0x9C924D5A4C61746Ellu, // seh_Latn_MZ
+ 0xA0924D584C61746Ellu, // sei_Latn_MX
+ 0xC8924D4C4C61746Ellu, // ses_Latn_ML
+ 0x736743464C61746Ellu, // sg_Latn_CF
+ 0x80D249454F67616Dllu, // sga_Ogam_IE
+ 0xC8D24C544C61746Ellu, // sgs_Latn_LT
+ 0xA0F24D4154666E67llu, // shi_Tfng_MA
+ 0xB4F24D4D4D796D72llu, // shn_Mymr_MM
+ 0x73694C4B53696E68llu, // si_Sinh_LK
+ 0x8D1245544C61746Ellu, // sid_Latn_ET
+ 0x736B534B4C61746Ellu, // sk_Latn_SK
+ 0xC552504B41726162llu, // skr_Arab_PK
+ 0x736C53494C61746Ellu, // sl_Latn_SI
+ 0xA172504C4C61746Ellu, // sli_Latn_PL
+ 0xE17249444C61746Ellu, // sly_Latn_ID
+ 0x736D57534C61746Ellu, // sm_Latn_WS
+ 0x819253454C61746Ellu, // sma_Latn_SE
+ 0xA59253454C61746Ellu, // smj_Latn_SE
+ 0xB59246494C61746Ellu, // smn_Latn_FI
+ 0xBD92494C53616D72llu, // smp_Samr_IL
+ 0xC99246494C61746Ellu, // sms_Latn_FI
+ 0x736E5A574C61746Ellu, // sn_Latn_ZW
+ 0xA9B24D4C4C61746Ellu, // snk_Latn_ML
+ 0x736F534F4C61746Ellu, // so_Latn_SO
+ 0xD1D2544854686169llu, // sou_Thai_TH
+ 0x7371414C4C61746Ellu, // sq_Latn_AL
+ 0x737252534379726Cllu, // sr_Cyrl_RS
+ 0x737252534C61746Ellu, // sr_Latn_RS
+ 0x8632494E536F7261llu, // srb_Sora_IN
+ 0xB63253524C61746Ellu, // srn_Latn_SR
+ 0xC632534E4C61746Ellu, // srr_Latn_SN
+ 0xDE32494E44657661llu, // srx_Deva_IN
+ 0x73735A414C61746Ellu, // ss_Latn_ZA
+ 0xE25245524C61746Ellu, // ssy_Latn_ER
+ 0x73745A414C61746Ellu, // st_Latn_ZA
+ 0xC27244454C61746Ellu, // stq_Latn_DE
+ 0x737549444C61746Ellu, // su_Latn_ID
+ 0xAA92545A4C61746Ellu, // suk_Latn_TZ
+ 0xCA92474E4C61746Ellu, // sus_Latn_GN
+ 0x737653454C61746Ellu, // sv_Latn_SE
+ 0x7377545A4C61746Ellu, // sw_Latn_TZ
+ 0x86D2595441726162llu, // swb_Arab_YT
+ 0x8AD243444C61746Ellu, // swc_Latn_CD
+ 0x9AD244454C61746Ellu, // swg_Latn_DE
+ 0xD6D2494E44657661llu, // swv_Deva_IN
+ 0xB6F249444C61746Ellu, // sxn_Latn_ID
+ 0xAF12424442656E67llu, // syl_Beng_BD
+ 0xC712495153797263llu, // syr_Syrc_IQ
+ 0xAF32504C4C61746Ellu, // szl_Latn_PL
+ 0x7461494E54616D6Cllu, // ta_Taml_IN
+ 0xA4134E5044657661llu, // taj_Deva_NP
+ 0xD83350484C61746Ellu, // tbw_Latn_PH
+ 0xE053494E4B6E6461llu, // tcy_Knda_IN
+ 0x8C73434E54616C65llu, // tdd_Tale_CN
+ 0x98734E5044657661llu, // tdg_Deva_NP
+ 0x9C734E5044657661llu, // tdh_Deva_NP
+ 0x7465494E54656C75llu, // te_Telu_IN
+ 0xB093534C4C61746Ellu, // tem_Latn_SL
+ 0xB89355474C61746Ellu, // teo_Latn_UG
+ 0xCC93544C4C61746Ellu, // tet_Latn_TL
+ 0x7467504B41726162llu, // tg_Arab_PK
+ 0x7467544A4379726Cllu, // tg_Cyrl_TJ
+ 0x7468544854686169llu, // th_Thai_TH
+ 0xACF34E5044657661llu, // thl_Deva_NP
+ 0xC0F34E5044657661llu, // thq_Deva_NP
+ 0xC4F34E5044657661llu, // thr_Deva_NP
+ 0x7469455445746869llu, // ti_Ethi_ET
+ 0x9913455245746869llu, // tig_Ethi_ER
+ 0xD5134E474C61746Ellu, // tiv_Latn_NG
+ 0x746B544D4C61746Ellu, // tk_Latn_TM
+ 0xAD53544B4C61746Ellu, // tkl_Latn_TK
+ 0xC553415A4C61746Ellu, // tkr_Latn_AZ
+ 0xCD534E5044657661llu, // tkt_Deva_NP
+ 0x746C50484C61746Ellu, // tl_Latn_PH
+ 0xE173415A4C61746Ellu, // tly_Latn_AZ
+ 0x9D934E454C61746Ellu, // tmh_Latn_NE
+ 0x746E5A414C61746Ellu, // tn_Latn_ZA
+ 0x746F544F4C61746Ellu, // to_Latn_TO
+ 0x99D34D574C61746Ellu, // tog_Latn_MW
+ 0xA1F350474C61746Ellu, // tpi_Latn_PG
+ 0x747254524C61746Ellu, // tr_Latn_TR
+ 0xD23354524C61746Ellu, // tru_Latn_TR
+ 0xD63354574C61746Ellu, // trv_Latn_TW
+ 0x74735A414C61746Ellu, // ts_Latn_ZA
+ 0x8E5347524772656Bllu, // tsd_Grek_GR
+ 0x96534E5044657661llu, // tsf_Deva_NP
+ 0x9A5350484C61746Ellu, // tsg_Latn_PH
+ 0xA653425454696274llu, // tsj_Tibt_BT
+ 0x747452554379726Cllu, // tt_Cyrl_RU
+ 0xA67355474C61746Ellu, // ttj_Latn_UG
+ 0xCA73544854686169llu, // tts_Thai_TH
+ 0xCE73415A4C61746Ellu, // ttt_Latn_AZ
+ 0xB2934D574C61746Ellu, // tum_Latn_MW
+ 0xAEB354564C61746Ellu, // tvl_Latn_TV
+ 0xC2D34E454C61746Ellu, // twq_Latn_NE
+ 0x747950464C61746Ellu, // ty_Latn_PF
+ 0xD71352554379726Cllu, // tyv_Cyrl_RU
+ 0xB3334D414C61746Ellu, // tzm_Latn_MA
+ 0xB07452554379726Cllu, // udm_Cyrl_RU
+ 0x7567434E41726162llu, // ug_Arab_CN
+ 0x75674B5A4379726Cllu, // ug_Cyrl_KZ
+ 0x80D4535955676172llu, // uga_Ugar_SY
+ 0x756B55414379726Cllu, // uk_Cyrl_UA
+ 0xA174464D4C61746Ellu, // uli_Latn_FM
+ 0x8594414F4C61746Ellu, // umb_Latn_AO
+ 0xC5B4494E42656E67llu, // unr_Beng_IN
+ 0xC5B44E5044657661llu, // unr_Deva_NP
+ 0xDDB4494E42656E67llu, // unx_Beng_IN
+ 0x7572504B41726162llu, // ur_Arab_PK
+ 0x757A414641726162llu, // uz_Arab_AF
+ 0x757A555A4C61746Ellu, // uz_Latn_UZ
+ 0xA0154C5256616969llu, // vai_Vaii_LR
+ 0x76655A414C61746Ellu, // ve_Latn_ZA
+ 0x889549544C61746Ellu, // vec_Latn_IT
+ 0xBC9552554C61746Ellu, // vep_Latn_RU
+ 0x7669564E4C61746Ellu, // vi_Latn_VN
+ 0x891553584C61746Ellu, // vic_Latn_SX
+ 0xC97542454C61746Ellu, // vls_Latn_BE
+ 0x959544454C61746Ellu, // vmf_Latn_DE
+ 0xD9954D5A4C61746Ellu, // vmw_Latn_MZ
+ 0xCDD552554C61746Ellu, // vot_Latn_RU
+ 0xBA3545454C61746Ellu, // vro_Latn_EE
+ 0xB695545A4C61746Ellu, // vun_Latn_TZ
+ 0x776142454C61746Ellu, // wa_Latn_BE
+ 0x901643484C61746Ellu, // wae_Latn_CH
+ 0xAC16455445746869llu, // wal_Ethi_ET
+ 0xC41650484C61746Ellu, // war_Latn_PH
+ 0xBC3641554C61746Ellu, // wbp_Latn_AU
+ 0xC036494E54656C75llu, // wbq_Telu_IN
+ 0xC436494E44657661llu, // wbr_Deva_IN
+ 0xC97657464C61746Ellu, // wls_Latn_WF
+ 0xA1B64B4D41726162llu, // wni_Arab_KM
+ 0x776F534E4C61746Ellu, // wo_Latn_SN
+ 0xB276494E44657661llu, // wtm_Deva_IN
+ 0xD296434E48616E73llu, // wuu_Hans_CN
+ 0xD41742524C61746Ellu, // xav_Latn_BR
+ 0xC457545243617269llu, // xcr_Cari_TR
+ 0x78685A414C61746Ellu, // xh_Latn_ZA
+ 0x897754524C796369llu, // xlc_Lyci_TR
+ 0x8D7754524C796469llu, // xld_Lydi_TR
+ 0x9597474547656F72llu, // xmf_Geor_GE
+ 0xB597434E4D616E69llu, // xmn_Mani_CN
+ 0xC59753444D657263llu, // xmr_Merc_SD
+ 0x81B753414E617262llu, // xna_Narb_SA
+ 0xC5B7494E44657661llu, // xnr_Deva_IN
+ 0x99D755474C61746Ellu, // xog_Latn_UG
+ 0xC5F7495250727469llu, // xpr_Prti_IR
+ 0x8257594553617262llu, // xsa_Sarb_YE
+ 0xC6574E5044657661llu, // xsr_Deva_NP
+ 0xB8184D5A4C61746Ellu, // yao_Latn_MZ
+ 0xBC18464D4C61746Ellu, // yap_Latn_FM
+ 0xD418434D4C61746Ellu, // yav_Latn_CM
+ 0x8438434D4C61746Ellu, // ybb_Latn_CM
+ 0x796F4E474C61746Ellu, // yo_Latn_NG
+ 0xAE3842524C61746Ellu, // yrl_Latn_BR
+ 0x82984D584C61746Ellu, // yua_Latn_MX
+ 0x7A61434E4C61746Ellu, // za_Latn_CN
+ 0x981953444C61746Ellu, // zag_Latn_SD
+ 0xA4794B4D41726162llu, // zdj_Arab_KM
+ 0x80994E4C4C61746Ellu, // zea_Latn_NL
+ 0x9CD94D4154666E67llu, // zgh_Tfng_MA
+ 0x7A685457426F706Fllu, // zh_Bopo_TW
+ 0x7A68434E48616E73llu, // zh_Hans_CN
+ 0x7A68545748616E74llu, // zh_Hant_TW
+ 0xA1994D594C61746Ellu, // zmi_Latn_MY
+ 0x7A755A414C61746Ellu, // zu_Latn_ZA
+ 0x833954524C61746Ellu, // zza_Latn_TR
+});
+
+const std::unordered_map<uint32_t, uint32_t> ARAB_PARENTS({
+ {0x6172445Au, 0x61729420u}, // ar-DZ -> ar-015
+ {0x61724548u, 0x61729420u}, // ar-EH -> ar-015
+ {0x61724C59u, 0x61729420u}, // ar-LY -> ar-015
+ {0x61724D41u, 0x61729420u}, // ar-MA -> ar-015
+ {0x6172544Eu, 0x61729420u}, // ar-TN -> ar-015
+});
+
+const std::unordered_map<uint32_t, uint32_t> HANT_PARENTS({
+ {0x7A684D4Fu, 0x7A68484Bu}, // zh-Hant-MO -> zh-Hant-HK
+});
+
+const std::unordered_map<uint32_t, uint32_t> LATN_PARENTS({
+ {0x656E80A1u, 0x656E8400u}, // en-150 -> en-001
+ {0x656E4147u, 0x656E8400u}, // en-AG -> en-001
+ {0x656E4149u, 0x656E8400u}, // en-AI -> en-001
+ {0x656E4154u, 0x656E80A1u}, // en-AT -> en-150
+ {0x656E4155u, 0x656E8400u}, // en-AU -> en-001
+ {0x656E4242u, 0x656E8400u}, // en-BB -> en-001
+ {0x656E4245u, 0x656E8400u}, // en-BE -> en-001
+ {0x656E424Du, 0x656E8400u}, // en-BM -> en-001
+ {0x656E4253u, 0x656E8400u}, // en-BS -> en-001
+ {0x656E4257u, 0x656E8400u}, // en-BW -> en-001
+ {0x656E425Au, 0x656E8400u}, // en-BZ -> en-001
+ {0x656E4341u, 0x656E8400u}, // en-CA -> en-001
+ {0x656E4343u, 0x656E8400u}, // en-CC -> en-001
+ {0x656E4348u, 0x656E80A1u}, // en-CH -> en-150
+ {0x656E434Bu, 0x656E8400u}, // en-CK -> en-001
+ {0x656E434Du, 0x656E8400u}, // en-CM -> en-001
+ {0x656E4358u, 0x656E8400u}, // en-CX -> en-001
+ {0x656E4359u, 0x656E8400u}, // en-CY -> en-001
+ {0x656E4445u, 0x656E80A1u}, // en-DE -> en-150
+ {0x656E4447u, 0x656E8400u}, // en-DG -> en-001
+ {0x656E444Bu, 0x656E80A1u}, // en-DK -> en-150
+ {0x656E444Du, 0x656E8400u}, // en-DM -> en-001
+ {0x656E4552u, 0x656E8400u}, // en-ER -> en-001
+ {0x656E4649u, 0x656E80A1u}, // en-FI -> en-150
+ {0x656E464Au, 0x656E8400u}, // en-FJ -> en-001
+ {0x656E464Bu, 0x656E8400u}, // en-FK -> en-001
+ {0x656E464Du, 0x656E8400u}, // en-FM -> en-001
+ {0x656E4742u, 0x656E8400u}, // en-GB -> en-001
+ {0x656E4744u, 0x656E8400u}, // en-GD -> en-001
+ {0x656E4747u, 0x656E8400u}, // en-GG -> en-001
+ {0x656E4748u, 0x656E8400u}, // en-GH -> en-001
+ {0x656E4749u, 0x656E8400u}, // en-GI -> en-001
+ {0x656E474Du, 0x656E8400u}, // en-GM -> en-001
+ {0x656E4759u, 0x656E8400u}, // en-GY -> en-001
+ {0x656E484Bu, 0x656E8400u}, // en-HK -> en-001
+ {0x656E4945u, 0x656E8400u}, // en-IE -> en-001
+ {0x656E494Cu, 0x656E8400u}, // en-IL -> en-001
+ {0x656E494Du, 0x656E8400u}, // en-IM -> en-001
+ {0x656E494Eu, 0x656E8400u}, // en-IN -> en-001
+ {0x656E494Fu, 0x656E8400u}, // en-IO -> en-001
+ {0x656E4A45u, 0x656E8400u}, // en-JE -> en-001
+ {0x656E4A4Du, 0x656E8400u}, // en-JM -> en-001
+ {0x656E4B45u, 0x656E8400u}, // en-KE -> en-001
+ {0x656E4B49u, 0x656E8400u}, // en-KI -> en-001
+ {0x656E4B4Eu, 0x656E8400u}, // en-KN -> en-001
+ {0x656E4B59u, 0x656E8400u}, // en-KY -> en-001
+ {0x656E4C43u, 0x656E8400u}, // en-LC -> en-001
+ {0x656E4C52u, 0x656E8400u}, // en-LR -> en-001
+ {0x656E4C53u, 0x656E8400u}, // en-LS -> en-001
+ {0x656E4D47u, 0x656E8400u}, // en-MG -> en-001
+ {0x656E4D4Fu, 0x656E8400u}, // en-MO -> en-001
+ {0x656E4D53u, 0x656E8400u}, // en-MS -> en-001
+ {0x656E4D54u, 0x656E8400u}, // en-MT -> en-001
+ {0x656E4D55u, 0x656E8400u}, // en-MU -> en-001
+ {0x656E4D57u, 0x656E8400u}, // en-MW -> en-001
+ {0x656E4D59u, 0x656E8400u}, // en-MY -> en-001
+ {0x656E4E41u, 0x656E8400u}, // en-NA -> en-001
+ {0x656E4E46u, 0x656E8400u}, // en-NF -> en-001
+ {0x656E4E47u, 0x656E8400u}, // en-NG -> en-001
+ {0x656E4E4Cu, 0x656E80A1u}, // en-NL -> en-150
+ {0x656E4E52u, 0x656E8400u}, // en-NR -> en-001
+ {0x656E4E55u, 0x656E8400u}, // en-NU -> en-001
+ {0x656E4E5Au, 0x656E8400u}, // en-NZ -> en-001
+ {0x656E5047u, 0x656E8400u}, // en-PG -> en-001
+ {0x656E5048u, 0x656E8400u}, // en-PH -> en-001
+ {0x656E504Bu, 0x656E8400u}, // en-PK -> en-001
+ {0x656E504Eu, 0x656E8400u}, // en-PN -> en-001
+ {0x656E5057u, 0x656E8400u}, // en-PW -> en-001
+ {0x656E5257u, 0x656E8400u}, // en-RW -> en-001
+ {0x656E5342u, 0x656E8400u}, // en-SB -> en-001
+ {0x656E5343u, 0x656E8400u}, // en-SC -> en-001
+ {0x656E5344u, 0x656E8400u}, // en-SD -> en-001
+ {0x656E5345u, 0x656E80A1u}, // en-SE -> en-150
+ {0x656E5347u, 0x656E8400u}, // en-SG -> en-001
+ {0x656E5348u, 0x656E8400u}, // en-SH -> en-001
+ {0x656E5349u, 0x656E80A1u}, // en-SI -> en-150
+ {0x656E534Cu, 0x656E8400u}, // en-SL -> en-001
+ {0x656E5353u, 0x656E8400u}, // en-SS -> en-001
+ {0x656E5358u, 0x656E8400u}, // en-SX -> en-001
+ {0x656E535Au, 0x656E8400u}, // en-SZ -> en-001
+ {0x656E5443u, 0x656E8400u}, // en-TC -> en-001
+ {0x656E544Bu, 0x656E8400u}, // en-TK -> en-001
+ {0x656E544Fu, 0x656E8400u}, // en-TO -> en-001
+ {0x656E5454u, 0x656E8400u}, // en-TT -> en-001
+ {0x656E5456u, 0x656E8400u}, // en-TV -> en-001
+ {0x656E545Au, 0x656E8400u}, // en-TZ -> en-001
+ {0x656E5547u, 0x656E8400u}, // en-UG -> en-001
+ {0x656E5643u, 0x656E8400u}, // en-VC -> en-001
+ {0x656E5647u, 0x656E8400u}, // en-VG -> en-001
+ {0x656E5655u, 0x656E8400u}, // en-VU -> en-001
+ {0x656E5753u, 0x656E8400u}, // en-WS -> en-001
+ {0x656E5A41u, 0x656E8400u}, // en-ZA -> en-001
+ {0x656E5A4Du, 0x656E8400u}, // en-ZM -> en-001
+ {0x656E5A57u, 0x656E8400u}, // en-ZW -> en-001
+ {0x65734152u, 0x6573A424u}, // es-AR -> es-419
+ {0x6573424Fu, 0x6573A424u}, // es-BO -> es-419
+ {0x6573434Cu, 0x6573A424u}, // es-CL -> es-419
+ {0x6573434Fu, 0x6573A424u}, // es-CO -> es-419
+ {0x65734352u, 0x6573A424u}, // es-CR -> es-419
+ {0x65734355u, 0x6573A424u}, // es-CU -> es-419
+ {0x6573444Fu, 0x6573A424u}, // es-DO -> es-419
+ {0x65734543u, 0x6573A424u}, // es-EC -> es-419
+ {0x65734754u, 0x6573A424u}, // es-GT -> es-419
+ {0x6573484Eu, 0x6573A424u}, // es-HN -> es-419
+ {0x65734D58u, 0x6573A424u}, // es-MX -> es-419
+ {0x65734E49u, 0x6573A424u}, // es-NI -> es-419
+ {0x65735041u, 0x6573A424u}, // es-PA -> es-419
+ {0x65735045u, 0x6573A424u}, // es-PE -> es-419
+ {0x65735052u, 0x6573A424u}, // es-PR -> es-419
+ {0x65735059u, 0x6573A424u}, // es-PY -> es-419
+ {0x65735356u, 0x6573A424u}, // es-SV -> es-419
+ {0x65735553u, 0x6573A424u}, // es-US -> es-419
+ {0x65735559u, 0x6573A424u}, // es-UY -> es-419
+ {0x65735645u, 0x6573A424u}, // es-VE -> es-419
+ {0x7074414Fu, 0x70745054u}, // pt-AO -> pt-PT
+ {0x70744356u, 0x70745054u}, // pt-CV -> pt-PT
+ {0x70744757u, 0x70745054u}, // pt-GW -> pt-PT
+ {0x70744D4Fu, 0x70745054u}, // pt-MO -> pt-PT
+ {0x70744D5Au, 0x70745054u}, // pt-MZ -> pt-PT
+ {0x70745354u, 0x70745054u}, // pt-ST -> pt-PT
+ {0x7074544Cu, 0x70745054u}, // pt-TL -> pt-PT
+});
+
+const struct {
+ const char script[4];
+ const std::unordered_map<uint32_t, uint32_t>* map;
+} SCRIPT_PARENTS[] = {
+ {{'A', 'r', 'a', 'b'}, &ARAB_PARENTS},
+ {{'H', 'a', 'n', 't'}, &HANT_PARENTS},
+ {{'L', 'a', 't', 'n'}, &LATN_PARENTS},
+};
+
+const size_t MAX_PARENT_DEPTH = 3;
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 44f92c7..71e9c92 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -1868,7 +1868,10 @@
}
// The language & region are equal, so compare the scripts and variants.
- int script = memcmp(l.localeScript, r.localeScript, sizeof(l.localeScript));
+ const char emptyScript[sizeof(l.localeScript)] = {'\0', '\0', '\0', '\0'};
+ const char *lScript = l.localeScriptWasProvided ? l.localeScript : emptyScript;
+ const char *rScript = r.localeScriptWasProvided ? r.localeScript : emptyScript;
+ int script = memcmp(lScript, rScript, sizeof(l.localeScript));
if (script) {
return script;
}
@@ -2012,10 +2015,10 @@
// scripts since it seems more useful to do so. We will consider
// "en-US-POSIX" to be more specific than "en-Latn-US".
- const int score = ((localeScript[0] != 0) ? 1 : 0) +
+ const int score = (localeScriptWasProvided ? 1 : 0) +
((localeVariant[0] != 0) ? 2 : 0);
- const int oScore = ((o.localeScript[0] != 0) ? 1 : 0) +
+ const int oScore = (o.localeScriptWasProvided ? 1 : 0) +
((o.localeVariant[0] != 0) ? 2 : 0);
return score - oScore;
@@ -2165,6 +2168,63 @@
return false;
}
+bool ResTable_config::isLocaleBetterThan(const ResTable_config& o,
+ const ResTable_config* requested) const {
+ if (requested->locale == 0) {
+ // The request doesn't have a locale, so no resource is better
+ // than the other.
+ return false;
+ }
+
+ if (locale == 0 && o.locale == 0) {
+ // The locales parts of both resources are empty, so no one is better
+ // than the other.
+ return false;
+ }
+
+ // Non-matching locales have been filtered out, so both resources
+ // match the requested locale.
+ //
+ // Because of the locale-related checks in match() and the checks, we know
+ // that:
+ // 1) The resource languages are either empty or match the request;
+ // and
+ // 2) If the request's script is known, the resource scripts are either
+ // unknown or match the request.
+
+ if (language[0] != o.language[0]) {
+ // The languages of the two resources are not the same. We can only
+ // assume that one of the two resources matched the request because one
+ // doesn't have a language and the other has a matching language.
+ return (language[0] != 0);
+ }
+
+ // If we are here, both the resources have the same non-empty language as
+ // the request.
+ //
+ // Because the languages are the same, computeScript() always
+ // returns a non-empty script for languages it knows about, and we have passed
+ // the script checks in match(), the scripts are either all unknown or are
+ // all the same. So we can't gain anything by checking the scripts. We need
+ // to check the region and variant.
+
+ // See if any of the regions is better than the other
+ const int region_comparison = localeDataCompareRegions(
+ country, o.country,
+ language, localeScript, requested->country);
+ if (region_comparison != 0) {
+ return (region_comparison > 0);
+ }
+
+ // The regions are the same. Try the variant.
+ if (requested->localeVariant[0] != '\0'
+ && strncmp(localeVariant, requested->localeVariant, sizeof(localeVariant)) == 0) {
+ return (strncmp(o.localeVariant, requested->localeVariant, sizeof(localeVariant)) != 0);
+ }
+
+ return false;
+}
+
bool ResTable_config::isBetterThan(const ResTable_config& o,
const ResTable_config* requested) const {
if (requested) {
@@ -2178,26 +2238,8 @@
}
}
- if (locale || o.locale) {
- if ((language[0] != o.language[0]) && requested->language[0]) {
- return (language[0]);
- }
-
- if ((country[0] != o.country[0]) && requested->country[0]) {
- return (country[0]);
- }
- }
-
- if (localeScript[0] || o.localeScript[0]) {
- if (localeScript[0] != o.localeScript[0] && requested->localeScript[0]) {
- return localeScript[0];
- }
- }
-
- if (localeVariant[0] || o.localeVariant[0]) {
- if (localeVariant[0] != o.localeVariant[0] && requested->localeVariant[0]) {
- return localeVariant[0];
- }
+ if (isLocaleBetterThan(o, requested)) {
+ return true;
}
if (screenLayout || o.screenLayout) {
@@ -2445,20 +2487,33 @@
}
}
if (locale != 0) {
- // Don't consider the script & variants when deciding matches.
+ // Don't consider country and variants when deciding matches.
+ // (Theoretically, the variant can also affect the script. For
+ // example, "ar-alalc97" probably implies the Latin script, but since
+ // CLDR doesn't support getting likely scripts for that, we'll assume
+ // the variant doesn't change the script.)
//
- // If we two configs differ only in their script or language, they
- // can be weeded out in the isMoreSpecificThan test.
- if (language[0] != 0
- && (language[0] != settings.language[0]
- || language[1] != settings.language[1])) {
+ // If two configs differ only in their country and variant,
+ // they can be weeded out in the isMoreSpecificThan test.
+ if (language[0] != settings.language[0] || language[1] != settings.language[1]) {
return false;
}
- if (country[0] != 0
- && (country[0] != settings.country[0]
- || country[1] != settings.country[1])) {
- return false;
+ // For backward compatibility and supporting private-use locales, we
+ // fall back to old behavior if we couldn't determine the script for
+ // either of the desired locale or the provided locale.
+ if (localeScript[0] == '\0' || localeScript[1] == '\0') {
+ if (country[0] != '\0'
+ && (country[0] != settings.country[0]
+ || country[1] != settings.country[1])) {
+ return false;
+ }
+ } else {
+ // But if we could determine the scripts, they should be the same
+ // for the locales to match.
+ if (memcmp(localeScript, settings.localeScript, sizeof(localeScript)) != 0) {
+ return false;
+ }
}
}
@@ -2587,7 +2642,7 @@
return;
}
- if (!localeScript[0] && !localeVariant[0]) {
+ if (!localeScriptWasProvided && !localeVariant[0]) {
// Legacy format.
if (out.size() > 0) {
out.append("-");
@@ -2605,7 +2660,7 @@
return;
}
- // We are writing the modified bcp47 tag.
+ // We are writing the modified BCP 47 tag.
// It starts with 'b+' and uses '+' as a separator.
if (out.size() > 0) {
@@ -2617,7 +2672,7 @@
size_t len = unpackLanguage(buf);
out.append(buf, len);
- if (localeScript[0]) {
+ if (localeScriptWasProvided) {
out.append("+");
out.append(localeScript, sizeof(localeScript));
}
@@ -2630,7 +2685,7 @@
if (localeVariant[0]) {
out.append("+");
- out.append(localeVariant, sizeof(localeVariant));
+ out.append(localeVariant, strnlen(localeVariant, sizeof(localeVariant)));
}
}
@@ -2648,7 +2703,7 @@
charsWritten += unpackLanguage(str);
}
- if (localeScript[0]) {
+ if (localeScriptWasProvided) {
if (charsWritten) {
str[charsWritten++] = '-';
}
@@ -2682,11 +2737,16 @@
config->language[0] ? config->packRegion(start) : config->packLanguage(start);
break;
case 4:
- config->localeScript[0] = toupper(start[0]);
- for (size_t i = 1; i < 4; ++i) {
- config->localeScript[i] = tolower(start[i]);
+ if ('0' <= start[0] && start[0] <= '9') {
+ // this is a variant, so fall through
+ } else {
+ config->localeScript[0] = toupper(start[0]);
+ for (size_t i = 1; i < 4; ++i) {
+ config->localeScript[i] = tolower(start[i]);
+ }
+ config->localeScriptWasProvided = true;
+ break;
}
- break;
case 5:
case 6:
case 7:
@@ -2704,6 +2764,7 @@
void ResTable_config::setBcp47Locale(const char* in) {
locale = 0;
+ localeScriptWasProvided = false;
memset(localeScript, 0, sizeof(localeScript));
memset(localeVariant, 0, sizeof(localeVariant));
@@ -2720,6 +2781,9 @@
const size_t size = in + strlen(in) - start;
assignLocaleComponent(this, start, size);
+ if (localeScript[0] == '\0') {
+ computeScript();
+ };
}
String8 ResTable_config::toString() const {
diff --git a/libs/androidfw/tests/ConfigLocale_test.cpp b/libs/androidfw/tests/ConfigLocale_test.cpp
index 4999594..1941563 100644
--- a/libs/androidfw/tests/ConfigLocale_test.cpp
+++ b/libs/androidfw/tests/ConfigLocale_test.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include <androidfw/LocaleData.h>
#include <androidfw/ResourceTypes.h>
#include <utils/Log.h>
#include <utils/String8.h>
@@ -28,7 +29,7 @@
EXPECT_EQ('e', config.language[0]);
EXPECT_EQ('n', config.language[1]);
- char out[4] = { 1, 1, 1, 1};
+ char out[4] = {1, 1, 1, 1};
config.unpackLanguage(out);
EXPECT_EQ('e', out[0]);
EXPECT_EQ('n', out[1]);
@@ -51,7 +52,7 @@
EXPECT_EQ('U', config.country[0]);
EXPECT_EQ('S', config.country[1]);
- char out[4] = { 1, 1, 1, 1};
+ char out[4] = {1, 1, 1, 1};
config.unpackRegion(out);
EXPECT_EQ('U', out[0]);
EXPECT_EQ('S', out[1]);
@@ -67,7 +68,7 @@
EXPECT_EQ('\x99', config.language[0]);
EXPECT_EQ('\xA4', config.language[1]);
- char out[4] = { 1, 1, 1, 1};
+ char out[4] = {1, 1, 1, 1};
config.unpackLanguage(out);
EXPECT_EQ('e', out[0]);
EXPECT_EQ('n', out[1]);
@@ -91,7 +92,7 @@
EXPECT_EQ(char(0xbc), config.language[0]);
EXPECT_EQ(char(0xd3), config.language[1]);
- char out[4] = { 1, 1, 1, 1};
+ char out[4] = {1, 1, 1, 1};
config.unpackLanguage(out);
EXPECT_EQ('t', out[0]);
EXPECT_EQ('g', out[1]);
@@ -103,7 +104,7 @@
ResTable_config config;
config.packRegion("419");
- char out[4] = { 1, 1, 1, 1};
+ char out[4] = {1, 1, 1, 1};
config.unpackRegion(out);
EXPECT_EQ('4', out[0]);
@@ -124,6 +125,10 @@
if (script != NULL) {
memcpy(out->localeScript, script, 4);
+ out->localeScriptWasProvided = true;
+ } else {
+ out->computeScript();
+ out->localeScriptWasProvided = false;
}
if (variant != NULL) {
@@ -177,11 +182,12 @@
EXPECT_EQ('n', test.language[1]);
EXPECT_EQ('U', test.country[0]);
EXPECT_EQ('S', test.country[1]);
- EXPECT_EQ(0, test.localeScript[0]);
+ EXPECT_FALSE(test.localeScriptWasProvided);
+ EXPECT_EQ(0, memcmp("Latn", test.localeScript, 4));
EXPECT_EQ(0, test.localeVariant[0]);
test.setBcp47Locale("eng-419");
- char out[4] = { 1, 1, 1, 1};
+ char out[4] = {1, 1, 1, 1};
test.unpackLanguage(out);
EXPECT_EQ('e', out[0]);
EXPECT_EQ('n', out[1]);
@@ -193,17 +199,397 @@
EXPECT_EQ('1', out[1]);
EXPECT_EQ('9', out[2]);
-
test.setBcp47Locale("en-Latn-419");
- memset(out, 1, 4);
EXPECT_EQ('e', test.language[0]);
EXPECT_EQ('n', test.language[1]);
-
EXPECT_EQ(0, memcmp("Latn", test.localeScript, 4));
+ EXPECT_TRUE(test.localeScriptWasProvided);
+ memset(out, 1, 4);
test.unpackRegion(out);
EXPECT_EQ('4', out[0]);
EXPECT_EQ('1', out[1]);
EXPECT_EQ('9', out[2]);
+
+ test.setBcp47Locale("de-1901");
+ memset(out, 1, 4);
+ test.unpackLanguage(out);
+ EXPECT_EQ('d', out[0]);
+ EXPECT_EQ('e', out[1]);
+ EXPECT_EQ('\0', out[2]);
+ EXPECT_FALSE(test.localeScriptWasProvided);
+ EXPECT_EQ(0, memcmp("Latn", test.localeScript, 4));
+ memset(out, 1, 4);
+ test.unpackRegion(out);
+ EXPECT_EQ('\0', out[0]);
+ EXPECT_EQ(0, strcmp("1901", test.localeVariant));
+
+ test.setBcp47Locale("de-Latn-1901");
+ memset(out, 1, 4);
+ test.unpackLanguage(out);
+ EXPECT_EQ('d', out[0]);
+ EXPECT_EQ('e', out[1]);
+ EXPECT_EQ('\0', out[2]);
+ EXPECT_TRUE(test.localeScriptWasProvided);
+ EXPECT_EQ(0, memcmp("Latn", test.localeScript, 4));
+ memset(out, 1, 4);
+ test.unpackRegion(out);
+ EXPECT_EQ('\0', out[0]);
+ EXPECT_EQ(0, strcmp("1901", test.localeVariant));
}
-} // namespace android.
+TEST(ConfigLocaleTest, computeScript) {
+ ResTable_config config;
+
+ fillIn(NULL, NULL, NULL, NULL, &config);
+ EXPECT_EQ(0, memcmp("\0\0\0\0", config.localeScript, 4));
+
+ fillIn("zh", "TW", NULL, NULL, &config);
+ EXPECT_EQ(0, memcmp("Hant", config.localeScript, 4));
+
+ fillIn("zh", "CN", NULL, NULL, &config);
+ EXPECT_EQ(0, memcmp("Hans", config.localeScript, 4));
+
+ fillIn("az", NULL, NULL, NULL, &config);
+ EXPECT_EQ(0, memcmp("Latn", config.localeScript, 4));
+
+ fillIn("az", "AZ", NULL, NULL, &config);
+ EXPECT_EQ(0, memcmp("Latn", config.localeScript, 4));
+
+ fillIn("az", "IR", NULL, NULL, &config);
+ EXPECT_EQ(0, memcmp("Arab", config.localeScript, 4));
+
+ fillIn("peo", NULL, NULL, NULL, &config);
+ EXPECT_EQ(0, memcmp("Xpeo", config.localeScript, 4));
+
+ fillIn("qaa", NULL, NULL, NULL, &config);
+ EXPECT_EQ(0, memcmp("\0\0\0\0", config.localeScript, 4));
+}
+
+TEST(ConfigLocaleTest, getBcp47Locale_script) {
+ ResTable_config config;
+ fillIn("en", NULL, "Latn", NULL, &config);
+
+ char out[RESTABLE_MAX_LOCALE_LEN];
+ config.localeScriptWasProvided = true;
+ config.getBcp47Locale(out);
+ EXPECT_EQ(0, strcmp("en-Latn", out));
+
+ config.localeScriptWasProvided = false;
+ config.getBcp47Locale(out);
+ EXPECT_EQ(0, strcmp("en", out));
+}
+
+TEST(ConfigLocaleTest, match) {
+ ResTable_config supported, requested;
+
+ fillIn(NULL, NULL, NULL, NULL, &supported);
+ fillIn("fr", "CA", NULL, NULL, &requested);
+ // Empty locale matches everything (as a default).
+ EXPECT_TRUE(supported.match(requested));
+
+ fillIn("en", "CA", NULL, NULL, &supported);
+ fillIn("fr", "CA", NULL, NULL, &requested);
+ // Different languages don't match.
+ EXPECT_FALSE(supported.match(requested));
+
+ fillIn("qaa", "FR", NULL, NULL, &supported);
+ fillIn("qaa", "CA", NULL, NULL, &requested);
+ // If we can't infer the scripts, different regions don't match.
+ EXPECT_FALSE(supported.match(requested));
+
+ fillIn("qaa", "FR", "Latn", NULL, &supported);
+ fillIn("qaa", "CA", NULL, NULL, &requested);
+ // If we can't infer any of the scripts, different regions don't match.
+ EXPECT_FALSE(supported.match(requested));
+
+ fillIn("qaa", "FR", NULL, NULL, &supported);
+ fillIn("qaa", "CA", "Latn", NULL, &requested);
+ // If we can't infer any of the scripts, different regions don't match.
+ EXPECT_FALSE(supported.match(requested));
+
+ fillIn("qaa", NULL, NULL, NULL, &supported);
+ fillIn("qaa", "CA", NULL, NULL, &requested);
+ // language-only resources still support language+region requests, even if we can't infer the
+ // script.
+ EXPECT_TRUE(supported.match(requested));
+
+ fillIn("qaa", "CA", NULL, NULL, &supported);
+ fillIn("qaa", "CA", NULL, NULL, &requested);
+ // Even if we can't infer the scripts, exactly equal locales match.
+ EXPECT_TRUE(supported.match(requested));
+
+ fillIn("az", NULL, NULL, NULL, &supported);
+ fillIn("az", NULL, "Latn", NULL, &requested);
+ // If the resolved scripts are the same, it doesn't matter if they were explicitly provided
+ // or not, and they match.
+ EXPECT_TRUE(supported.match(requested));
+
+ fillIn("az", NULL, NULL, NULL, &supported);
+ fillIn("az", NULL, "Cyrl", NULL, &requested);
+ // If the resolved scripts are different, they don't match.
+ EXPECT_FALSE(supported.match(requested));
+
+ fillIn("az", NULL, NULL, NULL, &supported);
+ fillIn("az", "IR", NULL, NULL, &requested);
+ // If the resolved scripts are different, they don't match.
+ EXPECT_FALSE(supported.match(requested));
+
+ fillIn("az", "IR", NULL, NULL, &supported);
+ fillIn("az", NULL, "Arab", NULL, &requested);
+ // If the resolved scripts are the same, it doesn't matter if they were explicitly provided
+ // or not, and they match.
+ EXPECT_TRUE(supported.match(requested));
+
+ fillIn("en", NULL, NULL, NULL, &supported);
+ fillIn("en", "XA", NULL, NULL, &requested);
+ // en-XA is a pseudo-locale, and English resources are not a match for it.
+ EXPECT_FALSE(supported.match(requested));
+
+ fillIn("en", "XA", NULL, NULL, &supported);
+ fillIn("en", NULL, NULL, NULL, &requested);
+ // en-XA is a pseudo-locale, and its resources don't support English locales.
+ EXPECT_FALSE(supported.match(requested));
+
+ fillIn("en", "XA", NULL, NULL, &supported);
+ fillIn("en", "XA", NULL, NULL, &requested);
+ // Even if they are pseudo-locales, exactly equal locales match.
+ EXPECT_TRUE(supported.match(requested));
+
+ fillIn("ar", NULL, NULL, NULL, &supported);
+ fillIn("ar", "XB", NULL, NULL, &requested);
+ // ar-XB is a pseudo-locale, and Arabic resources are not a match for it.
+ EXPECT_FALSE(supported.match(requested));
+
+ fillIn("ar", "XB", NULL, NULL, &supported);
+ fillIn("ar", NULL, NULL, NULL, &requested);
+ // ar-XB is a pseudo-locale, and its resources don't support Arabic locales.
+ EXPECT_FALSE(supported.match(requested));
+
+ fillIn("ar", "XB", NULL, NULL, &supported);
+ fillIn("ar", "XB", NULL, NULL, &requested);
+ // Even if they are pseudo-locales, exactly equal locales match.
+ EXPECT_TRUE(supported.match(requested));
+}
+
+TEST(ConfigLocaleTest, isLocaleBetterThan_basics) {
+ ResTable_config config1, config2, request;
+
+ fillIn(NULL, NULL, NULL, NULL, &request);
+ fillIn("fr", "FR", NULL, NULL, &config1);
+ fillIn("fr", "CA", NULL, NULL, &config2);
+ EXPECT_FALSE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("fr", "CA", NULL, NULL, &request);
+ fillIn(NULL, NULL, NULL, NULL, &config1);
+ fillIn(NULL, NULL, NULL, NULL, &config2);
+ EXPECT_FALSE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("fr", "CA", NULL, NULL, &request);
+ fillIn("fr", "FR", NULL, NULL, &config1);
+ fillIn(NULL, NULL, NULL, NULL, &config2);
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("de", "DE", NULL, NULL, &request);
+ fillIn("de", "DE", NULL, "1901", &config1);
+ fillIn("de", "DE", NULL, "1996", &config2);
+ EXPECT_FALSE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("de", "DE", NULL, "1901", &request);
+ fillIn("de", "DE", NULL, "1901", &config1);
+ fillIn("de", "DE", NULL, NULL, &config2);
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("de", "DE", NULL, "1901", &request);
+ fillIn("de", "DE", NULL, "1996", &config1);
+ fillIn("de", "DE", NULL, NULL, &config2);
+ EXPECT_FALSE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+}
+
+TEST(ConfigLocaleTest, isLocaleBetterThan_regionComparison) {
+ ResTable_config config1, config2, request;
+
+ fillIn("es", "AR", NULL, NULL, &request);
+ fillIn("es", "419", NULL, NULL, &config1);
+ fillIn("es", "419", NULL, NULL, &config2);
+ // Both supported locales are the same, so none is better than the other.
+ EXPECT_FALSE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("es", "AR", NULL, NULL, &request);
+ fillIn("es", "AR", NULL, NULL, &config1);
+ fillIn("es", "419", NULL, NULL, &config2);
+ // An exact locale match is better than a parent.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("es", "AR", NULL, NULL, &request);
+ fillIn("es", "419", NULL, NULL, &config1);
+ fillIn("es", NULL, NULL, NULL, &config2);
+ // A closer parent is better.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("es", "AR", NULL, NULL, &request);
+ fillIn("es", "419", NULL, NULL, &config1);
+ fillIn("es", "ES", NULL, NULL, &config2);
+ // A parent is better than a non-parent representative locale.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("es", "AR", NULL, NULL, &request);
+ fillIn("es", NULL, NULL, NULL, &config1);
+ fillIn("es", "ES", NULL, NULL, &config2);
+ // A parent is better than a non-parent representative locale.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("es", "AR", NULL, NULL, &request);
+ fillIn("es", "PE", NULL, NULL, &config1);
+ fillIn("es", "ES", NULL, NULL, &config2);
+ // A closer locale is better.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("es", "AR", NULL, NULL, &request);
+ fillIn("es", "MX", NULL, NULL, &config1);
+ fillIn("es", "BO", NULL, NULL, &config2);
+ // A representative locale is better if they are equidistant.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("es", "AR", NULL, NULL, &request);
+ fillIn("es", "US", NULL, NULL, &config1);
+ fillIn("es", "BO", NULL, NULL, &config2);
+ // A representative locale is better if they are equidistant.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("es", "AR", NULL, NULL, &request);
+ fillIn("es", "MX", NULL, NULL, &config1);
+ fillIn("es", "US", NULL, NULL, &config2);
+ // If all is equal, the locale earlier in the dictionary is better.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("es", "GQ", NULL, NULL, &request);
+ fillIn("es", "IC", NULL, NULL, &config1);
+ fillIn("es", "419", NULL, NULL, &config2);
+ // If all is equal, the locale earlier in the dictionary is better and
+ // letters are better than numbers.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("en", "GB", NULL, NULL, &request);
+ fillIn("en", "001", NULL, NULL, &config1);
+ fillIn("en", NULL, NULL, NULL, &config2);
+ // A closer parent is better.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("en", "PR", NULL, NULL, &request);
+ fillIn("en", NULL, NULL, NULL, &config1);
+ fillIn("en", "001", NULL, NULL, &config2);
+ // A parent is better than a non-parent.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("en", "DE", NULL, NULL, &request);
+ fillIn("en", "150", NULL, NULL, &config1);
+ fillIn("en", "001", NULL, NULL, &config2);
+ // A closer parent is better.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("en", "IN", NULL, NULL, &request);
+ fillIn("en", "AU", NULL, NULL, &config1);
+ fillIn("en", "US", NULL, NULL, &config2);
+ // A closer locale is better.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("en", "PR", NULL, NULL, &request);
+ fillIn("en", "001", NULL, NULL, &config1);
+ fillIn("en", "GB", NULL, NULL, &config2);
+ // A closer locale is better.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("en", "IN", NULL, NULL, &request);
+ fillIn("en", "GB", NULL, NULL, &config1);
+ fillIn("en", "AU", NULL, NULL, &config2);
+ // A representative locale is better if they are equidistant.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("en", "IN", NULL, NULL, &request);
+ fillIn("en", "AU", NULL, NULL, &config1);
+ fillIn("en", "CA", NULL, NULL, &config2);
+ // If all is equal, the locale earlier in the dictionary is better.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("pt", "MZ", NULL, NULL, &request);
+ fillIn("pt", "PT", NULL, NULL, &config1);
+ fillIn("pt", NULL, NULL, NULL, &config2);
+ // A closer parent is better.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("pt", "MZ", NULL, NULL, &request);
+ fillIn("pt", "PT", NULL, NULL, &config1);
+ fillIn("pt", "BR", NULL, NULL, &config2);
+ // A parent is better than a non-parent.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("zh", "MO", "Hant", NULL, &request);
+ fillIn("zh", "HK", "Hant", NULL, &config1);
+ fillIn("zh", "TW", "Hant", NULL, &config2);
+ // A parent is better than a non-parent.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("zh", "US", "Hant", NULL, &request);
+ fillIn("zh", "TW", "Hant", NULL, &config1);
+ fillIn("zh", "HK", "Hant", NULL, &config2);
+ // A representative locale is better if they are equidistant.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("ar", "DZ", NULL, NULL, &request);
+ fillIn("ar", "015", NULL, NULL, &config1);
+ fillIn("ar", NULL, NULL, NULL, &config2);
+ // A closer parent is better.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("ar", "EG", NULL, NULL, &request);
+ fillIn("ar", NULL, NULL, NULL, &config1);
+ fillIn("ar", "015", NULL, NULL, &config2);
+ // A parent is better than a non-parent.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("ar", "QA", NULL, NULL, &request);
+ fillIn("ar", "EG", NULL, NULL, &config1);
+ fillIn("ar", "BH", NULL, NULL, &config2);
+ // A representative locale is better if they are equidistant.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("ar", "QA", NULL, NULL, &request);
+ fillIn("ar", "SA", NULL, NULL, &config1);
+ fillIn("ar", "015", NULL, NULL, &config2);
+ // If all is equal, the locale earlier in the dictionary is better and
+ // letters are better than numbers.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+}
+
+} // namespace android
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 1bfa308..db017fe 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -1630,6 +1630,7 @@
Texture* texture = entry ? entry->texture : mCaches.textureCache.get(bitmap);
if (!texture) return;
+ const AutoTexture autoCleanup(texture);
// 9 patches are built for stretching - always filter
int textureFillFlags = TextureFillFlags::ForceFilter;
diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp
index 9fc0c19..5046d37 100644
--- a/libs/hwui/Texture.cpp
+++ b/libs/hwui/Texture.cpp
@@ -41,9 +41,7 @@
void Texture::setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture, bool force,
GLenum renderTarget) {
- if (mFirstWrap || force || wrapS != mWrapS || wrapT != mWrapT) {
- mFirstWrap = false;
-
+ if (force || wrapS != mWrapS || wrapT != mWrapT) {
mWrapS = wrapS;
mWrapT = wrapT;
@@ -59,9 +57,7 @@
void Texture::setFilterMinMag(GLenum min, GLenum mag, bool bindTexture, bool force,
GLenum renderTarget) {
- if (mFirstFilter || force || min != mMinFilter || mag != mMagFilter) {
- mFirstFilter = false;
-
+ if (force || min != mMinFilter || mag != mMagFilter) {
mMinFilter = min;
mMagFilter = mag;
@@ -92,31 +88,31 @@
return true;
}
+void Texture::resetCachedParams() {
+ mWrapS = GL_REPEAT;
+ mWrapT = GL_REPEAT;
+ mMinFilter = GL_NEAREST_MIPMAP_LINEAR;
+ mMagFilter = GL_LINEAR;
+}
+
void Texture::upload(GLint internalformat, uint32_t width, uint32_t height,
GLenum format, GLenum type, const void* pixels) {
GL_CHECKPOINT();
bool needsAlloc = updateSize(width, height, internalformat);
- if (!needsAlloc && !pixels) {
- return;
- }
- mCaches.textureState().activateTexture(0);
- GL_CHECKPOINT();
if (!mId) {
glGenTextures(1, &mId);
needsAlloc = true;
+ resetCachedParams();
}
- GL_CHECKPOINT();
mCaches.textureState().bindTexture(GL_TEXTURE_2D, mId);
- GL_CHECKPOINT();
if (needsAlloc) {
glTexImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0,
format, type, pixels);
- GL_CHECKPOINT();
- } else {
+ } else if (pixels) {
glTexSubImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0,
format, type, pixels);
- GL_CHECKPOINT();
}
+ GL_CHECKPOINT();
}
static void uploadToTexture(bool resize, GLenum format, GLenum type, GLsizei stride, GLsizei bpp,
@@ -187,6 +183,10 @@
*outFormat = GL_RGBA;
*outType = GL_UNSIGNED_BYTE;
break;
+ case kGray_8_SkColorType:
+ *outFormat = GL_LUMINANCE;
+ *outType = GL_UNSIGNED_BYTE;
+ break;
default:
LOG_ALWAYS_FATAL("Unsupported bitmap colorType: %d", colorType);
break;
@@ -210,10 +210,12 @@
// If the texture had mipmap enabled but not anymore,
// force a glTexImage2D to discard the mipmap levels
bool needsAlloc = canMipMap && mipMap && !bitmap.hasHardwareMipMap();
+ bool setDefaultParams = false;
if (!mId) {
glGenTextures(1, &mId);
needsAlloc = true;
+ setDefaultParams = true;
}
GLint format, type;
@@ -248,11 +250,8 @@
}
}
- if (mFirstFilter) {
+ if (setDefaultParams) {
setFilter(GL_NEAREST);
- }
-
- if (mFirstWrap) {
setWrap(GL_CLAMP_TO_EDGE);
}
}
diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h
index 4e8e6dc..9749f73 100644
--- a/libs/hwui/Texture.h
+++ b/libs/hwui/Texture.h
@@ -149,26 +149,22 @@
// Returns true if the size changed, false if it was the same
bool updateSize(uint32_t width, uint32_t height, GLint format);
+ void resetCachedParams();
GLuint mId = 0;
uint32_t mWidth = 0;
uint32_t mHeight = 0;
GLint mFormat = 0;
- /**
- * Last wrap modes set on this texture.
+ /* See GLES spec section 3.8.14
+ * "In the initial state, the value assigned to TEXTURE_MIN_FILTER is
+ * NEAREST_MIPMAP_LINEAR and the value for TEXTURE_MAG_FILTER is LINEAR.
+ * s, t, and r wrap modes are all set to REPEAT."
*/
- GLenum mWrapS = GL_CLAMP_TO_EDGE;
- GLenum mWrapT = GL_CLAMP_TO_EDGE;
-
- /**
- * Last filters set on this texture.
- */
- GLenum mMinFilter = GL_NEAREST;
- GLenum mMagFilter = GL_NEAREST;
-
- bool mFirstFilter = true;
- bool mFirstWrap = true;
+ GLenum mWrapS = GL_REPEAT;
+ GLenum mWrapT = GL_REPEAT;
+ GLenum mMinFilter = GL_NEAREST_MIPMAP_LINEAR;
+ GLenum mMagFilter = GL_LINEAR;
Caches& mCaches;
}; // struct Texture
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 793df92..3e20608 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -31,7 +31,7 @@
const int Tree::MAX_CACHED_BITMAP_SIZE = 2048;
-void Path::draw(Canvas* outCanvas, const SkMatrix& groupStackedMatrix, float scaleX, float scaleY) {
+void Path::draw(SkCanvas* outCanvas, const SkMatrix& groupStackedMatrix, float scaleX, float scaleY) {
float matrixScale = getMatrixScale(groupStackedMatrix);
if (matrixScale == 0) {
// When either x or y is scaled to 0, we don't need to draw anything.
@@ -186,7 +186,7 @@
return SkColorSetA(color, alphaBytes * alpha);
}
-void FullPath::drawPath(Canvas* outCanvas, const SkPath& renderPath, float strokeScale){
+void FullPath::drawPath(SkCanvas* outCanvas, const SkPath& renderPath, float strokeScale){
// Draw path's fill, if fill color isn't transparent.
if (mFillColor != SK_ColorTRANSPARENT) {
mPaint.setStyle(SkPaint::Style::kFill_Style);
@@ -287,9 +287,9 @@
return true;
}
-void ClipPath::drawPath(Canvas* outCanvas, const SkPath& renderPath,
+void ClipPath::drawPath(SkCanvas* outCanvas, const SkPath& renderPath,
float strokeScale){
- outCanvas->clipPath(&renderPath, SkRegion::kIntersect_Op);
+ outCanvas->clipPath(renderPath, SkRegion::kIntersect_Op);
}
Group::Group(const Group& group) : Node(group) {
@@ -302,7 +302,7 @@
mTranslateY = group.mTranslateY;
}
-void Group::draw(Canvas* outCanvas, const SkMatrix& currentMatrix, float scaleX,
+void Group::draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix, float scaleX,
float scaleY) {
// TODO: Try apply the matrix to the canvas instead of passing it down the tree
@@ -315,7 +315,7 @@
stackedMatrix.postConcat(currentMatrix);
// Save the current clip information, which is local to this group.
- outCanvas->save(SkCanvas::kMatrixClip_SaveFlag);
+ outCanvas->save();
// Draw the group tree in the same order as the XML file.
for (Node* child : mChildren) {
child->draw(outCanvas, stackedMatrix, scaleX, scaleY);
@@ -465,10 +465,10 @@
void Tree::updateCachedBitmap(int width, int height) {
mCachedBitmap.eraseColor(SK_ColorTRANSPARENT);
- Canvas* outCanvas = Canvas::create_canvas(mCachedBitmap);
+ SkCanvas outCanvas(mCachedBitmap);
float scaleX = width / mViewportWidth;
float scaleY = height / mViewportHeight;
- mRootNode->draw(outCanvas, SkMatrix::I(), scaleX, scaleY);
+ mRootNode->draw(&outCanvas, SkMatrix::I(), scaleX, scaleY);
mCacheDirty = false;
}
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index 6c84b05..5ae5f6a 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -20,6 +20,7 @@
#include "Canvas.h"
#include <SkBitmap.h>
#include <SkColor.h>
+#include <SkCanvas.h>
#include <SkMatrix.h>
#include <SkPaint.h>
#include <SkPath.h>
@@ -56,7 +57,7 @@
mName = node.mName;
}
Node() {}
- virtual void draw(Canvas* outCanvas, const SkMatrix& currentMatrix,
+ virtual void draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix,
float scaleX, float scaleY) = 0;
virtual void dump() = 0;
void setName(const char* name) {
@@ -85,7 +86,7 @@
void dump() override;
bool canMorph(const Data& path);
bool canMorph(const Path& path);
- void draw(Canvas* outCanvas, const SkMatrix& groupStackedMatrix,
+ void draw(SkCanvas* outCanvas, const SkMatrix& groupStackedMatrix,
float scaleX, float scaleY) override;
void setPath(const char* path, size_t strLength);
void setPathData(const Data& data);
@@ -93,7 +94,7 @@
protected:
virtual const SkPath& getUpdatedPath();
- virtual void drawPath(Canvas *outCanvas, const SkPath& renderPath,
+ virtual void drawPath(SkCanvas *outCanvas, const SkPath& renderPath,
float strokeScale) = 0;
Data mData;
SkPath mSkPath;
@@ -163,7 +164,7 @@
protected:
const SkPath& getUpdatedPath() override;
- void drawPath(Canvas* outCanvas, const SkPath& renderPath,
+ void drawPath(SkCanvas* outCanvas, const SkPath& renderPath,
float strokeScale) override;
private:
@@ -193,7 +194,7 @@
ClipPath(const Data& nodes) : Path(nodes) {}
protected:
- void drawPath(Canvas* outCanvas, const SkPath& renderPath,
+ void drawPath(SkCanvas* outCanvas, const SkPath& renderPath,
float strokeScale) override;
};
@@ -243,7 +244,7 @@
void setTranslateY(float translateY) {
mTranslateY = translateY;
}
- virtual void draw(Canvas* outCanvas, const SkMatrix& currentMatrix,
+ virtual void draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix,
float scaleX, float scaleY) override;
void updateLocalMatrix(float rotate, float pivotX, float pivotY,
float scaleX, float scaleY, float translateX, float translateY);
diff --git a/libs/hwui/renderstate/OffscreenBufferPool.cpp b/libs/hwui/renderstate/OffscreenBufferPool.cpp
index 98c94df..54f38e8 100644
--- a/libs/hwui/renderstate/OffscreenBufferPool.cpp
+++ b/libs/hwui/renderstate/OffscreenBufferPool.cpp
@@ -41,6 +41,7 @@
, texture(caches) {
uint32_t width = computeIdealDimension(viewportWidth);
uint32_t height = computeIdealDimension(viewportHeight);
+ caches.textureState().activateTexture(0);
texture.resize(width, height, GL_RGBA);
texture.blend = true;
texture.setWrap(GL_CLAMP_TO_EDGE);
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index e71d6ee..75dcf16 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -340,6 +340,9 @@
SkiaShader::apply(*mCaches, fill.skiaShaderData);
GL_CHECKPOINT();
+ Texture* texture = (fill.skiaShaderData.skiaShaderType & kBitmap_SkiaShaderType) ?
+ fill.skiaShaderData.bitmapData.bitmapTexture : nullptr;
+ const AutoTexture autoCleanup(texture);
// ------------------------------------
// ---------- GL state setup ----------
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 5ad6b08..dc534be 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2163,7 +2163,7 @@
* audio service.
*/
private final ServiceEventHandlerDelegate mServiceEventHandlerDelegate =
- new ServiceEventHandlerDelegate();
+ new ServiceEventHandlerDelegate(null);
/**
* Event types
@@ -2177,10 +2177,14 @@
private class ServiceEventHandlerDelegate {
private final Handler mHandler;
- ServiceEventHandlerDelegate() {
+ ServiceEventHandlerDelegate(Handler handler) {
Looper looper;
- if ((looper = Looper.myLooper()) == null) {
- looper = Looper.getMainLooper();
+ if (handler == null) {
+ if ((looper = Looper.myLooper()) == null) {
+ looper = Looper.getMainLooper();
+ }
+ } else {
+ looper = handler.getLooper();
}
if (looper != null) {
@@ -2201,27 +2205,9 @@
}
break;
case MSSG_RECORDING_CONFIG_CHANGE:
- // optimizing for the case of a single callback
- AudioRecordingCallback singleCallback = null;
- ArrayList<AudioRecordingCallback> multipleCallbacks = null;
- synchronized(mRecordCallbackLock) {
- if ((mRecordCallbackList != null)
- && (mRecordCallbackList.size() != 0)) {
- if (mRecordCallbackList.size() == 1) {
- singleCallback = mRecordCallbackList.get(0);
- } else {
- multipleCallbacks =
- new ArrayList<AudioRecordingCallback>(
- mRecordCallbackList);
- }
- }
- }
- if (singleCallback != null) {
- singleCallback.onRecordConfigChanged();
- } else if (multipleCallbacks != null) {
- for (int i=0 ; i < multipleCallbacks.size() ; i++) {
- multipleCallbacks.get(i).onRecordConfigChanged();
- }
+ final AudioRecordingCallback cb = (AudioRecordingCallback) msg.obj;
+ if (cb != null) {
+ cb.onRecordConfigChanged();
}
break;
default:
@@ -2798,34 +2784,51 @@
//====================================================================
// Recording configuration
/**
- * @hide
- * candidate for public API
+ * Interface for receiving update notifications about the recording configuration. Extend
+ * this abstract class and register it with
+ * {@link AudioManager#registerAudioRecordingCallback(AudioRecordingCallback, Handler)}
+ * to be notified.
+ * Use {@link AudioManager#getActiveRecordConfigurations()} to query the current configuration.
*/
public static abstract class AudioRecordingCallback {
/**
- * @hide
- * candidate for public API
+ * Called whenever the device recording configuration has changed.
*/
public void onRecordConfigChanged() {}
}
+ private static class AudioRecordingCallbackInfo {
+ final AudioRecordingCallback mCb;
+ final Handler mHandler;
+ AudioRecordingCallbackInfo(AudioRecordingCallback cb, Handler handler) {
+ mCb = cb;
+ mHandler = handler;
+ }
+ }
+
/**
- * @hide
- * candidate for public API
- * @param non-null callback
+ * Register a callback to be notified of audio recording changes through
+ * {@link AudioRecordingCallback}
+ * @param cb non-null callback to register
+ * @param handler the {@link Handler} object for the thread on which to execute
+ * the callback. If <code>null</code>, the {@link Handler} associated with the main
+ * {@link Looper} will be used.
*/
- public void registerAudioRecordingCallback(@NonNull AudioRecordingCallback cb) {
+ public void registerAudioRecordingCallback(@NonNull AudioRecordingCallback cb, Handler handler)
+ {
if (cb == null) {
throw new IllegalArgumentException("Illegal null AudioRecordingCallback argument");
}
+
synchronized(mRecordCallbackLock) {
// lazy initialization of the list of recording callbacks
if (mRecordCallbackList == null) {
- mRecordCallbackList = new ArrayList<AudioRecordingCallback>();
+ mRecordCallbackList = new ArrayList<AudioRecordingCallbackInfo>();
}
final int oldCbCount = mRecordCallbackList.size();
- if (!mRecordCallbackList.contains(cb)) {
- mRecordCallbackList.add(cb);
+ if (!hasRecordCallback_sync(cb)) {
+ mRecordCallbackList.add(new AudioRecordingCallbackInfo(cb,
+ new ServiceEventHandlerDelegate(handler).getHandler()));
final int newCbCount = mRecordCallbackList.size();
if ((oldCbCount == 0) && (newCbCount > 0)) {
// register binder for callbacks
@@ -2844,9 +2847,9 @@
}
/**
- * @hide
- * candidate for public API
- * @param non-null callback
+ * Unregister an audio recording callback previously registered with
+ * {@link #registerAudioRecordingCallback(AudioRecordingCallback, Handler)}.
+ * @param cb non-null callback to unregister
*/
public void unregisterAudioRecordingCallback(@NonNull AudioRecordingCallback cb) {
if (cb == null) {
@@ -2857,7 +2860,7 @@
return;
}
final int oldCbCount = mRecordCallbackList.size();
- if (mRecordCallbackList.remove(cb)) {
+ if (removeRecordCallback_sync(cb)) {
final int newCbCount = mRecordCallbackList.size();
if ((oldCbCount > 0) && (newCbCount == 0)) {
// unregister binder for callbacks
@@ -2876,8 +2879,7 @@
}
/**
- * @hide
- * candidate for public API
+ * Returns the current active audio recording configurations of the device.
* @return a non-null array of recording configurations. An array of length 0 indicates there is
* no recording active when queried.
*/
@@ -2902,18 +2904,57 @@
/**
* All operations on this list are sync'd on mRecordCallbackLock.
- * List is lazy-initialized in {@link #registerAudioRecordingCallback(AudioRecordingCallback)}.
+ * List is lazy-initialized in
+ * {@link #registerAudioRecordingCallback(AudioRecordingCallback, Handler)}.
* List can be null.
*/
- private List<AudioRecordingCallback> mRecordCallbackList;
+ private List<AudioRecordingCallbackInfo> mRecordCallbackList;
private final Object mRecordCallbackLock = new Object();
+ /**
+ * Must be called synchronized on mRecordCallbackLock
+ */
+ private boolean hasRecordCallback_sync(@NonNull AudioRecordingCallback cb) {
+ if (mRecordCallbackList != null) {
+ for (int i=0 ; i < mRecordCallbackList.size() ; i++) {
+ if (cb.equals(mRecordCallbackList.get(i).mCb)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Must be called synchronized on mRecordCallbackLock
+ */
+ private boolean removeRecordCallback_sync(@NonNull AudioRecordingCallback cb) {
+ if (mRecordCallbackList != null) {
+ for (int i=0 ; i < mRecordCallbackList.size() ; i++) {
+ if (cb.equals(mRecordCallbackList.get(i).mCb)) {
+ mRecordCallbackList.remove(i);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
private final IRecordingConfigDispatcher mRecCb = new IRecordingConfigDispatcher.Stub() {
public void dispatchRecordingConfigChange() {
- final Message m = mServiceEventHandlerDelegate.getHandler().obtainMessage(
- MSSG_RECORDING_CONFIG_CHANGE/*what*/);
- mServiceEventHandlerDelegate.getHandler().sendMessage(m);
+ synchronized(mRecordCallbackLock) {
+ if (mRecordCallbackList != null) {
+ for (int i=0 ; i < mRecordCallbackList.size() ; i++) {
+ final AudioRecordingCallbackInfo arci = mRecordCallbackList.get(i);
+ if (arci.mHandler != null) {
+ final Message m = arci.mHandler.obtainMessage(
+ MSSG_RECORDING_CONFIG_CHANGE/*what*/, arci.mCb/*obj*/);
+ arci.mHandler.sendMessage(m);
+ }
+ }
+ }
+ }
}
};
diff --git a/media/java/android/media/AudioRecordConfiguration.java b/media/java/android/media/AudioRecordConfiguration.java
index aefe692..c7d219d 100644
--- a/media/java/android/media/AudioRecordConfiguration.java
+++ b/media/java/android/media/AudioRecordConfiguration.java
@@ -22,8 +22,9 @@
import java.util.Objects;
/**
- * @hide
- * Candidate for public API, see AudioManager.getActiveRecordConfiguration()
+ * The AudioRecordConfiguration class collects the information describing an audio recording
+ * session. This information is returned through the
+ * {@link AudioManager#getActiveRecordConfigurations()} method.
*
*/
public class AudioRecordConfiguration implements Parcelable {
@@ -41,19 +42,23 @@
}
/**
- * @return one of AudioSource.MIC, AudioSource.VOICE_UPLINK,
- * AudioSource.VOICE_DOWNLINK, AudioSource.VOICE_CALL,
- * AudioSource.CAMCORDER, AudioSource.VOICE_RECOGNITION,
- * AudioSource.VOICE_COMMUNICATION.
+ * Returns the audio source being used for the recording.
+ * @return one of {@link MediaRecorder.AudioSource#MIC},
+ * {@link MediaRecorder.AudioSource#VOICE_UPLINK},
+ * {@link MediaRecorder.AudioSource#VOICE_DOWNLINK},
+ * {@link MediaRecorder.AudioSource#VOICE_CALL},
+ * {@link MediaRecorder.AudioSource#CAMCORDER},
+ * {@link MediaRecorder.AudioSource#VOICE_RECOGNITION},
+ * {@link MediaRecorder.AudioSource#VOICE_COMMUNICATION}.
*/
public int getClientAudioSource() { return mClientSource; }
/**
- * @return the session number of the recorder.
+ * Returns the session number of the recording, see {@link AudioRecord#getAudioSessionId()}.
+ * @return the session number.
*/
public int getAudioSessionId() { return mSessionId; }
-
public static final Parcelable.Creator<AudioRecordConfiguration> CREATOR
= new Parcelable.Creator<AudioRecordConfiguration>() {
/**
diff --git a/media/java/android/mtp/MtpDeviceInfo.java b/media/java/android/mtp/MtpDeviceInfo.java
index 1ceca84..2e4f451 100644
--- a/media/java/android/mtp/MtpDeviceInfo.java
+++ b/media/java/android/mtp/MtpDeviceInfo.java
@@ -30,6 +30,7 @@
private String mVersion;
private String mSerialNumber;
private int[] mOperationsSupported;
+ private int[] mEventsSupported;
// only instantiated via JNI
private MtpDeviceInfo() {
@@ -74,9 +75,71 @@
/**
* Returns operation code supported by the device.
*
- * @return supported operation code
+ * @return supported operation code. Can be null if device does not provide the property.
+ * @see MtpConstants#OPERATION_GET_DEVICE_INFO
+ * @see MtpConstants#OPERATION_OPEN_SESSION
+ * @see MtpConstants#OPERATION_CLOSE_SESSION
+ * @see MtpConstants#OPERATION_GET_STORAGE_I_DS
+ * @see MtpConstants#OPERATION_GET_STORAGE_INFO
+ * @see MtpConstants#OPERATION_GET_NUM_OBJECTS
+ * @see MtpConstants#OPERATION_GET_OBJECT_HANDLES
+ * @see MtpConstants#OPERATION_GET_OBJECT_INFO
+ * @see MtpConstants#OPERATION_GET_OBJECT
+ * @see MtpConstants#OPERATION_GET_THUMB
+ * @see MtpConstants#OPERATION_DELETE_OBJECT
+ * @see MtpConstants#OPERATION_SEND_OBJECT_INFO
+ * @see MtpConstants#OPERATION_SEND_OBJECT
+ * @see MtpConstants#OPERATION_INITIATE_CAPTURE
+ * @see MtpConstants#OPERATION_FORMAT_STORE
+ * @see MtpConstants#OPERATION_RESET_DEVICE
+ * @see MtpConstants#OPERATION_SELF_TEST
+ * @see MtpConstants#OPERATION_SET_OBJECT_PROTECTION
+ * @see MtpConstants#OPERATION_POWER_DOWN
+ * @see MtpConstants#OPERATION_GET_DEVICE_PROP_DESC
+ * @see MtpConstants#OPERATION_GET_DEVICE_PROP_VALUE
+ * @see MtpConstants#OPERATION_SET_DEVICE_PROP_VALUE
+ * @see MtpConstants#OPERATION_RESET_DEVICE_PROP_VALUE
+ * @see MtpConstants#OPERATION_TERMINATE_OPEN_CAPTURE
+ * @see MtpConstants#OPERATION_MOVE_OBJECT
+ * @see MtpConstants#OPERATION_COPY_OBJECT
+ * @see MtpConstants#OPERATION_GET_PARTIAL_OBJECT
+ * @see MtpConstants#OPERATION_INITIATE_OPEN_CAPTURE
+ * @see MtpConstants#OPERATION_GET_OBJECT_PROPS_SUPPORTED
+ * @see MtpConstants#OPERATION_GET_OBJECT_PROP_DESC
+ * @see MtpConstants#OPERATION_GET_OBJECT_PROP_VALUE
+ * @see MtpConstants#OPERATION_SET_OBJECT_PROP_VALUE
+ * @see MtpConstants#OPERATION_GET_OBJECT_REFERENCES
+ * @see MtpConstants#OPERATION_SET_OBJECT_REFERENCES
+ * @see MtpConstants#OPERATION_SKIP
*/
public final @Nullable int[] getOperationsSupported() {
return mOperationsSupported;
}
+
+ /**
+ * Returns event code supported by the device.
+ *
+ * @return supported event code. Can be null if device does not provide the property.
+ * @see MtpConstants#EVENT_UNDEFINED
+ * @see MtpConstants#EVENT_CANCEL_TRANSACTION
+ * @see MtpConstants#EVENT_OBJECT_ADDED
+ * @see MtpConstants#EVENT_OBJECT_REMOVED
+ * @see MtpConstants#EVENT_STORE_ADDED
+ * @see MtpConstants#EVENT_STORE_REMOVED
+ * @see MtpConstants#EVENT_DEVICE_PROP_CHANGED
+ * @see MtpConstants#EVENT_OBJECT_INFO_CHANGED
+ * @see MtpConstants#EVENT_DEVICE_INFO_CHANGED
+ * @see MtpConstants#EVENT_REQUEST_OBJECT_TRANSFER
+ * @see MtpConstants#EVENT_STORE_FULL
+ * @see MtpConstants#EVENT_DEVICE_RESET
+ * @see MtpConstants#EVENT_STORAGE_INFO_CHANGED
+ * @see MtpConstants#EVENT_CAPTURE_COMPLETE
+ * @see MtpConstants#EVENT_UNREPORTED_STATUS
+ * @see MtpConstants#EVENT_OBJECT_PROP_CHANGED
+ * @see MtpConstants#EVENT_OBJECT_PROP_DESC_CHANGED
+ * @see MtpConstants#EVENT_OBJECT_REFERENCES_CHANGED
+ */
+ public final @Nullable int[] getEventsSupported() {
+ return mEventsSupported;
+ }
}
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 1c043e0..4ac62f5 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -574,6 +574,7 @@
case HAL_PIXEL_FORMAT_Y8:
// Single plane 8bpp data.
ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
+ pixelStride = 1;
break;
case HAL_PIXEL_FORMAT_YV12:
pixelStride = 1;
diff --git a/media/jni/android_mtp_MtpDevice.cpp b/media/jni/android_mtp_MtpDevice.cpp
index b1b3b62..8b7a926 100644
--- a/media/jni/android_mtp_MtpDevice.cpp
+++ b/media/jni/android_mtp_MtpDevice.cpp
@@ -25,6 +25,8 @@
#include <unistd.h>
#include <fcntl.h>
+#include <memory>
+
#include "jni.h"
#include "JNIHelp.h"
#include "ScopedPrimitiveArray.h"
@@ -66,6 +68,7 @@
static jfieldID field_deviceInfo_version;
static jfieldID field_deviceInfo_serialNumber;
static jfieldID field_deviceInfo_operationsSupported;
+static jfieldID field_deviceInfo_eventsSupported;
// MtpStorageInfo fields
static jfieldID field_storageInfo_storageId;
@@ -216,7 +219,7 @@
ALOGD("android_mtp_MtpDevice_get_device_info device is null");
return NULL;
}
- MtpDeviceInfo* deviceInfo = device->getDeviceInfo();
+ std::unique_ptr<MtpDeviceInfo> deviceInfo(device->getDeviceInfo());
if (!deviceInfo) {
ALOGD("android_mtp_MtpDevice_get_device_info deviceInfo is null");
return NULL;
@@ -224,7 +227,6 @@
jobject info = env->NewObject(clazz_deviceInfo, constructor_deviceInfo);
if (info == NULL) {
ALOGE("Could not create a MtpDeviceInfo object");
- delete deviceInfo;
return NULL;
}
@@ -242,17 +244,35 @@
env->NewStringUTF(deviceInfo->mSerial));
if (deviceInfo->mOperations) {
const size_t size = deviceInfo->mOperations->size();
- const jintArray operations = env->NewIntArray(size);
+ ScopedLocalRef<jintArray> operations(env, static_cast<jintArray>(env->NewIntArray(size)));
{
- ScopedIntArrayRW elements(env, operations);
+ ScopedIntArrayRW elements(env, operations.get());
+ if (elements.get() == NULL) {
+ ALOGE("Could not create operationsSupported element.");
+ return NULL;
+ }
for (size_t i = 0; i < size; ++i) {
elements[i] = deviceInfo->mOperations->itemAt(i);
}
+ env->SetObjectField(info, field_deviceInfo_operationsSupported, operations.get());
}
- env->SetObjectField(info, field_deviceInfo_operationsSupported, operations);
+ }
+ if (deviceInfo->mEvents) {
+ const size_t size = deviceInfo->mEvents->size();
+ ScopedLocalRef<jintArray> events(env, static_cast<jintArray>(env->NewIntArray(size)));
+ {
+ ScopedIntArrayRW elements(env, events.get());
+ if (elements.get() == NULL) {
+ ALOGE("Could not create eventsSupported element.");
+ return NULL;
+ }
+ for (size_t i = 0; i < size; ++i) {
+ elements[i] = deviceInfo->mEvents->itemAt(i);
+ }
+ env->SetObjectField(info, field_deviceInfo_eventsSupported, events.get());
+ }
}
- delete deviceInfo;
return info;
}
@@ -686,6 +706,11 @@
ALOGE("Can't find MtpDeviceInfo.mOperationsSupported");
return -1;
}
+ field_deviceInfo_eventsSupported = env->GetFieldID(clazz, "mEventsSupported", "[I");
+ if (field_deviceInfo_eventsSupported == NULL) {
+ ALOGE("Can't find MtpDeviceInfo.mEventsSupported");
+ return -1;
+ }
clazz_deviceInfo = (jclass)env->NewGlobalRef(clazz);
clazz = env->FindClass("android/mtp/MtpStorageInfo");
diff --git a/native/android/Android.mk b/native/android/Android.mk
index 1742bee..5386e6f 100644
--- a/native/android/Android.mk
+++ b/native/android/Android.mk
@@ -7,6 +7,7 @@
#
LOCAL_SRC_FILES:= \
asset_manager.cpp \
+ choreographer.cpp \
configuration.cpp \
input.cpp \
looper.cpp \
@@ -43,4 +44,7 @@
LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
+# Required because of b/25642296
+LOCAL_CLANG_arm64 := false
+
include $(BUILD_SHARED_LIBRARY)
diff --git a/native/android/choreographer.cpp b/native/android/choreographer.cpp
new file mode 100644
index 0000000..cc2fd77
--- /dev/null
+++ b/native/android/choreographer.cpp
@@ -0,0 +1,205 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "Choreographer"
+//#define LOG_NDEBUG 0
+
+#include <cinttypes>
+#include <queue>
+#include <thread>
+
+#include <android/choreographer.h>
+#include <androidfw/DisplayEventDispatcher.h>
+#include <gui/ISurfaceComposer.h>
+#include <utils/Looper.h>
+#include <utils/Mutex.h>
+#include <utils/Timers.h>
+
+namespace android {
+
+static inline const char* toString(bool value) {
+ return value ? "true" : "false";
+}
+
+struct FrameCallback {
+ AChoreographer_frameCallback callback;
+ void* data;
+ nsecs_t dueTime;
+
+ inline bool operator<(const FrameCallback& rhs) const {
+ // Note that this is intentionally flipped because we want callbacks due sooner to be at
+ // the head of the queue
+ return dueTime > rhs.dueTime;
+ }
+};
+
+
+class Choreographer : public DisplayEventDispatcher, public MessageHandler {
+public:
+ void postFrameCallback(AChoreographer_frameCallback cb, void* data);
+ void postFrameCallbackDelayed(AChoreographer_frameCallback cb, void* data, nsecs_t delay);
+
+ enum {
+ MSG_SCHEDULE_CALLBACKS = 0,
+ MSG_SCHEDULE_VSYNC = 1
+ };
+ virtual void handleMessage(const Message& message) override;
+
+ static Choreographer* getForThread();
+
+protected:
+ virtual ~Choreographer() = default;
+
+private:
+ Choreographer(const sp<Looper>& looper);
+ Choreographer(const Choreographer&) = delete;
+
+ virtual void dispatchVsync(nsecs_t timestamp, int32_t id, uint32_t count);
+ virtual void dispatchHotplug(nsecs_t timestamp, int32_t id, bool connected);
+
+ void scheduleCallbacks();
+
+ // Protected by mLock
+ std::priority_queue<FrameCallback> mCallbacks;
+
+ mutable Mutex mLock;
+
+ const sp<Looper> mLooper;
+ const std::thread::id mThreadId;
+};
+
+
+thread_local Choreographer* gChoreographer;
+Choreographer* Choreographer::getForThread() {
+ if (gChoreographer == nullptr) {
+ sp<Looper> looper = Looper::getForThread();
+ if (!looper.get()) {
+ ALOGW("No looper prepared for thread");
+ return nullptr;
+ }
+ gChoreographer = new Choreographer(looper);
+ status_t result = gChoreographer->initialize();
+ if (result != OK) {
+ ALOGW("Failed to initialize");
+ return nullptr;
+ }
+ }
+ return gChoreographer;
+}
+
+Choreographer::Choreographer(const sp<Looper>& looper) :
+ DisplayEventDispatcher(looper), mLooper(looper), mThreadId(std::this_thread::get_id()) {
+}
+
+void Choreographer::postFrameCallback(AChoreographer_frameCallback cb, void* data) {
+ postFrameCallbackDelayed(cb, data, 0);
+}
+
+void Choreographer::postFrameCallbackDelayed(
+ AChoreographer_frameCallback cb, void* data, nsecs_t delay) {
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ FrameCallback callback{cb, data, now + delay};
+ {
+ AutoMutex _l{mLock};
+ mCallbacks.push(callback);
+ }
+ if (callback.dueTime <= now) {
+ if (std::this_thread::get_id() != mThreadId) {
+ Message m{MSG_SCHEDULE_VSYNC};
+ mLooper->sendMessage(this, m);
+ } else {
+ scheduleVsync();
+ }
+ } else {
+ Message m{MSG_SCHEDULE_CALLBACKS};
+ mLooper->sendMessageDelayed(delay, this, m);
+ }
+}
+
+void Choreographer::scheduleCallbacks() {
+ AutoMutex _{mLock};
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ if (mCallbacks.top().dueTime <= now) {
+ ALOGV("choreographer %p ~ scheduling vsync", this);
+ scheduleVsync();
+ return;
+ }
+}
+
+
+void Choreographer::dispatchVsync(nsecs_t timestamp, int32_t id, uint32_t) {
+ if (id != ISurfaceComposer::eDisplayIdMain) {
+ ALOGV("choreographer %p ~ ignoring vsync signal for non-main display (id=%d)", this, id);
+ scheduleVsync();
+ return;
+ }
+ std::vector<FrameCallback> callbacks{};
+ {
+ AutoMutex _l{mLock};
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ while (!mCallbacks.empty() && mCallbacks.top().dueTime < now) {
+ callbacks.push_back(mCallbacks.top());
+ mCallbacks.pop();
+ }
+ }
+ for (const auto& cb : callbacks) {
+ cb.callback(timestamp, cb.data);
+ }
+}
+
+void Choreographer::dispatchHotplug(nsecs_t, int32_t id, bool connected) {
+ ALOGV("choreographer %p ~ received hotplug event (id=%" PRId32 ", connected=%s), ignoring.",
+ this, id, toString(connected));
+}
+
+void Choreographer::handleMessage(const Message& message) {
+ switch (message.what) {
+ case MSG_SCHEDULE_CALLBACKS:
+ scheduleCallbacks();
+ break;
+ case MSG_SCHEDULE_VSYNC:
+ scheduleVsync();
+ break;
+ }
+}
+
+}
+
+/* Glue for the NDK interface */
+
+using android::Choreographer;
+
+static inline Choreographer* AChoreographer_to_Choreographer(AChoreographer* choreographer) {
+ return reinterpret_cast<Choreographer*>(choreographer);
+}
+
+static inline AChoreographer* Choreographer_to_AChoreographer(Choreographer* choreographer) {
+ return reinterpret_cast<AChoreographer*>(choreographer);
+}
+
+AChoreographer* AChoreographer_getInstance() {
+ return Choreographer_to_AChoreographer(Choreographer::getForThread());
+}
+
+void AChoreographer_postFrameCallback(AChoreographer* choreographer,
+ AChoreographer_frameCallback callback, void* data) {
+ AChoreographer_to_Choreographer(choreographer)->postFrameCallback(callback, data);
+}
+void AChoreographer_postFrameCallbackDelayed(AChoreographer* choreographer,
+ AChoreographer_frameCallback callback, void* data, long delayMillis) {
+ AChoreographer_to_Choreographer(choreographer)->postFrameCallbackDelayed(
+ callback, data, ms2ns(delayMillis));
+}
diff --git a/packages/DocumentsUI/res/drawable/cabinet.png b/packages/DocumentsUI/res/drawable/cabinet.png
new file mode 100644
index 0000000..da44023
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable/cabinet.png
Binary files differ
diff --git a/packages/DocumentsUI/res/layout/dialog_create_dir.xml b/packages/DocumentsUI/res/layout/dialog_file_name.xml
similarity index 100%
rename from packages/DocumentsUI/res/layout/dialog_create_dir.xml
rename to packages/DocumentsUI/res/layout/dialog_file_name.xml
diff --git a/packages/DocumentsUI/res/layout/fragment_directory.xml b/packages/DocumentsUI/res/layout/fragment_directory.xml
index 1b5911d..223d729 100644
--- a/packages/DocumentsUI/res/layout/fragment_directory.xml
+++ b/packages/DocumentsUI/res/layout/fragment_directory.xml
@@ -36,8 +36,8 @@
android:background="@color/material_grey_50"
android:visibility="gone"/>
- <!-- The empty directory view -->
- <LinearLayout
+ <!-- The empty container view -->
+ <FrameLayout
android:id="@android:id/empty"
android:gravity="center"
android:layout_width="match_parent"
@@ -45,21 +45,34 @@
android:orientation="vertical"
android:visibility="gone">
- <TextView
- android:id="@+id/message"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/empty"
- style="@android:style/TextAppearance.Material.Subhead" />
+ <LinearLayout
+ android:id="@+id/content"
+ android:gravity="center"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
- <Button
- android:id="@+id/button_retry"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/button_retry"
- style="?android:attr/buttonBarPositiveButtonStyle" />
+ <ImageView
+ android:id="@+id/artwork"
+ android:src="@drawable/cabinet"
+ android:adjustViewBounds="true"
+ android:layout_height="250dp"
+ android:layout_width="fill_parent"
+ android:alpha="1"
+ android:layout_centerVertical="true"
+ android:layout_marginBottom="25dp"
+ android:scaleType="fitCenter"
+ android:contentDescription="@null" />
- </LinearLayout>
+ <TextView
+ android:id="@+id/message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/empty"
+ style="@android:style/TextAppearance.Material.Subhead" />
+
+ </LinearLayout>
+ </FrameLayout>
<!-- This FrameLayout works around b/24189541 -->
<FrameLayout
diff --git a/packages/DocumentsUI/res/menu/mode_directory.xml b/packages/DocumentsUI/res/menu/mode_directory.xml
index 4ff396f..6f9bfb5 100644
--- a/packages/DocumentsUI/res/menu/mode_directory.xml
+++ b/packages/DocumentsUI/res/menu/mode_directory.xml
@@ -48,4 +48,9 @@
android:title="@string/menu_move"
android:showAsAction="never"
android:visible="false" />
+ <item
+ android:id="@+id/menu_rename"
+ android:title="@string/menu_rename"
+ android:showAsAction="never"
+ android:visible="false" />
</menu>
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index 016657e..8ac228e 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -87,7 +87,7 @@
<!-- Button label that hides the error bar [CHAR LIMIT=24] -->
<string name="button_dismiss">Dismiss</string>
<string name="button_retry">Try Again</string>
-
+
<!-- Mode that sorts documents by their display name alphabetically [CHAR LIMIT=24] -->
<string name="sort_name">By name</string>
<!-- Mode that sorts documents by their last modified time in descending order; most recent first [CHAR LIMIT=24] -->
@@ -123,6 +123,8 @@
<!-- Text shown when a directory of documents is empty [CHAR LIMIT=24] -->
<string name="empty">No items</string>
+ <!-- Text shown when a file search returns no items [CHAR LIMIT=32] -->
+ <string name="no_results">No matches in %1$s</string>
<!-- Toast shown when no app can be found to open the selected document [CHAR LIMIT=48] -->
<string name="toast_no_application">Can\'t open file</string>
@@ -158,6 +160,8 @@
<string name="copy_preparing">Preparing for copy\u2026</string>
<!-- Text shown on the notification while DocumentsUI performs setup in preparation for moving files [CHAR LIMIT=32] -->
<string name="move_preparing">Preparing for move\u2026</string>
+ <!-- Text shown on the notification while DocumentsUI performs setup in preparation for deleting files [CHAR LIMIT=32] -->
+ <string name="delete_preparing">Preparing for delete\u2026</string>
<!-- Title of the copy error notification [CHAR LIMIT=48] -->
<plurals name="copy_error_notification_title">
<item quantity="one">Couldn\'t copy <xliff:g id="count" example="1">%1$d</xliff:g> file</item>
@@ -168,8 +172,13 @@
<item quantity="one">Couldn\'t move <xliff:g id="count" example="1">%1$d</xliff:g> file</item>
<item quantity="other">Couldn\'t move <xliff:g id="count" example="2">%1$d</xliff:g> files</item>
</plurals>
+ <!-- Title of the delete error notification [CHAR LIMIT=48] -->
+ <plurals name="delete_error_notification_title">
+ <item quantity="one">Couldn\'t delete <xliff:g id="count" example="1">%1$d</xliff:g> file</item>
+ <item quantity="other">Couldn\'t delete <xliff:g id="count" example="2">%1$d</xliff:g> files</item>
+ </plurals>
<!-- Second line for notifications saying that more information will be shown after touching [CHAR LIMIT=48] -->
- <string name="notification_touch_for_details">Touch to view details</string>
+ <string name="notification_touch_for_details">Tap to view details</string>
<!-- Label of a dialog button for retrying a failed operation [CHAR LIMIT=24] -->
<string name="retry">Retry</string>
<!-- Contents of the copying failure alert dialog. [CHAR LIMIT=48] -->
@@ -183,4 +192,8 @@
</plurals>
<!-- Toast shown when a user tries to paste files into an unsupported location. -->
<string name="clipboard_files_cannot_paste">Cannot paste the selected files in this location.</string>
+ <!-- Menu item that renames the selected document [CHAR LIMIT=24] -->
+ <string name="menu_rename">Rename</string>
+ <!-- Toast shown when renaming document failed with an error [CHAR LIMIT=48] -->
+ <string name="rename_error">Failed to rename document</string>
</resources>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 0fb8daf..f43b81c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -437,7 +437,7 @@
DirectoryFragment.get(getFragmentManager()).onUserModeChanged();
}
- void setPending(boolean pending) {
+ public void setPending(boolean pending) {
final SaveFragment save = SaveFragment.get(getFragmentManager());
if (save != null) {
save.setPending(pending);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
index f3c3f2f..df036b9 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
@@ -63,7 +63,7 @@
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
- final View view = dialogInflater.inflate(R.layout.dialog_create_dir, null, false);
+ final View view = dialogInflater.inflate(R.layout.dialog_file_name, null, false);
final EditText editText = (EditText) view.findViewById(android.R.id.text1);
builder.setTitle(R.string.menu_create_dir);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Events.java b/packages/DocumentsUI/src/com/android/documentsui/Events.java
index 1b5b60de..10a78b9 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Events.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Events.java
@@ -111,17 +111,15 @@
public static final class MotionInputEvent implements InputEvent {
private final MotionEvent mEvent;
- private final RecyclerView mView;
private final int mPosition;
public MotionInputEvent(MotionEvent event, RecyclerView view) {
mEvent = event;
- mView = view;
// Consider determining position lazily as an optimization.
- View child = mView.findChildViewUnder(mEvent.getX(), mEvent.getY());
- mPosition = (child != null)
- ? mView.getChildAdapterPosition(child)
+ View child = view.findChildViewUnder(mEvent.getX(), mEvent.getY());
+ mPosition = (child!= null)
+ ? view.getChildAdapterPosition(child)
: RecyclerView.NO_POSITION;
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java b/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java
index 5a80c39..4543162 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java
@@ -72,18 +72,12 @@
String trustedPkg = mResources.getString(R.string.trusted_quick_viewer_package);
- Intent intent = new Intent(Intent.ACTION_QUICK_VIEW);
- intent.setDataAndType(mDocument.derivedUri, mDocument.mimeType);
- intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
- if (TextUtils.isEmpty(trustedPkg)) {
- if (hasRegisteredHandler(intent)) {
- return intent;
- }
- } else {
+ if (!TextUtils.isEmpty(trustedPkg)) {
+ Intent intent = new Intent(Intent.ACTION_QUICK_VIEW);
+ intent.setDataAndType(mDocument.derivedUri, mDocument.mimeType);
+ intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setPackage(trustedPkg);
if (hasRegisteredHandler(intent)) {
- // We have a trusted handler. Load all of the docs into the intent.
Cursor cursor = mSiblings.getCursor();
for (int i = 0; i < cursor.getCount(); i++) {
onNextItem(i, cursor);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 580e2d8..809db31 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -28,6 +28,7 @@
import static com.android.internal.util.Preconditions.checkState;
import static com.google.common.base.Preconditions.checkArgument;
+import android.annotation.StringRes;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.Fragment;
@@ -84,6 +85,7 @@
import com.android.documentsui.DocumentsActivity;
import com.android.documentsui.DocumentsApplication;
import com.android.documentsui.Events;
+import com.android.documentsui.Events.MotionInputEvent;
import com.android.documentsui.Menus;
import com.android.documentsui.MessageBar;
import com.android.documentsui.MimePredicate;
@@ -91,6 +93,7 @@
import com.android.documentsui.RecentLoader;
import com.android.documentsui.RecentsProvider;
import com.android.documentsui.RecentsProvider.StateColumns;
+import com.android.documentsui.dirlist.RenameDocumentFragment;
import com.android.documentsui.RootsCache;
import com.android.documentsui.Shared;
import com.android.documentsui.Snackbars;
@@ -124,11 +127,14 @@
public static final int REQUEST_COPY_DESTINATION = 1;
- private static final String TAG = "DirectoryFragment";
-
- private static final int LOADER_ID = 42;
static final boolean DEBUG_ENABLE_DND = true;
+ private static final String TAG = "DirectoryFragment";
+ private static final int LOADER_ID = 42;
+ private static final int DELETE_UNDO_TIMEOUT = 5000;
+ private static final int DELETE_JOB_DELAY = 5500;
+ private static final int EMPTY_REVEAL_DURATION = 250;
+
private static final String EXTRA_TYPE = "type";
private static final String EXTRA_ROOT = "root";
private static final String EXTRA_DOC = "doc";
@@ -138,12 +144,13 @@
private Model mModel;
private MultiSelectManager mSelectionManager;
private Model.UpdateListener mModelUpdateListener = new ModelUpdateListener();
- private ItemClickListener mItemClickListener = new ItemClickListener();
+ private ItemEventListener mItemEventListener = new ItemEventListener();
private IconHelper mIconHelper;
private View mEmptyView;
private RecyclerView mRecView;
+ private ListeningGestureDetector mGestureDetector;
private int mType = TYPE_NORMAL;
private String mStateKey;
@@ -163,63 +170,6 @@
private MessageBar mMessageBar;
private View mProgressBar;
- public static void showDirectory(FragmentManager fm, RootInfo root, DocumentInfo doc, int anim) {
- show(fm, TYPE_NORMAL, root, doc, null, anim);
- }
-
- public static void showSearch(FragmentManager fm, RootInfo root, String query, int anim) {
- show(fm, TYPE_SEARCH, root, null, query, anim);
- }
-
- public static void showRecentsOpen(FragmentManager fm, int anim) {
- show(fm, TYPE_RECENT_OPEN, null, null, null, anim);
- }
-
- private static void show(FragmentManager fm, int type, RootInfo root, DocumentInfo doc,
- String query, int anim) {
- final Bundle args = new Bundle();
- args.putInt(EXTRA_TYPE, type);
- args.putParcelable(EXTRA_ROOT, root);
- args.putParcelable(EXTRA_DOC, doc);
- args.putString(EXTRA_QUERY, query);
-
- final FragmentTransaction ft = fm.beginTransaction();
- switch (anim) {
- case ANIM_SIDE:
- args.putBoolean(EXTRA_IGNORE_STATE, true);
- break;
- case ANIM_DOWN:
- args.putBoolean(EXTRA_IGNORE_STATE, true);
- ft.setCustomAnimations(R.animator.dir_down, R.animator.dir_frozen);
- break;
- case ANIM_UP:
- ft.setCustomAnimations(R.animator.dir_frozen, R.animator.dir_up);
- break;
- }
-
- final DirectoryFragment fragment = new DirectoryFragment();
- fragment.setArguments(args);
-
- ft.replace(R.id.container_directory, fragment);
- ft.commitAllowingStateLoss();
- }
-
- private static String buildStateKey(RootInfo root, DocumentInfo doc) {
- final StringBuilder builder = new StringBuilder();
- builder.append(root != null ? root.authority : "null").append(';');
- builder.append(root != null ? root.rootId : "null").append(';');
- builder.append(doc != null ? doc.documentId : "null");
- return builder.toString();
- }
-
- public static @Nullable DirectoryFragment get(FragmentManager fm) {
- // TODO: deal with multiple directories shown at once
- Fragment fragment = fm.findFragmentById(R.id.container_directory);
- return fragment instanceof DirectoryFragment
- ? (DirectoryFragment) fragment
- : null;
- }
-
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@@ -297,36 +247,9 @@
mRecView.setAdapter(mAdapter);
- GestureDetector.SimpleOnGestureListener listener =
- new GestureDetector.SimpleOnGestureListener() {
- @Override
- public boolean onSingleTapUp(MotionEvent e) {
- return DirectoryFragment.this.onSingleTapUp(e);
- }
- @Override
- public boolean onDoubleTap(MotionEvent e) {
- Log.d(TAG, "Handling double tap.");
- return DirectoryFragment.this.onDoubleTap(e);
- }
- };
+ mGestureDetector = new ListeningGestureDetector(this.getContext(), new GestureListener());
- final GestureDetector detector = new GestureDetector(this.getContext(), listener);
- detector.setOnDoubleTapListener(listener);
-
- mRecView.addOnItemTouchListener(
- new OnItemTouchListener() {
- @Override
- public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
- detector.onTouchEvent(e);
- return false;
- }
-
- @Override
- public void onTouchEvent(RecyclerView rv, MotionEvent e) {}
-
- @Override
- public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}
- });
+ mRecView.addOnItemTouchListener(mGestureDetector);
// TODO: instead of inserting the view into the constructor, extract listener-creation code
// and set the listener on the view after the fact. Then the view doesn't need to be passed
@@ -339,7 +262,7 @@
: MultiSelectManager.MODE_SINGLE);
mSelectionManager.addCallback(new SelectionModeListener());
- mModel = new Model(context);
+ mModel = new Model();
mModel.addUpdateListener(mAdapter);
mModel.addUpdateListener(mModelUpdateListener);
@@ -466,22 +389,8 @@
operationType);
}
- private boolean onSingleTapUp(MotionEvent e) {
- // Only respond to touch events. Single-click mouse events are selection events and are
- // handled by the selection manager. Tap events that occur while the selection manager is
- // active are also selection events.
- if (Events.isTouchEvent(e) && !mSelectionManager.hasSelection()) {
- String id = getModelId(e);
- if (id != null) {
- return handleViewItem(id);
- }
- }
- return false;
- }
-
protected boolean onDoubleTap(MotionEvent e) {
if (Events.isMouseEvent(e)) {
- Log.d(TAG, "Handling double tap from mouse.");
String id = getModelId(e);
if (id != null) {
return handleViewItem(id);
@@ -651,6 +560,7 @@
private Selection mSelected = new Selection();
private ActionMode mActionMode;
private int mNoDeleteCount = 0;
+ private int mNoRenameCount = -1;
private Menu mMenu;
@Override
@@ -674,6 +584,9 @@
if ((docFlags & Document.FLAG_SUPPORTS_DELETE) == 0) {
mNoDeleteCount += selected ? 1 : -1;
}
+ if ((docFlags & Document.FLAG_SUPPORTS_RENAME) != 0) {
+ mNoRenameCount += selected ? 1 : -1;
+ }
}
@Override
@@ -712,6 +625,7 @@
mSelectionManager.clearSelection();
mSelected.clear();
mNoDeleteCount = 0;
+ mNoRenameCount = -1;
}
@Override
@@ -729,10 +643,19 @@
return true;
}
+ boolean canRenameSelection() {
+ return mNoRenameCount == 0 && mSelectionManager.getSelection().size() == 1;
+ }
+
+ boolean canDeleteSelection() {
+ return mNoDeleteCount == 0;
+ }
+
private void updateActionMenu() {
checkNotNull(mMenu);
+
// Delegate update logic to our owning action, since specialized logic is desired.
- mTuner.updateActionMenu(mMenu, mType, mNoDeleteCount == 0);
+ mTuner.updateActionMenu(mMenu, mType, canDeleteSelection(), canRenameSelection());
Menus.disableHiddenItems(mMenu);
}
@@ -772,6 +695,7 @@
case R.id.menu_copy_to_clipboard:
if (!selection.isEmpty()) {
copySelectionToClipboard(selection);
+ mode.finish();
}
return true;
@@ -779,6 +703,11 @@
selectAllFiles();
return true;
+ case R.id.menu_rename:
+ renameDocuments(selection);
+ mode.finish();
+ return true;
+
default:
if (DEBUG) Log.d(TAG, "Unhandled menu item selected: " + item);
return false;
@@ -852,16 +781,32 @@
}
private void deleteDocuments(final Selection selected) {
- Context context = getActivity();
- String message = Shared.getQuantityString(context, R.plurals.deleting, selected.size());
- // Hide the files in the UI.
- final SparseArray<String> toDelete = mAdapter.hide(selected.getAll());
+ checkArgument(!selected.isEmpty());
+ new GetDocumentsTask() {
+ @Override
+ void onDocumentsReady(List<DocumentInfo> docs) {
+ // Hide the files in the UI.
+ final SparseArray<String> hidden = mAdapter.hide(selected.getAll());
+
+ checkState(DELETE_JOB_DELAY > DELETE_UNDO_TIMEOUT);
+ String operationId = FileOperations.delete(
+ getActivity(), docs, getDisplayState().stack,
+ DELETE_JOB_DELAY);
+ showDeleteSnackbar(hidden, operationId);
+ }
+ }.execute(selected);
+ }
+
+ private void showDeleteSnackbar(final SparseArray<String> hidden, final String jobId) {
+
+ Context context = getActivity();
+ String message = Shared.getQuantityString(context, R.plurals.deleting, hidden.size());
// Show a snackbar informing the user that files will be deleted, and give them an option to
// cancel.
final Activity activity = getActivity();
- Snackbars.makeSnackbar(activity, message, Snackbar.LENGTH_LONG)
+ Snackbars.makeSnackbar(activity, message, DELETE_UNDO_TIMEOUT)
.setAction(
R.string.undo,
new View.OnClickListener() {
@@ -874,22 +819,8 @@
public void onDismissed(Snackbar snackbar, int event) {
if (event == Snackbar.Callback.DISMISS_EVENT_ACTION) {
// If the delete was cancelled, just unhide the files.
- mAdapter.unhide(toDelete);
- } else {
- // Actually kick off the delete.
- mModel.delete(
- selected,
- new Model.DeletionListener() {
- @Override
- public void onError() {
- Snackbars.makeSnackbar(
- activity,
- R.string.toast_failed_delete,
- Snackbar.LENGTH_LONG)
- .show();
-
- }
- });
+ FileOperations.cancel(activity, jobId);
+ mAdapter.unhide(hidden);
}
}
})
@@ -924,9 +855,22 @@
}.execute(selected);
}
+ private void renameDocuments(Selection selected) {
+ // Batch renaming not supported
+ // Rename option is only available in menu when 1 document selected
+ checkArgument(selected.size() == 1);
+
+ new GetDocumentsTask() {
+ @Override
+ void onDocumentsReady(List<DocumentInfo> docs) {
+ RenameDocumentFragment.show(getFragmentManager(), docs.get(0));
+ }
+ }.execute(selected);
+ }
+
@Override
public void initDocumentHolder(DocumentHolder holder) {
- holder.addClickListener(mItemClickListener);
+ holder.addEventListener(mItemEventListener);
holder.addOnKeyListener(mSelectionManager);
}
@@ -952,25 +896,43 @@
return mTuner.isDocumentEnabled(docMimeType, docFlags);
}
- void showEmptyView() {
- mEmptyView.setVisibility(View.VISIBLE);
- mRecView.setVisibility(View.GONE);
- TextView msg = (TextView) mEmptyView.findViewById(R.id.message);
- msg.setText(R.string.empty);
- // No retry button for the empty view.
- mEmptyView.findViewById(R.id.button_retry).setVisibility(View.GONE);
+ private void showEmptyDirectory() {
+ showEmptyView(R.string.empty);
}
- void showErrorView() {
- mEmptyView.setVisibility(View.VISIBLE);
- mRecView.setVisibility(View.GONE);
- TextView msg = (TextView) mEmptyView.findViewById(R.id.message);
- msg.setText(R.string.query_error);
- // TODO: Enable this once the retry button does something.
- mEmptyView.findViewById(R.id.button_retry).setVisibility(View.GONE);
+ private void showNoResults(RootInfo root) {
+ CharSequence msg = getContext().getResources().getText(R.string.no_results);
+ showEmptyView(String.format(String.valueOf(msg), root.title));
}
- void showRecyclerView() {
+ // Shows an error indicating documents couldn't be queried.
+ private void showQueryError() {
+ showEmptyView(R.string.query_error);
+ }
+
+ private void showEmptyView(@StringRes int id) {
+ showEmptyView(getContext().getResources().getText(id));
+ }
+
+ private void showEmptyView(CharSequence msg) {
+ View content = mEmptyView.findViewById(R.id.content);
+ TextView msgView = (TextView) mEmptyView.findViewById(R.id.message);
+ msgView.setText(msg);
+
+ content.animate().cancel(); // cancel any ongoing animations
+
+ content.setAlpha(0);
+ mEmptyView.setVisibility(View.VISIBLE);
+ mRecView.setVisibility(View.GONE);
+
+ // fade in the content, so it looks purdy like
+ content.animate()
+ .alpha(1f)
+ .setDuration(EMPTY_REVEAL_DURATION)
+ .setListener(null);
+ }
+
+ private void showDirectory() {
mEmptyView.setVisibility(View.GONE);
mRecView.setVisibility(View.VISIBLE);
}
@@ -1150,11 +1112,7 @@
view.setOnDragListener(mOnDragListener);
}
- // Temporary: attaching the listener to the title only.
- // Attaching to the entire item conflicts with the item long click handler responsible
- // for item selection.
- final View title = view.findViewById(android.R.id.title);
- title.setOnLongClickListener(mLongClickListener);
+ view.setOnLongClickListener(mLongClickListener);
}
private View.OnDragListener mOnDragListener = new View.OnDragListener() {
@@ -1229,24 +1187,6 @@
}
}
- private View.OnLongClickListener mLongClickListener = new View.OnLongClickListener() {
- @Override
- public boolean onLongClick(View v) {
- final List<DocumentInfo> docs = getDraggableDocuments(v);
- if (docs.isEmpty()) {
- return false;
- }
- v.startDrag(
- getClipDataFromDocuments(docs),
- new DrawableShadowBuilder(getDragShadowIcon(docs)),
- null,
- View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ |
- View.DRAG_FLAG_GLOBAL_URI_WRITE
- );
- return true;
- }
- };
-
private List<DocumentInfo> getDraggableDocuments(View currentItemView) {
String modelId = getModelId(currentItemView);
if (modelId == null) {
@@ -1330,15 +1270,18 @@
return mSelectionManager.getSelection().contains(modelId);
}
- private class ItemClickListener implements DocumentHolder.ClickListener {
+ private class ItemEventListener implements DocumentHolder.EventListener {
@Override
- public void onClick(DocumentHolder doc) {
- if (mSelectionManager.hasSelection()) {
- mSelectionManager.toggleSelection(doc.modelId);
- mSelectionManager.setSelectionRangeBegin(doc.getAdapterPosition());
- } else {
- handleViewItem(doc.modelId);
- }
+ public boolean onActivate(DocumentHolder doc) {
+ handleViewItem(doc.modelId);
+ return true;
+ }
+
+ @Override
+ public boolean onSelect(DocumentHolder doc) {
+ mSelectionManager.toggleSelection(doc.modelId);
+ mSelectionManager.setSelectionRangeBegin(doc.getAdapterPosition());
+ return true;
}
}
@@ -1354,16 +1297,185 @@
mProgressBar.setVisibility(model.isLoading() ? View.VISIBLE : View.GONE);
if (model.isEmpty()) {
- showEmptyView();
+ if (getDisplayState().currentSearch != null) {
+ showNoResults(getDisplayState().stack.root);
+ } else {
+ showEmptyDirectory();
+ }
} else {
- showRecyclerView();
+ showDirectory();
mAdapter.notifyDataSetChanged();
}
}
@Override
public void onModelUpdateFailed(Exception e) {
- showErrorView();
+ showQueryError();
}
}
+
+ private View.OnLongClickListener mLongClickListener = new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ if (mGestureDetector.mouseSpawnedLastEvent()) {
+ List<DocumentInfo> docs = getDraggableDocuments(v);
+ if (docs.isEmpty()) {
+ return false;
+ }
+ v.startDrag(
+ getClipDataFromDocuments(docs),
+ new DrawableShadowBuilder(getDragShadowIcon(docs)),
+ null,
+ View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ |
+ View.DRAG_FLAG_GLOBAL_URI_WRITE
+ );
+ return true;
+ }
+
+ return false;
+ }
+ };
+
+ // Previously we listened to events with one class, only to bounce them forward
+ // to GestureDetector. We're still doing that here, but with a single class
+ // that reduces overall complexity in our glue code.
+ private static final class ListeningGestureDetector extends GestureDetector
+ implements OnItemTouchListener {
+
+ private int mLastTool = -1;
+
+ public ListeningGestureDetector(Context context, GestureListener listener) {
+ super(context, listener);
+ setOnDoubleTapListener(listener);
+ }
+
+ boolean mouseSpawnedLastEvent() {
+ return Events.isMouseType(mLastTool);
+ }
+
+ boolean touchSpawnedLastEvent() {
+ return Events.isTouchType(mLastTool);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
+ mLastTool = e.getToolType(0);
+ onTouchEvent(e); // bounce this forward to our detecty heart
+ return false;
+ }
+
+ @Override
+ public void onTouchEvent(RecyclerView rv, MotionEvent e) {}
+
+ @Override
+ public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}
+ }
+
+ /**
+ * The gesture listener for items in the list/grid view. Interprets gestures and sends the
+ * events to the target DocumentHolder, whence they are routed to the appropriate listener.
+ */
+ private class GestureListener extends GestureDetector.SimpleOnGestureListener {
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ // Single tap logic:
+ // If the selection manager is active, it gets first whack at handling tap
+ // events. Otherwise, tap events are routed to the target DocumentHolder.
+ boolean handled = mSelectionManager.onSingleTapUp(
+ new MotionInputEvent(e, mRecView));
+
+ if (handled) {
+ return handled;
+ }
+
+ // Give the DocumentHolder a crack at the event.
+ DocumentHolder holder = getTarget(e);
+ if (holder != null) {
+ handled = holder.onSingleTapUp(e);
+ }
+
+ return handled;
+ }
+
+ @Override
+ public void onLongPress(MotionEvent e) {
+ // Long-press events get routed directly to the selection manager. They can be
+ // changed to route through the DocumentHolder if necessary.
+ mSelectionManager.onLongPress(new MotionInputEvent(e, mRecView));
+ }
+
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ // Double-tap events are handled directly by the DirectoryFragment. They can be changed
+ // to route through the DocumentHolder if necessary.
+ return DirectoryFragment.this.onDoubleTap(e);
+ }
+
+ private @Nullable DocumentHolder getTarget(MotionEvent e) {
+ View childView = mRecView.findChildViewUnder(e.getX(), e.getY());
+ if (childView != null) {
+ return (DocumentHolder) mRecView.getChildViewHolder(childView);
+ } else {
+ return null;
+ }
+ }
+ }
+
+ public static void showDirectory(
+ FragmentManager fm, RootInfo root, DocumentInfo doc, int anim) {
+ show(fm, TYPE_NORMAL, root, doc, null, anim);
+ }
+
+ public static void showSearch(FragmentManager fm, RootInfo root, String query, int anim) {
+ show(fm, TYPE_SEARCH, root, null, query, anim);
+ }
+
+ public static void showRecentsOpen(FragmentManager fm, int anim) {
+ show(fm, TYPE_RECENT_OPEN, null, null, null, anim);
+ }
+
+ private static void show(FragmentManager fm, int type, RootInfo root, DocumentInfo doc,
+ String query, int anim) {
+ final Bundle args = new Bundle();
+ args.putInt(EXTRA_TYPE, type);
+ args.putParcelable(EXTRA_ROOT, root);
+ args.putParcelable(EXTRA_DOC, doc);
+ args.putString(EXTRA_QUERY, query);
+
+ final FragmentTransaction ft = fm.beginTransaction();
+ switch (anim) {
+ case ANIM_SIDE:
+ args.putBoolean(EXTRA_IGNORE_STATE, true);
+ break;
+ case ANIM_DOWN:
+ args.putBoolean(EXTRA_IGNORE_STATE, true);
+ ft.setCustomAnimations(R.animator.dir_down, R.animator.dir_frozen);
+ break;
+ case ANIM_UP:
+ ft.setCustomAnimations(R.animator.dir_frozen, R.animator.dir_up);
+ break;
+ }
+
+ final DirectoryFragment fragment = new DirectoryFragment();
+ fragment.setArguments(args);
+
+ ft.replace(R.id.container_directory, fragment);
+ ft.commitAllowingStateLoss();
+ }
+
+ private static String buildStateKey(RootInfo root, DocumentInfo doc) {
+ final StringBuilder builder = new StringBuilder();
+ builder.append(root != null ? root.authority : "null").append(';');
+ builder.append(root != null ? root.rootId : "null").append(';');
+ builder.append(doc != null ? doc.documentId : "null");
+ return builder.toString();
+ }
+
+ public static @Nullable DirectoryFragment get(FragmentManager fm) {
+ // TODO: deal with multiple directories shown at once
+ Fragment fragment = fm.findFragmentById(R.id.container_directory);
+ return fragment instanceof DirectoryFragment
+ ? (DirectoryFragment) fragment
+ : null;
+ }
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java
index 9ac9057..8acf1af 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java
@@ -16,17 +16,21 @@
package com.android.documentsui.dirlist;
+import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.internal.util.Preconditions.checkState;
import android.content.Context;
import android.database.Cursor;
+import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.view.KeyEvent;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import com.android.documentsui.Events;
import com.android.documentsui.R;
import com.android.documentsui.State;
@@ -41,8 +45,9 @@
final boolean mAlwaysShowSummary;
final Context mContext;
- private ListDocumentHolder.ClickListener mClickListener;
+ DocumentHolder.EventListener mEventListener;
private View.OnKeyListener mKeyListener;
+ private View mSelectionHotspot;
public DocumentHolder(Context context, ViewGroup parent, int layout) {
this(context, inflateLayout(context, parent, layout));
@@ -58,6 +63,8 @@
mDefaultItemColor = context.getColor(R.color.item_doc_background);
mSelectedItemColor = context.getColor(R.color.item_doc_background_selected);
mAlwaysShowSummary = context.getResources().getBoolean(R.bool.always_show_summary);
+
+ mSelectionHotspot = itemView.findViewById(R.id.icon_check);
}
/**
@@ -75,23 +82,21 @@
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
+ // Event listener should always be set.
+ checkNotNull(mEventListener);
// Intercept enter key-up events, and treat them as clicks. Forward other events.
- if (event.getAction() == KeyEvent.ACTION_UP &&
- keyCode == KeyEvent.KEYCODE_ENTER) {
- if (mClickListener != null) {
- mClickListener.onClick(this);
- }
- return true;
+ if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_ENTER) {
+ return mEventListener.onActivate(this);
} else if (mKeyListener != null) {
return mKeyListener.onKey(v, keyCode, event);
}
return false;
}
- public void addClickListener(ListDocumentHolder.ClickListener listener) {
+ public void addEventListener(DocumentHolder.EventListener listener) {
// Just handle one for now; switch to a list if necessary.
- checkState(mClickListener == null);
- mClickListener = listener;
+ checkState(mEventListener == null);
+ mEventListener = listener;
}
public void addOnKeyListener(View.OnKeyListener listener) {
@@ -104,6 +109,33 @@
setEnabledRecursive(itemView, enabled);
}
+ public boolean onSingleTapUp(MotionEvent event) {
+ if (Events.isMouseEvent(event)) {
+ // Mouse clicks select.
+ // TODO: && input.isPrimaryButtonPressed(), but it is returning false.
+ if (mEventListener != null) {
+ return mEventListener.onSelect(this);
+ }
+ } else if (Events.isTouchEvent(event)) {
+ // Touch events select if they occur in the selection hotspot, otherwise they activate.
+ if (mEventListener == null) {
+ return false;
+ }
+
+ // Do everything in global coordinates - it makes things simpler.
+ Rect rect = new Rect();
+ mSelectionHotspot.getGlobalVisibleRect(rect);
+
+ // If the tap occurred within the icon rect, consider it a selection.
+ if (rect.contains((int)event.getRawX(), (int)event.getRawY())) {
+ return mEventListener.onSelect(this);
+ } else {
+ return mEventListener.onActivate(this);
+ }
+ }
+ return false;
+ }
+
static void setEnabledRecursive(View itemView, boolean enabled) {
if (itemView == null) return;
if (itemView.isEnabled() == enabled) return;
@@ -122,7 +154,20 @@
return inflater.inflate(layout, parent, false);
}
- interface ClickListener {
- public void onClick(DocumentHolder doc);
+ /**
+ * Implement this in order to be able to respond to events coming from DocumentHolders.
+ */
+ interface EventListener {
+ /**
+ * @param doc The target DocumentHolder
+ * @return Whether the event was handled.
+ */
+ public boolean onActivate(DocumentHolder doc);
+
+ /**
+ * @param doc The target DocumentHolder
+ * @return Whether the event was handled.
+ */
+ public boolean onSelect(DocumentHolder doc);
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
index ef6d2c9..a295ab2 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
@@ -58,7 +58,8 @@
}
- public abstract void updateActionMenu(Menu menu, int dirType, boolean canDelete);
+ public abstract void updateActionMenu(Menu menu, int dirType, boolean canDelete,
+ boolean canRename);
// Subtly different from isDocumentEnabled. The reason may be illuminated as follows.
// A folder is enabled such that it may be double clicked, even in settings
@@ -129,7 +130,8 @@
}
@Override
- public void updateActionMenu(Menu menu, int dirType, boolean canDelete) {
+ public void updateActionMenu(Menu menu, int dirType, boolean canDelete,
+ boolean canRename) {
boolean copyEnabled = dirType != DirectoryFragment.TYPE_RECENT_OPEN;
boolean moveEnabled =
@@ -141,6 +143,7 @@
final MenuItem delete = menu.findItem(R.id.menu_delete);
final MenuItem copyTo = menu.findItem(R.id.menu_copy_to);
final MenuItem moveTo = menu.findItem(R.id.menu_move_to);
+ final MenuItem rename = menu.findItem(R.id.menu_rename);
open.setVisible(true);
share.setVisible(false);
@@ -149,6 +152,7 @@
copyTo.setEnabled(copyEnabled);
moveTo.setVisible(moveEnabled);
moveTo.setEnabled(moveEnabled);
+ rename.setVisible(false);
}
}
@@ -162,7 +166,8 @@
}
@Override
- public void updateActionMenu(Menu menu, int dirType, boolean canDelete) {
+ public void updateActionMenu(Menu menu, int dirType, boolean canDelete,
+ boolean canRename) {
checkArgument(dirType != DirectoryFragment.TYPE_RECENT_OPEN);
boolean moveEnabled =
@@ -174,6 +179,7 @@
final MenuItem delete = menu.findItem(R.id.menu_delete);
final MenuItem copyTo = menu.findItem(R.id.menu_copy_to);
final MenuItem moveTo = menu.findItem(R.id.menu_move_to);
+ final MenuItem rename = menu.findItem(R.id.menu_rename);
open.setVisible(false);
delete.setVisible(canDelete);
@@ -181,6 +187,7 @@
copyTo.setEnabled(true);
moveTo.setVisible(moveEnabled);
moveTo.setEnabled(moveEnabled);
+ rename.setVisible(false);
}
}
@@ -194,12 +201,17 @@
}
@Override
- public void updateActionMenu(Menu menu, int dirType, boolean canDelete) {
+ public void updateActionMenu(Menu menu, int dirType, boolean canDelete,
+ boolean canRename) {
MenuItem copy = menu.findItem(R.id.menu_copy_to_clipboard);
MenuItem paste = menu.findItem(R.id.menu_paste_from_clipboard);
copy.setEnabled(dirType != DirectoryFragment.TYPE_RECENT_OPEN);
+ MenuItem rename = menu.findItem(R.id.menu_rename);
+ rename.setVisible(true);
+ rename.setEnabled(canRename);
+
menu.findItem(R.id.menu_share).setVisible(true);
menu.findItem(R.id.menu_delete).setVisible(canDelete);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/Model.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/Model.java
index cf21d15..075b3ea 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/Model.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/Model.java
@@ -24,11 +24,7 @@
import static com.android.documentsui.model.DocumentInfo.getCursorString;
import static com.android.internal.util.Preconditions.checkNotNull;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.Context;
import android.database.Cursor;
-import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Looper;
import android.provider.DocumentsContract;
@@ -39,7 +35,6 @@
import com.android.documentsui.BaseActivity.SiblingProvider;
import com.android.documentsui.DirectoryResult;
-import com.android.documentsui.DocumentsApplication;
import com.android.documentsui.RootCursorWrapper;
import com.android.documentsui.dirlist.MultiSelectManager.Selection;
import com.android.documentsui.model.DocumentInfo;
@@ -56,7 +51,6 @@
public class Model implements SiblingProvider {
private static final String TAG = "Model";
- private Context mContext;
private boolean mIsLoading;
private List<UpdateListener> mUpdateListeners = new ArrayList<>();
@Nullable private Cursor mCursor;
@@ -73,10 +67,6 @@
@Nullable String info;
@Nullable String error;
- Model(Context context) {
- mContext = context;
- }
-
/**
* Generates a Model ID for a cursor entry that refers to a document. The Model ID is a unique
* string that can be used to identify the document referred to by the cursor.
@@ -406,91 +396,6 @@
return mCursor;
}
- public void delete(Selection selected, DeletionListener listener) {
- final ContentResolver resolver = mContext.getContentResolver();
- new DeleteFilesTask(resolver, listener).execute(selected);
- }
-
- /**
- * A Task which collects the DocumentInfo for documents that have been marked for deletion,
- * and actually deletes them.
- */
- private class DeleteFilesTask extends AsyncTask<Selection, Void, Void> {
- private ContentResolver mResolver;
- private DeletionListener mListener;
- private boolean mHadTrouble;
-
- /**
- * @param resolver A ContentResolver for performing the actual file deletions.
- * @param errorCallback A Runnable that is executed in the event that one or more errors
- * occurred while copying files. Execution will occur on the UI thread.
- */
- public DeleteFilesTask(ContentResolver resolver, DeletionListener listener) {
- mResolver = resolver;
- mListener = listener;
- }
-
- @Override
- protected Void doInBackground(Selection... selected) {
- List<DocumentInfo> toDelete = null;
- try {
- toDelete = getDocuments(selected[0]);
- } catch (NullPointerException e) {
- Log.w(TAG, "Failed to retrieve documents for delete.");
- mHadTrouble = true;
- return null;
- }
-
- for (DocumentInfo doc : toDelete) {
- if (!doc.isDeleteSupported()) {
- Log.w(TAG, doc + " could not be deleted. Skipping...");
- mHadTrouble = true;
- continue;
- }
-
- ContentProviderClient client = null;
- try {
- if (DEBUG) Log.d(TAG, "Deleting: " + doc.displayName);
- client = DocumentsApplication.acquireUnstableProviderOrThrow(
- mResolver, doc.derivedUri.getAuthority());
- DocumentsContract.deleteDocument(client, doc.derivedUri);
- } catch (Exception e) {
- Log.w(TAG, "Failed to delete " + doc, e);
- mHadTrouble = true;
- } finally {
- ContentProviderClient.releaseQuietly(client);
- }
- }
-
- return null;
- }
-
- @Override
- protected void onPostExecute(Void _) {
- if (mHadTrouble) {
- // TODO show which files failed? b/23720103
- mListener.onError();
- if (DEBUG) Log.d(TAG, "Deletion task completed. Some deletions failed.");
- } else {
- if (DEBUG) Log.d(TAG, "Deletion task completed successfully.");
- }
-
- mListener.onCompletion();
- }
- }
-
- static class DeletionListener {
- /**
- * Called when deletion has completed (regardless of whether an error occurred).
- */
- void onCompletion() {}
-
- /**
- * Called at the end of a deletion operation that produced one or more errors.
- */
- void onError() {}
- }
-
void addUpdateListener(UpdateListener listener) {
mUpdateListeners.add(listener);
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
index d868fb4..71e87cb 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
@@ -17,6 +17,8 @@
package com.android.documentsui.dirlist;
import static com.android.documentsui.Shared.DEBUG;
+import static com.android.documentsui.dirlist.ModelBackedDocumentsAdapter.ITEM_TYPE_DIRECTORY;
+import static com.android.documentsui.dirlist.ModelBackedDocumentsAdapter.ITEM_TYPE_DOCUMENT;
import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.internal.util.Preconditions.checkState;
@@ -32,7 +34,6 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
-import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
@@ -90,29 +91,10 @@
mBandManager = new BandController();
}
- GestureDetector.SimpleOnGestureListener listener =
- new GestureDetector.SimpleOnGestureListener() {
- @Override
- public boolean onSingleTapUp(MotionEvent e) {
- return MultiSelectManager.this.onSingleTapUp(
- new MotionInputEvent(e, recyclerView));
- }
- @Override
- public void onLongPress(MotionEvent e) {
- MultiSelectManager.this.onLongPress(
- new MotionInputEvent(e, recyclerView));
- }
- };
-
- final GestureDetector detector = new GestureDetector(recyclerView.getContext(), listener);
- detector.setOnDoubleTapListener(listener);
-
recyclerView.addOnItemTouchListener(
new RecyclerView.OnItemTouchListener() {
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
- detector.onTouchEvent(e);
-
if (mBandManager != null) {
return mBandManager.handleEvent(new MotionInputEvent(e, recyclerView));
}
@@ -287,13 +269,7 @@
boolean onSingleTapUp(InputEvent input) {
if (DEBUG) Log.d(TAG, "Processing tap event.");
if (!hasSelection()) {
- // if this is a mouse click on an item, start selection mode.
- // TODO: && input.isPrimaryButtonPressed(), but it is returning false.
- if (input.isOverItem() && input.isMouseEvent()) {
- int position = input.getItemPosition();
- toggleSelection(position);
- setSelectionRangeBegin(position);
- }
+ // No selection active - do nothing.
return false;
}
@@ -814,6 +790,10 @@
int getChildCount();
int getVisibleChildCount();
void focusItem(int position);
+ /**
+ * Layout items are excluded from the GridModel.
+ */
+ boolean isLayoutItem(int adapterPosition);
}
/** Recycler view facade implementation backed by good ol' RecyclerView. */
@@ -970,6 +950,20 @@
});
}
}
+
+ @Override
+ public boolean isLayoutItem(int pos) {
+ // The band selection model only operates on documents and directories. Exclude other
+ // types of adapter items (e.g. whitespace items like dividers).
+ RecyclerView.ViewHolder vh = mView.findViewHolderForAdapterPosition(pos);
+ switch (vh.getItemViewType()) {
+ case ITEM_TYPE_DOCUMENT:
+ case ITEM_TYPE_DIRECTORY:
+ return false;
+ default:
+ return true;
+ }
+ }
}
public interface Callback {
@@ -1433,7 +1427,8 @@
private void recordVisibleChildren() {
for (int i = 0; i < mHelper.getVisibleChildCount(); i++) {
int adapterPosition = mHelper.getAdapterPositionAt(i);
- if (!mKnownPositions.get(adapterPosition)) {
+ if (!mHelper.isLayoutItem(adapterPosition) &&
+ !mKnownPositions.get(adapterPosition)) {
mKnownPositions.put(adapterPosition, true);
recordItemData(mHelper.getAbsoluteRectForChildViewAt(i), adapterPosition);
}
@@ -1519,31 +1514,29 @@
* @param rect Rectangle including all covered items.
*/
private void updateSelection(Rect rect) {
- int columnStartIndex =
+ int columnStart =
Collections.binarySearch(mColumnBounds, new Limits(rect.left, rect.left));
- checkState(columnStartIndex >= 0);
- int columnEndIndex = columnStartIndex;
+ checkState(columnStart >= 0);
+ int columnEnd = columnStart;
- for (int i = columnStartIndex; i < mColumnBounds.size()
+ for (int i = columnStart; i < mColumnBounds.size()
&& mColumnBounds.get(i).lowerLimit <= rect.right; i++) {
- columnEndIndex = i;
+ columnEnd = i;
}
- SparseIntArray firstColumn =
- mColumns.get(mColumnBounds.get(columnStartIndex).lowerLimit);
- int rowStartIndex = firstColumn.indexOfKey(rect.top);
- if (rowStartIndex < 0) {
+ int rowStart = Collections.binarySearch(mRowBounds, new Limits(rect.top, rect.top));
+ if (rowStart < 0) {
mPositionNearestOrigin = NOT_SET;
return;
}
- int rowEndIndex = rowStartIndex;
- for (int i = rowStartIndex;
- i < firstColumn.size() && firstColumn.keyAt(i) <= rect.bottom; i++) {
- rowEndIndex = i;
+ int rowEnd = rowStart;
+ for (int i = rowStart; i < mRowBounds.size()
+ && mRowBounds.get(i).lowerLimit <= rect.bottom; i++) {
+ rowEnd = i;
}
- updateSelection(columnStartIndex, columnEndIndex, rowStartIndex, rowEndIndex);
+ updateSelection(columnStart, columnEnd, rowStart, rowEnd);
}
/**
@@ -1552,13 +1545,17 @@
*/
private void updateSelection(
int columnStartIndex, int columnEndIndex, int rowStartIndex, int rowEndIndex) {
+ if (DEBUG) Log.d(TAG, String.format("updateSelection: %d, %d, %d, %d",
+ columnStartIndex, columnEndIndex, rowStartIndex, rowEndIndex));
+
mSelection.clear();
for (int column = columnStartIndex; column <= columnEndIndex; column++) {
SparseIntArray items = mColumns.get(mColumnBounds.get(column).lowerLimit);
for (int row = rowStartIndex; row <= rowEndIndex; row++) {
// The default return value for SparseIntArray.get is 0, which is a valid
// position. Use a sentry value to prevent erroneously selecting item 0.
- int position = items.get(items.keyAt(row), NOT_SET);
+ final int rowKey = mRowBounds.get(row).lowerLimit;
+ int position = items.get(rowKey, NOT_SET);
if (position != NOT_SET) {
String id = mAdapter.getModelId(position);
if (id != null) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/RenameDocumentFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/RenameDocumentFragment.java
new file mode 100644
index 0000000..71708c1
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/RenameDocumentFragment.java
@@ -0,0 +1,158 @@
+/*
+ * 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.
+ */
+
+package com.android.documentsui.dirlist;
+
+import static com.android.documentsui.Shared.TAG;
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.FragmentManager;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.provider.DocumentsContract;
+import android.support.annotation.Nullable;
+import android.support.design.widget.Snackbar;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+import com.android.documentsui.BaseActivity;
+import com.android.documentsui.DocumentsApplication;
+import com.android.documentsui.model.DocumentInfo;
+import com.android.documentsui.R;
+import com.android.documentsui.Snackbars;
+/**
+ * Dialog to rename file or directory.
+ */
+class RenameDocumentFragment extends DialogFragment {
+ private static final String TAG_RENAME_DOCUMENT = "rename_document";
+ private DocumentInfo mDocument;
+
+ public static void show(FragmentManager fm, DocumentInfo document) {
+ final RenameDocumentFragment dialog = new RenameDocumentFragment();
+ dialog.mDocument = document;
+ dialog.show(fm, TAG_RENAME_DOCUMENT);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Context context = getActivity();
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
+ View view = dialogInflater.inflate(R.layout.dialog_file_name, null, false);
+
+ final EditText editText = (EditText) view.findViewById(android.R.id.text1);
+ editText.setText(mDocument.displayName);
+
+ builder.setTitle(R.string.menu_rename);
+ builder.setView(view);
+
+ builder.setPositiveButton(
+ android.R.string.ok,
+ new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ renameDocuments(editText.getText().toString());
+ }
+ });
+
+ builder.setNegativeButton(android.R.string.cancel, null);
+
+ final AlertDialog dialog = builder.create();
+
+ editText.setOnEditorActionListener(
+ new OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(
+ TextView view, int actionId, @Nullable KeyEvent event) {
+ if (event != null
+ && event.getKeyCode() == KeyEvent.KEYCODE_ENTER
+ && event.hasNoModifiers()) {
+ renameDocuments(editText.getText().toString());
+ dialog.dismiss();
+ return true;
+ }
+ return false;
+ }
+ });
+
+ return dialog;
+ }
+
+ private void renameDocuments(String newDisplayName) {
+ BaseActivity activity = (BaseActivity) getActivity();
+
+ new RenameDocumentsTask(activity, newDisplayName).execute(mDocument);
+ }
+
+ private class RenameDocumentsTask extends AsyncTask<DocumentInfo, Void, DocumentInfo> {
+ private final BaseActivity mActivity;
+ private final String mNewDisplayName;
+
+ public RenameDocumentsTask(BaseActivity activity, String newDisplayName) {
+ mActivity = activity;
+ mNewDisplayName = newDisplayName;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ mActivity.setPending(true);
+ }
+
+ @Override
+ protected DocumentInfo doInBackground(DocumentInfo... document) {
+ checkArgument(document.length == 1);
+ final ContentResolver resolver = mActivity.getContentResolver();
+ ContentProviderClient client = null;
+
+ try {
+ client = DocumentsApplication.acquireUnstableProviderOrThrow(
+ resolver, document[0].derivedUri.getAuthority());
+ Uri newUri = DocumentsContract.renameDocument(
+ client, document[0].derivedUri, mNewDisplayName);
+ return DocumentInfo.fromUri(resolver, newUri);
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to rename file", e);
+ return null;
+ } finally {
+ ContentProviderClient.releaseQuietly(client);
+ }
+ }
+
+ @Override
+ protected void onPostExecute(DocumentInfo result) {
+ if (result == null) {
+ Snackbars.makeSnackbar(mActivity, R.string.rename_error, Snackbar.LENGTH_SHORT)
+ .show();
+ }
+
+ mActivity.setPending(false);
+ }
+ }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
index 83df18c..4b5499a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
@@ -239,6 +239,10 @@
return (flags & Document.FLAG_SUPPORTS_DELETE) != 0;
}
+ public boolean isRenameSupported() {
+ return (flags & Document.FLAG_SUPPORTS_RENAME) != 0;
+ }
+
public boolean isGridTitlesHidden() {
return (flags & Document.FLAG_DIR_HIDE_GRID_TITLES) != 0;
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java b/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
index b1932b8..f3195a7 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
@@ -21,7 +21,6 @@
import static android.provider.DocumentsContract.buildDocumentUri;
import static android.provider.DocumentsContract.getDocumentId;
import static android.provider.DocumentsContract.isChildDocument;
-import static com.android.documentsui.DocumentsApplication.acquireUnstableProviderOrThrow;
import static com.android.documentsui.Shared.DEBUG;
import static com.android.documentsui.model.DocumentInfo.getCursorLong;
import static com.android.documentsui.model.DocumentInfo.getCursorString;
@@ -62,12 +61,7 @@
class CopyJob extends Job {
private static final String TAG = "CopyJob";
private static final int PROGRESS_INTERVAL_MILLIS = 1000;
- final List<DocumentInfo> mSrcFiles;
-
- // Provider clients are acquired for the duration of each copy job. Note that there is an
- // implicit assumption that all srcs come from the same authority.
- ContentProviderClient srcClient;
- ContentProviderClient dstClient;
+ final List<DocumentInfo> mSrcs;
private long mStartTime = -1;
private long mBatchSize;
@@ -86,11 +80,11 @@
* @param srcs List of files to be copied.
*/
CopyJob(Context service, Context appContext, Listener listener,
- String id, DocumentStack destination, List<DocumentInfo> srcs) {
- super(service, appContext, listener, OPERATION_COPY, id, destination);
+ String id, DocumentStack stack, List<DocumentInfo> srcs) {
+ super(service, appContext, listener, OPERATION_COPY, id, stack);
checkArgument(!srcs.isEmpty());
- this.mSrcFiles = srcs;
+ this.mSrcs = srcs;
}
/**
@@ -103,7 +97,7 @@
super(service, appContext, listener, opType, id, destination);
checkArgument(!srcs.isEmpty());
- this.mSrcFiles = srcs;
+ this.mSrcs = srcs;
}
@Override
@@ -185,21 +179,13 @@
void start() throws RemoteException {
mStartTime = elapsedRealtime();
- // Acquire content providers.
- srcClient = acquireUnstableProviderOrThrow(
- getContentResolver(),
- mSrcFiles.get(0).authority);
- dstClient = acquireUnstableProviderOrThrow(
- getContentResolver(),
- stack.peek().authority);
-
// client
- mBatchSize = calculateSize(srcClient, mSrcFiles);
+ mBatchSize = calculateSize(mSrcs);
DocumentInfo srcInfo;
DocumentInfo dstInfo;
- for (int i = 0; i < mSrcFiles.size() && !isCanceled(); ++i) {
- srcInfo = mSrcFiles.get(i);
+ for (int i = 0; i < mSrcs.size() && !isCanceled(); ++i) {
+ srcInfo = mSrcs.get(i);
dstInfo = stack.peek();
// Guard unsupported recursive operation.
@@ -233,24 +219,24 @@
/**
* Copies a the given document to the given location.
*
- * @param srcInfo DocumentInfos for the documents to copy.
+ * @param src DocumentInfos for the documents to copy.
* @param dstDirInfo The destination directory.
* @return True on success, false on failure.
* @throws RemoteException
*/
- boolean processDocument(DocumentInfo srcInfo, DocumentInfo dstDirInfo) throws RemoteException {
+ boolean processDocument(DocumentInfo src, DocumentInfo dstDirInfo) throws RemoteException {
// TODO: When optimized copy kicks in, we'll not making any progress updates.
// For now. Local storage isn't using optimized copy.
// When copying within the same provider, try to use optimized copying.
// If not supported, then fallback to byte-by-byte copy/move.
- if (srcInfo.authority.equals(dstDirInfo.authority)) {
- if ((srcInfo.flags & Document.FLAG_SUPPORTS_COPY) != 0) {
- if (DocumentsContract.copyDocument(srcClient, srcInfo.derivedUri,
+ if (src.authority.equals(dstDirInfo.authority)) {
+ if ((src.flags & Document.FLAG_SUPPORTS_COPY) != 0) {
+ if (DocumentsContract.copyDocument(getClient(src), src.derivedUri,
dstDirInfo.derivedUri) == null) {
- onFileFailed(srcInfo,
- "Provider side copy failed for documents: " + srcInfo.derivedUri + ".");
+ onFileFailed(src,
+ "Provider side copy failed for documents: " + src.derivedUri + ".");
return false;
}
return true;
@@ -258,44 +244,44 @@
}
// If we couldn't do an optimized copy...we fall back to vanilla byte copy.
- return byteCopyDocument(srcInfo, dstDirInfo);
+ return byteCopyDocument(src, dstDirInfo);
}
- boolean byteCopyDocument(DocumentInfo srcInfo, DocumentInfo dstDirInfo)
+ boolean byteCopyDocument(DocumentInfo src, DocumentInfo dest)
throws RemoteException {
final String dstMimeType;
final String dstDisplayName;
- if (DEBUG) Log.d(TAG, "Doing byte copy of document: " + srcInfo);
+ if (DEBUG) Log.d(TAG, "Doing byte copy of document: " + src);
// If the file is virtual, but can be converted to another format, then try to copy it
// as such format. Also, append an extension for the target mime type (if known).
- if (srcInfo.isVirtualDocument()) {
+ if (src.isVirtualDocument()) {
final String[] streamTypes = getContentResolver().getStreamTypes(
- srcInfo.derivedUri, "*/*");
+ src.derivedUri, "*/*");
if (streamTypes != null && streamTypes.length > 0) {
dstMimeType = streamTypes[0];
final String extension = MimeTypeMap.getSingleton().
getExtensionFromMimeType(dstMimeType);
- dstDisplayName = srcInfo.displayName +
- (extension != null ? "." + extension : srcInfo.displayName);
+ dstDisplayName = src.displayName +
+ (extension != null ? "." + extension : src.displayName);
} else {
- onFileFailed(srcInfo, "Cannot copy virtual file. No streamable formats available.");
+ onFileFailed(src, "Cannot copy virtual file. No streamable formats available.");
return false;
}
} else {
- dstMimeType = srcInfo.mimeType;
- dstDisplayName = srcInfo.displayName;
+ dstMimeType = src.mimeType;
+ dstDisplayName = src.displayName;
}
// Create the target document (either a file or a directory), then copy recursively the
// contents (bytes or children).
- final Uri dstUri = DocumentsContract.createDocument(dstClient,
- dstDirInfo.derivedUri, dstMimeType, dstDisplayName);
+ final Uri dstUri = DocumentsContract.createDocument(
+ getClient(dest), dest.derivedUri, dstMimeType, dstDisplayName);
if (dstUri == null) {
// If this is a directory, the entire subdir will not be copied over.
- onFileFailed(srcInfo,
+ onFileFailed(src,
"Couldn't create destination document " + dstDisplayName
- + " in directory " + dstDirInfo.displayName + ".");
+ + " in directory " + dest.displayName + ".");
return false;
}
@@ -303,16 +289,16 @@
try {
dstInfo = DocumentInfo.fromUri(getContentResolver(), dstUri);
} catch (FileNotFoundException e) {
- onFileFailed(srcInfo,
+ onFileFailed(src,
"Could not load DocumentInfo for newly created file: " + dstUri + ".");
return false;
}
final boolean success;
- if (Document.MIME_TYPE_DIR.equals(srcInfo.mimeType)) {
- success = copyDirectoryHelper(srcInfo, dstInfo);
+ if (Document.MIME_TYPE_DIR.equals(src.mimeType)) {
+ success = copyDirectoryHelper(src, dstInfo);
} else {
- success = copyFileHelper(srcInfo, dstInfo, dstMimeType);
+ success = copyFileHelper(src, dstInfo, dstMimeType);
}
return success;
@@ -322,13 +308,13 @@
* Handles recursion into a directory and copying its contents. Note that in linux terms, this
* does the equivalent of "cp src/* dst", not "cp -r src dst".
*
- * @param srcDirInfo Info of the directory to copy from. The routine will copy the directory's
+ * @param srcDir Info of the directory to copy from. The routine will copy the directory's
* contents, not the directory itself.
- * @param dstDirInfo Info of the directory to copy to. Must be created beforehand.
+ * @param destDir Info of the directory to copy to. Must be created beforehand.
* @return True on success, false if some of the children failed to copy.
* @throws RemoteException
*/
- private boolean copyDirectoryHelper(DocumentInfo srcDirInfo, DocumentInfo dstDirInfo)
+ private boolean copyDirectoryHelper(DocumentInfo srcDir, DocumentInfo destDir)
throws RemoteException {
// Recurse into directories. Copy children into the new subdirectory.
final String queryColumns[] = new String[] {
@@ -342,13 +328,11 @@
boolean success = true;
try {
// Iterate over srcs in the directory; copy to the destination directory.
- final Uri queryUri = DocumentsContract.buildChildDocumentsUri(srcDirInfo.authority,
- srcDirInfo.documentId);
- cursor = srcClient.query(queryUri, queryColumns, null, null, null);
- DocumentInfo srcInfo;
+ final Uri queryUri = buildChildDocumentsUri(srcDir.authority, srcDir.documentId);
+ cursor = getClient(srcDir).query(queryUri, queryColumns, null, null, null);
while (cursor.moveToNext() && !isCanceled()) {
- srcInfo = DocumentInfo.fromCursor(cursor, srcDirInfo.authority);
- success &= processDocument(srcInfo, dstDirInfo);
+ DocumentInfo src = DocumentInfo.fromCursor(cursor, srcDir.authority);
+ success &= processDocument(src, destDir);
}
} finally {
IoUtils.closeQuietly(cursor);
@@ -366,50 +350,49 @@
* @return True on success, false on error.
* @throws RemoteException
*/
- private boolean copyFileHelper(DocumentInfo srcInfo, DocumentInfo dstInfo, String mimeType)
+ private boolean copyFileHelper(DocumentInfo src, DocumentInfo dest, String mimeType)
throws RemoteException {
- // Copy an individual file.
CancellationSignal canceller = new CancellationSignal();
ParcelFileDescriptor srcFile = null;
ParcelFileDescriptor dstFile = null;
- InputStream src = null;
- OutputStream dst = null;
+ InputStream in = null;
+ OutputStream out = null;
boolean success = true;
try {
// If the file is virtual, but can be converted to another format, then try to copy it
// as such format.
- if (srcInfo.isVirtualDocument()) {
+ if (src.isVirtualDocument()) {
final AssetFileDescriptor srcFileAsAsset =
- srcClient.openTypedAssetFileDescriptor(
- srcInfo.derivedUri, mimeType, null, canceller);
+ getClient(src).openTypedAssetFileDescriptor(
+ src.derivedUri, mimeType, null, canceller);
srcFile = srcFileAsAsset.getParcelFileDescriptor();
- src = new AssetFileDescriptor.AutoCloseInputStream(srcFileAsAsset);
+ in = new AssetFileDescriptor.AutoCloseInputStream(srcFileAsAsset);
} else {
- srcFile = srcClient.openFile(srcInfo.derivedUri, "r", canceller);
- src = new ParcelFileDescriptor.AutoCloseInputStream(srcFile);
+ srcFile = getClient(src).openFile(src.derivedUri, "r", canceller);
+ in = new ParcelFileDescriptor.AutoCloseInputStream(srcFile);
}
- dstFile = dstClient.openFile(dstInfo.derivedUri, "w", canceller);
- dst = new ParcelFileDescriptor.AutoCloseOutputStream(dstFile);
+ dstFile = getClient(dest).openFile(dest.derivedUri, "w", canceller);
+ out = new ParcelFileDescriptor.AutoCloseOutputStream(dstFile);
byte[] buffer = new byte[32 * 1024];
int len;
- while ((len = src.read(buffer)) != -1) {
+ while ((len = in.read(buffer)) != -1) {
if (isCanceled()) {
if (DEBUG) Log.d(TAG, "Canceled copy mid-copy. Id:" + id);
success = false;
break;
}
- dst.write(buffer, 0, len);
+ out.write(buffer, 0, len);
makeCopyProgress(len);
}
srcFile.checkError();
} catch (IOException e) {
success = false;
- onFileFailed(srcInfo, "Exception thrown while copying from "
- + srcInfo.derivedUri + " to " + dstInfo.derivedUri + ".");
+ onFileFailed(src, "Exception thrown while copying from "
+ + src.derivedUri + " to " + dest.derivedUri + ".");
if (dstFile != null) {
try {
@@ -420,20 +403,20 @@
}
} finally {
// This also ensures the file descriptors are closed.
- IoUtils.closeQuietly(src);
- IoUtils.closeQuietly(dst);
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(out);
}
if (!success) {
if (DEBUG) Log.d(TAG, "Cleaning up failed operation leftovers.");
canceller.cancel();
try {
- DocumentsContract.deleteDocument(dstClient, dstInfo.derivedUri);
+ DocumentsContract.deleteDocument(getClient(dest), dest.derivedUri);
} catch (RemoteException e) {
// RemoteExceptions usually signal that the connection is dead, so there's no
// point attempting to continue. Propagate the exception up so the copy job is
// cancelled.
- Log.w(TAG, "Failed to cleanup after copy error: " + srcInfo.derivedUri, e);
+ Log.w(TAG, "Failed to cleanup after copy error: " + src.derivedUri, e);
throw e;
}
}
@@ -449,14 +432,14 @@
* @return Size in bytes.
* @throws RemoteException
*/
- private static long calculateSize(ContentProviderClient client, List<DocumentInfo> srcs)
+ private long calculateSize(List<DocumentInfo> srcs)
throws RemoteException {
long result = 0;
for (DocumentInfo src : srcs) {
if (src.isDirectory()) {
// Directories need to be recursed into.
- result += calculateFileSizesRecursively(client, src.derivedUri);
+ result += calculateFileSizesRecursively(getClient(src), src.derivedUri);
} else {
result += src.size;
}
@@ -503,20 +486,14 @@
return result;
}
- @Override
- void cleanup() {
- ContentProviderClient.releaseQuietly(srcClient);
- ContentProviderClient.releaseQuietly(dstClient);
- }
-
/**
* Returns true if {@code doc} is a descendant of {@code parentDoc}.
* @throws RemoteException
*/
- boolean isDescendentOf(DocumentInfo doc, DocumentInfo parentDoc)
+ boolean isDescendentOf(DocumentInfo doc, DocumentInfo parent)
throws RemoteException {
- if (parentDoc.isDirectory() && doc.authority.equals(parentDoc.authority)) {
- return isChildDocument(dstClient, doc.derivedUri, parentDoc.derivedUri);
+ if (parent.isDirectory() && doc.authority.equals(parent.authority)) {
+ return isChildDocument(getClient(doc), doc.derivedUri, parent.derivedUri);
}
return false;
}
@@ -525,4 +502,16 @@
Log.w(TAG, msg);
onFileFailed(file);
}
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("CopyJob")
+ .append("{")
+ .append("id=" + id)
+ .append("srcs=" + mSrcs)
+ .append(", destination=" + stack)
+ .append("}")
+ .toString();
+ }
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java b/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java
new file mode 100644
index 0000000..6a2a794
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ */
+
+package com.android.documentsui.services;
+
+import static com.android.documentsui.Shared.DEBUG;
+import static com.android.documentsui.services.FileOperationService.OPERATION_DELETE;
+
+import android.app.Notification;
+import android.app.Notification.Builder;
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.documentsui.R;
+import com.android.documentsui.model.DocumentInfo;
+import com.android.documentsui.model.DocumentStack;
+
+import java.util.List;
+
+final class DeleteJob extends Job {
+
+ private static final String TAG = "DeleteJob";
+ private List<DocumentInfo> mSrcs;
+
+ /**
+ * Moves files to a destination identified by {@code destination}.
+ * Performs most work by delegating to CopyJob, then deleting
+ * a file after it has been copied.
+ *
+ * @see @link {@link Job} constructor for most param descriptions.
+ *
+ * @param srcs List of files to delete
+ */
+ DeleteJob(Context service, Context appContext, Listener listener,
+ String id, DocumentStack stack, List<DocumentInfo> srcs) {
+ super(service, appContext, listener, OPERATION_DELETE, id, stack);
+ this.mSrcs = srcs;
+ }
+
+ @Override
+ Builder createProgressBuilder() {
+ return super.createProgressBuilder(
+ service.getString(R.string.move_notification_title),
+ R.drawable.ic_menu_copy,
+ service.getString(android.R.string.cancel),
+ R.drawable.ic_cab_cancel);
+ }
+
+ @Override
+ public Notification getSetupNotification() {
+ return getSetupNotification(service.getString(R.string.delete_preparing));
+ }
+
+ @Override
+ Notification getFailureNotification() {
+ return getFailureNotification(
+ R.plurals.delete_error_notification_title, R.drawable.ic_menu_delete);
+ }
+
+ @Override
+ void start() throws RemoteException {
+ for (DocumentInfo doc : mSrcs) {
+ if (DEBUG) Log.d(TAG, "Deleting document @ " + doc.derivedUri);
+ if (!deleteDocument(doc)) {
+ Log.w(TAG, "Failed to delete document @ " + doc.derivedUri);
+ onFileFailed(doc);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("DeleteJob")
+ .append("{")
+ .append("id=" + id)
+ .append("srcs=" + mSrcs)
+ .append(", location=" + stack)
+ .append("}")
+ .toString();
+ }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java b/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java
index 1df20ac..aca2d7a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java
@@ -27,6 +27,7 @@
import android.content.Intent;
import android.os.IBinder;
import android.os.PowerManager;
+import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
@@ -51,9 +52,11 @@
private static final int DEFAULT_DELAY = 0;
private static final int MAX_DELAY = 10 * 1000; // ten seconds
+ private static final int POOL_SIZE = 2; // "pool size", not *max* "pool size".
+ private static final int NOTIFICATION_ID_PROGRESS = 0;
+ private static final int NOTIFICATION_ID_FAILURE = 1;
public static final String TAG = "FileOperationService";
- private static final int POOL_SIZE = 2; // "pool size", not *max* "pool size".
public static final String EXTRA_JOB_ID = "com.android.documentsui.JOB_ID";
public static final String EXTRA_DELAY = "com.android.documentsui.DELAY";
@@ -92,7 +95,7 @@
@GuardedBy("mRunning")
private Map<String, JobRecord> mRunning = new HashMap<>();
- private int mLastStarted;
+ private int mLastServiceId;
@Override
public void onCreate() {
@@ -111,7 +114,18 @@
}
@Override
- public int onStartCommand(Intent intent, int flags, int startTime) {
+ public void onDestroy() {
+ if (DEBUG) Log.d(TAG, "Shutting down executor.");
+ List<Runnable> unfinished = executor.shutdownNow();
+ if (!unfinished.isEmpty()) {
+ Log.w(TAG, "Shutting down, but executor reports running jobs: " + unfinished);
+ }
+ executor = null;
+ if (DEBUG) Log.d(TAG, "Destroyed.");
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int serviceId) {
// TODO: Ensure we're not being called with retry or redeliver.
// checkArgument(flags == 0); // retry and redeliver are not supported.
@@ -123,17 +137,17 @@
handleCancel(intent);
} else {
checkArgument(operationType != OPERATION_UNKNOWN);
- handleOperation(intent, startTime, jobId, operationType);
+ handleOperation(intent, serviceId, jobId, operationType);
}
return START_NOT_STICKY;
}
- private void handleOperation(Intent intent, int startTime, String jobId, int operationType) {
- if (DEBUG) Log.d(TAG, "onStartCommand: " + jobId + " with start time " + startTime);
+ private void handleOperation(Intent intent, int serviceId, String jobId, int operationType) {
+ if (DEBUG) Log.d(TAG, "onStartCommand: " + jobId + " with serviceId " + serviceId);
- // Track start time so we can stop the service once we're out of work to do.
- mLastStarted = startTime;
+ // Track the service supplied id so we can stop the service once we're out of work to do.
+ mLastServiceId = serviceId;
Job job = null;
synchronized (mRunning) {
@@ -147,12 +161,18 @@
job = createJob(operationType, jobId, srcs, stack);
+ if (job == null) {
+ return;
+ }
+
mWakeLock.acquire();
}
checkState(job != null);
int delay = intent.getIntExtra(EXTRA_DELAY, DEFAULT_DELAY);
checkArgument(delay <= MAX_DELAY);
+ if (DEBUG) Log.d(
+ TAG, "Scheduling job " + job.id + " to run in " + delay + " milliseconds.");
ScheduledFuture<?> future = executor.schedule(job, delay, TimeUnit.MILLISECONDS);
mRunning.put(jobId, new JobRecord(job, future));
}
@@ -191,16 +211,24 @@
// interactivity for the user in case the copy loop is stalled.
// Try to cancel it even if we don't have a job id...in case there is some sad
// orphan notification.
- mNotificationManager.cancel(jobId, 0);
+ mNotificationManager.cancel(jobId, NOTIFICATION_ID_PROGRESS);
// TODO: Guarantee the job is being finalized
}
+ /**
+ * Creates a new job. Returns null if a job with {@code id} already exists.
+ * @return
+ */
@GuardedBy("mRunning")
- private Job createJob(
+ private @Nullable Job createJob(
@OpType int operationType, String id, List<DocumentInfo> srcs, DocumentStack stack) {
- checkArgument(!mRunning.containsKey(id));
+ if (mRunning.containsKey(id)) {
+ Log.w(TAG, "Duplicate job id: " + id
+ + ". Ignoring job request for srcs: " + srcs + ", stack: " + stack + ".");
+ return null;
+ }
Job job = null;
switch (operationType) {
@@ -211,7 +239,8 @@
job = jobFactory.createMove(this, getApplicationContext(), this, id, stack, srcs);
break;
case OPERATION_DELETE:
- throw new UnsupportedOperationException();
+ job = jobFactory.createDelete(this, getApplicationContext(), this, id, stack, srcs);
+ break;
default:
throw new UnsupportedOperationException();
}
@@ -234,20 +263,21 @@
/**
* Most likely shuts down. Won't shut down if service has a pending
- * message.
+ * message. Thread pool is deal with in onDestroy.
*/
private void shutdown() {
- if (DEBUG) Log.d(TAG, "Shutting down. Last start time: " + mLastStarted);
+ if (DEBUG) Log.d(TAG, "Shutting down. Last serviceId was " + mLastServiceId);
mWakeLock.release();
mWakeLock = null;
- boolean gonnaStop = stopSelfResult(mLastStarted);
+
+ // Turns out, for us, stopSelfResult always returns false in tests,
+ // so we can't guard executor shutdown. For this reason we move
+ // executor shutdown to #onDestroy.
+ boolean gonnaStop = stopSelfResult(mLastServiceId);
if (DEBUG) Log.d(TAG, "Stopping service: " + gonnaStop);
if (!gonnaStop) {
Log.w(TAG, "Service should be stopping, but reports otherwise.");
}
- // Sadly "gonnaStop" is always false in tests, so we can't guard executor shutdown.
- List<Runnable> unfinished = executor.shutdownNow();
- checkState(unfinished.isEmpty());
}
@VisibleForTesting
@@ -258,7 +288,7 @@
@Override
public void onStart(Job job) {
if (DEBUG) Log.d(TAG, "onStart: " + job.id);
- mNotificationManager.notify(job.id, 0, job.getSetupNotification());
+ mNotificationManager.notify(job.id, NOTIFICATION_ID_PROGRESS, job.getSetupNotification());
}
@Override
@@ -266,7 +296,7 @@
if (DEBUG) Log.d(TAG, "onFinished: " + job.id);
// Dismiss the ongoing copy notification when the copy is done.
- mNotificationManager.cancel(job.id, 0);
+ mNotificationManager.cancel(job.id, NOTIFICATION_ID_PROGRESS);
synchronized (mRunning) {
deleteJob(job);
@@ -276,7 +306,8 @@
@Override
public void onProgress(CopyJob job) {
if (DEBUG) Log.d(TAG, "onProgress: " + job.id);
- mNotificationManager.notify(job.id, 0, job.getProgressNotification());
+ mNotificationManager.notify(
+ job.id, NOTIFICATION_ID_PROGRESS, job.getProgressNotification());
}
@Override
@@ -284,8 +315,8 @@
if (DEBUG) Log.d(TAG, "onFailed: " + job.id);
checkArgument(job.failed());
Log.e(TAG, "Job failed on files: " + job.failedFiles.size() + ".");
- mNotificationManager.notify(job.id, 0, job.getFailureNotification());
- onFinished(job); // failed jobs don't call finished, so we do.
+ mNotificationManager.notify(job.id, NOTIFICATION_ID_FAILURE, job.getFailureNotification());
+ onFinished(job); // Failed jobs don't call finished, so we do.
}
private static final class JobRecord {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/FileOperations.java b/packages/DocumentsUI/src/com/android/documentsui/services/FileOperations.java
index 0f1730a3..f59a32a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/FileOperations.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/FileOperations.java
@@ -22,6 +22,7 @@
import static com.android.documentsui.Shared.asArrayList;
import static com.android.documentsui.Shared.getQuantityString;
import static com.android.documentsui.services.FileOperationService.EXTRA_CANCEL;
+import static com.android.documentsui.services.FileOperationService.EXTRA_DELAY;
import static com.android.documentsui.services.FileOperationService.EXTRA_JOB_ID;
import static com.android.documentsui.services.FileOperationService.EXTRA_OPERATION;
import static com.android.documentsui.services.FileOperationService.EXTRA_SRC_LIST;
@@ -52,10 +53,12 @@
private static final String TAG = "FileOperations";
+ private static final IdBuilder idBuilder = new IdBuilder();
+
private FileOperations() {}
public static String createJobId() {
- return String.valueOf(elapsedRealtime());
+ return idBuilder.getNext();
}
/**
@@ -73,7 +76,7 @@
case OPERATION_MOVE:
return FileOperations.move(activity, srcDocs, stack);
case OPERATION_DELETE:
- return FileOperations.delete(activity, srcDocs, stack);
+ throw new UnsupportedOperationException("Delete isn't currently supported.");
default:
throw new UnsupportedOperationException("Unknown operation: " + operationType);
}
@@ -151,14 +154,17 @@
* @param jobId A unique jobid for this job.
* Use {@link #createJobId} if you don't have one handy.
* @param srcDocs A list of src files to copy.
+ * @param delay Number of milliseconds to wait before executing the job.
* @return Id of the job.
*/
public static String delete(
- Activity activity, List<DocumentInfo> srcDocs, DocumentStack location) {
+ Activity activity, List<DocumentInfo> srcDocs, DocumentStack location, int delay) {
String jobId = createJobId();
- if (DEBUG) Log.d(TAG, "Initiating 'delete' operation id: " + jobId);
+ if (DEBUG) Log.d(TAG, "Initiating 'delete' operation id " + jobId
+ + " delayed by " + delay + " milliseconds.");
Intent intent = createBaseIntent(OPERATION_DELETE, activity, jobId, srcDocs, location);
+ intent.putExtra(EXTRA_DELAY, delay);
activity.startService(intent);
return jobId;
@@ -193,4 +199,24 @@
getQuantityString(activity, contentId, fileCount),
Snackbar.LENGTH_SHORT);
}
+
+ private static final class IdBuilder {
+
+ // Remember last job time so we can guard against collisions.
+ private long mLastJobTime;
+
+ // If we detect a collision, use subId to make distinct.
+ private int mSubId;
+
+ public synchronized String getNext() {
+ long time = elapsedRealtime();
+ if (time == mLastJobTime) {
+ mSubId++;
+ } else {
+ mSubId = 0;
+ }
+ mLastJobTime = time;
+ return String.valueOf(mLastJobTime) + "-" + String.valueOf(mSubId);
+ }
+ }
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/Job.java b/packages/DocumentsUI/src/com/android/documentsui/services/Job.java
index c7939eb..f351df9 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/Job.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/Job.java
@@ -16,6 +16,7 @@
package com.android.documentsui.services;
+import static com.android.documentsui.DocumentsApplication.acquireUnstableProviderOrThrow;
import static com.android.documentsui.services.FileOperationService.EXTRA_CANCEL;
import static com.android.documentsui.services.FileOperationService.EXTRA_FAILURE;
import static com.android.documentsui.services.FileOperationService.EXTRA_JOB_ID;
@@ -24,18 +25,21 @@
import static com.android.documentsui.services.FileOperationService.FAILURE_COPY;
import static com.android.documentsui.services.FileOperationService.OPERATION_UNKNOWN;
import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.internal.util.Preconditions.checkNotNull;
import android.annotation.DrawableRes;
import android.annotation.PluralsRes;
import android.app.Notification;
import android.app.Notification.Builder;
import android.app.PendingIntent;
+import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.os.Parcelable;
import android.os.RemoteException;
import android.provider.DocumentsContract;
+import android.util.Log;
import com.android.documentsui.FilesActivity;
import com.android.documentsui.R;
@@ -45,7 +49,9 @@
import com.android.documentsui.services.FileOperationService.OpType;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* A mashup of work item and ui progress update factory. Used by {@link FileOperationService}
@@ -53,6 +59,7 @@
*/
abstract class Job implements Runnable {
+ private static final String TAG = "Job";
final Context service;
final Context appContext;
final Listener listener;
@@ -64,6 +71,7 @@
final ArrayList<DocumentInfo> failedFiles = new ArrayList<>();
final Notification.Builder mProgressBuilder;
+ private final Map<String, ContentProviderClient> mClients = new HashMap<>();
private volatile boolean mCanceled;
/**
@@ -118,14 +126,31 @@
abstract void start() throws RemoteException;
- // Service will call this when it is done with the job.
- abstract void cleanup();
-
abstract Notification getSetupNotification();
// TODO: Progress notification for deletes.
// abstract Notification getProgressNotification(long bytesCopied);
abstract Notification getFailureNotification();
+ ContentProviderClient getClient(DocumentInfo doc) throws RemoteException {
+ ContentProviderClient client = mClients.get(doc.authority);
+ if (client == null) {
+ // Acquire content providers.
+ client = acquireUnstableProviderOrThrow(
+ getContentResolver(),
+ doc.authority);
+
+ mClients.put(doc.authority, client);
+ }
+
+ return checkNotNull(client);
+ }
+
+ final void cleanup() {
+ for (ContentProviderClient client : mClients.values()) {
+ ContentProviderClient.releaseQuietly(client);
+ }
+ }
+
final void cancel() {
mCanceled = true;
}
@@ -146,6 +171,17 @@
return !failedFiles.isEmpty();
}
+ final boolean deleteDocument(DocumentInfo doc) {
+ try {
+ DocumentsContract.deleteDocument(getClient(doc), doc.derivedUri);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to delete file: " + doc.derivedUri, e);
+ return false;
+ }
+
+ return true; // victory dance!
+ }
+
Notification getSetupNotification(String content) {
mProgressBuilder.setProgress(0, 0, true);
mProgressBuilder.setContentText(content);
@@ -215,6 +251,16 @@
return cancelIntent;
}
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("Job")
+ .append("{")
+ .append("id=" + id)
+ .append("}")
+ .toString();
+ }
+
/**
* Factory class that facilitates our testing FileOperationService.
*/
@@ -231,6 +277,11 @@
String id, DocumentStack stack, List<DocumentInfo> srcs) {
return new MoveJob(service, appContext, listener, id, stack, srcs);
}
+
+ Job createDelete(Context service, Context appContext, Listener listener,
+ String id, DocumentStack stack, List<DocumentInfo> srcs) {
+ return new DeleteJob(service, appContext, listener, id, stack, srcs);
+ }
}
/**
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java b/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java
index f46f234..7a238bd 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java
@@ -76,40 +76,47 @@
}
@Override
- boolean processDocument(DocumentInfo srcInfo, DocumentInfo dstDirInfo) throws RemoteException {
+ boolean processDocument(DocumentInfo src, DocumentInfo dest) throws RemoteException {
// TODO: When optimized move kicks in, we're not making any progress updates. FIX IT!
// When moving within the same provider, try to use optimized moving.
// If not supported, then fallback to byte-by-byte copy/move.
- if (srcInfo.authority.equals(dstDirInfo.authority)) {
- if ((srcInfo.flags & Document.FLAG_SUPPORTS_MOVE) != 0) {
- if (DocumentsContract.moveDocument(srcClient, srcInfo.derivedUri,
- dstDirInfo.derivedUri) == null) {
- onFileFailed(srcInfo);
+ if (src.authority.equals(dest.authority)) {
+ if ((src.flags & Document.FLAG_SUPPORTS_MOVE) != 0) {
+ if (DocumentsContract.moveDocument(getClient(src), src.derivedUri,
+ dest.derivedUri) == null) {
+ onFileFailed(src);
return false;
}
return true;
}
}
- // If we couldn't do an optimized copy...we fall back to vanilla byte copy.
- boolean copied = byteCopyDocument(srcInfo, dstDirInfo);
-
- return copied && !isCanceled() && deleteSrcDocument(srcInfo);
- }
-
- private boolean deleteSrcDocument(DocumentInfo srcInfo) {
- // This is racey. We should make sure that we never delete a directory after
- // it changed, so we don't remove a file which had not been copied earlier
- // to the target location.
- try {
- DocumentsContract.deleteDocument(srcClient, srcInfo.derivedUri);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to delete source after copy: " + srcInfo.derivedUri, e);
+ // Moving virtual files by bytes is not supported. This is because, it would involve
+ // conversion, and the source file should not be deleted in such case (as it's a different
+ // file).
+ if (src.isVirtualDocument()) {
+ Log.w(TAG, "Cannot move virtual files byte by byte.");
+ onFileFailed(src);
return false;
}
- return true; // victory dance!
+ // If we couldn't do an optimized copy...we fall back to vanilla byte copy.
+ boolean copied = byteCopyDocument(src, dest);
+
+ return copied && !isCanceled() && deleteDocument(src);
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("MoveJob")
+ .append("{")
+ .append("id=" + id)
+ .append("srcs=" + mSrcs)
+ .append(", destination=" + stack)
+ .append("}")
+ .toString();
}
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java
index 1b8bd4e..6c9c5d9f 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java
@@ -223,7 +223,8 @@
assertFalse(mDocsList.exists());
assertTrue(mMessageTextView.exists());
- assertEquals(mContext.getString(R.string.empty), mMessageTextView.getText());
+ String msg = String.valueOf(mContext.getString(R.string.no_results));
+ assertEquals(String.format(msg, "TEST_ROOT_0"), mMessageTextView.getText());
assertSearchTextField(false, query);
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
index a47d350..cc48786 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
@@ -660,7 +660,7 @@
this.documentId = getDocumentIdForFile(file);
this.mimeType = Document.MIME_TYPE_DIR;
this.streamTypes = new ArrayList<String>();
- this.flags = Document.FLAG_DIR_SUPPORTS_CREATE;
+ this.flags = Document.FLAG_DIR_SUPPORTS_CREATE | Document.FLAG_SUPPORTS_RENAME;
this.parentId = null;
this.rootInfo = rootInfo;
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DocumentHolderTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DocumentHolderTest.java
new file mode 100644
index 0000000..16efc6e
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DocumentHolderTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.
+ */
+
+package com.android.documentsui.dirlist;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.MotionEvent.PointerCoords;
+import android.view.MotionEvent.PointerProperties;
+
+import com.android.documentsui.R;
+import com.android.documentsui.State;
+
+@SmallTest
+public class DocumentHolderTest extends AndroidTestCase {
+
+ DocumentHolder mHolder;
+ TestListener mListener;
+
+ public void setUp() throws Exception {
+ Context context = getContext();
+ LayoutInflater inflater = LayoutInflater.from(context);
+ mHolder = new DocumentHolder(getContext(), inflater.inflate(R.layout.item_doc_list, null)) {
+ @Override
+ public void bind(Cursor cursor, String modelId, State state) {}
+ };
+
+ mListener = new TestListener();
+ mHolder.addEventListener(mListener);
+
+ mHolder.itemView.requestLayout();
+ mHolder.itemView.invalidate();
+ }
+
+ public void testClickActivates() {
+ click();
+ mListener.assertSelected();
+ }
+
+ public void testTapActivates() {
+ tap();
+ mListener.assertActivated();
+ }
+
+ public void click() {
+ mHolder.onSingleTapUp(createEvent(MotionEvent.TOOL_TYPE_MOUSE));
+ }
+
+ public void tap() {
+ mHolder.onSingleTapUp(createEvent(MotionEvent.TOOL_TYPE_FINGER));
+ }
+
+ public MotionEvent createEvent(int tooltype) {
+ long time = SystemClock.uptimeMillis();
+
+ PointerProperties properties[] = new PointerProperties[] {
+ new PointerProperties()
+ };
+ properties[0].toolType = tooltype;
+
+ PointerCoords coords[] = new PointerCoords[] {
+ new PointerCoords()
+ };
+
+ Rect rect = new Rect();
+ mHolder.itemView.getHitRect(rect);
+ coords[0].x = rect.left;
+ coords[0].y = rect.top;
+
+ return MotionEvent.obtain(
+ time, // down time
+ time, // event time
+ MotionEvent.ACTION_UP, // action
+ 1, // pointer count
+ properties, // pointer properties
+ coords, // pointer coords
+ 0, // metastate
+ 0, // button state
+ 0, // xprecision
+ 0, // yprecision
+ 0, // deviceid
+ 0, // edgeflags
+ 0, // source
+ 0 // flags
+ );
+ }
+
+ private class TestListener implements DocumentHolder.EventListener {
+ private boolean mActivated = false;
+ private boolean mSelected = false;
+
+ public void assertActivated() {
+ assertTrue(mActivated);
+ assertFalse(mSelected);
+ }
+
+ public void assertSelected() {
+ assertTrue(mSelected);
+ assertFalse(mActivated);
+ }
+
+ @Override
+ public boolean onActivate(DocumentHolder doc) {
+ mActivated = true;
+ return true;
+ }
+
+ @Override
+ public boolean onSelect(DocumentHolder doc) {
+ mSelected = true;
+ return true;
+ }
+
+ }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java
index 5ce1823..2244be9 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java
@@ -51,7 +51,7 @@
public void setUp() {
final Context testContext = TestContext.createStorageTestContext(getContext(), AUTHORITY);
- mModel = new TestModel(testContext, AUTHORITY);
+ mModel = new TestModel(AUTHORITY);
mModel.update(NAMES);
DocumentsAdapter.Environment env = new TestEnvironment(testContext);
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelTest.java
index a5f0656..83299f0 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelTest.java
@@ -38,7 +38,6 @@
import java.util.List;
import java.util.Random;
import java.util.Set;
-import java.util.concurrent.CountDownLatch;
@SmallTest
public class ModelTest extends AndroidTestCase {
@@ -96,7 +95,7 @@
r.cursor = cursor;
// Instantiate the model with a dummy view adapter and listener that (for now) do nothing.
- model = new Model(context);
+ model = new Model();
model.addUpdateListener(new DummyListener());
model.update(r);
}
@@ -303,16 +302,6 @@
}
}
- // Tests that Model.delete works correctly.
- public void testDelete() throws Exception {
- // Simulate deleting 2 files.
- List<DocumentInfo> docsBefore = getDocumentInfo(2, 3);
- delete(2, 3);
-
- provider.assertWasDeleted(docsBefore.get(0));
- provider.assertWasDeleted(docsBefore.get(1));
- }
-
private void setupTestContext() {
final MockContentResolver resolver = new MockContentResolver();
context = new ContextWrapper(getContext()) {
@@ -335,29 +324,6 @@
return s;
}
- private void delete(int... positions) throws InterruptedException {
- Selection s = positionToSelection(positions);
- final CountDownLatch latch = new CountDownLatch(1);
-
- model.delete(
- s,
- new Model.DeletionListener() {
- @Override
- public void onError() {
- latch.countDown();
- }
- @Override
- void onCompletion() {
- latch.countDown();
- }
- });
- latch.await();
- }
-
- private List<DocumentInfo> getDocumentInfo(int... positions) {
- return model.getDocuments(positionToSelection(positions));
- }
-
private static class DummyListener implements Model.UpdateListener {
public void onModelUpdate(Model model) {}
public void onModelUpdateFailed(Exception e) {}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
index 7a3b6d4..d3ef9aa 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
@@ -23,7 +23,6 @@
import com.android.documentsui.TestInputEvent;
import com.android.documentsui.dirlist.MultiSelectManager.Selection;
-
import com.google.common.collect.Lists;
import java.util.ArrayList;
@@ -55,13 +54,21 @@
mManager.addCallback(mCallback);
}
- public void testMouseClick_StartsSelectionMode() {
- click(7);
+ public void testSelection() {
+ // Check selection.
+ mManager.toggleSelection(items.get(7));
assertSelection(items.get(7));
+ // Check deselection.
+ mManager.toggleSelection(items.get(7));
+ assertSelectionSize(0);
}
- public void testMouseClick_NotifiesSelectionChanged() {
- click(7);
+ public void testSelection_NotifiesSelectionChanged() {
+ // Selection should notify.
+ mManager.toggleSelection(items.get(7));
+ mCallback.assertSelectionChanged();
+ // Deselection should notify.
+ mManager.toggleSelection(items.get(7));
mCallback.assertSelectionChanged();
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
index 5c04db9..7920c50 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
@@ -334,5 +334,10 @@
public void focusItem(int i) {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public boolean isLayoutItem(int adapterPosition) {
+ return false;
+ }
}
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapperTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapperTest.java
index 398885c..7c324e7 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapperTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapperTest.java
@@ -23,15 +23,12 @@
import android.support.v7.widget.RecyclerView;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
-import android.util.SparseArray;
import android.view.ViewGroup;
import com.android.documentsui.DirectoryResult;
import com.android.documentsui.RootCursorWrapper;
import com.android.documentsui.State;
-import java.util.List;
-
@SmallTest
public class SectionBreakDocumentsAdapterWrapperTest extends AndroidTestCase {
@@ -57,7 +54,7 @@
final Context testContext = TestContext.createStorageTestContext(getContext(), AUTHORITY);
DocumentsAdapter.Environment env = new TestEnvironment(testContext);
- mModel = new TestModel(testContext, AUTHORITY);
+ mModel = new TestModel(AUTHORITY);
mAdapter = new SectionBreakDocumentsAdapterWrapper(
env,
new ModelBackedDocumentsAdapter(
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestModel.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestModel.java
index f9cd3b2..d8c29db 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestModel.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestModel.java
@@ -16,16 +16,13 @@
package com.android.documentsui.dirlist;
-import android.content.Context;
import android.database.MatrixCursor;
import android.provider.DocumentsContract.Document;
import com.android.documentsui.DirectoryResult;
import com.android.documentsui.RootCursorWrapper;
-import com.android.documentsui.dirlist.MultiSelectManager.Selection;
import java.util.Random;
-import java.util.Set;
public class TestModel extends Model {
@@ -39,14 +36,9 @@
};
private final String mAuthority;
- private Set<String> mDeleted;
- /**
- * Creates a new context. context must be configured with provider for authority.
- * @see TestContext#createStorageTestContext(Context, String).
- */
- public TestModel(Context context, String authority) {
- super(context);
+ public TestModel(String authority) {
+ super();
mAuthority = authority;
}
@@ -75,12 +67,4 @@
String idForPosition(int p) {
return createModelId(mAuthority, Integer.toString(p));
}
-
- @Override
- public void delete(Selection selected, DeletionListener listener) {
- for (String id : selected.getAll()) {
- mDeleted.add(id);
- }
- listener.onCompletion();
- }
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java
index c4cfd3a..0e79561 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java
@@ -110,4 +110,9 @@
@Override
public void focusItem(int position) {
}
+
+ @Override
+ public boolean isLayoutItem(int adapterPosition) {
+ return false;
+ }
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/services/BaseCopyJobTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/services/AbstractCopyJobTest.java
similarity index 72%
rename from packages/DocumentsUI/tests/src/com/android/documentsui/services/BaseCopyJobTest.java
rename to packages/DocumentsUI/tests/src/com/android/documentsui/services/AbstractCopyJobTest.java
index f57ce53..ec21150 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/services/BaseCopyJobTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/services/AbstractCopyJobTest.java
@@ -16,72 +16,18 @@
package com.android.documentsui.services;
-import static com.android.documentsui.StubProvider.ROOT_0_ID;
-import static com.android.documentsui.StubProvider.ROOT_1_ID;
import static com.google.common.collect.Lists.newArrayList;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.Context;
import android.net.Uri;
-import android.os.RemoteException;
import android.provider.DocumentsContract;
-import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.MediumTest;
-import com.android.documentsui.DocumentsProviderHelper;
-import com.android.documentsui.StubProvider;
import com.android.documentsui.model.DocumentInfo;
-import com.android.documentsui.model.RootInfo;
import java.util.List;
@MediumTest
-public abstract class BaseCopyJobTest extends AndroidTestCase {
-
- static String AUTHORITY = StubProvider.DEFAULT_AUTHORITY;
- static final byte[] HAM_BYTES = "ham and cheese".getBytes();
- static final byte[] FRUITY_BYTES = "I love fruit cakes!".getBytes();
-
- Context mContext;
- ContentResolver mResolver;
- ContentProviderClient mClient;
- DocumentsProviderHelper mDocs;
- TestJobListener mJobListener;
- RootInfo mSrcRoot;
- RootInfo mDestRoot;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mJobListener = new TestJobListener();
-
- // NOTE: Must be the "target" context, else security checks in content provider will fail.
- mContext = getContext();
- mResolver = mContext.getContentResolver();
-
- mClient = mResolver.acquireContentProviderClient(AUTHORITY);
- mDocs = new DocumentsProviderHelper(AUTHORITY, mClient);
-
- initTestFiles();
- }
-
- @Override
- protected void tearDown() throws Exception {
- resetStorage();
- mClient.release();
- super.tearDown();
- }
-
- private void resetStorage() throws RemoteException {
- mClient.call("clear", null, null);
- }
-
- private void initTestFiles() throws RemoteException {
- mSrcRoot = mDocs.getRoot(ROOT_0_ID);
- mDestRoot = mDocs.getRoot(ROOT_1_ID);
- }
+public abstract class AbstractCopyJobTest<T extends CopyJob> extends AbstractJobTest<T> {
public void runCopyFilesTest() throws Exception {
Uri testFile1 = mDocs.createDocument(mSrcRoot, "text/plain", "test1.txt");
@@ -174,9 +120,9 @@
public void runNoCopyDirToDescendentTest() throws Exception {
Uri testDir = mDocs.createFolder(mSrcRoot, "someDir");
- Uri descDir = mDocs.createFolder(testDir, "theDescendent");
+ Uri destDir = mDocs.createFolder(testDir, "theDescendent");
- createJob(newArrayList(testDir), descDir).run();
+ createJob(newArrayList(testDir), destDir).run();
mJobListener.waitForFinished();
mJobListener.assertFailed();
@@ -201,10 +147,11 @@
mDocs.assertChildCount(mDestRoot, 0);
}
- final CopyJob createJob(List<Uri> srcs) throws Exception {
+ /**
+ * Creates a job with a stack consisting to the default destination.
+ */
+ final T createJob(List<Uri> srcs) throws Exception {
Uri destination = DocumentsContract.buildDocumentUri(AUTHORITY, mDestRoot.documentId);
return createJob(srcs, destination);
}
-
- abstract CopyJob createJob(List<Uri> srcs, Uri destination) throws Exception;
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/services/AbstractJobTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/services/AbstractJobTest.java
new file mode 100644
index 0000000..691af6a
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/services/AbstractJobTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ */
+
+package com.android.documentsui.services;
+
+import static com.android.documentsui.StubProvider.ROOT_0_ID;
+import static com.android.documentsui.StubProvider.ROOT_1_ID;
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.provider.DocumentsContract;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import com.android.documentsui.DocumentsProviderHelper;
+import com.android.documentsui.StubProvider;
+import com.android.documentsui.model.DocumentInfo;
+import com.android.documentsui.model.DocumentStack;
+import com.android.documentsui.model.RootInfo;
+
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+@MediumTest
+public abstract class AbstractJobTest<T extends Job> extends AndroidTestCase {
+
+ static String AUTHORITY = StubProvider.DEFAULT_AUTHORITY;
+ static final byte[] HAM_BYTES = "ham and cheese".getBytes();
+ static final byte[] FRUITY_BYTES = "I love fruit cakes!".getBytes();
+
+ Context mContext;
+ ContentResolver mResolver;
+ ContentProviderClient mClient;
+ DocumentsProviderHelper mDocs;
+ TestJobListener mJobListener;
+ RootInfo mSrcRoot;
+ RootInfo mDestRoot;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mJobListener = new TestJobListener();
+
+ // NOTE: Must be the "target" context, else security checks in content provider will fail.
+ mContext = getContext();
+ mResolver = mContext.getContentResolver();
+
+ mClient = mResolver.acquireContentProviderClient(AUTHORITY);
+ mDocs = new DocumentsProviderHelper(AUTHORITY, mClient);
+
+ initTestFiles();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ resetStorage();
+ mClient.release();
+ super.tearDown();
+ }
+
+ private void resetStorage() throws RemoteException {
+ mClient.call("clear", null, null);
+ }
+
+ private void initTestFiles() throws RemoteException {
+ mSrcRoot = mDocs.getRoot(ROOT_0_ID);
+ mDestRoot = mDocs.getRoot(ROOT_1_ID);
+ }
+
+ final T createJob(List<Uri> srcs, Uri destination) throws Exception {
+ DocumentStack stack = new DocumentStack();
+ stack.push(DocumentInfo.fromUri(mResolver, destination));
+
+ List<DocumentInfo> srcDocs = Lists.newArrayList();
+ for (Uri src : srcs) {
+ srcDocs.add(DocumentInfo.fromUri(mResolver, src));
+ }
+
+ return createJob(srcDocs, stack);
+ }
+
+ abstract T createJob(List<DocumentInfo> srcs, DocumentStack destination) throws Exception;
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/services/CopyJobTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/services/CopyJobTest.java
index c0ce993..1acf2dc 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/services/CopyJobTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/services/CopyJobTest.java
@@ -16,18 +16,15 @@
package com.android.documentsui.services;
-import android.net.Uri;
import android.test.suitebuilder.annotation.MediumTest;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
-import com.google.common.collect.Lists;
-
import java.util.List;
@MediumTest
-public class CopyJobTest extends BaseCopyJobTest {
+public class CopyJobTest extends AbstractCopyJobTest<CopyJob> {
public void testCopyFiles() throws Exception {
runCopyFilesTest();
@@ -62,16 +59,8 @@
}
@Override
- CopyJob createJob(List<Uri> srcs, Uri destination) throws Exception {
- DocumentStack stack = new DocumentStack();
- stack.push(DocumentInfo.fromUri(mResolver, destination));
-
- List<DocumentInfo> srcDocs = Lists.newArrayList();
- for (Uri src : srcs) {
- srcDocs.add(DocumentInfo.fromUri(mResolver, src));
- }
-
+ CopyJob createJob(List<DocumentInfo> srcs, DocumentStack stack) throws Exception {
return new CopyJob(
- mContext, mContext, mJobListener, FileOperations.createJobId(), stack, srcDocs);
+ mContext, mContext, mJobListener, FileOperations.createJobId(), stack, srcs);
}
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/services/DeleteJobTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/services/DeleteJobTest.java
new file mode 100644
index 0000000..d6d10239
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/services/DeleteJobTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+package com.android.documentsui.services;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+import android.net.Uri;
+import android.provider.DocumentsContract;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import com.android.documentsui.model.DocumentInfo;
+import com.android.documentsui.model.DocumentStack;
+
+import java.util.List;
+
+@MediumTest
+public class DeleteJobTest extends AbstractJobTest<DeleteJob> {
+
+ public void testDeleteFiles() throws Exception {
+ Uri testFile1 = mDocs.createDocument(mSrcRoot, "text/plain", "test1.txt");
+ mDocs.writeDocument(testFile1, HAM_BYTES);
+
+ Uri testFile2 = mDocs.createDocument(mSrcRoot, "text/plain", "test2.txt");
+ mDocs.writeDocument(testFile2, FRUITY_BYTES);
+
+ createJob(newArrayList(testFile1, testFile2)).run();
+ mJobListener.waitForFinished();
+
+ mDocs.assertChildCount(mSrcRoot, 0);
+ }
+
+ /**
+ * Creates a job with a stack consisting to the default src directory.
+ */
+ private final DeleteJob createJob(List<Uri> srcs) throws Exception {
+ Uri stack = DocumentsContract.buildDocumentUri(AUTHORITY, mSrcRoot.documentId);
+ return createJob(srcs, stack);
+ }
+
+ @Override
+ DeleteJob createJob(List<DocumentInfo> srcs, DocumentStack stack) throws Exception {
+ return new DeleteJob(
+ mContext, mContext, mJobListener, FileOperations.createJobId(), stack, srcs);
+ }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/services/FileOperationServiceTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/services/FileOperationServiceTest.java
index d55b6f0..4d5392e 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/services/FileOperationServiceTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/services/FileOperationServiceTest.java
@@ -114,8 +114,11 @@
public void testShutdownStopsExecutor_AfterSuccess() throws Exception {
startService(createCopyIntent(newArrayList(ALPHA_DOC), BETA_DOC));
- mExecutor.isAlive();
+ mExecutor.assertAlive();
+
mExecutor.runAll();
+ shutdownService();
+
mExecutor.assertShutdown();
}
@@ -126,6 +129,8 @@
mJobFactory.jobs.get(0).fail(ALPHA_DOC);
mExecutor.runAll();
+ shutdownService();
+
mExecutor.assertShutdown();
}
@@ -137,6 +142,8 @@
mJobFactory.jobs.get(1).fail(GAMMA_DOC);
mExecutor.runAll();
+ shutdownService();
+
mExecutor.assertShutdown();
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/services/MoveJobTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/services/MoveJobTest.java
index 5e41524..69d2db7 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/services/MoveJobTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/services/MoveJobTest.java
@@ -16,18 +16,18 @@
package com.android.documentsui.services;
+import static com.google.common.collect.Lists.newArrayList;
+
import android.net.Uri;
import android.test.suitebuilder.annotation.MediumTest;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
-import com.google.common.collect.Lists;
-
import java.util.List;
@MediumTest
-public class MoveJobTest extends BaseCopyJobTest {
+public class MoveJobTest extends AbstractCopyJobTest<MoveJob> {
public void testMoveFiles() throws Exception {
runCopyFilesTest();
@@ -36,15 +36,23 @@
}
public void testMoveVirtualTypedFile() throws Exception {
- runCopyVirtualTypedFileTest();
+ Uri testFile = mDocs.createVirtualFile(
+ mSrcRoot, "/virtual.sth", "virtual/mime-type",
+ FRUITY_BYTES, "application/pdf", "text/html");
+ createJob(newArrayList(testFile)).run();
- mDocs.assertChildCount(mSrcRoot, 0);
+ mJobListener.waitForFinished();
+
+ // Should have failed, source not deleted. Moving by bytes for virtual files
+ // is not supported.
+ mDocs.assertChildCount(mDestRoot, 0);
+ mDocs.assertChildCount(mSrcRoot, 1);
}
public void testMoveVirtualNonTypedFile() throws Exception {
runCopyVirtualNonTypedFileTest();
- // should have failed, source not deleted
+ // Should have failed, source not deleted.
mDocs.assertChildCount(mSrcRoot, 1);
}
@@ -82,16 +90,8 @@
}
@Override
- CopyJob createJob(List<Uri> srcs, Uri destination) throws Exception {
- DocumentStack stack = new DocumentStack();
- stack.push(DocumentInfo.fromUri(mResolver, destination));
-
- List<DocumentInfo> srcDocs = Lists.newArrayList();
- for (Uri src : srcs) {
- srcDocs.add(DocumentInfo.fromUri(mResolver, src));
- }
-
+ MoveJob createJob(List<DocumentInfo> srcs, DocumentStack stack) throws Exception {
return new MoveJob(
- mContext, mContext, mJobListener, FileOperations.createJobId(), stack, srcDocs);
+ mContext, mContext, mJobListener, FileOperations.createJobId(), stack, srcs);
}
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/services/TestJob.java b/packages/DocumentsUI/tests/src/com/android/documentsui/services/TestJob.java
index 72da9a1..9c58780 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/services/TestJob.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/services/TestJob.java
@@ -46,9 +46,6 @@
assertTrue(mStarted);
}
- @Override
- void cleanup() {}
-
void fail(DocumentInfo doc) {
onFileFailed(doc);
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/services/TestScheduledExecutorService.java b/packages/DocumentsUI/tests/src/com/android/documentsui/services/TestScheduledExecutorService.java
index 5c39b78..4d417cfc 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/services/TestScheduledExecutorService.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/services/TestScheduledExecutorService.java
@@ -146,7 +146,7 @@
scheduled.get(taskIndex).runnable.run();
}
- public void isAlive() {
+ public void assertAlive() {
assertFalse(isShutdown());
}
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
index 409f6a7..c7d17dc 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -18,6 +18,7 @@
import android.app.Activity;
import android.app.AlertDialog;
import android.app.admin.DevicePolicyManager;
+import android.auditing.SecurityLog;
import android.content.Context;
import android.os.UserHandle;
import android.util.AttributeSet;
@@ -423,6 +424,11 @@
}
public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) {
+ if (SecurityLog.isLoggingEnabled()) {
+ SecurityLog.writeEvent(SecurityLog.TAG_DEVICE_UNLOCK_ATTEMPT,
+ (success ? 1 : 0),
+ mCurrentSecuritySelection.name());
+ }
KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
if (success) {
monitor.clearFailedUnlockAttempts();
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDeviceRecord.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDeviceRecord.java
index 02d07b9..fa99a38 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDeviceRecord.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDeviceRecord.java
@@ -16,19 +16,23 @@
package com.android.mtp;
+import android.annotation.Nullable;
+
class MtpDeviceRecord {
public final int deviceId;
public final String name;
public final boolean opened;
public final MtpRoot[] roots;
- public final int[] operationsSupported;
+ public final @Nullable int[] operationsSupported;
+ public final @Nullable int[] eventsSupported;
- MtpDeviceRecord(
- int deviceId, String name, boolean opened, MtpRoot[] roots, int[] operationsSupported) {
+ MtpDeviceRecord(int deviceId, String name, boolean opened, MtpRoot[] roots,
+ @Nullable int[] operationsSupported, @Nullable int[] eventsSupported) {
this.deviceId = deviceId;
this.name = name;
this.opened = opened;
this.roots = roots;
this.operationsSupported = operationsSupported;
+ this.eventsSupported = eventsSupported;
}
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
index 0d81a30..efe5ff1 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
@@ -129,6 +129,7 @@
final String name = device.getProductName();
MtpRoot[] roots;
int[] operationsSupported = null;
+ int[] eventsSupported = null;
if (opened) {
try {
roots = getRoots(device.getDeviceId());
@@ -142,16 +143,14 @@
final MtpDeviceInfo info = mtpDevice.getDeviceInfo();
if (info != null) {
operationsSupported = mtpDevice.getDeviceInfo().getOperationsSupported();
- }
- if (operationsSupported == null) {
- operationsSupported = new int[0];
+ eventsSupported = mtpDevice.getDeviceInfo().getEventsSupported();
}
} else {
roots = new MtpRoot[0];
- operationsSupported = new int[0];
}
devices.add(new MtpDeviceRecord(
- device.getDeviceId(), name, opened, roots, operationsSupported));
+ device.getDeviceId(), name, opened, roots, operationsSupported,
+ eventsSupported));
}
return devices.toArray(new MtpDeviceRecord[devices.size()]);
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
index c39d5b3..97ea717 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -77,7 +77,7 @@
public void testPutSingleStorageDocuments() throws Exception {
mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putDeviceDocument(
- new MtpDeviceRecord(0, "Device", true, new MtpRoot[0], new int[0]));
+ new MtpDeviceRecord(0, "Device", true, new MtpRoot[0], null, null));
mDatabase.getMapper().stopAddingDocuments(null);
mDatabase.getMapper().startAddingDocuments("1");
@@ -425,9 +425,9 @@
};
mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putDeviceDocument(
- new MtpDeviceRecord(0, "Device A", true, new MtpRoot[0], new int[0]));
+ new MtpDeviceRecord(0, "Device A", true, new MtpRoot[0], null, null));
mDatabase.getMapper().putDeviceDocument(
- new MtpDeviceRecord(1, "Device B", true, new MtpRoot[0], new int[0]));
+ new MtpDeviceRecord(1, "Device B", true, new MtpRoot[0], null, null));
mDatabase.getMapper().stopAddingDocuments(null);
mDatabase.getMapper().startAddingDocuments("1");
@@ -562,7 +562,7 @@
mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putDeviceDocument(
- new MtpDeviceRecord(0, "Device", false, new MtpRoot[0], new int[0]));
+ new MtpDeviceRecord(0, "Device", false, new MtpRoot[0], null, null));
mDatabase.getMapper().stopAddingDocuments(null);
mDatabase.getMapper().startAddingDocuments("1");
@@ -640,7 +640,7 @@
public void testReplaceExistingRoots() {
mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putDeviceDocument(
- new MtpDeviceRecord(0, "Device", true, new MtpRoot[0], new int[0]));
+ new MtpDeviceRecord(0, "Device", true, new MtpRoot[0], null, null));
mDatabase.getMapper().stopAddingDocuments(null);
// The client code should be able to replace existing rows with new information.
@@ -691,7 +691,7 @@
// Add one.
mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putDeviceDocument(
- new MtpDeviceRecord(0, "Device", true, new MtpRoot[0], new int[0]));
+ new MtpDeviceRecord(0, "Device", true, new MtpRoot[0], null, null));
mDatabase.getMapper().stopAddingDocuments(null);
mDatabase.getMapper().startAddingDocuments("1");
@@ -745,7 +745,7 @@
// Add device document.
mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putDeviceDocument(
- new MtpDeviceRecord(0, "Device", false, new MtpRoot[0], new int[0]));
+ new MtpDeviceRecord(0, "Device", false, new MtpRoot[0], null, null));
mDatabase.getMapper().stopAddingDocuments(null);
// It the device does not have storages, it shows a device root.
@@ -895,7 +895,7 @@
public void testGetDocumentIdForDevice() {
mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putDeviceDocument(
- new MtpDeviceRecord(100, "Device", true, new MtpRoot[0], new int[0]));
+ new MtpDeviceRecord(100, "Device", true, new MtpRoot[0], null, null));
mDatabase.getMapper().stopAddingDocuments(null);
assertEquals("1", mDatabase.getDocumentIdForDevice(100));
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index 44841af..b51cf71 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -69,7 +69,8 @@
2048 /* total space */,
"" /* no volume identifier */)
},
- new int[0]));
+ null,
+ null));
mProvider.openDevice(0);
mResolver.waitForNotification(ROOTS_URI, 1);
@@ -109,7 +110,8 @@
2048 /* total space */,
"" /* no volume identifier */)
},
- new int[0]));
+ null,
+ null));
mProvider.openDevice(0);
mResolver.waitForNotification(ROOTS_URI, 1);
}
@@ -130,7 +132,8 @@
2048 /* total space */,
"" /* no volume identifier */)
},
- new int[0]));
+ null,
+ null));
mMtpManager.addValidDevice(new MtpDeviceRecord(
1,
"Device",
@@ -145,7 +148,8 @@
4096 /* total space */,
"Identifier B" /* no volume identifier */)
},
- new int[0]));
+ null,
+ null));
{
mProvider.openDevice(0);
@@ -180,7 +184,7 @@
public void testQueryRoots_error() throws Exception {
setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mMtpManager.addValidDevice(new MtpDeviceRecord(
- 0, "Device A", false /* unopened */, new MtpRoot[0], new int[0]));
+ 0, "Device A", false /* unopened */, new MtpRoot[0], null, null));
mMtpManager.addValidDevice(new MtpDeviceRecord(
1,
"Device",
@@ -195,7 +199,8 @@
4096 /* total space */,
"Identifier B" /* no volume identifier */)
},
- new int[0]));
+ null,
+ null));
{
mProvider.openDevice(0);
mProvider.openDevice(1);
@@ -438,7 +443,7 @@
throws InterruptedException, TimeoutException, IOException {
final int changeCount = mResolver.getChangeCount(ROOTS_URI);
mMtpManager.addValidDevice(
- new MtpDeviceRecord(deviceId, "Device", false /* unopened */, roots, new int[0]));
+ new MtpDeviceRecord(deviceId, "Device", false /* unopened */, roots, null, null));
mProvider.openDevice(deviceId);
mResolver.waitForNotification(ROOTS_URI, changeCount + 1);
return getStrings(mProvider.queryRoots(strings(DocumentsContract.Root.COLUMN_ROOT_ID)));
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java
index 914a4af..9ebe4d1 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java
@@ -87,6 +87,13 @@
getInstrumentation().show(Arrays.toString(records[0].operationsSupported));
}
+ public void testEventsSupported() {
+ final MtpDeviceRecord[] records = mManager.getDevices();
+ assertEquals(1, records.length);
+ assertNotNull(records[0].eventsSupported);
+ getInstrumentation().show(Arrays.toString(records[0].eventsSupported));
+ }
+
public void testEventObjectAdded() throws Exception {
while (true) {
getInstrumentation().show("Please take a photo by using connected MTP device.");
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
index 3934b88..a1732dc 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
@@ -76,7 +76,7 @@
result[i] = device;
} else {
result[i] = new MtpDeviceRecord(
- device.deviceId, device.name, device.opened, new MtpRoot[0], new int[0]);
+ device.deviceId, device.name, device.opened, new MtpRoot[0], null, null);
}
}
return result;
@@ -90,7 +90,7 @@
}
mDevices.put(
deviceId,
- new MtpDeviceRecord(device.deviceId, device.name, true, device.roots, new int[0]));
+ new MtpDeviceRecord(device.deviceId, device.name, true, device.roots, null, null));
}
@Override
@@ -101,7 +101,7 @@
}
mDevices.put(
deviceId,
- new MtpDeviceRecord(device.deviceId, device.name, false, device.roots, new int[0]));
+ new MtpDeviceRecord(device.deviceId, device.name, false, device.roots, null, null));
}
@Override
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
index 13105aa..1aec253 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
@@ -635,9 +635,9 @@
CharSequence description = printer.getDescription();
CharSequence subtitle;
- if (printServiceLabel == null) {
+ if (TextUtils.isEmpty(printServiceLabel)) {
subtitle = description;
- } else if (description == null) {
+ } else if (TextUtils.isEmpty(description)) {
subtitle = printServiceLabel;
} else {
subtitle = getString(R.string.printer_extended_description_template,
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
index ca0eda5f..d69250b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
@@ -128,15 +128,17 @@
boolean isDisabledByMultipleAdmins = false;
ComponentName adminComponent = null;
List<ComponentName> admins = dpm.getActiveAdmins();
- int disabledKeyguardFeatures;
- for (ComponentName admin : admins) {
- disabledKeyguardFeatures = dpm.getKeyguardDisabledFeatures(admin);
- if ((disabledKeyguardFeatures & keyguardNotificationFeatures) != 0) {
- if (adminComponent == null) {
- adminComponent = admin;
- } else {
- isDisabledByMultipleAdmins = true;
- break;
+ if (admins != null) {
+ int disabledKeyguardFeatures;
+ for (ComponentName admin : admins) {
+ disabledKeyguardFeatures = dpm.getKeyguardDisabledFeatures(admin);
+ if ((disabledKeyguardFeatures & keyguardNotificationFeatures) != 0) {
+ if (adminComponent == null) {
+ adminComponent = admin;
+ } else {
+ isDisabledByMultipleAdmins = true;
+ break;
+ }
}
}
}
@@ -242,14 +244,16 @@
ComponentName adminComponent = null;
List<ComponentName> admins = dpm.getActiveAdmins();
int quality;
- for (ComponentName admin : admins) {
- quality = dpm.getPasswordQuality(admin);
- if (quality >= DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
- if (adminComponent == null) {
- adminComponent = admin;
- } else {
- isDisabledByMultipleAdmins = true;
- break;
+ if (admins != null) {
+ for (ComponentName admin : admins) {
+ quality = dpm.getPasswordQuality(admin);
+ if (quality >= DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
+ if (adminComponent == null) {
+ adminComponent = admin;
+ } else {
+ isDisabledByMultipleAdmins = true;
+ break;
+ }
}
}
}
@@ -430,4 +434,4 @@
other.userId = userId;
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index e5aeb3c..d89abf42 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -235,23 +235,27 @@
// it means that the user has performed a global gesture to enable accessibility or set
// these settings in the Accessibility portion of the Setup Wizard, and definitely needs
// these features working after the restore.
- if (Settings.Secure.ACCESSIBILITY_ENABLED.equals(name)
- || Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION.equals(name)
- || Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD.equals(name)
- || Settings.Secure.TOUCH_EXPLORATION_ENABLED.equals(name)
- || Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED.equals(name)
- || Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED.equals(name)
- || Settings.Secure.UI_NIGHT_MODE.equals(name)) {
- return Settings.Secure.getInt(mContext.getContentResolver(), name, 0) != 0;
- } else if (Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES.equals(name)
- || Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES.equals(name)
- || Settings.Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX.equals(name)
- || Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER.equals(name)
- || Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE.equals(name)) {
- return !TextUtils.isEmpty(Settings.Secure.getString(
- mContext.getContentResolver(), name));
+ switch (name) {
+ case Settings.Secure.ACCESSIBILITY_ENABLED:
+ case Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION:
+ case Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD:
+ case Settings.Secure.TOUCH_EXPLORATION_ENABLED:
+ case Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED:
+ case Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED:
+ case Settings.Secure.UI_NIGHT_MODE:
+ return Settings.Secure.getInt(mContext.getContentResolver(), name, 0) != 0;
+ case Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES:
+ case Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES:
+ case Settings.Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX:
+ case Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER:
+ case Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE:
+ return !TextUtils.isEmpty(Settings.Secure.getString(
+ mContext.getContentResolver(), name));
+ case Settings.System.FONT_SCALE:
+ return Settings.System.getFloat(mContext.getContentResolver(), name, 1.0f) != 1.0f;
+ default:
+ return false;
}
- return false;
}
private void setAutoRestore(boolean enabled) {
diff --git a/packages/Shell/res/values/strings.xml b/packages/Shell/res/values/strings.xml
index 1459d53..d992bc3 100644
--- a/packages/Shell/res/values/strings.xml
+++ b/packages/Shell/res/values/strings.xml
@@ -21,6 +21,10 @@
<string name="bugreport_in_progress_title">Bug report is being generated</string>
<!-- Title of notification indicating a bugreport has been successfully captured. [CHAR LIMIT=50] -->
<string name="bugreport_finished_title">Bug report captured</string>
+ <!-- Title of notification indicating a bugreport is being updated before it can be shared. [CHAR LIMIT=50] -->
+ <string name="bugreport_updating_title">Adding details to the bug report</string>
+ <!-- Content notification indicating a bugreport is being updated before it can be shared, asking the user to wait [CHAR LIMIT=50] -->
+ <string name="bugreport_updating_wait">Please wait\u2026</string>
<!-- Text of notification indicating that swipe left will share the captured bugreport. [CHAR LIMIT=100] -->
<string name="bugreport_finished_text" product="watch">Swipe left to share your bug report</string>
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 874a946d..5b83796 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -464,6 +464,7 @@
+ info + ")");
return;
}
+ Log.v(TAG, "Sending 'Progress' notification for pid " + info.pid + ": " + percentText);
NotificationManager.from(mContext).notify(TAG, info.pid, notification);
}
@@ -852,7 +853,7 @@
}
/**
- * Sends a notitication indicating the bugreport has finished so use can share it.
+ * Sends a notification indicating the bugreport has finished so use can share it.
*/
private static void sendBugreportNotification(Context context, BugreportInfo info) {
final Intent shareIntent = new Intent(INTENT_BUGREPORT_SHARE);
@@ -878,10 +879,30 @@
builder.setContentInfo(info.name);
}
+ Log.v(TAG, "Sending 'Share' notification for pid " + info.pid + ": " + title);
NotificationManager.from(context).notify(TAG, info.pid, builder.build());
}
/**
+ * Sends a notification indicating the bugreport is being updated so the user can wait until it
+ * finishes - at this point there is nothing to be done other than waiting, hence it has no
+ * pending action.
+ */
+ private static void sendBugreportBeingUpdatedNotification(Context context, int pid) {
+ final String title = context.getString(R.string.bugreport_updating_title);
+ final Notification.Builder builder = new Notification.Builder(context)
+ .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+ .setContentTitle(title)
+ .setTicker(title)
+ .setContentText(context.getString(R.string.bugreport_updating_wait))
+ .setLocalOnly(true)
+ .setColor(context.getColor(
+ com.android.internal.R.color.system_notification_accent_color));
+ Log.v(TAG, "Sending 'Updating zip' notification for pid " + pid + ": " + title);
+ NotificationManager.from(context).notify(TAG, pid, builder.build());
+ }
+
+ /**
* Sends a zipped bugreport notification.
*/
private static void sendZippedBugreportNotification(final Context context,
@@ -938,11 +959,13 @@
Log.d(TAG, "Not touching zip file since neither title nor description are set");
return;
}
+
// It's not possible to add a new entry into an existing file, so we need to create a new
// zip, copy all entries, then rename it.
+ sendBugreportBeingUpdatedNotification(mContext, info.pid); // ...and that takes time
final File dir = info.bugreportFile.getParentFile();
final File tmpZip = new File(dir, "tmp-" + info.bugreportFile.getName());
- Log.d(TAG, "Writing temporary zip file (" + tmpZip + ")");
+ Log.d(TAG, "Writing temporary zip file (" + tmpZip + ") with title and/or description");
try (ZipFile oldZip = new ZipFile(info.bugreportFile);
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(tmpZip))) {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 6ec757d..4cdfcb4 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -152,6 +152,9 @@
<!-- Needed for passing extras with intent ACTION_SHOW_ADMIN_SUPPORT_DETAILS -->
<uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" />
+ <!-- TV picture-in-picture -->
+ <uses-permission android:name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE" />
+
<application
android:name=".SystemUIApplication"
android:persistent="true"
@@ -226,7 +229,7 @@
android:resumeWhilePausing="true"
android:screenOrientation="behind"
android:resizeableActivity="true"
- android:configChanges="orientation|screenSize|smallestScreenSize"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|layoutDirection"
android:theme="@style/RecentsTheme.Wallpaper">
<intent-filter>
<action android:name="com.android.systemui.recents.TOGGLE_RECENTS" />
diff --git a/packages/SystemUI/res/drawable/recents_info_dark.xml b/packages/SystemUI/res/drawable/recents_info_dark.xml
new file mode 100644
index 0000000..b1a2242
--- /dev/null
+++ b/packages/SystemUI/res/drawable/recents_info_dark.xml
@@ -0,0 +1,24 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48.0dp"
+ android:height="48.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="@color/recents_task_bar_dark_icon_color"
+ android:pathData="M12.000000,2.000000C6.500000,2.000000 2.000000,6.500000 2.000000,12.000000s4.500000,10.000000 10.000000,10.000000c5.500000,0.000000 10.000000,-4.500000 10.000000,-10.000000S17.500000,2.000000 12.000000,2.000000zM13.000000,17.000000l-2.000000,0.000000l0.000000,-6.000000l2.000000,0.000000L13.000000,17.000000zM13.000000,9.000000l-2.000000,0.000000L11.000000,7.000000l2.000000,0.000000L13.000000,9.000000z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/recents_info_light.xml b/packages/SystemUI/res/drawable/recents_info_light.xml
new file mode 100644
index 0000000..bc58c3b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/recents_info_light.xml
@@ -0,0 +1,24 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48.0dp"
+ android:height="48.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M12.000000,2.000000C6.500000,2.000000 2.000000,6.500000 2.000000,12.000000s4.500000,10.000000 10.000000,10.000000c5.500000,0.000000 10.000000,-4.500000 10.000000,-10.000000S17.500000,2.000000 12.000000,2.000000zM13.000000,17.000000l-2.000000,0.000000l0.000000,-6.000000l2.000000,0.000000L13.000000,17.000000zM13.000000,9.000000l-2.000000,0.000000L11.000000,7.000000l2.000000,0.000000L13.000000,9.000000z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/SystemUI/res/layout-sw600dp/navigation_bar.xml b/packages/SystemUI/res/layout-sw600dp/navigation_bar.xml
deleted file mode 100644
index 1fcb2df..0000000
--- a/packages/SystemUI/res/layout-sw600dp/navigation_bar.xml
+++ /dev/null
@@ -1,322 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2012, 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.
--->
-
-<!-- navigation bar for sw600dp (small tablets) -->
-<com.android.systemui.statusbar.phone.NavigationBarView
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:systemui="http://schemas.android.com/apk/res-auto"
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- android:background="@drawable/system_bar_background"
- >
-
- <FrameLayout android:id="@+id/rot0"
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- >
-
- <LinearLayout
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- android:orientation="horizontal"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:id="@+id/nav_buttons"
- android:animateLayoutChanges="true"
- >
-
- <!-- navigation controls -->
- <View
- android:layout_width="@dimen/navigation_extra_key_width"
- android:layout_height="match_parent"
- android:layout_weight="0"
- android:layout_marginStart="2dp"
- android:visibility="invisible"
- />
- <Space
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="1"
- />
- <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/back"
- android:layout_width="128dp" android:paddingStart="25dp" android:paddingEnd="25dp"
- android:layout_height="match_parent"
- android:src="@drawable/ic_sysbar_back"
- android:scaleType="centerInside"
- systemui:keyCode="4"
- android:layout_weight="0"
- android:contentDescription="@string/accessibility_back"
- />
- <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/home"
- android:layout_width="128dp" android:paddingStart="25dp" android:paddingEnd="25dp"
- android:layout_height="match_parent"
- android:src="@drawable/ic_sysbar_home"
- android:scaleType="centerInside"
- systemui:keyCode="3"
- systemui:keyRepeat="true"
- android:layout_weight="0"
- android:contentDescription="@string/accessibility_home"
- />
- <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/recent_apps"
- android:layout_width="128dp" android:paddingStart="25dp" android:paddingEnd="25dp"
- android:layout_height="match_parent"
- android:src="@drawable/ic_sysbar_recent"
- android:scaleType="centerInside"
- android:layout_weight="0"
- android:contentDescription="@string/accessibility_recent"
- />
- <Space
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="1"
- />
- <FrameLayout
- android:layout_width="@dimen/navigation_extra_key_width"
- android:layout_height="match_parent"
- android:layout_weight="0"
- android:layout_marginEnd="2dp" >
- <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/menu"
- android:layout_width="@dimen/navigation_extra_key_width"
- android:layout_height="match_parent"
- android:src="@drawable/ic_sysbar_menu"
- android:scaleType="centerInside"
- android:layout_marginEnd="2dp"
- systemui:keyCode="82"
- android:visibility="invisible"
- android:contentDescription="@string/accessibility_menu"
- />
- <com.android.systemui.statusbar.policy.KeyButtonView
- android:id="@+id/ime_switcher"
- android:layout_width="@dimen/navigation_extra_key_width"
- android:layout_height="match_parent"
- android:layout_marginEnd="2dp"
- android:scaleType="centerInside"
- android:src="@drawable/ic_ime_switcher_default"
- android:visibility="invisible"
- android:contentDescription="@string/accessibility_ime_switch_button" />
- </FrameLayout>
- </LinearLayout>
-
- <!-- lights out layout to match exactly -->
- <LinearLayout
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- android:orientation="horizontal"
- android:id="@+id/lights_out"
- android:visibility="gone"
- >
- <Space
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="1"
- />
- <ImageView
- android:layout_width="128dp" android:paddingStart="25dp" android:paddingEnd="25dp"
- android:layout_height="match_parent"
- android:layout_marginStart="40dp"
- android:src="@drawable/ic_sysbar_lights_out_dot_small"
- android:scaleType="center"
- android:layout_weight="0"
- android:contentDescription="@string/accessibility_back"
- />
- <ImageView
- android:layout_width="128dp" android:paddingStart="25dp" android:paddingEnd="25dp"
- android:layout_height="match_parent"
- android:src="@drawable/ic_sysbar_lights_out_dot_large"
- android:scaleType="center"
- android:layout_weight="0"
- android:contentDescription="@string/accessibility_home"
- />
- <ImageView
- android:layout_width="128dp" android:paddingStart="25dp" android:paddingEnd="25dp"
- android:layout_marginEnd="40dp"
- android:layout_height="match_parent"
- android:src="@drawable/ic_sysbar_lights_out_dot_small"
- android:scaleType="center"
- android:layout_weight="0"
- android:contentDescription="@string/accessibility_recent"
- />
- <Space
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="1"
- />
- </LinearLayout>
-
- <com.android.systemui.statusbar.policy.DeadZone
- android:id="@+id/deadzone"
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- systemui:minSize="@dimen/navigation_bar_deadzone_size"
- systemui:maxSize="@dimen/navigation_bar_deadzone_size_max"
- systemui:holdTime="@integer/navigation_bar_deadzone_hold"
- systemui:decayTime="@integer/navigation_bar_deadzone_decay"
- systemui:orientation="horizontal"
- android:layout_gravity="top"
- />
- </FrameLayout>
-
- <FrameLayout android:id="@+id/rot90"
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- android:visibility="gone"
- android:paddingTop="0dp"
- >
-
- <LinearLayout
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- android:orientation="horizontal"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:id="@+id/nav_buttons"
- android:animateLayoutChanges="true"
- >
-
- <!-- navigation controls -->
- <View
- android:layout_width="@dimen/navigation_extra_key_width"
- android:layout_height="match_parent"
- android:layout_weight="0"
- android:layout_marginStart="2dp"
- android:visibility="invisible"
- />
- <Space
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="1"
- />
- <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/back"
- android:layout_width="162dp" android:paddingStart="42dp" android:paddingEnd="42dp"
- android:layout_height="match_parent"
- android:src="@drawable/ic_sysbar_back"
- android:scaleType="centerInside"
- systemui:keyCode="4"
- android:layout_weight="0"
- android:contentDescription="@string/accessibility_back"
- />
- <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/home"
- android:layout_width="162dp" android:paddingStart="42dp" android:paddingEnd="42dp"
- android:layout_height="match_parent"
- android:src="@drawable/ic_sysbar_home"
- android:scaleType="centerInside"
- systemui:keyCode="3"
- systemui:keyRepeat="true"
- android:layout_weight="0"
- android:contentDescription="@string/accessibility_home"
- />
- <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/recent_apps"
- android:layout_width="162dp" android:paddingStart="42dp" android:paddingEnd="42dp"
- android:layout_height="match_parent"
- android:src="@drawable/ic_sysbar_recent"
- android:scaleType="centerInside"
- android:layout_weight="0"
- android:contentDescription="@string/accessibility_recent"
- />
- <Space
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="1"
- />
- <FrameLayout
- android:layout_width="@dimen/navigation_extra_key_width"
- android:layout_height="match_parent"
- android:layout_marginEnd="2dp"
- android:layout_weight="0" >
- <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/menu"
- android:layout_width="@dimen/navigation_extra_key_width"
- android:layout_height="match_parent"
- android:layout_marginEnd="2dp"
- android:src="@drawable/ic_sysbar_menu"
- android:scaleType="centerInside"
- systemui:keyCode="82"
- android:visibility="invisible"
- android:contentDescription="@string/accessibility_menu" />
- <com.android.systemui.statusbar.policy.KeyButtonView
- android:id="@+id/ime_switcher"
- android:layout_width="@dimen/navigation_extra_key_width"
- android:layout_height="match_parent"
- android:layout_marginEnd="2dp"
- android:src="@drawable/ic_ime_switcher_default"
- android:visibility="invisible"
- android:contentDescription="@string/accessibility_ime_switch_button"
- android:scaleType="centerInside" />
- </FrameLayout>
- </LinearLayout>
-
- <!-- lights out layout to match exactly -->
- <LinearLayout
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- android:orientation="horizontal"
- android:id="@+id/lights_out"
- android:visibility="gone"
- >
- <Space
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="1"
- />
- <ImageView
- android:layout_width="162dp" android:paddingStart="42dp" android:paddingEnd="42dp"
- android:layout_height="match_parent"
- android:layout_marginStart="40dp"
- android:src="@drawable/ic_sysbar_lights_out_dot_small"
- android:scaleType="center"
- android:layout_weight="0"
- android:contentDescription="@string/accessibility_back"
- />
- <ImageView
- android:layout_width="162dp" android:paddingStart="42dp" android:paddingEnd="42dp"
- android:layout_height="match_parent"
- android:src="@drawable/ic_sysbar_lights_out_dot_large"
- android:scaleType="center"
- android:layout_weight="0"
- android:contentDescription="@string/accessibility_home"
- />
- <ImageView
- android:layout_width="162dp" android:paddingStart="42dp" android:paddingEnd="42dp"
- android:layout_marginEnd="40dp"
- android:layout_height="match_parent"
- android:src="@drawable/ic_sysbar_lights_out_dot_small"
- android:scaleType="center"
- android:layout_weight="0"
- android:contentDescription="@string/accessibility_recent"
- />
- <Space
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="1"
- />
- </LinearLayout>
-
- <!-- On tablets in landscape the navbar is on the bottom, so use a
- horizontal dead zone. -->
- <com.android.systemui.statusbar.policy.DeadZone
- android:id="@+id/deadzone"
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- systemui:minSize="@dimen/navigation_bar_deadzone_size"
- systemui:maxSize="@dimen/navigation_bar_deadzone_size_max"
- systemui:holdTime="@integer/navigation_bar_deadzone_hold"
- systemui:decayTime="@integer/navigation_bar_deadzone_decay"
- systemui:orientation="horizontal"
- android:layout_gravity="top"
- />
- </FrameLayout>
-</com.android.systemui.statusbar.phone.NavigationBarView>
diff --git a/packages/SystemUI/res/layout-sw600dp/navigation_layout_rot90.xml b/packages/SystemUI/res/layout-sw600dp/navigation_layout_rot90.xml
new file mode 100644
index 0000000..dd559c5
--- /dev/null
+++ b/packages/SystemUI/res/layout-sw600dp/navigation_layout_rot90.xml
@@ -0,0 +1,90 @@
+<?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.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <FrameLayout
+ android:id="@+id/nav_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:id="@+id/start_group"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="start"
+ android:orientation="horizontal" />
+
+ <LinearLayout
+ android:id="@+id/center_group"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="horizontal" />
+
+ <LinearLayout
+ android:id="@+id/end_group"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="end"
+ android:orientation="horizontal" />
+
+ </FrameLayout>
+
+ <FrameLayout
+ android:id="@+id/lights_out"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:id="@+id/start_group_lightsout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="start"
+ android:orientation="horizontal" />
+
+ <LinearLayout
+ android:id="@+id/center_group_lightsout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="horizontal" />
+
+ <LinearLayout
+ android:id="@+id/end_group_lightsout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="end"
+ android:orientation="horizontal" />
+
+ </FrameLayout>
+
+ <com.android.systemui.statusbar.policy.DeadZone
+ android:id="@+id/deadzone"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:layout_gravity="top"
+ systemui:minSize="@dimen/navigation_bar_deadzone_size"
+ systemui:maxSize="@dimen/navigation_bar_deadzone_size_max"
+ systemui:holdTime="@integer/navigation_bar_deadzone_hold"
+ systemui:decayTime="@integer/navigation_bar_deadzone_decay"
+ systemui:orientation="horizontal"
+ />
+
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/back.xml b/packages/SystemUI/res/layout/back.xml
new file mode 100644
index 0000000..d256622
--- /dev/null
+++ b/packages/SystemUI/res/layout/back.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+
+<com.android.systemui.statusbar.policy.KeyButtonView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/back"
+ android:layout_width="@dimen/navigation_key_width"
+ android:layout_height="match_parent"
+ android:layout_weight="0"
+ android:src="@drawable/ic_sysbar_back"
+ systemui:keyCode="4"
+ android:scaleType="center"
+ android:contentDescription="@string/accessibility_back"
+ android:paddingStart="@dimen/navigation_key_padding"
+ android:paddingEnd="@dimen/navigation_key_padding"
+ />
+
diff --git a/packages/SystemUI/res/layout/fullscreen_user_pod.xml b/packages/SystemUI/res/layout/car_fullscreen_user_pod.xml
similarity index 84%
rename from packages/SystemUI/res/layout/fullscreen_user_pod.xml
rename to packages/SystemUI/res/layout/car_fullscreen_user_pod.xml
index 12f0a80..b7e666f 100644
--- a/packages/SystemUI/res/layout/fullscreen_user_pod.xml
+++ b/packages/SystemUI/res/layout/car_fullscreen_user_pod.xml
@@ -26,13 +26,13 @@
<ImageView android:id="@+id/user_avatar"
android:padding="10dp"
android:layout_gravity="center"
- android:layout_width="160dp"
- android:layout_height="160dp" />
+ android:layout_width="@dimen/car_fullscreen_user_pod_image_avatar_width"
+ android:layout_height="@dimen/car_fullscreen_user_pod_image_avatar_height" />
<TextView android:id="@+id/user_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textSize="@dimen/qs_detail_item_secondary_text_size"
+ android:textSize="@dimen/car_fullscreen_user_pod_text_size"
android:textColor="@color/qs_user_detail_name"
android:gravity="center_horizontal" />
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/car_fullscreen_user_switcher.xml b/packages/SystemUI/res/layout/car_fullscreen_user_switcher.xml
new file mode 100644
index 0000000..b953ff2
--- /dev/null
+++ b/packages/SystemUI/res/layout/car_fullscreen_user_switcher.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:fitsSystemWindows="true"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/car_lockscreen_disclaimer_title"
+ android:textSize="@dimen/car_lockscreen_disclaimer_title_size"
+ android:paddingStart="@dimen/car_lockscreen_disclaimer_title_padding_start"
+ android:paddingTop="@dimen/car_lockscreen_disclaimer_title_padding_top" />
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/car_lockscreen_disclaimer_text"
+ android:textSize="@dimen/car_lockscreen_disclaimer_text_size"
+ android:paddingStart="@dimen/car_lockscreen_disclaimer_text_padding_start"
+ android:paddingEnd="@dimen/car_lockscreen_disclaimer_text_padding_end"
+ android:paddingTop="@dimen/car_lockscreen_disclaimer_text_padding_top" />
+ <com.android.systemui.statusbar.UserGridView
+ android:id="@+id/user_grid"
+ android:layout_gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="@dimen/car_lockscreen_user_grid_view_padding_top"
+ android:stretchMode="columnWidth">
+ </com.android.systemui.statusbar.UserGridView>
+ </LinearLayout>
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/fullscreen_user_switcher.xml b/packages/SystemUI/res/layout/fullscreen_user_switcher.xml
deleted file mode 100644
index 46c1896..0000000
--- a/packages/SystemUI/res/layout/fullscreen_user_switcher.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:fitsSystemWindows="true"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone">
- <com.android.systemui.statusbar.UserGridView
- android:id="@+id/user_grid"
- android:layout_gravity="center"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingStart="10dp"
- android:paddingEnd="10dp"
- android:columnWidth="180dp"
- android:verticalSpacing="10dp"
- android:horizontalSpacing="10dp"
- android:stretchMode="columnWidth"
- android:gravity="center">
- </com.android.systemui.statusbar.UserGridView>
-</FrameLayout>
diff --git a/packages/SystemUI/res/layout/home.xml b/packages/SystemUI/res/layout/home.xml
new file mode 100644
index 0000000..f11592de
--- /dev/null
+++ b/packages/SystemUI/res/layout/home.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+<com.android.systemui.statusbar.policy.KeyButtonView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/home"
+ android:layout_width="@dimen/navigation_key_width"
+ android:layout_height="match_parent"
+ android:layout_weight="0"
+ android:src="@drawable/ic_sysbar_home"
+ systemui:keyCode="3"
+ android:scaleType="center"
+ android:contentDescription="@string/accessibility_home"
+ android:paddingStart="@dimen/navigation_key_padding"
+ android:paddingEnd="@dimen/navigation_key_padding"
+ />
+
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
index 2e67376..c6e453a 100644
--- a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
+++ b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
@@ -43,4 +43,4 @@
sysui:frameWidth="@dimen/keyguard_user_switcher_border_thickness"
sysui:framePadding="6dp"
sysui:activeFrameColor="@color/current_user_border_color" />
-</com.android.systemui.qs.tiles.UserDetailItemView>
\ No newline at end of file
+</com.android.systemui.qs.tiles.UserDetailItemView>
diff --git a/packages/SystemUI/res/layout/menu_ime.xml b/packages/SystemUI/res/layout/menu_ime.xml
new file mode 100644
index 0000000..90b74d0
--- /dev/null
+++ b/packages/SystemUI/res/layout/menu_ime.xml
@@ -0,0 +1,44 @@
+<?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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:layout_width="@dimen/navigation_side_padding"
+ android:layout_height="match_parent"
+ android:layout_weight="0"
+ >
+ <com.android.systemui.statusbar.policy.KeyButtonView
+ android:id="@+id/menu"
+ android:layout_width="@dimen/navigation_extra_key_width"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="2dp"
+ android:src="@drawable/ic_sysbar_menu"
+ android:scaleType="centerInside"
+ systemui:keyCode="82"
+ android:visibility="invisible"
+ android:contentDescription="@string/accessibility_menu"
+ />
+ <com.android.systemui.statusbar.policy.KeyButtonView
+ android:id="@+id/ime_switcher"
+ android:layout_width="@dimen/navigation_extra_key_width"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="2dp"
+ android:src="@drawable/ic_ime_switcher_default"
+ android:visibility="invisible"
+ android:contentDescription="@string/accessibility_ime_switch_button"
+ android:scaleType="centerInside"
+ />
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/nav_key_space.xml b/packages/SystemUI/res/layout/nav_key_space.xml
new file mode 100644
index 0000000..3986841
--- /dev/null
+++ b/packages/SystemUI/res/layout/nav_key_space.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+** Copyright 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.
+*/
+-->
+
+<Space xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:layout_width="@dimen/navigation_side_padding"
+ android:layout_height="match_parent"
+ android:layout_weight="0"
+ >
+</Space>
diff --git a/packages/SystemUI/res/layout/navigation_bar.xml b/packages/SystemUI/res/layout/navigation_bar.xml
index 8498a4f..36e937d 100644
--- a/packages/SystemUI/res/layout/navigation_bar.xml
+++ b/packages/SystemUI/res/layout/navigation_bar.xml
@@ -22,309 +22,17 @@
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent"
android:layout_width="match_parent"
- android:background="@drawable/system_bar_background"
- >
+ android:background="@drawable/system_bar_background">
- <FrameLayout android:id="@+id/rot0"
- android:layout_height="match_parent"
+ <com.android.systemui.statusbar.phone.NavigationBarInflaterView
+ android:id="@+id/navigation_inflater"
android:layout_width="match_parent"
- >
+ android:layout_height="match_parent">
- <LinearLayout
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- android:orientation="horizontal"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:id="@+id/nav_buttons"
- android:animateLayoutChanges="true"
- >
+ <include android:id="@+id/rot0" layout="@layout/navigation_layout" />
- <!-- navigation controls -->
- <View
- android:layout_width="@dimen/navigation_side_padding"
- android:layout_height="match_parent"
- android:layout_weight="0"
- android:visibility="invisible"
- />
- <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/back"
- android:layout_width="@dimen/navigation_key_width"
- android:layout_height="match_parent"
- android:src="@drawable/ic_sysbar_back"
- systemui:keyCode="4"
- android:layout_weight="0"
- android:scaleType="center"
- android:contentDescription="@string/accessibility_back"
- />
- <View
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:visibility="invisible"
- />
- <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/home"
- android:layout_width="@dimen/navigation_key_width"
- android:layout_height="match_parent"
- android:src="@drawable/ic_sysbar_home"
- systemui:keyCode="3"
- systemui:keyRepeat="false"
- android:layout_weight="0"
- android:scaleType="center"
- android:contentDescription="@string/accessibility_home"
- />
- <View
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:visibility="invisible"
- />
- <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/recent_apps"
- android:layout_width="@dimen/navigation_key_width"
- android:layout_height="match_parent"
- android:src="@drawable/ic_sysbar_recent"
- android:layout_weight="0"
- android:scaleType="center"
- android:contentDescription="@string/accessibility_recent"
- />
- <FrameLayout
- android:layout_width="@dimen/navigation_side_padding"
- android:layout_height="match_parent"
- android:layout_weight="0" >
- <com.android.systemui.statusbar.policy.KeyButtonView
- android:id="@+id/menu"
- android:layout_width="@dimen/navigation_extra_key_width"
- android:layout_height="match_parent"
- android:contentDescription="@string/accessibility_menu"
- android:src="@drawable/ic_sysbar_menu"
- android:visibility="invisible"
- android:scaleType="centerInside"
- android:layout_gravity="end"
- systemui:keyCode="82" />
+ <include android:id="@+id/rot90" layout="@layout/navigation_layout_rot90" />
- <com.android.systemui.statusbar.policy.KeyButtonView
- android:id="@+id/ime_switcher"
- android:layout_width="@dimen/navigation_extra_key_width"
- android:layout_height="match_parent"
- android:contentDescription="@string/accessibility_ime_switch_button"
- android:scaleType="centerInside"
- android:src="@drawable/ic_ime_switcher_default"
- android:visibility="invisible"
- android:layout_gravity="end" />
- </FrameLayout>
-
- </LinearLayout>
-
- <!-- lights out layout to match exactly -->
- <LinearLayout
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- android:orientation="horizontal"
- android:id="@+id/lights_out"
- android:visibility="gone"
- >
- <ImageView
- android:layout_width="@dimen/navigation_key_width"
- android:layout_height="match_parent"
- android:layout_marginStart="@dimen/navigation_side_padding"
- android:src="@drawable/ic_sysbar_lights_out_dot_small"
- android:scaleType="center"
- android:layout_weight="0"
- android:contentDescription="@string/accessibility_back"
- />
- <View
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:visibility="invisible"
- />
- <ImageView
- android:layout_width="@dimen/navigation_key_width"
- android:layout_height="match_parent"
- android:src="@drawable/ic_sysbar_lights_out_dot_large"
- android:scaleType="center"
- android:layout_weight="0"
- android:contentDescription="@string/accessibility_home"
- />
- <View
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:visibility="invisible"
- />
- <ImageView
- android:layout_width="@dimen/navigation_key_width"
- android:layout_marginEnd="@dimen/navigation_side_padding"
- android:layout_height="match_parent"
- android:src="@drawable/ic_sysbar_lights_out_dot_small"
- android:scaleType="center"
- android:layout_weight="0"
- android:contentDescription="@string/accessibility_recent"
- />
- </LinearLayout>
-
- <com.android.systemui.statusbar.policy.DeadZone
- android:id="@+id/deadzone"
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- systemui:minSize="@dimen/navigation_bar_deadzone_size"
- systemui:maxSize="@dimen/navigation_bar_deadzone_size_max"
- systemui:holdTime="@integer/navigation_bar_deadzone_hold"
- systemui:decayTime="@integer/navigation_bar_deadzone_decay"
- systemui:orientation="horizontal"
- android:layout_gravity="top"
- />
- </FrameLayout>
-
- <FrameLayout android:id="@+id/rot90"
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- android:visibility="gone"
- android:paddingTop="0dp"
- >
-
- <LinearLayout
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- android:orientation="vertical"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:id="@+id/nav_buttons"
- android:animateLayoutChanges="true"
- >
-
- <!-- navigation controls -->
- <FrameLayout
- android:layout_weight="0"
- android:layout_width="match_parent"
- android:layout_height="@dimen/navigation_side_padding" >
- <com.android.systemui.statusbar.policy.KeyButtonView
- android:id="@+id/ime_switcher"
- android:layout_width="match_parent"
- android:layout_height="@dimen/navigation_extra_key_width"
- android:contentDescription="@string/accessibility_ime_switch_button"
- android:scaleType="centerInside"
- android:src="@drawable/ic_ime_switcher_default"
- android:layout_gravity="top"
- android:visibility="invisible" />
-
- <com.android.systemui.statusbar.policy.KeyButtonView
- android:id="@+id/menu"
- android:layout_width="match_parent"
- android:layout_height="40dp"
- android:contentDescription="@string/accessibility_menu"
- android:src="@drawable/ic_sysbar_menu"
- android:scaleType="centerInside"
- android:layout_gravity="top"
- android:visibility="invisible"
- systemui:keyCode="82" />
- </FrameLayout>
-
- <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/recent_apps"
- android:layout_height="@dimen/navigation_key_width"
- android:layout_width="match_parent"
- android:src="@drawable/ic_sysbar_recent"
- android:scaleType="center"
- android:layout_weight="0"
- android:contentDescription="@string/accessibility_recent"
- />
- <View
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- android:layout_weight="1"
- android:visibility="invisible"
- />
- <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/home"
- android:layout_height="@dimen/navigation_key_width"
- android:layout_width="match_parent"
- android:src="@drawable/ic_sysbar_home"
- android:scaleType="center"
- systemui:keyCode="3"
- systemui:keyRepeat="false"
- android:layout_weight="0"
- android:contentDescription="@string/accessibility_home"
- />
- <View
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- android:layout_weight="1"
- android:visibility="invisible"
- />
- <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/back"
- android:layout_height="@dimen/navigation_key_width"
- android:layout_width="match_parent"
- android:src="@drawable/ic_sysbar_back"
- android:scaleType="center"
- systemui:keyCode="4"
- android:layout_weight="0"
- android:contentDescription="@string/accessibility_back"
- />
- <View
- android:layout_height="@dimen/navigation_side_padding"
- android:layout_width="match_parent"
- android:layout_weight="0"
- android:visibility="invisible"
- />
- </LinearLayout>
-
- <!-- lights out layout to match exactly -->
- <LinearLayout
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- android:orientation="vertical"
- android:id="@+id/lights_out"
- android:visibility="gone"
- >
- <ImageView
- android:layout_height="@dimen/navigation_key_width"
- android:layout_marginTop="@dimen/navigation_side_padding"
- android:layout_width="match_parent"
- android:src="@drawable/ic_sysbar_lights_out_dot_small"
- android:scaleType="center"
- android:layout_weight="0"
- android:contentDescription="@string/accessibility_recent"
- />
- <View
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- android:layout_weight="1"
- android:visibility="invisible"
- />
- <ImageView
- android:layout_height="@dimen/navigation_key_width"
- android:layout_width="match_parent"
- android:src="@drawable/ic_sysbar_lights_out_dot_large"
- android:scaleType="center"
- android:layout_weight="0"
- android:contentDescription="@string/accessibility_home"
- />
- <View
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- android:layout_weight="1"
- android:visibility="invisible"
- />
- <ImageView
- android:layout_height="@dimen/navigation_key_width"
- android:layout_marginBottom="@dimen/navigation_side_padding"
- android:layout_width="match_parent"
- android:src="@drawable/ic_sysbar_lights_out_dot_small"
- android:scaleType="center"
- android:layout_weight="0"
- android:contentDescription="@string/accessibility_back"
- />
- </LinearLayout>
-
- <com.android.systemui.statusbar.policy.DeadZone
- android:id="@+id/deadzone"
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- systemui:minSize="@dimen/navigation_bar_deadzone_size"
- systemui:maxSize="@dimen/navigation_bar_deadzone_size_max"
- systemui:holdTime="@integer/navigation_bar_deadzone_hold"
- systemui:decayTime="@integer/navigation_bar_deadzone_decay"
- systemui:orientation="vertical"
- android:layout_gravity="top"
- />
- </FrameLayout>
+ </com.android.systemui.statusbar.phone.NavigationBarInflaterView>
</com.android.systemui.statusbar.phone.NavigationBarView>
diff --git a/packages/SystemUI/res/layout/navigation_layout.xml b/packages/SystemUI/res/layout/navigation_layout.xml
new file mode 100644
index 0000000..7ebf4ed
--- /dev/null
+++ b/packages/SystemUI/res/layout/navigation_layout.xml
@@ -0,0 +1,91 @@
+<?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.
+-->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <FrameLayout
+ android:id="@+id/nav_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:id="@+id/start_group"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="start"
+ android:orientation="horizontal" />
+
+ <LinearLayout
+ android:id="@+id/center_group"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="horizontal" />
+
+ <LinearLayout
+ android:id="@+id/end_group"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="end"
+ android:orientation="horizontal" />
+
+ </FrameLayout>
+
+ <FrameLayout
+ android:id="@+id/lights_out"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:id="@+id/start_group_lightsout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="start"
+ android:orientation="horizontal" />
+
+ <LinearLayout
+ android:id="@+id/center_group_lightsout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="horizontal" />
+
+ <LinearLayout
+ android:id="@+id/end_group_lightsout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="end"
+ android:orientation="horizontal" />
+
+ </FrameLayout>
+
+ <com.android.systemui.statusbar.policy.DeadZone
+ android:id="@+id/deadzone"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:layout_gravity="top"
+ systemui:minSize="@dimen/navigation_bar_deadzone_size"
+ systemui:maxSize="@dimen/navigation_bar_deadzone_size_max"
+ systemui:holdTime="@integer/navigation_bar_deadzone_hold"
+ systemui:decayTime="@integer/navigation_bar_deadzone_decay"
+ systemui:orientation="horizontal"
+ />
+
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/navigation_layout_rot90.xml b/packages/SystemUI/res/layout/navigation_layout_rot90.xml
new file mode 100644
index 0000000..46df973
--- /dev/null
+++ b/packages/SystemUI/res/layout/navigation_layout_rot90.xml
@@ -0,0 +1,91 @@
+<?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.
+-->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <FrameLayout
+ android:id="@+id/nav_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <com.android.systemui.statusbar.phone.ReverseLinearLayout
+ android:id="@+id/start_group"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="bottom"
+ android:orientation="vertical" />
+
+ <com.android.systemui.statusbar.phone.ReverseLinearLayout
+ android:id="@+id/center_group"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="vertical" />
+
+ <com.android.systemui.statusbar.phone.ReverseLinearLayout
+ android:id="@+id/end_group"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="top"
+ android:orientation="vertical" />
+
+ </FrameLayout>
+
+ <FrameLayout
+ android:id="@+id/lights_out"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <com.android.systemui.statusbar.phone.ReverseLinearLayout
+ android:id="@+id/start_group_lightsout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="bottom"
+ android:orientation="vertical" />
+
+ <com.android.systemui.statusbar.phone.ReverseLinearLayout
+ android:id="@+id/center_group_lightsout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="vertical" />
+
+ <com.android.systemui.statusbar.phone.ReverseLinearLayout
+ android:id="@+id/end_group_lightsout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="top"
+ android:orientation="vertical" />
+
+ </FrameLayout>
+
+ <com.android.systemui.statusbar.policy.DeadZone
+ android:id="@+id/deadzone"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:layout_gravity="top"
+ systemui:minSize="@dimen/navigation_bar_deadzone_size"
+ systemui:maxSize="@dimen/navigation_bar_deadzone_size_max"
+ systemui:holdTime="@integer/navigation_bar_deadzone_hold"
+ systemui:decayTime="@integer/navigation_bar_deadzone_decay"
+ systemui:orientation="vertical"
+ />
+
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/notification_guts.xml b/packages/SystemUI/res/layout/notification_guts.xml
index 03451b4..e550d9c 100644
--- a/packages/SystemUI/res/layout/notification_guts.xml
+++ b/packages/SystemUI/res/layout/notification_guts.xml
@@ -99,7 +99,8 @@
android:src="@*android:drawable/ic_notification_block"
android:layout_gravity="center_vertical|start"
android:layout_width="24dp"
- android:layout_height="24dp" />
+ android:layout_height="24dp"
+ android:tint="@color/notification_guts_icon_tint"/>
<SeekBar
android:id="@+id/seekbar"
@@ -121,7 +122,8 @@
android:src="@*android:drawable/ic_notification_alert"
android:layout_gravity="center_vertical|end"
android:layout_width="24dp"
- android:layout_height="24dp"/>
+ android:layout_height="24dp"
+ android:tint="@color/notification_guts_icon_tint" />
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/notification_public_default.xml b/packages/SystemUI/res/layout/notification_public_default.xml
deleted file mode 100644
index 044ba09..0000000
--- a/packages/SystemUI/res/layout/notification_public_default.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2014 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/status_bar_latest_event_content"
- android:layout_width="match_parent"
- android:layout_height="64dp"
- >
- <ImageView android:id="@+id/icon"
- android:layout_width="40dp"
- android:layout_height="40dp"
- android:layout_marginTop="12dp"
- android:layout_marginStart="12dp"
- android:layout_marginEnd="12dp"
- android:scaleType="centerInside"
- />
- <DateTimeView android:id="@+id/time"
- android:textAppearance="@android:style/TextAppearance.Material.Notification.Time"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginEnd="8dp"
- android:layout_alignParentEnd="true"
- android:layout_alignBaseline="@+id/title"
- android:singleLine="true"
- android:gravity="center"
- android:paddingStart="8dp"
- android:visibility="gone"
- />
- <TextView android:id="@+id/title"
- android:textAppearance="@android:style/TextAppearance.Material.Notification.Title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_toEndOf="@id/icon"
- android:layout_toStartOf="@id/time"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal"
- />
- <ImageView android:id="@+id/profile_badge_line3"
- android:layout_width="@*android:dimen/notification_badge_size"
- android:layout_height="@*android:dimen/notification_badge_size"
- android:layout_below="@id/title"
- android:layout_marginStart="4dp"
- android:layout_marginEnd="8dp"
- android:layout_alignParentEnd="true"
- android:scaleType="fitCenter"
- android:visibility="gone"
- />
- <TextView android:id="@+id/text"
- android:textAppearance="@android:style/TextAppearance.Material.Notification"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignStart="@id/title"
- android:layout_below="@id/title"
- android:layout_toStartOf="@id/profile_badge_line3"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal"
- />
-</RelativeLayout>
diff --git a/packages/SystemUI/res/layout/qs_customize_layout.xml b/packages/SystemUI/res/layout/qs_customize_layout.xml
index 91cf8948..0b8e02f 100644
--- a/packages/SystemUI/res/layout/qs_customize_layout.xml
+++ b/packages/SystemUI/res/layout/qs_customize_layout.xml
@@ -21,14 +21,6 @@
android:layout_height="wrap_content"
android:orientation="vertical">
- <com.android.systemui.qs.QuickTileLayout
- android:id="@+id/quick_tile_layout"
- android:layout_width="match_parent"
- android:layout_height="@dimen/qs_quick_actions_height"
- android:orientation="horizontal"
- android:paddingStart="@dimen/qs_quick_actions_padding"
- android:paddingEnd="@dimen/qs_quick_actions_padding" />
-
<view
class="com.android.systemui.qs.PagedTileLayout$TilePage"
android:id="@+id/tile_page"
diff --git a/packages/SystemUI/res/layout/recent_apps.xml b/packages/SystemUI/res/layout/recent_apps.xml
new file mode 100644
index 0000000..eb8ee43c
--- /dev/null
+++ b/packages/SystemUI/res/layout/recent_apps.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+
+<com.android.systemui.statusbar.policy.KeyButtonView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/recent_apps"
+ android:layout_width="@dimen/navigation_key_width"
+ android:layout_height="match_parent"
+ android:layout_weight="0"
+ android:src="@drawable/ic_sysbar_recent"
+ android:scaleType="center"
+ android:contentDescription="@string/accessibility_recent"
+ android:paddingStart="@dimen/navigation_key_padding"
+ android:paddingEnd="@dimen/navigation_key_padding"
+ />
+
diff --git a/packages/SystemUI/res/layout/recents_task_view_header.xml b/packages/SystemUI/res/layout/recents_task_view_header.xml
index 07ac39a..5c67f80 100644
--- a/packages/SystemUI/res/layout/recents_task_view_header.xml
+++ b/packages/SystemUI/res/layout/recents_task_view_header.xml
@@ -23,12 +23,10 @@
<com.android.systemui.recents.views.FixedSizeImageView
android:id="@+id/icon"
android:contentDescription="@string/recents_app_info_button_label"
- android:layout_width="@dimen/recents_task_view_application_icon_size"
- android:layout_height="@dimen/recents_task_view_application_icon_size"
- android:layout_marginStart="8dp"
+ android:layout_width="@dimen/recents_task_view_header_icon_width"
+ android:layout_height="@dimen/recents_task_view_header_icon_height"
android:layout_gravity="center_vertical|start"
- android:padding="8dp"
- android:background="@drawable/recents_button_bg" />
+ android:padding="9dp" />
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
@@ -46,30 +44,36 @@
android:fadingEdge="horizontal" />
<com.android.systemui.recents.views.FixedSizeImageView
android:id="@+id/move_task"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:layout_marginEnd="52dp"
+ android:layout_width="@dimen/recents_task_view_header_button_width"
+ android:layout_height="@dimen/recents_task_view_header_button_height"
+ android:layout_marginEnd="@dimen/recents_task_view_header_button_width"
android:layout_gravity="center_vertical|end"
- android:padding="12dp"
+ android:padding="15dp"
android:background="@drawable/recents_button_bg"
android:src="@drawable/star"
android:visibility="gone" />
<com.android.systemui.recents.views.FixedSizeImageView
android:id="@+id/dismiss_task"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:layout_marginEnd="4dp"
+ android:layout_width="@dimen/recents_task_view_header_button_width"
+ android:layout_height="@dimen/recents_task_view_header_button_height"
android:layout_gravity="center_vertical|end"
- android:padding="12dp"
+ android:padding="15dp"
android:background="@drawable/recents_button_bg"
android:visibility="invisible"
android:src="@drawable/recents_dismiss_light" />
- <ProgressBar
- android:id="@+id/focus_timer_indicator"
- style="?android:attr/progressBarStyleHorizontal"
- android:layout_width="match_parent"
- android:layout_height="5dp"
- android:layout_gravity="bottom"
- android:indeterminateOnly="false"
- android:visibility="invisible" />
+
+ <!-- The progress indicator shows if auto-paging is enabled -->
+ <ViewStub android:id="@+id/focus_timer_indicator_stub"
+ android:inflatedId="@+id/focus_timer_indicator"
+ android:layout="@layout/recents_task_view_header_progress_bar"
+ android:layout_width="match_parent"
+ android:layout_height="5dp"
+ android:layout_gravity="bottom" />
+
+ <!-- The app overlay shows as the user long-presses on the app icon -->
+ <ViewStub android:id="@+id/app_overlay_stub"
+ android:inflatedId="@+id/app_overlay"
+ android:layout="@layout/recents_task_view_header_overlay"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
</com.android.systemui.recents.views.TaskViewHeader>
diff --git a/packages/SystemUI/res/layout/recents_task_view_header_overlay.xml b/packages/SystemUI/res/layout/recents_task_view_header_overlay.xml
new file mode 100644
index 0000000..dabfc80
--- /dev/null
+++ b/packages/SystemUI/res/layout/recents_task_view_header_overlay.xml
@@ -0,0 +1,50 @@
+<?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.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <com.android.systemui.recents.views.FixedSizeImageView
+ android:id="@+id/app_icon"
+ android:contentDescription="@string/recents_app_info_button_label"
+ android:layout_width="@dimen/recents_task_view_header_icon_width"
+ android:layout_height="@dimen/recents_task_view_header_icon_height"
+ android:layout_gravity="center_vertical|start"
+ android:padding="9dp" />
+ <TextView
+ android:id="@+id/app_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="64dp"
+ android:layout_marginEnd="112dp"
+ android:textSize="16sp"
+ android:textColor="#ffffffff"
+ android:text="@string/recents_empty_message"
+ android:fontFamily="sans-serif-medium"
+ android:singleLine="true"
+ android:maxLines="2"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal" />
+ <com.android.systemui.recents.views.FixedSizeImageView
+ android:id="@+id/app_info"
+ android:layout_width="@dimen/recents_task_view_header_button_width"
+ android:layout_height="@dimen/recents_task_bar_height"
+ android:layout_gravity="center_vertical|end"
+ android:padding="15dp"
+ android:background="@drawable/recents_button_bg"
+ android:src="@drawable/recents_info_light" />
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/recents_task_view_header_progress_bar.xml b/packages/SystemUI/res/layout/recents_task_view_header_progress_bar.xml
new file mode 100644
index 0000000..f352632
--- /dev/null
+++ b/packages/SystemUI/res/layout/recents_task_view_header_progress_bar.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<ProgressBar
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:indeterminateOnly="false"
+ android:visibility="invisible" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/signal_cluster_view.xml b/packages/SystemUI/res/layout/signal_cluster_view.xml
index 198e658..c634cd6 100644
--- a/packages/SystemUI/res/layout/signal_cluster_view.xml
+++ b/packages/SystemUI/res/layout/signal_cluster_view.xml
@@ -77,7 +77,7 @@
</FrameLayout>
<View
android:id="@+id/wifi_signal_spacer"
- android:layout_width="4dp"
+ android:layout_width="@dimen/status_bar_wifi_signal_spacer_width"
android:layout_height="4dp"
android:visibility="gone"
/>
@@ -112,7 +112,7 @@
</FrameLayout>
<View
android:id="@+id/wifi_airplane_spacer"
- android:layout_width="4dp"
+ android:layout_width="@dimen/status_bar_airplane_spacer_width"
android:layout_height="4dp"
android:visibility="gone"
/>
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index bea9f78..39c16d7 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -70,7 +70,8 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:singleLine="true"
- android:paddingStart="7dp"
+ android:paddingStart="@dimen/status_bar_clock_starting_padding"
+ android:paddingEnd="@dimen/status_bar_clock_end_padding"
android:gravity="center_vertical|start"
/>
</com.android.keyguard.AlphaOptimizedLinearLayout>
diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml
index 39da8d0..4c80b48 100644
--- a/packages/SystemUI/res/layout/super_status_bar.xml
+++ b/packages/SystemUI/res/layout/super_status_bar.xml
@@ -80,7 +80,7 @@
</FrameLayout>
<ViewStub android:id="@+id/fullscreen_user_switcher_stub"
- android:layout="@layout/fullscreen_user_switcher"
+ android:layout="@layout/car_fullscreen_user_switcher"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
diff --git a/packages/SystemUI/res/layout/system_icons.xml b/packages/SystemUI/res/layout/system_icons.xml
index 943e8462..e9448db 100644
--- a/packages/SystemUI/res/layout/system_icons.xml
+++ b/packages/SystemUI/res/layout/system_icons.xml
@@ -30,11 +30,11 @@
android:id="@+id/signal_cluster"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginStart="2.5dp"/>
+ android:layout_marginStart="@dimen/signal_cluster_margin_start"/>
<!-- battery must be padded below to match assets -->
<com.android.systemui.BatteryMeterView android:id="@+id/battery"
- android:layout_height="14.5dp"
- android:layout_width="9.5dp"
+ android:layout_height="@dimen/status_bar_battery_icon_height"
+ android:layout_width="@dimen/status_bar_battery_icon_width"
android:layout_marginBottom="@dimen/battery_margin_bottom"/>
</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml
index 43e7bac..e0affa1 100644
--- a/packages/SystemUI/res/values-land/config.xml
+++ b/packages/SystemUI/res/values-land/config.xml
@@ -38,4 +38,6 @@
while the stack is not focused. -->
<item name="recents_layout_unfocused_range_min" format="float" type="integer">-2</item>
<item name="recents_layout_unfocused_range_max" format="float" type="integer">1.5</item>
+
+ <integer name="quick_settings_num_columns">4</integer>
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml
index f9b01c8..7e8d802 100644
--- a/packages/SystemUI/res/values-sw600dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/config.xml
@@ -18,4 +18,6 @@
<!-- The maximum count of notifications on Keyguard. The rest will be collapsed in an overflow
card. -->
<integer name="keyguard_max_notification_count">3</integer>
+
+ <integer name="quick_settings_num_columns">3</integer>
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index f084bc2..be5b856 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -34,4 +34,7 @@
In sw600dp we want the buttons centered so this fills the space,
(screen_pinning_request_width - 3 * screen_pinning_request_button_width) / 2 -->
<dimen name="screen_pinning_request_side_width">2dp</dimen>
+
+ <dimen name="navigation_key_width">162dp</dimen>
+ <dimen name="navigation_key_padding">42dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index 6795da4..db4da10 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -35,11 +35,15 @@
<!-- Recents: The relative range of visible tasks from the current scroll position
while the stack is focused. -->
- <item name="recents_layout_focused_range_min" format="float" type="integer">-4</item>
+ <item name="recents_layout_focused_range_min" format="float" type="integer">-3</item>
<item name="recents_layout_focused_range_max" format="float" type="integer">3</item>
<!-- Recents: The relative range of visible tasks from the current scroll position
while the stack is not focused. -->
<item name="recents_layout_unfocused_range_min" format="float" type="integer">-2</item>
<item name="recents_layout_unfocused_range_max" format="float" type="integer">2.5</item>
+
+ <!-- Nav bar button default ordering/layout -->
+ <string name="config_navBarLayout" translatable="false">space;back,home,recent;menu_ime</string>
+
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 49dbac2..71f92fd 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -90,4 +90,7 @@
<dimen name="screen_pinning_request_side_width">8dp</dimen>
<dimen name="fab_margin">24dp</dimen>
+
+ <dimen name="navigation_key_width">128dp</dimen>
+ <dimen name="navigation_key_padding">25dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index a80a5de..b2190ec 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -75,13 +75,13 @@
<color name="notification_legacy_background_color">#ff1a1a1a</color>
<!-- The color of the material notification background -->
- <color name="notification_material_background_color">#fffafafa</color>
+ <color name="notification_material_background_color">#ffffffff</color>
<!-- The color of the material notification background when dimmed -->
- <color name="notification_material_background_dimmed_color">#d4ffffff</color>
+ <color name="notification_material_background_dimmed_color">#f2ffffff</color>
<!-- The color of the material notification background when low priority -->
- <color name="notification_material_background_low_priority_color">#ffe0e0e0</color>
+ <color name="notification_material_background_low_priority_color">#fff5f5f5</color>
<!-- The color of the material notification background for media notifications when no custom
color is specified -->
@@ -100,9 +100,11 @@
<color name="current_user_border_color">@color/system_accent_color</color>
<!-- The "inside" of a notification, reached via longpress -->
- <color name="notification_guts_bg_color">@*android:color/material_grey_50</color>
+ <color name="notification_guts_bg_color">#eeeeee</color>
<color name="notification_guts_slider_color">@*android:color/material_deep_teal_500</color>
<color name="notification_guts_secondary_slider_color">#858383</color>
+ <color name="notification_guts_icon_tint">#8a000000</color>
+ <color name="notification_guts_disabled_icon_tint">#4d000000</color>
<color name="assist_orb_color">#ffffff</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index e98ec82..aedc2c5 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -147,6 +147,9 @@
<!-- The duration for animating the task decorations in after transitioning from an app. -->
<integer name="recents_task_enter_from_app_duration">200</integer>
+ <!-- The duration for animating the task decorations in after transitioning from an app. -->
+ <integer name="recents_task_enter_from_affiliated_app_duration">125</integer>
+
<!-- The duration for animating the task decorations out before transitioning to an app. -->
<integer name="recents_task_exit_to_app_duration">125</integer>
@@ -194,7 +197,7 @@
<!-- Recents: The relative range of visible tasks from the current scroll position
while the stack is focused. -->
- <item name="recents_layout_focused_range_min" format="float" type="integer">-4</item>
+ <item name="recents_layout_focused_range_min" format="float" type="integer">-3</item>
<item name="recents_layout_focused_range_max" format="float" type="integer">3</item>
<!-- Recents: The relative range of visible tasks from the current scroll position
@@ -285,5 +288,8 @@
<!-- SystemUIFactory component -->
<string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIFactory</string>
+ <!-- Nav bar button default ordering/layout -->
+ <string name="config_navBarLayout" translatable="false">space,back;home;recent,menu_ime</string>
+
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 3fb5f18..46a0f2a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -33,9 +33,30 @@
<!-- Height of notification icons in the status bar -->
<dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen>
- <!-- The font size for the clock -->
+ <!-- Height of the battery icon in the status bar. -->
+ <dimen name="status_bar_battery_icon_height">14.5dp</dimen>
+
+ <!-- Width of the battery icon in the status bar. -->
+ <dimen name="status_bar_battery_icon_width">9.5dp</dimen>
+
+ <!-- The font size for the clock in the status bar. -->
<dimen name="status_bar_clock_size">14sp</dimen>
+ <!-- The starting padding for the clock in the status bar. -->
+ <dimen name="status_bar_clock_starting_padding">7dp</dimen>
+
+ <!-- The end padding for the clock in the status bar. -->
+ <dimen name="status_bar_clock_end_padding">0dp</dimen>
+
+ <!-- Spacing after the wifi signals that is present if there are any icons following it. -->
+ <dimen name="status_bar_wifi_signal_spacer_width">4dp</dimen>
+
+ <!-- Spacing before the airplane mode icon if there are any icons preceding it. -->
+ <dimen name="status_bar_airplane_spacer_width">4dp</dimen>
+
+ <!-- The amount to scale each of the status bar icons by. A value of 1 means no scaling. -->
+ <item name="status_bar_icon_scale_factor" format="float" type="dimen">1.0</item>
+
<!-- Height of a small notification in the status bar-->
<dimen name="notification_min_height">84dp</dimen>
@@ -75,6 +96,11 @@
<!-- The width of the view containing navigation buttons -->
<dimen name="navigation_key_width">70dp</dimen>
+ <dimen name="navigation_key_padding">0dp</dimen>
+
+ <dimen name="navigation_key_width_sw600dp_land">162dp</dimen>
+ <dimen name="navigation_key_padding_sw600dp_land">42dp</dimen>
+
<!-- The width of the view containing the menu/ime navigation bar icons -->
<dimen name="navigation_extra_key_width">36dp</dimen>
@@ -134,9 +160,7 @@
<dimen name="pull_span_min">25dp</dimen>
<dimen name="qs_tile_height">88dp</dimen>
- <dimen name="qs_new_tile_height">100dp</dimen>
- <dimen name="qs_quick_actions_height">88dp</dimen>
- <dimen name="qs_quick_actions_padding">25dp</dimen>
+ <dimen name="qs_tile_margin">16dp</dimen>
<dimen name="qs_quick_tile_size">48dp</dimen>
<dimen name="qs_quick_tile_padding">12dp</dimen>
<dimen name="qs_date_anim_translation">44.5dp</dimen>
@@ -193,8 +217,13 @@
<!-- Default distance from each snap target that GlowPadView considers a "hit" -->
<dimen name="glowpadview_inner_radius">15dip</dimen>
- <!-- The size of the application icon in the recents task view. -->
- <dimen name="recents_task_view_application_icon_size">48dp</dimen>
+ <!-- The size of the icon in the recents task view header. -->
+ <dimen name="recents_task_view_header_icon_width">64dp</dimen>
+ <dimen name="recents_task_view_header_icon_height">@dimen/recents_task_bar_height</dimen>
+
+ <!-- The size of a button in the recents task view header. -->
+ <dimen name="recents_task_view_header_button_width">@dimen/recents_task_bar_height</dimen>
+ <dimen name="recents_task_view_header_button_height">@dimen/recents_task_bar_height</dimen>
<!-- The radius of the rounded corners on a task view. -->
<dimen name="recents_task_view_rounded_corners_radius">2dp</dimen>
@@ -212,7 +241,7 @@
<dimen name="recents_task_view_highlight">1dp</dimen>
<!-- The amount to offset when animating into an affiliate group. -->
- <dimen name="recents_task_view_affiliate_group_enter_offset">64dp</dimen>
+ <dimen name="recents_task_view_affiliate_group_enter_offset">32dp</dimen>
<!-- The height of a task view bar. -->
<dimen name="recents_task_bar_height">56dp</dimen>
@@ -518,6 +547,9 @@
<dimen name="fake_shadow_size">8dp</dimen>
+ <!-- Starting margin before the signal cluster -->
+ <dimen name="signal_cluster_margin_start">2.5dp</dimen>
+
<!-- Padding between signal cluster and battery icon -->
<dimen name="signal_cluster_battery_padding">7dp</dimen>
diff --git a/packages/SystemUI/res/values/dimens_car.xml b/packages/SystemUI/res/values/dimens_car.xml
new file mode 100644
index 0000000..ecdccee
--- /dev/null
+++ b/packages/SystemUI/res/values/dimens_car.xml
@@ -0,0 +1,32 @@
+<?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.
+*/
+-->
+<resources>
+ <dimen name="car_lockscreen_disclaimer_title_size">48sp</dimen>
+ <dimen name="car_lockscreen_disclaimer_title_padding_start">96dp</dimen>
+ <dimen name="car_lockscreen_disclaimer_title_padding_top">96dp</dimen>
+ <dimen name="car_lockscreen_disclaimer_text_size">28sp</dimen>
+ <dimen name="car_lockscreen_disclaimer_text_padding_start">96dp</dimen>
+ <dimen name="car_lockscreen_disclaimer_text_padding_end">96dp</dimen>
+ <dimen name="car_lockscreen_disclaimer_text_padding_top">32dp</dimen>
+ <dimen name="car_lockscreen_user_grid_view_padding_start">10dp</dimen>
+ <dimen name="car_lockscreen_user_grid_view_padding_end">10dp</dimen>
+ <dimen name="car_lockscreen_user_grid_view_padding_top">128dp</dimen>
+ <dimen name="car_fullscreen_user_pod_image_avatar_width">128dp</dimen>
+ <dimen name="car_fullscreen_user_pod_image_avatar_height">128dp</dimen>
+ <dimen name="car_fullscreen_user_pod_text_size">24sp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 13128b7..0bd350b 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -38,8 +38,8 @@
<item type="id" name="qs_icon_tag"/>
<item type="id" name="scrim"/>
<item type="id" name="scrim_target"/>
- <item type="id" name="hun_scrim_alpha_start"/>
- <item type="id" name="hun_scrim_alpha_end"/>
+ <item type="id" name="scrim_alpha_start"/>
+ <item type="id" name="scrim_alpha_end"/>
<item type="id" name="notification_power"/>
<item type="id" name="notification_screenshot"/>
<item type="id" name="notification_hidden"/>
@@ -50,6 +50,11 @@
<!-- For notification icons for which targetSdk < L, this caches whether the icon is grayscale -->
<item type="id" name="icon_is_grayscale" />
+ <item type="id" name="clip_children_tag" />
+ <item type="id" name="clip_children_set_tag" />
+ <item type="id" name="clip_to_padding_tag" />
+ <item type="id" name="image_icon_tag" />
+ <item type="id" name="contains_transformed_view" />
<item type="id" name="is_clicked_heads_up_tag" />
</resources>
diff --git a/packages/SystemUI/res/values/strings_car.xml b/packages/SystemUI/res/values/strings_car.xml
new file mode 100644
index 0000000..882773a
--- /dev/null
+++ b/packages/SystemUI/res/values/strings_car.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.
+ */
+-->
+<resources>
+ <string name="car_lockscreen_disclaimer_title">Drive safely</string>
+ <string name="car_lockscreen_disclaimer_text">
+ Stay fully aware of driving conditions and always obey applicable laws. Directions may be
+ inaccurate, incomplete, dangerous, not suitable, prohibited, or involve crossing
+ administrative areas. Business information may also be inaccurate or incomplete. Data is
+ not real-time, and location accuracy cannot be guaranteed. Do not handle your mobile device
+ or use apps not intended for Android Auto while driving.
+ </string>
+
+</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index d28da41..fd4161f 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -367,6 +367,7 @@
}
anim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
+ updateSwipeProgressFromOffset(animView, canAnimViewBeDismissed);
mCallback.onChildDismissed(view);
if (endAction != null) {
endAction.run();
@@ -381,9 +382,17 @@
updateSwipeProgressFromOffset(animView, canAnimViewBeDismissed);
}
});
+ prepareDismissAnimation(animView, anim);
anim.start();
}
+ /**
+ * Called to update the dismiss animation.
+ */
+ protected void prepareDismissAnimation(View view, Animator anim) {
+ // Do nothing
+ }
+
public void snapChild(final View view, float velocity) {
final View animView = mCallback.getChildContentView(view);
final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(animView);
@@ -401,14 +410,14 @@
mCallback.onChildSnappedBack(animView);
}
});
- updateSnapBackAnimation(anim);
+ prepareSnapBackAnimation(animView, anim);
anim.start();
}
/**
* Called to update the snap back animation.
*/
- protected void updateSnapBackAnimation(Animator anim) {
+ protected void prepareSnapBackAnimation(View view, Animator anim) {
// Do nothing
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 9a00b4b..704de97 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -24,6 +24,7 @@
import android.app.SearchManager;
import android.app.StatusBarManager;
import android.app.trust.TrustManager;
+import android.auditing.SecurityLog;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -1352,6 +1353,11 @@
* @see #KEYGUARD_DONE
*/
private void handleKeyguardDone(boolean authenticated) {
+ if (SecurityLog.isLoggingEnabled()
+ && mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())) {
+ SecurityLog.writeEvent(SecurityLog.TAG_DEVICE_UNLOCK_ATTEMPT,
+ (authenticated ? 1 : 0), "Unknown");
+ }
if (DEBUG) Log.d(TAG, "handleKeyguardDone");
synchronized (this) {
resetKeyguardDonePendingLocked();
@@ -1463,6 +1469,10 @@
* @see #SHOW
*/
private void handleShow(Bundle options) {
+ if (SecurityLog.isLoggingEnabled()
+ && mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())) {
+ SecurityLog.writeEvent(SecurityLog.TAG_DEVICE_LOCKED, "");
+ }
synchronized (KeyguardViewMediator.this) {
if (!mSystemReady) {
if (DEBUG) Log.d(TAG, "ignoring handleShow because system is not ready.");
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 28ddf060..6bc8b50 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -62,27 +62,32 @@
@Override
public int getOffsetTop(TileRecord tile) {
- return ((ViewGroup) tile.tileView.getParent()).getTop();
+ return ((ViewGroup) tile.tileView.getParent()).getTop() + getTop();
}
@Override
public void addTile(TileRecord tile) {
mTiles.add(tile);
- distributeTiles();
+ postDistributeTiles();
}
@Override
public void removeTile(TileRecord tile) {
if (mTiles.remove(tile)) {
- distributeTiles();
+ postDistributeTiles();
}
}
+ private void postDistributeTiles() {
+ removeCallbacks(mDistribute);
+ post(mDistribute);
+ }
+
private void distributeTiles() {
if (DEBUG) Log.d(TAG, "Distributing tiles");
final int NP = mPages.size();
for (int i = 0; i < NP; i++) {
- mPages.get(i).clear();
+ mPages.get(i).removeAllViews();
}
int index = 0;
final int NT = mTiles.size();
@@ -107,10 +112,15 @@
}
@Override
- public void updateResources() {
+ public boolean updateResources() {
+ boolean changed = false;
for (int i = 0; i < mPages.size(); i++) {
- mPages.get(i).updateResources();
+ changed |= mPages.get(i).updateResources();
}
+ if (changed) {
+ distributeTiles();
+ }
+ return changed;
}
@Override
@@ -129,6 +139,13 @@
setMeasuredDimension(getMeasuredWidth(), maxHeight + mPageIndicator.getMeasuredHeight());
}
+ private final Runnable mDistribute = new Runnable() {
+ @Override
+ public void run() {
+ distributeTiles();
+ }
+ };
+
public static class TilePage extends TileLayout {
private int mMaxRows = 3;
@@ -137,21 +154,19 @@
updateResources();
}
+ @Override
+ public boolean updateResources() {
+ if (super.updateResources()) {
+ mMaxRows = mColumns != 3 ? 2 : 3;
+ return true;
+ }
+ return false;
+ }
+
public void setMaxRows(int maxRows) {
mMaxRows = maxRows;
}
- @Override
- protected int getCellHeight() {
- return mContext.getResources().getDimensionPixelSize(R.dimen.qs_new_tile_height);
- }
-
- private void clear() {
- if (DEBUG) Log.d(TAG, "Clearing page");
- removeAllViews();
- mRecords.clear();
- }
-
public boolean isFull() {
return mRecords.size() >= mColumns * mMaxRows;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index fd07e50..b5f146b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -557,8 +557,6 @@
public static final class TileRecord extends Record {
public QSTile<?> tile;
public QSTileBaseView tileView;
- public int row;
- public int col;
public boolean scanState;
public boolean openingDetail;
}
@@ -607,6 +605,6 @@
void addTile(TileRecord tile);
void removeTile(TileRecord tile);
int getOffsetTop(TileRecord tile);
- void updateResources();
+ boolean updateResources();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 56364e9..72629a3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -129,6 +129,10 @@
mHandler.obtainMessage(H.ADD_CALLBACK, callback).sendToTarget();
}
+ public void removeCallbacks() {
+ mHandler.sendEmptyMessage(H.REMOVE_CALLBACKS);
+ }
+
public void click() {
mHandler.sendEmptyMessage(H.CLICK);
}
@@ -188,6 +192,10 @@
handleRefreshState(null);
}
+ private void handleRemoveCallbacks() {
+ mCallbacks.clear();
+ }
+
protected void handleSecondaryClick() {
// Default to normal click.
handleClick();
@@ -285,6 +293,7 @@
private static final int SCAN_STATE_CHANGED = 9;
private static final int DESTROY = 10;
private static final int CLEAR_STATE = 11;
+ private static final int REMOVE_CALLBACKS = 12;
private H(Looper looper) {
super(looper);
@@ -296,7 +305,10 @@
try {
if (msg.what == ADD_CALLBACK) {
name = "handleAddCallback";
- handleAddCallback((QSTile.Callback)msg.obj);
+ handleAddCallback((QSTile.Callback) msg.obj);
+ } else if (msg.what == REMOVE_CALLBACKS) {
+ name = "handleRemoveCallbacks";
+ handleRemoveCallbacks();
} else if (msg.what == CLICK) {
name = "handleClick";
if (mState.disabledByPolicy) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 5782800..b391c1e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -145,8 +145,9 @@
}
@Override
- public void updateResources() {
+ public boolean updateResources() {
// No resources here.
+ return false;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index ff11177..59a394f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -5,7 +5,6 @@
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
-
import com.android.systemui.R;
import com.android.systemui.qs.QSPanel.QSTileLayout;
import com.android.systemui.qs.QSPanel.TileRecord;
@@ -21,6 +20,7 @@
protected int mColumns;
private int mCellWidth;
private int mCellHeight;
+ private int mCellMargin;
protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
@@ -54,52 +54,33 @@
super.removeAllViews();
}
- public void updateResources() {
+ public boolean updateResources() {
final Resources res = mContext.getResources();
final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns));
- mCellHeight = getCellHeight();
- mCellWidth = (int) (mCellHeight * TILE_ASPECT);
+ mCellHeight = mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_height);
+ mCellMargin = res.getDimensionPixelSize(R.dimen.qs_tile_margin);
if (mColumns != columns) {
mColumns = columns;
postInvalidate();
+ return true;
}
- }
-
- protected int getCellHeight() {
- return mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_height);
+ return false;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int numTiles = mRecords.size();
final int width = MeasureSpec.getSize(widthMeasureSpec);
- int r = -1;
- int c = -1;
- int rows = 0;
- for (TileRecord record : mRecords) {
- if (record.tileView.getVisibility() == GONE) continue;
- // wrap to next column if we've reached the max # of columns
- // also don't allow dual + single tiles on the same row
- if (r == -1 || c == (mColumns - 1)) {
- r++;
- c = 0;
- } else {
- c++;
- }
- record.row = r;
- record.col = c;
- rows = r + 1;
- }
+ final int rows = (numTiles + mColumns - 1) / mColumns;
+ mCellWidth = (width - (mCellMargin * (mColumns + 1))) / mColumns;
View previousView = this;
for (TileRecord record : mRecords) {
if (record.tileView.getVisibility() == GONE) continue;
- final int cw = mCellWidth;
- final int ch = mCellHeight;
- record.tileView.measure(exactly(cw), exactly(ch));
+ record.tileView.measure(exactly(mCellWidth), exactly(mCellHeight));
previousView = record.tileView.updateAccessibilityOrder(previousView);
}
- int h = rows == 0 ? 0 : getRowTop(rows);
- setMeasuredDimension(width, h);
+ setMeasuredDimension(width, (mCellHeight + mCellMargin) * rows + mCellMargin);
}
private static int exactly(int size) {
@@ -110,37 +91,32 @@
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int w = getWidth();
boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
- for (TileRecord record : mRecords) {
- if (record.tileView.getVisibility() == GONE) continue;
- final int cols = getColumnCount(record.row);
- final int cw = mCellWidth;
- final int extra = (w - cw * cols) / (cols + 1);
- int left = record.col * cw + (record.col + 1) * extra;
- final int top = getRowTop(record.row);
+ int row = 0;
+ int column = 0;
+ for (int i = 0; i < mRecords.size(); i++, column++) {
+ if (column == mColumns) {
+ row++;
+ column -= mColumns;
+ }
+ TileRecord record = mRecords.get(i);
+ int left = getColumnStart(column);
+ final int top = getRowTop(row);
int right;
- int tileWith = record.tileView.getMeasuredWidth();
if (isRtl) {
right = w - left;
- left = right - tileWith;
+ left = right - mCellWidth;
} else {
- right = left + tileWith;
+ right = left + mCellWidth;
}
record.tileView.layout(left, top, right, top + record.tileView.getMeasuredHeight());
}
}
private int getRowTop(int row) {
- if (row <= 0) return 0;
- return row * mCellHeight;
+ return row * (mCellHeight + mCellMargin) + mCellMargin;
}
- private int getColumnCount(int row) {
- int cols = 0;
- for (TileRecord record : mRecords) {
- if (record.tileView.getVisibility() == GONE) continue;
- if (record.row == row) cols++;
- }
- return cols;
+ private int getColumnStart(int column) {
+ return column * (mCellWidth + mCellMargin) + mCellMargin;
}
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
index 3acbed8..8f0d194 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
@@ -31,7 +31,6 @@
import com.android.systemui.qs.QSPanel.QSTileLayout;
import com.android.systemui.qs.QSPanel.TileRecord;
import com.android.systemui.qs.QSTile;
-import com.android.systemui.qs.QuickTileLayout;
import java.util.ArrayList;
@@ -42,7 +41,6 @@
*/
public class NonPagedTileLayout extends LinearLayout implements QSTileLayout, OnTouchListener {
- private QuickTileLayout mQuickTiles;
private final ArrayList<TilePage> mPages = new ArrayList<>();
private final ArrayList<TileRecord> mTiles = new ArrayList<TileRecord>();
private CustomQSPanel mPanel;
@@ -58,8 +56,6 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mQuickTiles = (QuickTileLayout) findViewById(R.id.quick_tile_layout);
- mQuickTiles.setVisibility(View.GONE);
TilePage page = (PagedTileLayout.TilePage) findViewById(R.id.tile_page);
page.setMaxRows(3 /* First page only gets 3 */);
mPages.add(page);
@@ -95,7 +91,6 @@
}
private void distributeTiles() {
- mQuickTiles.removeAllViews();
final int NP = mPages.size();
for (int i = 0; i < NP; i++) {
mPages.get(i).removeAllViews();
@@ -124,7 +119,8 @@
}
@Override
- public void updateResources() {
+ public boolean updateResources() {
+ return false;
}
@Override
@@ -133,24 +129,20 @@
case DragEvent.ACTION_DRAG_LOCATION:
float x = event.getX();
float y = event.getY();
- if (contains(mQuickTiles, x, y)) {
- // TODO: Reset to pre-drag state.
- } else {
- final int NP = mPages.size();
- for (int i = 0; i < NP; i++) {
- TilePage page = mPages.get(i);
- if (contains(page, x, y)) {
- x -= page.getLeft();
- y -= page.getTop();
- final int NC = page.getChildCount();
- for (int j = 0; j < NC; j++) {
- View child = page.getChildAt(j);
- if (contains(child, x, y)) {
- mPanel.tileSelected((QSTile<?>) child.getTag(), mCurrentClip);
- }
+ final int NP = mPages.size();
+ for (int i = 0; i < NP; i++) {
+ TilePage page = mPages.get(i);
+ if (contains(page, x, y)) {
+ x -= page.getLeft();
+ y -= page.getTop();
+ final int NC = page.getChildCount();
+ for (int j = 0; j < NC; j++) {
+ View child = page.getChildAt(j);
+ if (contains(child, x, y)) {
+ mPanel.tileSelected((QSTile<?>) child.getTag(), mCurrentClip);
}
- break;
}
+ break;
}
}
break;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 4a7d67f..cc4ce70 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -45,7 +45,6 @@
import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.phone.QSTileHost;
import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.tuner.QSPagingSwitch;
import java.util.ArrayList;
@@ -145,7 +144,8 @@
private void reset() {
ArrayList<String> tiles = new ArrayList<>();
- for (String tile : QSPagingSwitch.QS_PAGE_TILES.split(",")) {
+ String defTiles = mContext.getString(R.string.quick_settings_tiles_default);
+ for (String tile : defTiles.split(",")) {
tiles.add(tile);
}
mQsPanel.setTiles(tiles);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index a6a7143..6e22dde 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -37,13 +37,11 @@
import android.widget.GridLayout;
import android.widget.ImageView;
import android.widget.TextView;
-
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.QSTile.Icon;
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.statusbar.phone.QSTileHost;
-import com.android.systemui.tuner.QSPagingSwitch;
import java.util.ArrayList;
import java.util.Collection;
@@ -74,8 +72,9 @@
}
mCurrentTiles = tileSpecs;
final TileGroup group = new TileGroup("com.android.settings", mContext);
- // TODO: Pull this list from a more authoritative place.
- String[] possibleTiles = QSPagingSwitch.QS_PAGE_TILES.split(",");
+ String possible = mContext.getString(R.string.quick_settings_tiles_default)
+ + ",user,hotspot,inversion";
+ String[] possibleTiles = possible.split(",");
for (int i = 0; i < possibleTiles.length; i++) {
final String spec = possibleTiles[i];
if (spec.startsWith("q")) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index db55f28..3a3b19d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -183,7 +183,7 @@
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
if (!plan.hasTasks()) {
- loader.preloadTasks(plan, launchState.launchedFromHome);
+ loader.preloadTasks(plan, -1, launchState.launchedFromHome);
}
RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
loadOpts.runningTaskId = launchState.launchedToTaskId;
@@ -192,24 +192,14 @@
loader.loadTasks(this, plan, loadOpts);
TaskStack stack = plan.getTaskStack();
- ArrayList<Task> tasks = stack.getStackTasks();
- int taskCount = stack.getTaskCount();
mRecentsView.setTaskStack(stack);
- // Mark the task that is the launch target
- int launchTaskIndexInStack = 0;
- if (launchState.launchedToTaskId != -1) {
- for (int j = 0; j < taskCount; j++) {
- Task t = tasks.get(j);
- if (t.key.id == launchState.launchedToTaskId) {
- t.isLaunchTarget = true;
- launchTaskIndexInStack = tasks.size() - j - 1;
- break;
- }
- }
- }
-
// Animate the SystemUI scrims into view
+ Task launchTarget = stack.getLaunchTarget();
+ int taskCount = stack.getTaskCount();
+ int launchTaskIndexInStack = launchTarget != null
+ ? stack.indexOfStackTask(launchTarget)
+ : 0;
boolean hasStatusBarScrim = taskCount > 0;
boolean animateStatusBarScrim = launchState.launchedFromHome;
boolean hasNavBarScrim = (taskCount > 0) && !config.hasTransposedNavBar;
@@ -527,7 +517,7 @@
launchOpts.loadThumbnails = false;
launchOpts.onlyLoadForCache = true;
RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(this);
- loader.preloadTasks(loadPlan, false);
+ loader.preloadTasks(loadPlan, -1, false);
loader.loadTasks(this, loadPlan, launchOpts);
EventBus.getDefault().send(new TaskStackUpdatedEvent(loadPlan.getTaskStack()));
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
index 3151fd7..881aa6a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
@@ -37,6 +37,8 @@
public static final boolean EnableSearchBar = false;
// This disables the bitmap and icon caches
public static final boolean DisableBackgroundCache = false;
+ // Enables the task affiliations
+ public static final boolean EnableAffiliatedTaskGroups = true;
// Enables the simulated task affiliations
public static final boolean EnableSimulatedTaskGroups = false;
// Defines the number of mock task affiliations per group
@@ -75,7 +77,7 @@
* @return whether we are enabling the fast toggle indicator.
*/
public boolean isFastToggleIndicatorEnabled() {
- return mFastToggleIndicator;
+ return mFastToggleRecents && mFastToggleIndicator;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 7c25d24..f8cbf65 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -118,7 +118,7 @@
// Load the next task only if we aren't svelte
RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
- loader.preloadTasks(plan, true);
+ loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
// This callback is made when a new activity is launched and the old one is paused
// so ignore the current activity and try and preload the thumbnail for the
@@ -198,7 +198,7 @@
// We can use a new plan since the caches will be the same.
RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
- loader.preloadTasks(plan, true /* isTopTaskHome */);
+ loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
launchOpts.numVisibleTasks = loader.getIconCacheSize();
launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
@@ -370,7 +370,7 @@
sInstanceLoadPlan = loader.createLoadPlan(mContext);
if (topTask != null && !ssp.isRecentsTopMost(topTask, topTaskHome)) {
sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
- loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value);
+ loader.preloadTasks(sInstanceLoadPlan, topTask.id, topTaskHome.value);
TaskStack stack = sInstanceLoadPlan.getTaskStack();
if (stack.getTaskCount() > 0) {
// We try and draw the thumbnail transition bitmap in parallel before
@@ -399,7 +399,7 @@
SystemServicesProxy ssp = Recents.getSystemServices();
RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
- loader.preloadTasks(plan, true /* isTopTaskHome */);
+ loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
TaskStack focusedStack = plan.getTaskStack();
// Return early if there are no tasks in the focused stack
@@ -451,7 +451,7 @@
SystemServicesProxy ssp = Recents.getSystemServices();
RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
- loader.preloadTasks(plan, true /* isTopTaskHome */);
+ loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
TaskStack focusedStack = plan.getTaskStack();
// Return early if there are no tasks in the focused stack
@@ -651,7 +651,7 @@
mDummyStackView.updateLayoutForStack(stack);
final Task toTask = new Task();
final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
- topTask.id, toTask);
+ toTask);
ForegroundThread.getHandler().postAtFrontOfQueue(new Runnable() {
@Override
public void run() {
@@ -721,7 +721,7 @@
// Update the destination rect
Task toTask = new Task();
TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
- topTask.id, toTask);
+ toTask);
RectF toTaskRect = toTransform.rect;
Bitmap thumbnail = getThumbnailBitmap(topTask, toTask, toTransform);
if (thumbnail != null) {
@@ -754,31 +754,20 @@
* Returns the transition rect for the given task id.
*/
private TaskViewTransform getThumbnailTransitionTransform(TaskStack stack,
- TaskStackView stackView, int runningTaskId, Task runningTaskOut) {
+ TaskStackView stackView, Task runningTaskOut) {
// Find the running task in the TaskStack
- Task task = null;
- ArrayList<Task> tasks = stack.getStackTasks();
- if (runningTaskId != -1) {
- // Otherwise, try and find the task with the
- int taskCount = tasks.size();
- for (int i = taskCount - 1; i >= 0; i--) {
- Task t = tasks.get(i);
- if (t.key.id == runningTaskId) {
- task = t;
- runningTaskOut.copyFrom(t);
- break;
- }
- }
- }
- if (task == null) {
+ Task launchTask = stack.getLaunchTarget();
+ if (launchTask != null) {
+ runningTaskOut.copyFrom(launchTask);
+ } else {
// If no task is specified or we can not find the task just use the front most one
- task = tasks.get(tasks.size() - 1);
- runningTaskOut.copyFrom(task);
+ launchTask = stack.getStackFrontMostTask(true /* includeFreeform */);
+ runningTaskOut.copyFrom(launchTask);
}
// Get the transform for the running task
stackView.getScroller().setStackScrollToInitialState();
- mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task,
+ mTmpTransform = stackView.getStackAlgorithm().getStackTransform(launchTask,
stackView.getScroller().getStackScroll(), mTmpTransform, null);
return mTmpTransform;
}
@@ -826,7 +815,7 @@
sInstanceLoadPlan = loader.createLoadPlan(mContext);
}
if (mReloadTasks || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
- loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
+ loader.preloadTasks(sInstanceLoadPlan, topTask.id, isTopTaskHome);
}
TaskStack stack = sInstanceLoadPlan.getTaskStack();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
index 80597bc..f6655c7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
@@ -36,6 +36,7 @@
import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.TaskViewAnimation;
import java.util.ArrayList;
import java.util.Calendar;
@@ -143,7 +144,6 @@
private Context mContext;
private LayoutInflater mLayoutInflater;
- private final List<Task> mTasks = new ArrayList<>();
private final List<Row> mRows = new ArrayList<>();
private final SparseIntArray mTaskRowCount = new SparseIntArray();
private TaskStack mStack;
@@ -268,8 +268,8 @@
public void onTaskRemoved(Task task, int position) {
// Since this is removed from the history, we need to update the stack as well to ensure
- // that the model is correct
- mStack.removeTask(task);
+ // that the model is correct. Since the stack is hidden, we can update it immediately.
+ mStack.removeTask(task, TaskViewAnimation.IMMEDIATE);
removeTaskRow(position);
if (mRows.isEmpty()) {
dismissHistory();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/RectFEvaluator.java b/packages/SystemUI/src/com/android/systemui/recents/misc/RectFEvaluator.java
new file mode 100644
index 0000000..72511de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/RectFEvaluator.java
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+package com.android.systemui.recents.misc;
+
+import android.animation.TypeEvaluator;
+import android.graphics.RectF;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>RectF</code> values.
+ */
+public class RectFEvaluator implements TypeEvaluator<RectF> {
+
+ private RectF mRect = new RectF();
+
+ /**
+ * This function returns the result of linearly interpolating the start and
+ * end Rect values, with <code>fraction</code> representing the proportion
+ * between the start and end values. The calculation is a simple parametric
+ * calculation on each of the separate components in the Rect objects
+ * (left, top, right, and bottom).
+ *
+ * <p>The object returned will be the <code>reuseRect</code> passed into the constructor.</p>
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue The start Rect
+ * @param endValue The end Rect
+ * @return A linear interpolation between the start and end values, given the
+ * <code>fraction</code> parameter.
+ */
+ @Override
+ public RectF evaluate(float fraction, RectF startValue, RectF endValue) {
+ float left = startValue.left + ((endValue.left - startValue.left) * fraction);
+ float top = startValue.top + ((endValue.top - startValue.top) * fraction);
+ float right = startValue.right + ((endValue.right - startValue.right) * fraction);
+ float bottom = startValue.bottom + ((endValue.bottom - startValue.bottom) * fraction);
+ mRect.set(left, top, right, bottom);
+ return mRect;
+ }
+}
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 adc1e92..3f52ae8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -30,6 +30,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -554,39 +555,41 @@
}
}
- /** Returns the activity label */
- public String getActivityLabel(ActivityInfo info) {
+ /**
+ * Returns the activity label, badging if necessary.
+ */
+ public String getBadgedActivityLabel(ActivityInfo info, int userId) {
if (mPm == null) return null;
// If we are mocking, then return a mock label
if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
- return "Recent Task";
+ return "Recent Task: " + userId;
}
- return info.loadLabel(mPm).toString();
+ return getBadgedLabel(info.loadLabel(mPm).toString(), userId);
}
- /** Returns the application label */
- public String getApplicationLabel(Intent baseIntent, int userId) {
+ /**
+ * Returns the application label, badging if necessary.
+ */
+ public String getBadgedApplicationLabel(ApplicationInfo appInfo, int userId) {
if (mPm == null) return null;
// If we are mocking, then return a mock label
if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
- return "Recent Task";
+ return "Recent Task App: " + userId;
}
- ResolveInfo ri = mPm.resolveActivityAsUser(baseIntent, 0, userId);
- CharSequence label = (ri != null) ? ri.loadLabel(mPm) : null;
- return (label != null) ? label.toString() : null;
+ return getBadgedLabel(appInfo.loadLabel(mPm).toString(), userId);
}
- /** Returns the content description for a given task */
- public String getContentDescription(Intent baseIntent, int userId, String activityLabel,
- Resources res) {
- String applicationLabel = getApplicationLabel(baseIntent, userId);
- if (applicationLabel == null) {
- return getBadgedLabel(activityLabel, userId);
- }
+ /**
+ * Returns the content description for a given task, badging it if necessary. The content
+ * description joins the app and activity labels.
+ */
+ public String getBadgedContentDescription(ActivityInfo info, int userId, Resources res) {
+ String activityLabel = info.loadLabel(mPm).toString();
+ String applicationLabel = info.applicationInfo.loadLabel(mPm).toString();
String badgedApplicationLabel = getBadgedLabel(applicationLabel, userId);
return applicationLabel.equals(activityLabel) ? badgedApplicationLabel
: res.getString(R.string.accessibility_recents_task_header,
@@ -610,6 +613,22 @@
}
/**
+ * Returns the application icon for the ApplicationInfo for a user, badging if
+ * necessary.
+ */
+ public Drawable getBadgedApplicationIcon(ApplicationInfo appInfo, int userId) {
+ if (mPm == null) return null;
+
+ // If we are mocking, then return a mock label
+ if (RecentsDebugFlags.Static.EnableSystemServicesProxy) {
+ return new ColorDrawable(0xFF666666);
+ }
+
+ Drawable icon = appInfo.loadIcon(mPm);
+ return getBadgedIcon(icon, userId);
+ }
+
+ /**
* Returns the task description icon, loading and badging it if it necessary.
*/
public Drawable getBadgedTaskDescriptionIcon(ActivityManager.TaskDescription taskDescription,
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 822ad77..9cdd703 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -25,7 +25,8 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArraySet;
-
+import android.util.SparseArray;
+import android.util.SparseIntArray;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
@@ -92,7 +93,7 @@
}
/**
- * An optimization to preload the raw list of tasks. The raw tasks are saved in least-recent
+ * An optimization to preload the raw list of tasks. The raw tasks are saved in least-recent
* to most-recent order.
*/
public synchronized void preloadRawTasks(boolean isTopTaskHome) {
@@ -107,7 +108,7 @@
}
/**
- * Preloads the list of recent tasks from the system. After this call, the TaskStack will
+ * Preloads the list of recent tasks from the system. After this call, the TaskStack will
* have a list of all the recent tasks with their metadata, not including icons or
* thumbnails which were not cached and have to be loaded.
*
@@ -115,13 +116,16 @@
* - least-recent to most-recent stack tasks
* - least-recent to most-recent freeform tasks
*/
- public synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) {
+ public synchronized void preloadPlan(RecentsTaskLoader loader, int topTaskId,
+ boolean isTopTaskHome) {
Resources res = mContext.getResources();
ArrayList<Task> allTasks = new ArrayList<>();
if (mRawTasks == null) {
preloadRawTasks(isTopTaskHome);
}
+ SparseArray<Task.TaskKey> affiliatedTasks = new SparseArray<>();
+ SparseIntArray affiliatedTaskCounts = new SparseIntArray();
String dismissDescFormat = mContext.getString(
R.string.accessibility_recents_item_will_be_dismissed);
long lastStackActiveTime = Prefs.getLong(mContext,
@@ -131,6 +135,17 @@
for (int i = 0; i < taskCount; i++) {
ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
+ // Affiliated tasks are returned in a specific order from ActivityManager but without a
+ // lastActiveTime since it hasn't yet been started. However, we later sort the task list
+ // by lastActiveTime, which rearranges the tasks. For now, we need to workaround this
+ // by updating the lastActiveTime of this task to the lastActiveTime of the task it is
+ // affiliated with, in the same order that we encounter it in the original list (just
+ // its index in the task group for the task it is affiliated with).
+ if (t.persistentId != t.affiliatedTaskId) {
+ t.lastActiveTime = affiliatedTasks.get(t.affiliatedTaskId).lastActiveTime +
+ affiliatedTaskCounts.get(t.affiliatedTaskId, 0) + 1;
+ }
+
// Compose the task key
Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.stackId, t.baseIntent,
t.userId, t.firstActiveTime, t.lastActiveTime);
@@ -140,13 +155,14 @@
boolean isFreeformTask = SystemServicesProxy.isFreeformStack(t.stackId);
boolean isStackTask = isFreeformTask || (!isHistoricalTask(t) ||
(t.lastActiveTime >= lastStackActiveTime && i >= (taskCount - MIN_NUM_TASKS)));
+ boolean isLaunchTarget = taskKey.id == topTaskId;
if (isStackTask && newLastStackActiveTime < 0) {
newLastStackActiveTime = t.lastActiveTime;
}
// Load the title, icon, and color
String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription);
- String contentDescription = loader.getAndUpdateContentDescription(taskKey, title, res);
+ String contentDescription = loader.getAndUpdateContentDescription(taskKey, res);
String dismissDescription = String.format(dismissDescFormat, contentDescription);
Drawable icon = isStackTask
? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false)
@@ -157,9 +173,11 @@
// Add the task to the stack
Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, icon,
thumbnail, title, contentDescription, dismissDescription, activityColor,
- !isStackTask, t.bounds, t.taskDescription);
+ !isStackTask, isLaunchTarget, t.bounds, t.taskDescription);
allTasks.add(task);
+ affiliatedTaskCounts.put(taskKey.id, affiliatedTaskCounts.get(taskKey.id, 0) + 1);
+ affiliatedTasks.put(taskKey.id, taskKey);
}
if (newLastStackActiveTime != -1) {
Prefs.putLong(mContext, Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME,
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 28338d83..44ad239 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -314,8 +314,8 @@
}
/** Preloads recents tasks using the specified plan to store the output. */
- public void preloadTasks(RecentsTaskLoadPlan plan, boolean isTopTaskHome) {
- plan.preloadPlan(this, isTopTaskHome);
+ public void preloadTasks(RecentsTaskLoadPlan plan, int topTaskId, boolean isTopTaskHome) {
+ plan.preloadPlan(this, topTaskId, isTopTaskHome);
}
/** Begins loading the heavy task data according to the specified options. */
@@ -436,7 +436,7 @@
// All short paths failed, load the label from the activity info and cache it
ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
if (activityInfo != null) {
- label = ssp.getActivityLabel(activityInfo);
+ label = ssp.getBadgedActivityLabel(activityInfo, taskKey.userId);
mActivityLabelCache.put(taskKey, label);
return label;
}
@@ -449,8 +449,7 @@
* Returns the cached task content description if the task key is not expired, updating the
* cache if it is.
*/
- String getAndUpdateContentDescription(Task.TaskKey taskKey, String activityLabel,
- Resources res) {
+ String getAndUpdateContentDescription(Task.TaskKey taskKey, Resources res) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Return the cached content description if it exists
@@ -458,13 +457,11 @@
if (label != null) {
return label;
}
- // If the given activity label is empty, don't compute or cache the content description
- if (activityLabel.isEmpty()) {
- return "";
- }
- label = ssp.getContentDescription(taskKey.baseIntent, taskKey.userId, activityLabel, res);
- if (label != null) {
+ // All short paths failed, load the label from the activity info and cache it
+ ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
+ if (activityInfo != null) {
+ label = ssp.getBadgedContentDescription(activityInfo, taskKey.userId, res);
mContentDescriptionCache.put(taskKey, label);
return label;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 29e7077..193bd17 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -154,7 +154,8 @@
public Task(TaskKey key, int affiliationTaskId, int affiliationColor, Drawable icon,
Bitmap thumbnail, String title, String contentDescription,
String dismissDescription, int colorPrimary, boolean isHistorical,
- Rect bounds, ActivityManager.TaskDescription taskDescription) {
+ boolean isLaunchTarget, Rect bounds,
+ ActivityManager.TaskDescription taskDescription) {
boolean isInAffiliationGroup = (affiliationTaskId != key.id);
boolean hasAffiliationGroupColor = isInAffiliationGroup && (affiliationColor != 0);
this.key = key;
@@ -170,6 +171,7 @@
Color.WHITE) > 3f;
this.bounds = bounds;
this.taskDescription = taskDescription;
+ this.isLaunchTarget = isLaunchTarget;
this.isHistorical = isHistorical;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 327cdf8..de1daa8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -42,6 +42,7 @@
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.views.DropTarget;
+import com.android.systemui.recents.views.TaskViewAnimation;
import java.util.ArrayList;
import java.util.Collections;
@@ -226,12 +227,12 @@
* Notifies when a task has been removed from the stack.
*/
void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
- Task newFrontMostTask);
+ Task newFrontMostTask, TaskViewAnimation animation);
/**
* Notifies when a task has been removed from the history.
*/
- void onHistoryTaskRemoved(TaskStack stack, Task removedTask);
+ void onHistoryTaskRemoved(TaskStack stack, Task removedTask, TaskViewAnimation animation);
}
/**
@@ -520,21 +521,24 @@
}
}
- /** Removes a task */
- public void removeTask(Task t) {
+ /**
+ * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
+ * how they should update themselves.
+ */
+ public void removeTask(Task t, TaskViewAnimation animation) {
if (mStackTaskList.contains(t)) {
- boolean wasFrontMostTask = (getStackFrontMostTask() == t);
+ boolean wasFrontMostTask = (getStackFrontMostTask(false /* includeFreeform */) == t);
removeTaskImpl(mStackTaskList, t);
- Task newFrontMostTask = getStackFrontMostTask();
+ Task newFrontMostTask = getStackFrontMostTask(false /* includeFreeform */);
if (mCb != null) {
// Notify that a task has been removed
- mCb.onStackTaskRemoved(this, t, wasFrontMostTask, newFrontMostTask);
+ mCb.onStackTaskRemoved(this, t, wasFrontMostTask, newFrontMostTask, animation);
}
} else if (mHistoryTaskList.contains(t)) {
removeTaskImpl(mHistoryTaskList, t);
if (mCb != null) {
// Notify that a task has been removed
- mCb.onHistoryTaskRemoved(this, t);
+ mCb.onHistoryTaskRemoved(this, t, animation);
}
}
mRawTaskList.remove(t);
@@ -564,7 +568,8 @@
Task task = mRawTaskList.get(i);
if (!newTasksMap.containsKey(task.key)) {
if (notifyStackChanges) {
- mCb.onStackTaskRemoved(this, task, i == (taskCount - 1), null);
+ mCb.onStackTaskRemoved(this, task, i == (taskCount - 1), null,
+ TaskViewAnimation.IMMEDIATE);
}
}
task.setGroup(null);
@@ -611,14 +616,14 @@
/**
* Gets the front-most task in the stack.
*/
- public Task getStackFrontMostTask() {
+ public Task getStackFrontMostTask(boolean includeFreeformTasks) {
ArrayList<Task> stackTasks = mStackTaskList.getTasks();
if (stackTasks.isEmpty()) {
return null;
}
for (int i = stackTasks.size() - 1; i >= 0; i--) {
Task task = stackTasks.get(i);
- if (!task.isFreeformTask()) {
+ if (!task.isFreeformTask() || includeFreeformTasks) {
return task;
}
}
@@ -843,12 +848,17 @@
for (int i = 0; i < taskCount; i++) {
Task t = tasks.get(i);
TaskGrouping group;
- int affiliation = t.affiliationTaskId > 0 ? t.affiliationTaskId :
- IndividualTaskIdOffset + t.key.id;
- if (mAffinitiesGroups.containsKey(affiliation)) {
- group = getGroupWithAffiliation(affiliation);
+ if (RecentsDebugFlags.Static.EnableAffiliatedTaskGroups) {
+ int affiliation = t.affiliationTaskId > 0 ? t.affiliationTaskId :
+ IndividualTaskIdOffset + t.key.id;
+ if (mAffinitiesGroups.containsKey(affiliation)) {
+ group = getGroupWithAffiliation(affiliation);
+ } else {
+ group = new TaskGrouping(affiliation);
+ addGroup(group);
+ }
} else {
- group = new TaskGrouping(affiliation);
+ group = new TaskGrouping(t.key.id);
addGroup(group);
}
group.addTask(t);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index e727652..e448101 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -513,7 +513,7 @@
taskViewRect.right, taskViewRect.bottom);
// Remove the task view after it is docked
- mTaskStackView.updateLayout(false /* boundScroll */);
+ mTaskStackView.updateLayoutAlgorithm(false /* boundScroll */);
stackLayout.getStackTransform(event.task, stackScroller.getStackScroll(), tmpTransform,
null);
tmpTransform.alpha = 0;
@@ -529,11 +529,14 @@
ssp.startTaskInDockedMode(getContext(), event.taskView,
event.task.key.id, dockState.createMode);
- mTaskStackView.getStack().removeTask(event.task);
+ // Animate the stack accordingly
+ TaskViewAnimation stackAnim = new TaskViewAnimation(
+ TaskStackView.DEFAULT_SYNC_STACK_DURATION,
+ mFastOutSlowInInterpolator);
+ mTaskStackView.getStack().removeTask(event.task, stackAnim);
}
}));
-
MetricsLogger.action(mContext,
MetricsLogger.ACTION_WINDOW_DOCK_DRAG_DROP);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
index 2930f4d..ccbb329 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
@@ -130,6 +130,7 @@
// Move the task view slightly lower so we can animate it in
RectF bounds = new RectF(mTmpTransform.rect);
bounds.offset(0, taskViewAffiliateGroupEnterOffset);
+ tv.setClipViewInStack(false);
tv.setAlpha(0f);
tv.setLeftTopRightBottom((int) bounds.left, (int) bounds.top,
(int) bounds.right, (int) bounds.bottom);
@@ -165,6 +166,8 @@
int taskViewEnterFromAppDuration = res.getInteger(
R.integer.recents_task_enter_from_app_duration);
+ int taskViewEnterFromAffiliatedAppDuration = res.getInteger(
+ R.integer.recents_task_enter_from_affiliated_app_duration);
int taskViewEnterFromHomeDuration = res.getInteger(
R.integer.recents_task_enter_from_home_duration);
int taskViewEnterFromHomeStaggerDelay = res.getInteger(
@@ -174,7 +177,7 @@
List<TaskView> taskViews = mStackView.getTaskViews();
int taskViewCount = taskViews.size();
for (int i = taskViewCount - 1; i >= 0; i--) {
- TaskView tv = taskViews.get(i);
+ final TaskView tv = taskViews.get(i);
Task task = tv.getTask();
boolean currentTaskOccludesLaunchTarget = false;
if (launchTargetTask != null) {
@@ -195,8 +198,14 @@
// Animate the task up if it was occluding the launch target
if (currentTaskOccludesLaunchTarget) {
TaskViewAnimation taskAnimation = new TaskViewAnimation(
- taskViewEnterFromAppDuration, PhoneStatusBar.ALPHA_IN,
- postAnimationTrigger.decrementOnAnimationEnd());
+ taskViewEnterFromAffiliatedAppDuration, PhoneStatusBar.ALPHA_IN,
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ postAnimationTrigger.decrement();
+ tv.setClipViewInStack(false);
+ }
+ });
postAnimationTrigger.increment();
mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
}
@@ -286,7 +295,7 @@
} else if (currentTaskOccludesLaunchTarget) {
// Animate this task out of view
TaskViewAnimation taskAnimation = new TaskViewAnimation(
- taskViewExitToAppDuration, mFastOutLinearInInterpolator,
+ taskViewExitToAppDuration, PhoneStatusBar.ALPHA_OUT,
postAnimationTrigger.decrementOnAnimationEnd());
postAnimationTrigger.increment();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index 68ff63c..93849dc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -110,6 +110,10 @@
public static final float STATE_FOCUSED = 1f;
public static final float STATE_UNFOCUSED = 0f;
+ public interface TaskStackLayoutAlgorithmCallbacks {
+ void onFocusStateChanged(float prevFocusState, float curFocusState);
+ }
+
/**
* The various stack/freeform states.
*/
@@ -210,6 +214,7 @@
Context mContext;
private Interpolator mLinearOutSlowInInterpolator;
private StackState mState = StackState.SPLIT;
+ private TaskStackLayoutAlgorithmCallbacks mCb;
// The task bounds (untransformed) for layout. This rect is anchored at mTaskRoot.
public Rect mTaskRect = new Rect();
@@ -279,8 +284,10 @@
TaskViewTransform mBackOfStackTransform = new TaskViewTransform();
TaskViewTransform mFrontOfStackTransform = new TaskViewTransform();
- public TaskStackLayoutAlgorithm(Context context) {
+ public TaskStackLayoutAlgorithm(Context context, TaskStackLayoutAlgorithmCallbacks cb) {
Resources res = context.getResources();
+ mContext = context;
+ mCb = cb;
mFocusedRange = new Range(res.getFloat(R.integer.recents_layout_focused_range_min),
res.getFloat(R.integer.recents_layout_focused_range_max));
@@ -291,7 +298,6 @@
mMinTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_min);
mMaxTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_max);
- mContext = context;
mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm(context);
mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
com.android.internal.R.interpolator.linear_out_slow_in);
@@ -315,8 +321,12 @@
* Sets the focused state.
*/
public void setFocusState(float focusState) {
+ float prevFocusState = mFocusState;
mFocusState = focusState;
updateFrontBackTransforms();
+ if (mCb != null) {
+ mCb.onFocusStateChanged(prevFocusState, focusState);
+ }
}
/**
@@ -373,7 +383,7 @@
* Computes the minimum and maximum scroll progress values and the progress values for each task
* in the stack.
*/
- void update(TaskStack stack, ArraySet<Task> ignoreTasksSet) {
+ void update(TaskStack stack, ArraySet<Task.TaskKey> ignoreTasksSet) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Clear the progress map
@@ -393,7 +403,7 @@
ArrayList<Task> stackTasks = new ArrayList<>();
for (int i = 0; i < tasks.size(); i++) {
Task task = tasks.get(i);
- if (ignoreTasksSet.contains(task)) {
+ if (ignoreTasksSet.contains(task.key)) {
continue;
}
if (task.isFreeformTask()) {
@@ -434,19 +444,25 @@
mFreeformLayoutAlgorithm.update(freeformTasks, this);
mInitialScrollP = mMaxScrollP;
} else {
+ Task launchTask = stack.getLaunchTarget();
+ int launchTaskIndex = launchTask != null
+ ? stack.indexOfStackTask(launchTask)
+ : mNumStackTasks - 1;
if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
mInitialScrollP = mMinScrollP;
} else if (getDefaultFocusState() > 0f) {
RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
if (launchState.launchedFromHome) {
- mInitialScrollP = Math.max(mMinScrollP, mNumStackTasks - 1);
+ mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP, launchTaskIndex));
} else {
- mInitialScrollP = Math.max(mMinScrollP, mNumStackTasks - 2);
+ mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP,
+ launchTaskIndex - 1));
}
} else {
float offsetPct = (float) (mTaskRect.height() / 2) / mStackRect.height();
float normX = mUnfocusedCurveInterpolator.getX(offsetPct);
- mInitialScrollP = (mNumStackTasks - 1) - mUnfocusedRange.getAbsoluteX(normX);
+ mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP,
+ launchTaskIndex - mUnfocusedRange.getAbsoluteX(normX)));
}
}
}
@@ -553,7 +569,8 @@
boolean isFrontMostTaskInGroup = task.group == null || task.group.isFrontMostTask(task);
if (isFrontMostTaskInGroup) {
- getStackTransform(taskProgress, mInitialScrollP, tmpTransform, null);
+ getStackTransform(taskProgress, mInitialScrollP, tmpTransform, null,
+ false /* ignoreSingleTaskCase */);
float screenY = tmpTransform.rect.top;
boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
if (hasVisibleThumbnail) {
@@ -596,13 +613,21 @@
return transformOut;
}
return getStackTransform(mTaskIndexMap.get(task.key), stackScroll, transformOut,
- frontTransform);
+ frontTransform, false /* ignoreSingleTaskCase */);
}
}
- /** Update/get the transform */
+ /**
+ * Update/get the transform.
+ *
+ * @param ignoreSingleTaskCase When set, will ensure that the transform computed does not take
+ * into account the special single-task case. This is only used
+ * internally to ensure that we can calculate the transform for any
+ * position in the stack.
+ */
public TaskViewTransform getStackTransform(float taskProgress, float stackScroll,
- TaskViewTransform transformOut, TaskViewTransform frontTransform) {
+ TaskViewTransform transformOut, TaskViewTransform frontTransform,
+ boolean ignoreSingleTaskCase) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Compute the focused and unfocused offset
@@ -632,7 +657,7 @@
int y;
float z;
float relP;
- if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
+ if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1 && !ignoreSingleTaskCase) {
// When there is exactly one task, then decouple the task from the stack and just move
// in screen space
p = (mMinScrollP - stackScroll) / mNumStackTasks;
@@ -762,8 +787,8 @@
mFocusState * (mFocusedRange.relativeMin - mUnfocusedRange.relativeMin);
float max = mUnfocusedRange.relativeMax +
mFocusState * (mFocusedRange.relativeMax - mUnfocusedRange.relativeMax);
- getStackTransform(min, 0f, mBackOfStackTransform, null);
- getStackTransform(max, 0f, mFrontOfStackTransform, null);
+ getStackTransform(min, 0f, mBackOfStackTransform, null, true /* ignoreSingleTaskCase */);
+ getStackTransform(max, 0f, mFrontOfStackTransform, null, true /* ignoreSingleTaskCase */);
mBackOfStackTransform.visible = true;
mFrontOfStackTransform.visible = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 7583a19..fe9c68e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -30,6 +30,7 @@
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.MutableBoolean;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -77,7 +78,6 @@
import com.android.systemui.recents.model.TaskStack;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
@@ -88,6 +88,7 @@
/* The visual representation of a task stack view */
public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
TaskView.TaskViewCallbacks, TaskStackViewScroller.TaskStackViewScrollerCallbacks,
+ TaskStackLayoutAlgorithm.TaskStackLayoutAlgorithmCallbacks,
ViewPool.ViewPoolConsumer<TaskView, Task> {
private final static String KEY_SAVED_STATE_SUPER = "saved_instance_state_super";
@@ -100,10 +101,12 @@
private static final float SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD = 0.3f;
private static final float HIDE_HISTORY_BUTTON_SCROLL_THRESHOLD = 0.3f;
- private static final int DEFAULT_SYNC_STACK_DURATION = 200;
+ public static final int DEFAULT_SYNC_STACK_DURATION = 200;
private static final int DRAG_SCALE_DURATION = 175;
private static final float DRAG_SCALE_FACTOR = 1.05f;
+ private static final ArraySet<Task.TaskKey> EMPTY_TASK_SET = new ArraySet<>();
+
TaskStack mStack;
TaskStackLayoutAlgorithm mLayoutAlgorithm;
TaskStackViewScroller mStackScroller;
@@ -116,7 +119,8 @@
ArrayList<TaskView> mTaskViews = new ArrayList<>();
ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>();
- TaskViewAnimation mDeferredTaskViewUpdateAnimation = null;
+ ArraySet<Task.TaskKey> mIgnoreTasks = new ArraySet<>();
+ TaskViewAnimation mDeferredTaskViewLayoutAnimation = null;
DozeTrigger mUIDozeTrigger;
Task mFocusedTask;
@@ -137,7 +141,6 @@
int[] mTmpVisibleRange = new int[2];
Rect mTmpRect = new Rect();
ArrayMap<Task.TaskKey, TaskView> mTmpTaskViewMap = new ArrayMap<>();
- ArraySet<Task> mTmpTaskSet = new ArraySet<>();
List<TaskView> mTmpTaskViews = new ArrayList<>();
TaskViewTransform mTmpTransform = new TaskViewTransform();
LayoutInflater mInflater;
@@ -190,9 +193,8 @@
setStack(stack);
mViewPool = new ViewPool<>(context, this);
mInflater = LayoutInflater.from(context);
- mLayoutAlgorithm = new TaskStackLayoutAlgorithm(context);
- mStackScroller = new TaskStackViewScroller(context, mLayoutAlgorithm);
- mStackScroller.setCallbacks(this);
+ mLayoutAlgorithm = new TaskStackLayoutAlgorithm(context, this);
+ mStackScroller = new TaskStackViewScroller(context, this, mLayoutAlgorithm);
mTouchHandler = new TaskStackViewTouchHandler(context, this, mStackScroller);
mAnimationHelper = new TaskStackAnimationHelper(context, this);
mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
@@ -345,30 +347,82 @@
}
/**
- * Gets the stack transforms of a list of tasks, and returns the visible range of tasks.
- * This call ignores freeform tasks.
+ * Adds a task to the ignored set.
*/
- private boolean updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms,
- ArrayList<Task> tasks, float stackScroll,
- int[] visibleRangeOut, ArraySet<Task> ignoreTasksSet) {
- int taskTransformCount = taskTransforms.size();
+ void addIgnoreTask(Task task) {
+ mIgnoreTasks.add(task.key);
+ }
+
+ /**
+ * Removes a task from the ignored set.
+ */
+ void removeIgnoreTask(Task task) {
+ mIgnoreTasks.remove(task.key);
+ }
+
+ /**
+ * Returns whether the specified {@param task} is ignored.
+ */
+ boolean isIgnoredTask(Task task) {
+ return mIgnoreTasks.contains(task.key);
+ }
+
+ /**
+ * Computes the task transforms at the current stack scroll for all visible tasks. If a valid
+ * target stack scroll is provided (ie. is different than {@param curStackScroll}), then the
+ * visible range includes all tasks at the target stack scroll. This is useful for ensure that
+ * all views necessary for a transition or animation will be visible at the start.
+ *
+ * This call ignores freeform tasks.
+ *
+ * @param taskTransforms The set of task view transforms to reuse, this list will be sized to
+ * match the size of {@param tasks}
+ * @param tasks The set of tasks for which to generate transforms
+ * @param curStackScroll The current stack scroll
+ * @param targetStackScroll The stack scroll that we anticipate we are going to be scrolling to.
+ * The range of the union of the visible views at the current and
+ * target stack scrolls will be returned.
+ * @param ignoreTasksSet The set of tasks to skip for purposes of calculaing the visible range.
+ * Transforms will still be calculated for the ignore tasks.
+ */
+ boolean computeVisibleTaskTransforms(ArrayList<TaskViewTransform> taskTransforms,
+ ArrayList<Task> tasks, float curStackScroll, float targetStackScroll,
+ int[] visibleRangeOut, ArraySet<Task.TaskKey> ignoreTasksSet) {
int taskCount = tasks.size();
int frontMostVisibleIndex = -1;
int backMostVisibleIndex = -1;
+ boolean useTargetStackScroll = Float.compare(curStackScroll, targetStackScroll) != 0;
// We can reuse the task transforms where possible to reduce object allocation
Utilities.matchTaskListSize(tasks, taskTransforms);
// Update the stack transforms
TaskViewTransform frontTransform = null;
+ TaskViewTransform frontTransformAtTarget = null;
+ TaskViewTransform transform = null;
+ TaskViewTransform transformAtTarget = null;
for (int i = taskCount - 1; i >= 0; i--) {
Task task = tasks.get(i);
- if (ignoreTasksSet.contains(task)) {
- continue;
+
+ // Calculate the current and (if necessary) the target transform for the task
+ transform = mLayoutAlgorithm.getStackTransform(task, curStackScroll,
+ taskTransforms.get(i), frontTransform);
+ if (useTargetStackScroll && !transform.visible) {
+ // If we have a target stack scroll and the task is not currently visible, then we
+ // just update the transform at the new scroll
+ // TODO: Optimize this
+ transformAtTarget = mLayoutAlgorithm.getStackTransform(task,
+ targetStackScroll, new TaskViewTransform(), frontTransformAtTarget);
+ if (transformAtTarget.visible) {
+ transform.copyFrom(transformAtTarget);
+ }
}
- TaskViewTransform transform = mLayoutAlgorithm.getStackTransform(task, stackScroll,
- taskTransforms.get(i), frontTransform);
+ // For ignore tasks, only calculate the stack transform and skip the calculation of the
+ // visible stack indices
+ if (ignoreTasksSet.contains(task.key)) {
+ continue;
+ }
// For freeform tasks, only calculate the stack transform and skip the calculation of
// the visible stack indices
@@ -392,7 +446,9 @@
break;
}
}
+
frontTransform = transform;
+ frontTransformAtTarget = transformAtTarget;
}
if (visibleRangeOut != null) {
visibleRangeOut[0] = frontMostVisibleIndex;
@@ -402,33 +458,48 @@
}
/**
- * Updates the children {@link TaskView}s to match the tasks in the current {@link TaskStack}.
- * This call does not update the {@link TaskView}s to their position in the layout except when
- * they are initially picked up from the pool, when they will be placed in a suitable initial
- * position.
+ * Binds the visible {@link TaskView}s at the given target scroll.
+ *
+ * @see #bindVisibleTaskViews(float, ArraySet<Task.TaskKey>)
*/
- private void bindTaskViewsWithStack(ArraySet<Task> ignoreTasksSet) {
- final float stackScroll = mStackScroller.getStackScroll();
+ void bindVisibleTaskViews(float targetStackScroll) {
+ bindVisibleTaskViews(targetStackScroll, mIgnoreTasks);
+ }
+
+ /**
+ * Synchronizes the set of children {@link TaskView}s to match the visible set of tasks in the
+ * current {@link TaskStack}. This call does not continue on to update their position to the
+ * computed {@link TaskViewTransform}s of the visible range, but only ensures that they will
+ * be added/removed from the view hierarchy and placed in the correct Z order and initial
+ * position (if not currently on screen).
+ *
+ * @param targetStackScroll If provided, will ensure that the set of visible {@link TaskView}s
+ * includes those visible at the current stack scroll, and all at the
+ * target stack scroll.
+ * @param ignoreTasksSet The set of tasks to ignore in this rebinding of the visible
+ * {@link TaskView}s
+ */
+ void bindVisibleTaskViews(float targetStackScroll, ArraySet<Task.TaskKey> ignoreTasksSet) {
final int[] visibleStackRange = mTmpVisibleRange;
// Get all the task transforms
final ArrayList<Task> tasks = mStack.getStackTasks();
- final boolean isValidVisibleStackRange = updateStackTransforms(mCurrentTaskTransforms,
- tasks, stackScroll, visibleStackRange, ignoreTasksSet);
+ final boolean isValidVisibleRange = computeVisibleTaskTransforms(mCurrentTaskTransforms,
+ tasks, mStackScroller.getStackScroll(), targetStackScroll, visibleStackRange,
+ ignoreTasksSet);
// Return all the invisible children to the pool
- final List<TaskView> taskViews = getTaskViews();
- final int taskViewCount = taskViews.size();
- int lastFocusedTaskIndex = -1;
mTmpTaskViewMap.clear();
- mTmpTaskViewMap.ensureCapacity(tasks.size());
+ List<TaskView> taskViews = getTaskViews();
+ int lastFocusedTaskIndex = -1;
+ int taskViewCount = taskViews.size();
for (int i = taskViewCount - 1; i >= 0; i--) {
- final TaskView tv = taskViews.get(i);
- final Task task = tv.getTask();
- final int taskIndex = mStack.indexOfStackTask(task);
+ TaskView tv = taskViews.get(i);
+ Task task = tv.getTask();
+ int taskIndex = mStack.indexOfStackTask(task);
// Skip ignored tasks
- if (ignoreTasksSet.contains(task)) {
+ if (ignoreTasksSet.contains(task.key)) {
continue;
}
@@ -445,13 +516,13 @@
}
// Pick up all the newly visible children
- int lastVisStackIndex = isValidVisibleStackRange ? visibleStackRange[1] : 0;
- for (int i = mStack.getTaskCount() - 1; i >= lastVisStackIndex; i--) {
- final Task task = tasks.get(i);
- final TaskViewTransform transform = mCurrentTaskTransforms.get(i);
+ int lastVisStackIndex = isValidVisibleRange ? visibleStackRange[1] : 0;
+ for (int i = tasks.size() - 1; i >= lastVisStackIndex; i--) {
+ Task task = tasks.get(i);
+ TaskViewTransform transform = mCurrentTaskTransforms.get(i);
// Skip ignored tasks
- if (ignoreTasksSet.contains(task)) {
+ if (ignoreTasksSet.contains(task.key)) {
continue;
}
@@ -502,26 +573,34 @@
}
/**
- * Cancels any existing {@link TaskView} animations, and updates each {@link TaskView} to its
- * current position as defined by the {@link TaskStackLayoutAlgorithm}.
+ * Relayout the the visible {@link TaskView}s to their current transforms as specified by the
+ * {@link TaskStackLayoutAlgorithm} with the given {@param animation}. This call cancels any
+ * animations that are current running on those task views, and will ensure that the children
+ * {@link TaskView}s will match the set of visible tasks in the stack.
*
- * @param ignoreTasks the set of tasks to ignore in the relayout
+ * @see #relayoutTaskViews(TaskViewAnimation, ArraySet<Task.TaskKey>)
*/
- private void updateTaskViewsToLayout(TaskViewAnimation animation, Task... ignoreTasks) {
- // Keep track of the ignore tasks
- ArraySet<Task> ignoreTasksSet = mTmpTaskSet;
- ignoreTasksSet.clear();
- ignoreTasksSet.ensureCapacity(ignoreTasks.length);
- Collections.addAll(ignoreTasksSet, ignoreTasks);
+ void relayoutTaskViews(TaskViewAnimation animation) {
+ relayoutTaskViews(animation, mIgnoreTasks);
+ }
+ /**
+ * Relayout the the visible {@link TaskView}s to their current transforms as specified by the
+ * {@link TaskStackLayoutAlgorithm} with the given {@param animation}. This call cancels any
+ * animations that are current running on those task views, and will ensure that the children
+ * {@link TaskView}s will match the set of visible tasks in the stack.
+ *
+ * @param ignoreTasksSet the set of tasks to ignore in the relayout
+ */
+ void relayoutTaskViews(TaskViewAnimation animation, ArraySet<Task.TaskKey> ignoreTasksSet) {
// If we had a deferred animation, cancel that
- mDeferredTaskViewUpdateAnimation = null;
+ mDeferredTaskViewLayoutAnimation = null;
// Cancel all task view animations
cancelAllTaskViewAnimations();
- // Fetch the current set of TaskViews
- bindTaskViewsWithStack(ignoreTasksSet);
+ // Synchronize the current set of TaskViews
+ bindVisibleTaskViews(mStackScroller.getStackScroll(), ignoreTasksSet);
// Animate them to their final transforms with the given animation
List<TaskView> taskViews = getTaskViews();
@@ -531,7 +610,7 @@
final int taskIndex = mStack.indexOfStackTask(tv.getTask());
final TaskViewTransform transform = mCurrentTaskTransforms.get(taskIndex);
- if (ignoreTasksSet.contains(tv.getTask())) {
+ if (ignoreTasksSet.contains(tv.getTask().key)) {
continue;
}
@@ -542,9 +621,9 @@
/**
* Posts an update to synchronize the {@link TaskView}s with the stack on the next frame.
*/
- private void updateTaskViewsToLayoutOnNextFrame(TaskViewAnimation animation) {
- mDeferredTaskViewUpdateAnimation = animation;
- postInvalidateOnAnimation();
+ void relayoutTaskViewsOnNextFrame(TaskViewAnimation animation) {
+ mDeferredTaskViewLayoutAnimation = animation;
+ invalidate();
}
/**
@@ -558,13 +637,62 @@
}
/**
- * Cancels all {@link TaskView} animations.
+ * Returns the current task transforms of all tasks, falling back to the stack layout if there
+ * is no {@link TaskView} for the task.
*/
- private void cancelAllTaskViewAnimations() {
+ public void getCurrentTaskTransforms(ArrayList<Task> tasks,
+ ArrayList<TaskViewTransform> transformsOut) {
+ Utilities.matchTaskListSize(tasks, transformsOut);
+ for (int i = tasks.size() - 1; i >= 0; i--) {
+ Task task = tasks.get(i);
+ TaskViewTransform transform = transformsOut.get(i);
+ TaskView tv = getChildViewForTask(task);
+ if (tv != null) {
+ transform.fillIn(tv);
+ } else {
+ mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(),
+ transform, null);
+ }
+ transform.visible = true;
+ }
+ }
+
+ /**
+ * Returns the task transforms for all the tasks in the stack if the stack was at the given
+ * {@param stackScroll}.
+ */
+ public void getLayoutTaskTransforms(float stackScroll, ArrayList<Task> tasks,
+ ArrayList<TaskViewTransform> transformsOut) {
+ Utilities.matchTaskListSize(tasks, transformsOut);
+ for (int i = tasks.size() - 1; i >= 0; i--) {
+ Task task = tasks.get(i);
+ TaskViewTransform transform = transformsOut.get(i);
+ mLayoutAlgorithm.getStackTransform(task, stackScroll, transform, null);
+ transform.visible = true;
+ }
+ }
+
+ /**
+ * Cancels all {@link TaskView} animations.
+ *
+ * @see #cancelAllTaskViewAnimations(ArraySet<Task.TaskKey>)
+ */
+ void cancelAllTaskViewAnimations() {
+ cancelAllTaskViewAnimations(mIgnoreTasks);
+ }
+
+ /**
+ * Cancels all {@link TaskView} animations.
+ *
+ * @param ignoreTasksSet The set of tasks to continue running their animations.
+ */
+ void cancelAllTaskViewAnimations(ArraySet<Task.TaskKey> ignoreTasksSet) {
List<TaskView> taskViews = getTaskViews();
for (int i = taskViews.size() - 1; i >= 0; i--) {
final TaskView tv = taskViews.get(i);
- tv.cancelTransformAnimation();
+ if (!ignoreTasksSet.contains(tv.getTask().key)) {
+ tv.cancelTransformAnimation();
+ }
}
}
@@ -577,11 +705,22 @@
// Update the clip on each task child
List<TaskView> taskViews = getTaskViews();
TaskView tmpTv = null;
+ TaskView prevVisibleTv = null;
int taskViewCount = taskViews.size();
for (int i = 0; i < taskViewCount; i++) {
TaskView tv = taskViews.get(i);
TaskView frontTv = null;
int clipBottom = 0;
+
+ if (mIgnoreTasks.contains(tv.getTask().key)) {
+ // For each of the ignore tasks, update the translationZ of its TaskView to be
+ // between the translationZ of the tasks immediately underneath it
+ if (prevVisibleTv != null) {
+ tv.setTranslationZ(Math.max(tv.getTranslationZ(),
+ prevVisibleTv.getTranslationZ() + 0.1f));
+ }
+ }
+
if (i < (taskViewCount - 1) && tv.shouldClipViewInStack()) {
// Find the next view to clip against
for (int j = i + 1; j < taskViewCount; j++) {
@@ -609,33 +748,37 @@
if (!config.useHardwareLayers) {
tv.mThumbnailView.updateThumbnailVisibility(clipBottom - tv.getPaddingBottom());
}
+ prevVisibleTv = tv;
}
mTaskViewsClipDirty = false;
}
/**
+ * Updates the layout algorithm min and max virtual scroll bounds.
+ *
+ * @see #updateLayoutAlgorithm(boolean, ArraySet<Task.TaskKey>)
+ */
+ void updateLayoutAlgorithm(boolean boundScrollToNewMinMax) {
+ updateLayoutAlgorithm(boundScrollToNewMinMax, mIgnoreTasks);
+ }
+
+ /**
* Updates the min and max virtual scroll bounds.
*
- * @param ignoreTasks the set of tasks to ignore in the relayout
+ * @param ignoreTasksSet the set of tasks to ignore in the relayout
*/
- void updateLayout(boolean boundScrollToNewMinMax, Task... ignoreTasks) {
- // Keep track of the ingore tasks
- ArraySet<Task> ignoreTasksSet = mTmpTaskSet;
- ignoreTasksSet.clear();
- ignoreTasksSet.ensureCapacity(ignoreTasks.length);
- Collections.addAll(ignoreTasksSet, ignoreTasks);
-
+ void updateLayoutAlgorithm(boolean boundScrollToNewMinMax,
+ ArraySet<Task.TaskKey> ignoreTasksSet) {
// Compute the min and max scroll values
mLayoutAlgorithm.update(mStack, ignoreTasksSet);
- // Update the freeform workspace
+ // Update the freeform workspace background
SystemServicesProxy ssp = Recents.getSystemServices();
if (ssp.hasFreeformWorkspaceSupport()) {
mTmpRect.set(mLayoutAlgorithm.mFreeformRect);
mFreeformWorkspaceBackground.setBounds(mTmpRect);
}
- // Debug logging
if (boundScrollToNewMinMax) {
mStackScroller.boundScroll();
}
@@ -671,8 +814,6 @@
// Reset the last focused task state if changed
if (mFocusedTask != null) {
- resetFocusedTask(mFocusedTask);
-
// Cancel the timer indicator, if applicable
if (showTimerIndicator) {
final TaskView tv = getChildViewForTask(mFocusedTask);
@@ -680,6 +821,8 @@
tv.getHeaderView().cancelFocusTimerIndicator();
}
}
+
+ resetFocusedTask(mFocusedTask);
}
boolean willScroll = false;
@@ -709,17 +852,17 @@
};
if (scrollToTask) {
+ // Cancel any running enter animations at this point when we scroll or change focus
+ if (!mEnterAnimationComplete) {
+ cancelAllTaskViewAnimations();
+ }
+
// TODO: Center the newly focused task view, only if not freeform
float newScroll = mLayoutAlgorithm.getStackScrollForTask(newFocusedTask);
if (Float.compare(newScroll, mStackScroller.getStackScroll()) != 0) {
mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll,
focusTaskRunnable);
willScroll = true;
-
- // Cancel any running enter animations at this point when we scroll as well
- if (!mEnterAnimationComplete) {
- cancelAllTaskViewAnimations();
- }
} else {
focusTaskRunnable.run();
}
@@ -759,7 +902,7 @@
*/
public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated,
boolean cancelWindowAnimations) {
- setRelativeFocusedTask(forward, stackTasksOnly, animated, false, false);
+ setRelativeFocusedTask(forward, stackTasksOnly, animated, cancelWindowAnimations, false);
}
/**
@@ -937,10 +1080,10 @@
// Notify accessibility
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
}
- if (mDeferredTaskViewUpdateAnimation != null) {
- updateTaskViewsToLayout(mDeferredTaskViewUpdateAnimation);
+ if (mDeferredTaskViewLayoutAnimation != null) {
+ relayoutTaskViews(mDeferredTaskViewLayoutAnimation);
mTaskViewsClipDirty = true;
- mDeferredTaskViewUpdateAnimation = null;
+ mDeferredTaskViewLayoutAnimation = null;
}
if (mTaskViewsClipDirty) {
clipTaskViews();
@@ -948,30 +1091,16 @@
}
/**
- * Computes the stack and task rects.
- *
- * @param ignoreTasks the set of tasks to ignore in the relayout
- */
- public void computeRects(Rect taskStackBounds, boolean boundScroll, Task... ignoreTasks) {
- // Compute the rects in the stack algorithm
- mLayoutAlgorithm.initialize(taskStackBounds,
- TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
-
- // Update the scroll bounds
- updateLayout(boundScroll, ignoreTasks);
- }
-
- /**
* This is ONLY used from the Recents component to update the dummy stack view for purposes
* of getting the task rect to animate to.
*/
public void updateLayoutForStack(TaskStack stack) {
mStack = stack;
- updateLayout(false);
+ updateLayoutAlgorithm(false /* boundScroll */, EMPTY_TASK_SET);
}
/**
- * Computes the maximum number of visible tasks and thumbnails. Requires that
+ * Computes the maximum number of visible tasks and thumbnails. Requires that
* updateLayoutForStack() is called first.
*/
public TaskStackLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {
@@ -1002,16 +1131,18 @@
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
- // Compute our stack/task rects
- computeRects(mStackBounds, false);
+ // Compute the rects in the stack algorithm
+ mLayoutAlgorithm.initialize(mStackBounds,
+ TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
+ updateLayoutAlgorithm(false /* boundScroll */, EMPTY_TASK_SET);
// If this is the first layout, then scroll to the front of the stack, then update the
// TaskViews with the stack so that we can lay them out
if (mAwaitingFirstLayout) {
mStackScroller.setStackScrollToInitialState();
}
- mTmpTaskSet.clear();
- bindTaskViewsWithStack(mTmpTaskSet);
+ // Rebind all the views, including the ignore ones
+ bindVisibleTaskViews(mStackScroller.getStackScroll(), EMPTY_TASK_SET);
// Measure each of the TaskViews
mTmpTaskViews.clear();
@@ -1066,7 +1197,8 @@
mStackScroller.boundScroll();
}
}
- updateTaskViewsToLayout(TaskViewAnimation.IMMEDIATE);
+ // Relayout all of the task views including the ignored ones
+ relayoutTaskViews(TaskViewAnimation.IMMEDIATE, EMPTY_TASK_SET);
clipTaskViews();
if (mAwaitingFirstLayout || !mEnterAnimationComplete) {
@@ -1088,9 +1220,12 @@
// Set the task focused state without requesting view focus, and leave the focus animations
// until after the enter-animation
+ Task launchTask = mStack.getLaunchTarget();
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
- int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getTaskCount());
+ int focusedTaskIndex = launchTask != null
+ ? mStack.indexOfStackTask(launchTask)
+ : launchState.getInitialFocusTaskIndex(mStack.getTaskCount());
if (focusedTaskIndex != -1) {
setFocusedTask(focusedTaskIndex, false /* scrollToTask */,
false /* requestViewFocus */);
@@ -1106,8 +1241,29 @@
}
public boolean isTouchPointInView(float x, float y, TaskView tv) {
- return (tv.getLeft() <= x && x <= tv.getRight()) &&
- (tv.getTop() <= y && y <= tv.getBottom());
+ mTmpRect.set(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom());
+ mTmpRect.offset((int) tv.getTranslationX(), (int) tv.getTranslationY());
+ return mTmpRect.contains((int) x, (int) y);
+ }
+
+ /**
+ * Returns a non-ignored task in the {@param tasks} list that can be used as an achor when
+ * calculating the scroll position before and after a layout change.
+ */
+ public Task findAnchorTask(List<Task> tasks, MutableBoolean isFrontMostTask) {
+ for (int i = tasks.size() - 1; i >= 0; i--) {
+ Task task = tasks.get(i);
+
+ // Ignore deleting tasks
+ if (mIgnoreTasks.contains(task.key)) {
+ if (i == tasks.size() - 1) {
+ isFrontMostTask.value = true;
+ }
+ continue;
+ }
+ return task;
+ }
+ return null;
}
@Override
@@ -1152,70 +1308,38 @@
@Override
public void onStackTaskAdded(TaskStack stack, Task newTask) {
// Update the min/max scroll and animate other task views into their new positions
- updateLayout(true);
+ updateLayoutAlgorithm(true /* boundScroll */);
// Animate all the tasks into place
- updateTaskViewsToLayout(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
+ relayoutTaskViews(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
mFastOutSlowInInterpolator));
}
+ /**
+ * We expect that the {@link TaskView} associated with the removed task is already hidden.
+ */
@Override
public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
- Task newFrontMostTask) {
+ Task newFrontMostTask, TaskViewAnimation animation) {
if (mFocusedTask == removedTask) {
resetFocusedTask(removedTask);
}
- if (!removedTask.isFreeformTask()) {
- // Remove the view associated with this task, we can't rely on updateTransforms
- // to work here because the task is no longer in the list
- TaskView tv = getChildViewForTask(removedTask);
- if (tv != null) {
- mViewPool.returnViewToPool(tv);
- }
-
- // Get the stack scroll of the task to anchor to (since we are removing something, the
- // front most task will be our anchor task)
- Task anchorTask = mStack.getStackFrontMostTask();
- float prevAnchorTaskScroll = 0;
- if (anchorTask != null) {
- prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
- }
-
- // Update the min/max scroll and animate other task views into their new positions
- updateLayout(true);
-
- if (wasFrontMostTask) {
- // Since the max scroll progress is offset from the bottom of the stack, just scroll
- // to ensure that the new front most task is now fully visible
- mStackScroller.setStackScroll(mLayoutAlgorithm.mMaxScrollP);
- } else if (anchorTask != null) {
- // Otherwise, offset the scroll by the movement of the anchor task
- float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
- float stackScrollOffset = (anchorTaskScroll - prevAnchorTaskScroll);
- if (mLayoutAlgorithm.getFocusState() != TaskStackLayoutAlgorithm.STATE_FOCUSED) {
- // If we are focused, we don't want the front task to move, but otherwise, we
- // allow the back task to move up, and the front task to move back
- stackScrollOffset /= 2;
- }
- mStackScroller.setStackScroll(mStackScroller.getStackScroll() + stackScrollOffset);
- mStackScroller.boundScroll();
- }
- } else {
- // Remove the view associated with this task, we can't rely on updateTransforms
- // to work here because the task is no longer in the list
- TaskView tv = getChildViewForTask(removedTask);
- if (tv != null) {
- mViewPool.returnViewToPool(tv);
- }
-
- // Update the min/max scroll and animate other task views into their new positions
- updateLayout(true);
+ // Remove the view associated with this task, we can't rely on updateTransforms
+ // to work here because the task is no longer in the list
+ TaskView tv = getChildViewForTask(removedTask);
+ if (tv != null) {
+ mViewPool.returnViewToPool(tv);
}
- // Animate all the tasks into place
- updateTaskViewsToLayout(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
- mFastOutSlowInInterpolator));
+ // Remove the task from the ignored set
+ removeIgnoreTask(removedTask);
+
+ // If requested, relayout with the given animation
+ if (animation != null) {
+ updateLayoutAlgorithm(true /* boundScroll */);
+ relayoutTaskViews(animation);
+ }
// Update the new front most task's action button
if (mScreenPinningEnabled && newFrontMostTask != null) {
@@ -1232,7 +1356,8 @@
}
@Override
- public void onHistoryTaskRemoved(TaskStack stack, Task removedTask) {
+ public void onHistoryTaskRemoved(TaskStack stack, Task removedTask,
+ TaskViewAnimation animation) {
// To be implemented
}
@@ -1302,7 +1427,8 @@
}
// Restore the action button visibility if it is the front most task view
- if (mScreenPinningEnabled && tv.getTask() == mStack.getStackFrontMostTask()) {
+ if (mScreenPinningEnabled && tv.getTask() ==
+ mStack.getStackFrontMostTask(false /* includeFreeform */)) {
tv.showActionButton(false /* fadeIn */, 0 /* fadeInDuration */);
}
}
@@ -1316,7 +1442,20 @@
@Override
public void onTaskViewClipStateChanged(TaskView tv) {
- clipTaskViews();
+ if (!mTaskViewsClipDirty) {
+ mTaskViewsClipDirty = true;
+ invalidate();
+ }
+ }
+
+ /**** TaskStackLayoutAlgorithm.TaskStackLayoutAlgorithmCallbacks ****/
+
+ @Override
+ public void onFocusStateChanged(float prevFocusState, float curFocusState) {
+ if (mDeferredTaskViewLayoutAnimation == null) {
+ mUIDozeTrigger.poke();
+ relayoutTaskViewsOnNextFrame(TaskViewAnimation.IMMEDIATE);
+ }
}
/**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/
@@ -1324,7 +1463,9 @@
@Override
public void onScrollChanged(float prevScroll, float curScroll, TaskViewAnimation animation) {
mUIDozeTrigger.poke();
- updateTaskViewsToLayoutOnNextFrame(animation);
+ if (animation != null) {
+ relayoutTaskViewsOnNextFrame(animation);
+ }
if (shouldShowHistoryButton() &&
prevScroll > SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD &&
@@ -1354,7 +1495,7 @@
tv.dismissTask();
} else {
// Otherwise, remove the task from the stack immediately
- mStack.removeTask(t);
+ mStack.removeTask(t, TaskViewAnimation.IMMEDIATE);
}
}
}
@@ -1402,7 +1543,7 @@
}
public final void onBusEvent(TaskViewDismissedEvent event) {
- removeTaskViewFromStack(event.taskView);
+ removeTaskViewFromStack(event.taskView, event.task);
EventBus.getDefault().send(new DeleteTaskDataEvent(event.task));
}
@@ -1419,7 +1560,10 @@
// Poke the doze trigger on user interaction
mUIDozeTrigger.poke();
if (event.showTimerIndicator && mFocusedTask != null) {
- getChildViewForTask(mFocusedTask).getHeaderView().cancelFocusTimerIndicator();
+ TaskView tv = getChildViewForTask(mFocusedTask);
+ if (tv != null) {
+ tv.getHeaderView().cancelFocusTimerIndicator();
+ }
}
}
@@ -1455,23 +1599,29 @@
}
public final void onBusEvent(DragDropTargetChangedEvent event) {
+ TaskViewAnimation animation = new TaskViewAnimation(250, mFastOutSlowInInterpolator);
if (event.dropTarget instanceof TaskStack.DockState) {
// Calculate the new task stack bounds that matches the window size that Recents will
// have after the drop
+ addIgnoreTask(event.task);
final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
mStackBounds.set(dockState.getDockedTaskStackBounds(getMeasuredWidth(),
getMeasuredHeight(), mDividerSize, mLayoutAlgorithm.mSystemInsets,
getResources()));
- computeRects(mStackBounds, true /* boundScroll */, event.task /* ignoreTask */);
- updateTaskViewsToLayout(new TaskViewAnimation(250, mFastOutSlowInInterpolator),
- event.task /* ignoreTask */);
+ mLayoutAlgorithm.initialize(mStackBounds,
+ TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
+ updateLayoutAlgorithm(true /* boundScroll */);
} else {
- // Restore the pre-drag task stack bounds
+ // Restore the pre-drag task stack bounds, but ensure that we don't layout the dragging
+ // task view, so add it back to the ignore set after updating the layout
mStackBounds.set(mStableStackBounds);
- computeRects(mStackBounds, true /* boundScroll */);
- updateTaskViewsToLayout(new TaskViewAnimation(250, mFastOutSlowInInterpolator),
- event.task /* ignoreTask */);
+ removeIgnoreTask(event.task);
+ mLayoutAlgorithm.initialize(mStackBounds,
+ TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
+ updateLayoutAlgorithm(true /* boundScroll */);
+ addIgnoreTask(event.task);
}
+ relayoutTaskViews(animation);
}
public final void onBusEvent(final DragEndEvent event) {
@@ -1487,14 +1637,14 @@
if (hasChangedStacks) {
// Move the task to the right position in the stack (ie. the front of the stack if
- // freeform or the front of the stack if fullscreen). Note, we MUST move the tasks
+ // freeform or the front of the stack if fullscreen). Note, we MUST move the tasks
// before we update their stack ids, otherwise, the keys will have changed.
if (event.dropTarget == mFreeformWorkspaceDropTarget) {
mStack.moveTaskToStack(event.task, FREEFORM_WORKSPACE_STACK_ID);
} else if (event.dropTarget == mStackDropTarget) {
mStack.moveTaskToStack(event.task, FULLSCREEN_WORKSPACE_STACK_ID);
}
- updateLayout(true);
+ updateLayoutAlgorithm(true /* boundScroll */);
// Move the task to the new stack in the system after the animation completes
event.addPostAnimationCallback(new Runnable() {
@@ -1522,11 +1672,12 @@
mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(),
mTmpTransform, null);
event.getAnimationTrigger().increment();
- updateTaskViewsToLayout(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
+ relayoutTaskViews(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
mFastOutSlowInInterpolator));
updateTaskViewToTransform(event.taskView, mTmpTransform,
new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, mFastOutSlowInInterpolator,
event.getAnimationTrigger().decrementOnAnimationEnd()));
+ removeIgnoreTask(event.task);
}
public final void onBusEvent(StackViewScrolledEvent event) {
@@ -1593,15 +1744,14 @@
* Removes the task from the stack, and updates the focus to the next task in the stack if the
* removed TaskView was focused.
*/
- private void removeTaskViewFromStack(TaskView tv) {
- Task task = tv.getTask();
-
+ private void removeTaskViewFromStack(TaskView tv, Task task) {
// Announce for accessibility
tv.announceForAccessibility(getContext().getString(
- R.string.accessibility_recents_item_dismissed, tv.getTask().title));
+ R.string.accessibility_recents_item_dismissed, task.title));
// Remove the task from the stack
- mStack.removeTask(task);
+ mStack.removeTask(task, new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
+ mFastOutSlowInInterpolator));
}
/**
@@ -1622,7 +1772,7 @@
}
/**
- * Returns the insert index for the task in the current set of task views. If the given task
+ * Returns the insert index for the task in the current set of task views. If the given task
* is already in the task view list, then this method returns the insert index assuming it
* is first removed at the previous index.
*
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index 32f02ac..4ec051f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -20,7 +20,9 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
+import android.util.FloatProperty;
import android.util.Log;
+import android.util.Property;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.OverScroller;
@@ -37,6 +39,24 @@
void onScrollChanged(float prevScroll, float curScroll, TaskViewAnimation animation);
}
+ /**
+ * A Property wrapper around the <code>stackScroll</code> functionality handled by the
+ * {@link #setStackScroll(float)} and
+ * {@link #getStackScroll()} methods.
+ */
+ private static final Property<TaskStackViewScroller, Float> STACK_SCROLL =
+ new FloatProperty<TaskStackViewScroller>("stackScroll") {
+ @Override
+ public void setValue(TaskStackViewScroller object, float value) {
+ object.setStackScroll(value);
+ }
+
+ @Override
+ public Float get(TaskStackViewScroller object) {
+ return object.getStackScroll();
+ }
+ };
+
Context mContext;
TaskStackLayoutAlgorithm mLayoutAlgorithm;
TaskStackViewScrollerCallbacks mCb;
@@ -51,8 +71,10 @@
private Interpolator mLinearOutSlowInInterpolator;
- public TaskStackViewScroller(Context context, TaskStackLayoutAlgorithm layoutAlgorithm) {
+ public TaskStackViewScroller(Context context, TaskStackViewScrollerCallbacks cb,
+ TaskStackLayoutAlgorithm layoutAlgorithm) {
mContext = context;
+ mCb = cb;
mScroller = new OverScroller(context);
mLayoutAlgorithm = layoutAlgorithm;
mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
@@ -64,11 +86,6 @@
mStackScrollP = 0f;
}
- /** Sets the callbacks */
- void setCallbacks(TaskStackViewScrollerCallbacks cb) {
- mCb = cb;
- }
-
/** Gets the current stack scroll */
public float getStackScroll() {
return mStackScrollP;
@@ -151,6 +168,7 @@
/** Animates the stack scroll into bounds */
ObjectAnimator animateBoundScroll() {
+ // TODO: Take duration for snap back
float curScroll = getStackScroll();
float newScroll = getBoundedStackScroll(curScroll);
if (Float.compare(newScroll, curScroll) != 0) {
@@ -171,7 +189,7 @@
stopBoundScrollAnimation();
mFinalAnimatedScroll = newScroll;
- mScrollAnimator = ObjectAnimator.ofFloat(this, "stackScroll", curScroll, newScroll);
+ mScrollAnimator = ObjectAnimator.ofFloat(this, STACK_SCROLL, curScroll, newScroll);
mScrollAnimator.setDuration(mContext.getResources().getInteger(
R.integer.recents_animate_task_stack_scroll_duration));
mScrollAnimator.setInterpolator(mLinearOutSlowInInterpolator);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 4813c19..e9f6f39 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -21,13 +21,16 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
-import android.util.Log;
+import android.util.ArrayMap;
+import android.util.MutableBoolean;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewParent;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.SwipeHelper;
@@ -37,19 +40,25 @@
import com.android.systemui.recents.events.activity.HideRecentsEvent;
import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
+import com.android.systemui.recents.misc.RectFEvaluator;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.recents.model.Task;
import com.android.systemui.statusbar.FlingAnimationUtils;
+import java.util.ArrayList;
import java.util.List;
-/* Handles touch events for a TaskStackView. */
+/**
+ * Handles touch events for a TaskStackView.
+ */
class TaskStackViewTouchHandler implements SwipeHelper.Callback {
- private static final String TAG = "TaskStackViewTouchHandler";
- private static final boolean DEBUG = false;
+ private static final int INACTIVE_POINTER_ID = -1;
- private static int INACTIVE_POINTER_ID = -1;
+ private static final RectFEvaluator RECT_EVALUATOR = new RectFEvaluator();
+ private static final Interpolator STACK_TRANSFORM_INTERPOLATOR =
+ new PathInterpolator(0.73f, 0.33f, 0.42f, 0.85f);
Context mContext;
TaskStackView mSv;
@@ -74,6 +83,15 @@
final int mWindowTouchSlop;
private final StackViewScrolledEvent mStackViewScrolledEvent = new StackViewScrolledEvent();
+
+ // The current and final set of task transforms, sized to match the list of tasks in the stack
+ private ArrayList<Task> mCurrentTasks = new ArrayList<>();
+ private ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>();
+ private ArrayList<TaskViewTransform> mFinalTaskTransforms = new ArrayList<>();
+ private ArrayMap<View, Animator> mSwipeHelperAnimations = new ArrayMap<>();
+ private TaskViewTransform mTmpTransform = new TaskViewTransform();
+ private float mTargetStackScroll;
+
SwipeHelper mSwipeHelper;
boolean mInterceptedBySwipeHelper;
@@ -97,8 +115,14 @@
}
@Override
- protected void updateSnapBackAnimation(Animator anim) {
+ protected void prepareDismissAnimation(View v, Animator anim) {
+ mSwipeHelperAnimations.put(v, anim);
+ }
+
+ @Override
+ protected void prepareSnapBackAnimation(View v, Animator anim) {
anim.setInterpolator(mSv.mFastOutSlowInInterpolator);
+ mSwipeHelperAnimations.put(v, anim);
}
};
mSwipeHelper.setDisableHardwareLayers(true);
@@ -119,21 +143,6 @@
}
}
- /** Returns the view at the specified coordinates */
- TaskView findViewAtPoint(int x, int y) {
- List<TaskView> taskViews = mSv.getTaskViews();
- int taskViewCount = taskViews.size();
- for (int i = taskViewCount - 1; i >= 0; i--) {
- TaskView tv = taskViews.get(i);
- if (tv.getVisibility() == View.VISIBLE) {
- if (mSv.isTouchPointInView(x, y, tv)) {
- return tv;
- }
- }
- }
- return null;
- }
-
/** Touch preprocessing for handling below */
public boolean onInterceptTouchEvent(MotionEvent ev) {
// Pass through to swipe helper if we are swiping
@@ -179,6 +188,15 @@
mScroller.stopBoundScrollAnimation();
Utilities.cancelAnimationWithoutCallbacks(mScrollFlingAnimator);
+ // Finish any existing task animations from the delete
+ mSv.cancelAllTaskViewAnimations();
+ // Finish any of the swipe helper animations
+ ArrayMap<View, Animator> existingAnimators = new ArrayMap<>(mSwipeHelperAnimations);
+ for (int i = 0; i < existingAnimators.size(); i++) {
+ existingAnimators.get(existingAnimators.keyAt(i)).end();
+ }
+ mSwipeHelperAnimations.clear();
+
// Initialize the velocity tracker
initOrResetVelocityTracker();
mVelocityTracker.addMovement(ev);
@@ -214,9 +232,6 @@
float deltaP = layoutAlgorithm.getDeltaPForY(mDownY, y);
float curScrollP = mDownScrollP + deltaP;
mScroller.setStackScroll(curScrollP);
- if (DEBUG) {
- Log.d(TAG, "scroll: " + curScrollP);
- }
mStackViewScrolledEvent.updateY(y - mLastY);
EventBus.getDefault().send(mStackViewScrolledEvent);
}
@@ -343,12 +358,19 @@
@Override
public View getChildAtPosition(MotionEvent ev) {
- return findViewAtPoint((int) ev.getX(), (int) ev.getY());
+ TaskView tv = findViewAtPoint((int) ev.getX(), (int) ev.getY());
+ if (tv != null && canChildBeDismissed(tv)) {
+ return tv;
+ }
+ return null;
}
@Override
public boolean canChildBeDismissed(View v) {
- return true;
+ // Disallow dismissing an already dismissed task
+ TaskView tv = (TaskView) v;
+ return !mSwipeHelperAnimations.containsKey(v) &&
+ (mSv.getStack().indexOfStackTask(tv.getTask()) != -1);
}
@Override
@@ -364,34 +386,113 @@
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
+
+ // Add this task to the set of tasks we are deleting
+ mSv.addIgnoreTask(tv.getTask());
+
+ // Determine if we are animating the other tasks while dismissing this task
+ mCurrentTasks = mSv.getStack().getStackTasks();
+ MutableBoolean isFrontMostTask = new MutableBoolean(false);
+ Task anchorTask = mSv.findAnchorTask(mCurrentTasks, isFrontMostTask);
+ TaskStackViewScroller stackScroller = mSv.getScroller();
+ if (anchorTask != null) {
+ // Get the current set of task transforms
+ mSv.getCurrentTaskTransforms(mCurrentTasks, mCurrentTaskTransforms);
+
+ // Get the stack scroll of the task to anchor to (since we are removing something, the
+ // front most task will be our anchor task)
+ float prevAnchorTaskScroll = 0;
+ boolean pullStackForward = mCurrentTasks.size() > 0;
+ if (pullStackForward) {
+ prevAnchorTaskScroll = mSv.getStackAlgorithm().getStackScrollForTask(anchorTask);
+ }
+
+ // Calculate where the views would be without the deleting tasks
+ mSv.updateLayoutAlgorithm(false /* boundScroll */);
+
+ float newStackScroll = stackScroller.getStackScroll();
+ if (isFrontMostTask.value) {
+ // Bound the stack scroll to pull tasks forward if necessary
+ newStackScroll = stackScroller.getBoundedStackScroll(newStackScroll);
+ } else if (pullStackForward) {
+ // Otherwise, offset the scroll by the movement of the anchor task
+ float anchorTaskScroll = mSv.getStackAlgorithm().getStackScrollForTask(anchorTask);
+ float stackScrollOffset = (anchorTaskScroll - prevAnchorTaskScroll);
+ if (mSv.getStackAlgorithm().getFocusState() !=
+ TaskStackLayoutAlgorithm.STATE_FOCUSED) {
+ // If we are focused, we don't want the front task to move, but otherwise, we
+ // allow the back task to move up, and the front task to move back
+ stackScrollOffset /= 2;
+ }
+ newStackScroll = stackScroller.getBoundedStackScroll(stackScroller.getStackScroll()
+ + stackScrollOffset);
+ }
+
+ // Pick up the newly visible views, not including the deleting tasks
+ mSv.bindVisibleTaskViews(newStackScroll);
+
+ // Get the final set of task transforms (with task removed)
+ mSv.getLayoutTaskTransforms(newStackScroll, mCurrentTasks, mFinalTaskTransforms);
+
+ // Set the target to scroll towards upon dismissal
+ mTargetStackScroll = newStackScroll;
+
+ /*
+ * Post condition: All views that will be visible as a part of the gesture are retrieved
+ * and at their initial positions. The stack is still at the current
+ * scroll, but the layout is updated without the task currently being
+ * dismissed.
+ */
+ }
}
@Override
public boolean updateSwipeProgress(View v, boolean dismissable, float swipeProgress) {
+ updateTaskViewTransforms(getDismissFraction(v));
return true;
}
+ /**
+ * Called after the {@link TaskView} is finished animating away.
+ */
@Override
public void onChildDismissed(View v) {
TaskView tv = (TaskView) v;
+
// Re-enable clipping with the stack (we will reuse this view)
tv.setClipViewInStack(true);
// Re-enable touch events from this task view
tv.setTouchEnabled(true);
+ // Update the scroll to the final scroll position from onBeginDrag()
+ mSv.getScroller().setStackScroll(mTargetStackScroll, null);
// Remove the task view from the stack
EventBus.getDefault().send(new TaskViewDismissedEvent(tv.getTask(), tv));
+ // Stop tracking this deletion animation
+ mSwipeHelperAnimations.remove(v);
// Keep track of deletions by keyboard
MetricsLogger.histogram(tv.getContext(), "overview_task_dismissed_source",
Constants.Metrics.DismissSourceSwipeGesture);
}
+ /**
+ * Called after the {@link TaskView} is finished animating back into the list.
+ * onChildDismissed() calls.
+ */
@Override
public void onChildSnappedBack(View v) {
TaskView tv = (TaskView) v;
+
// Re-enable clipping with the stack
tv.setClipViewInStack(true);
// Re-enable touch events from this task view
tv.setTouchEnabled(true);
+
+ // Stop tracking this deleting task, and update the layout to include this task again. The
+ // stack scroll does not need to be reset, since the scroll has not actually changed in
+ // onBeginDrag().
+ mSv.removeIgnoreTask(tv.getTask());
+ mSv.updateLayoutAlgorithm(false /* boundScroll */);
+ mSwipeHelperAnimations.remove(v);
}
@Override
@@ -414,4 +515,59 @@
return 0;
}
+ /**
+ * Interpolates the non-deleting tasks to their final transforms from their current transforms.
+ */
+ private void updateTaskViewTransforms(float dismissFraction) {
+ List<TaskView> taskViews = mSv.getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = 0; i < taskViewCount; i++) {
+ TaskView tv = taskViews.get(i);
+ Task task = tv.getTask();
+
+ if (mSv.isIgnoredTask(task)) {
+ continue;
+ }
+
+ int taskIndex = mCurrentTasks.indexOf(task);
+ TaskViewTransform fromTransform = mCurrentTaskTransforms.get(taskIndex);
+ TaskViewTransform toTransform = mFinalTaskTransforms.get(taskIndex);
+
+ mTmpTransform.copyFrom(fromTransform);
+ // We only really need to interpolate the bounds, progress and translation
+ mTmpTransform.rect.set(RECT_EVALUATOR.evaluate(dismissFraction, fromTransform.rect,
+ toTransform.rect));
+ mTmpTransform.p = fromTransform.p + (toTransform.p - fromTransform.p) * dismissFraction;
+ mTmpTransform.translationZ = fromTransform.translationZ +
+ (toTransform.translationZ - fromTransform.translationZ) * dismissFraction;
+
+ mSv.updateTaskViewToTransform(tv, mTmpTransform, TaskViewAnimation.IMMEDIATE);
+ }
+ }
+
+ /** Returns the view at the specified coordinates */
+ private TaskView findViewAtPoint(int x, int y) {
+ List<Task> tasks = mSv.getStack().getStackTasks();
+ int taskCount = tasks.size();
+ for (int i = taskCount - 1; i >= 0; i--) {
+ TaskView tv = mSv.getChildViewForTask(tasks.get(i));
+ if (tv != null && tv.getVisibility() == View.VISIBLE) {
+ if (mSv.isTouchPointInView(x, y, tv)) {
+ return tv;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the fraction which we should interpolate the other task views based on the dismissal
+ * of this given task.
+ *
+ * TODO: We can interpolate this to adjust when the other tasks should respond to the dismissal
+ */
+ private float getDismissFraction(View v) {
+ float fraction = Math.min(1f, Math.abs(v.getTranslationX() / mSv.getWidth()));
+ return STACK_TRANSFORM_INTERPOLATOR.getInterpolation(fraction);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 9b72702..32bebb3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -290,6 +290,7 @@
setDim(0);
setVisibility(View.VISIBLE);
getViewBounds().reset();
+ getHeaderView().reset();
TaskViewTransform.reset(this);
mActionButtonView.setScaleX(1f);
@@ -459,16 +460,14 @@
public void showActionButton(boolean fadeIn, int fadeInDuration) {
mActionButtonView.setVisibility(View.VISIBLE);
- if (fadeIn) {
- if (mActionButtonView.getAlpha() < 1f) {
- mActionButtonView.animate()
- .alpha(1f)
- .scaleX(1f)
- .scaleY(1f)
- .setDuration(fadeInDuration)
- .setInterpolator(PhoneStatusBar.ALPHA_IN)
- .start();
- }
+ if (fadeIn && mActionButtonView.getAlpha() < 1f) {
+ mActionButtonView.animate()
+ .alpha(1f)
+ .scaleX(1f)
+ .scaleY(1f)
+ .setDuration(fadeInDuration)
+ .setInterpolator(PhoneStatusBar.ALPHA_IN)
+ .start();
} else {
mActionButtonView.setScaleX(1f);
mActionButtonView.setScaleY(1f);
@@ -484,29 +483,27 @@
*/
public void hideActionButton(boolean fadeOut, int fadeOutDuration, boolean scaleDown,
final Animator.AnimatorListener animListener) {
- if (fadeOut) {
- if (mActionButtonView.getAlpha() > 0f) {
- if (scaleDown) {
- float toScale = 0.9f;
- mActionButtonView.animate()
- .scaleX(toScale)
- .scaleY(toScale);
- }
+ if (fadeOut && mActionButtonView.getAlpha() > 0f) {
+ if (scaleDown) {
+ float toScale = 0.9f;
mActionButtonView.animate()
- .alpha(0f)
- .setDuration(fadeOutDuration)
- .setInterpolator(PhoneStatusBar.ALPHA_OUT)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- if (animListener != null) {
- animListener.onAnimationEnd(null);
- }
- mActionButtonView.setVisibility(View.INVISIBLE);
- }
- })
- .start();
+ .scaleX(toScale)
+ .scaleY(toScale);
}
+ mActionButtonView.animate()
+ .alpha(0f)
+ .setDuration(fadeOutDuration)
+ .setInterpolator(PhoneStatusBar.ALPHA_OUT)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ if (animListener != null) {
+ animListener.onAnimationEnd(null);
+ }
+ mActionButtonView.setVisibility(View.INVISIBLE);
+ }
+ })
+ .start();
} else {
mActionButtonView.setAlpha(0f);
mActionButtonView.setVisibility(View.INVISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index e7717ac..827ee408 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -16,22 +16,27 @@
package com.android.systemui.recents.views;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.annotation.Nullable;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
-import android.graphics.PorterDuff;
import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.RippleDrawable;
-import android.support.v4.graphics.ColorUtils;
import android.os.CountDownTimer;
+import android.support.v4.graphics.ColorUtils;
import android.util.AttributeSet;
import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.view.ViewStub;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
@@ -39,10 +44,10 @@
import android.widget.ProgressBar;
import android.widget.TextView;
import com.android.internal.logging.MetricsLogger;
-
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.LaunchTaskEvent;
import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
@@ -59,6 +64,8 @@
implements View.OnClickListener, View.OnLongClickListener {
private static final float HIGHLIGHT_LIGHTNESS_INCREMENT = 0.125f;
+ private static final float OVERLAY_LIGHTNESS_INCREMENT = -0.0625f;
+ private static final int OVERLAY_REVEAL_DURATION = 250;
private static final long FOCUS_INDICATOR_INTERVAL_MS = 30;
/**
@@ -69,8 +76,6 @@
private Paint mHighlightPaint = new Paint();
private Paint mBackgroundPaint = new Paint();
- private float[] mTmpHSL = new float[3];
-
public HighlightColorDrawable() {
mBackgroundPaint.setColor(Color.argb(255, 0, 0, 0));
mBackgroundPaint.setAntiAlias(true);
@@ -122,11 +127,16 @@
Task mTask;
// Header views
- ImageView mMoveTaskButton;
- ImageView mDismissButton;
ImageView mIconView;
TextView mTitleView;
- int mMoveTaskTargetStackId = INVALID_STACK_ID;
+ ImageView mMoveTaskButton;
+ ImageView mDismissButton;
+ ViewStub mAppOverlayViewStub;
+ FrameLayout mAppOverlayView;
+ ImageView mAppIconView;
+ ImageView mAppInfoView;
+ TextView mAppTitleView;
+ ViewStub mFocusTimerIndicatorStub;
ProgressBar mFocusTimerIndicator;
// Header drawables
@@ -140,21 +150,26 @@
Drawable mDarkFreeformIcon;
Drawable mLightFullscreenIcon;
Drawable mDarkFullscreenIcon;
+ Drawable mLightInfoIcon;
+ Drawable mDarkInfoIcon;
int mTaskBarViewLightTextColor;
int mTaskBarViewDarkTextColor;
+ int mMoveTaskTargetStackId = INVALID_STACK_ID;
// Header background
private HighlightColorDrawable mBackground;
+ private HighlightColorDrawable mOverlayBackground;
+ private float[] mTmpHSL = new float[3];
// Header dim, which is only used when task view hardware layers are not used
private Paint mDimLayerPaint = new Paint();
Interpolator mFastOutSlowInInterpolator;
Interpolator mFastOutLinearInInterpolator;
+ Interpolator mLinearOutSlowInInterpolator;
- long mFocusIndicatorProgress;
private CountDownTimer mFocusTimerCountDown;
- long mFocusTimerDuration;
+ private long mFocusTimerDuration;
public TaskViewHeader(Context context) {
this(context, null);
@@ -184,36 +199,45 @@
mDarkFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_dark);
mLightFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_light);
mDarkFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_dark);
+ mLightInfoIcon = context.getDrawable(R.drawable.recents_info_light);
+ mDarkInfoIcon = context.getDrawable(R.drawable.recents_info_dark);
mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
com.android.internal.R.interpolator.fast_out_slow_in);
mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
com.android.internal.R.interpolator.fast_out_linear_in);
+ mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+ com.android.internal.R.interpolator.linear_out_slow_in);
// Configure the background and dim
mBackground = new HighlightColorDrawable();
mBackground.setColorAndDim(Color.argb(255, 0, 0, 0), 0f);
setBackground(mBackground);
+ mOverlayBackground = new HighlightColorDrawable();
mDimLayerPaint.setColor(Color.argb(255, 0, 0, 0));
mDimLayerPaint.setAntiAlias(true);
mFocusTimerDuration = res.getInteger(R.integer.recents_auto_advance_duration);
}
+ /**
+ * Resets this header along with the TaskView.
+ */
+ public void reset() {
+ hideAppOverlay(true /* immediate */);
+ }
+
@Override
protected void onFinishInflate() {
// Initialize the icon and description views
mIconView = (ImageView) findViewById(R.id.icon);
+ mIconView.setClickable(false);
mIconView.setOnLongClickListener(this);
mTitleView = (TextView) findViewById(R.id.title);
mDismissButton = (ImageView) findViewById(R.id.dismiss_task);
mDismissButton.setOnClickListener(this);
mMoveTaskButton = (ImageView) findViewById(R.id.move_task);
- mFocusTimerIndicator = (ProgressBar) findViewById(R.id.focus_timer_indicator);
-
- // Hide the backgrounds if they are ripple drawables
- if (mIconView.getBackground() instanceof RippleDrawable) {
- mIconView.setBackground(null);
- }
+ mFocusTimerIndicatorStub = (ViewStub) findViewById(R.id.focus_timer_indicator_stub);
+ mAppOverlayViewStub = (ViewStub) findViewById(R.id.app_overlay_stub);
}
/**
@@ -228,6 +252,7 @@
mTaskViewRect.set(0, 0, width, height);
boolean updateMoveTaskButton = mMoveTaskButton.getVisibility() != View.GONE;
+ boolean isFreeformTask = (mTask != null) && mTask.isFreeformTask();
int appIconWidth = mIconView.getMeasuredWidth();
int activityDescWidth = (mTask != null)
? (int) mTitleView.getPaint().measureText(mTask.title)
@@ -239,19 +264,20 @@
// Priority-wise, we show the activity icon first, the dismiss icon if there is room, the
// move-task icon if there is room, and then finally, the activity label if there is room
- if (width < (appIconWidth + dismissIconWidth)) {
+ if (isFreeformTask && width < (appIconWidth + dismissIconWidth)) {
mTitleView.setVisibility(View.INVISIBLE);
if (updateMoveTaskButton) {
mMoveTaskButton.setVisibility(View.INVISIBLE);
}
mDismissButton.setVisibility(View.INVISIBLE);
- } else if (width < (appIconWidth + dismissIconWidth + moveTaskIconWidth)) {
+ } else if (isFreeformTask && width < (appIconWidth + dismissIconWidth +
+ moveTaskIconWidth)) {
mTitleView.setVisibility(View.INVISIBLE);
if (updateMoveTaskButton) {
mMoveTaskButton.setVisibility(View.INVISIBLE);
}
mDismissButton.setVisibility(View.VISIBLE);
- } else if (width < (appIconWidth + dismissIconWidth + moveTaskIconWidth +
+ } else if (isFreeformTask && width < (appIconWidth + dismissIconWidth + moveTaskIconWidth +
activityDescWidth)) {
mTitleView.setVisibility(View.INVISIBLE);
if (updateMoveTaskButton) {
@@ -273,11 +299,6 @@
}
@Override
- protected boolean verifyDrawable(Drawable who) {
- return super.verifyDrawable(who) || (who == mBackground);
- }
-
- @Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
@@ -288,6 +309,10 @@
/** Starts the focus timer. */
public void startFocusTimerIndicator() {
+ if (mFocusTimerIndicator == null) {
+ return;
+ }
+
mFocusTimerIndicator.setVisibility(View.VISIBLE);
mFocusTimerIndicator.setMax((int) mFocusTimerDuration);
if (mFocusTimerCountDown == null) {
@@ -308,7 +333,11 @@
/** Cancels the focus timer. */
public void cancelFocusTimerIndicator() {
- if (mFocusTimerCountDown != null && mFocusTimerIndicator != null) {
+ if (mFocusTimerIndicator == null) {
+ return;
+ }
+
+ if (mFocusTimerCountDown != null) {
mFocusTimerCountDown.cancel();
mFocusTimerIndicator.setProgress(0);
mFocusTimerIndicator.setVisibility(View.INVISIBLE);
@@ -337,6 +366,10 @@
private void updateBackgroundColor(float dimAlpha) {
if (mTask != null) {
mBackground.setColorAndDim(mTask.colorPrimary, dimAlpha);
+ // TODO: Consider using the saturation of the color to adjust the lightness as well
+ ColorUtils.colorToHSL(mTask.colorPrimary, mTmpHSL);
+ mTmpHSL[2] = Math.min(1f, mTmpHSL[2] + OVERLAY_LIGHTNESS_INCREMENT * (1.0f - dimAlpha));
+ mOverlayBackground.setColorAndDim(ColorUtils.HSLToColor(mTmpHSL), dimAlpha);
mDimLayerPaint.setAlpha((int) (dimAlpha * 255));
}
}
@@ -382,10 +415,15 @@
mMoveTaskButton.setOnClickListener(this);
}
- mFocusTimerIndicator.getProgressDrawable()
- .setColorFilter(
- getSecondaryColor(t.colorPrimary, t.useLightOnPrimaryColor),
- PorterDuff.Mode.SRC_IN);
+ if (Recents.getDebugFlags().isFastToggleIndicatorEnabled()) {
+ if (mFocusTimerIndicator == null) {
+ mFocusTimerIndicator = (ProgressBar) mFocusTimerIndicatorStub.inflate();
+ }
+ mFocusTimerIndicator.getProgressDrawable()
+ .setColorFilter(
+ getSecondaryColor(t.colorPrimary, t.useLightOnPrimaryColor),
+ PorterDuff.Mode.SRC_IN);
+ }
// In accessibility, a single click on the focused app info button will show it
if (ssp.isTouchExplorationEnabled()) {
@@ -447,8 +485,11 @@
@Override
public void onClick(View v) {
if (v == mIconView) {
- // In accessibility, a single click on the focused app info button will show it
- EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ if (ssp.isTouchExplorationEnabled()) {
+ // In accessibility, a single click on the focused app info button will show it
+ EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
+ }
} else if (v == mDismissButton) {
TaskView tv = Utilities.findParent(this, TaskView.class);
tv.dismissTask();
@@ -463,15 +504,95 @@
: new Rect();
EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, bounds,
mMoveTaskTargetStackId, false));
+ } else if (v == mAppInfoView) {
+ EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
+ } else if (v == mAppIconView) {
+ hideAppOverlay(false /* immediate */);
}
}
@Override
public boolean onLongClick(View v) {
if (v == mIconView) {
- EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
+ showAppOverlay();
+ return true;
+ } else if (v == mAppIconView) {
+ hideAppOverlay(false /* immediate */);
return true;
}
return false;
}
+
+ /**
+ * Shows the application overlay.
+ */
+ private void showAppOverlay() {
+ // Skip early if the task is invalid
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ ComponentName cn = mTask.key.getComponent();
+ int userId = mTask.key.userId;
+ ActivityInfo activityInfo = ssp.getActivityInfo(cn, userId);
+ if (activityInfo == null) {
+ return;
+ }
+
+ // Inflate the overlay if necessary
+ if (mAppOverlayView == null) {
+ mAppOverlayView = (FrameLayout) mAppOverlayViewStub.inflate();
+ mAppOverlayView.setBackground(mOverlayBackground);
+ mAppIconView = (ImageView) mAppOverlayView.findViewById(R.id.app_icon);
+ mAppIconView.setOnClickListener(this);
+ mAppIconView.setOnLongClickListener(this);
+ mAppInfoView = (ImageView) mAppOverlayView.findViewById(R.id.app_info);
+ mAppInfoView.setOnClickListener(this);
+ mAppTitleView = (TextView) mAppOverlayView.findViewById(R.id.app_title);
+ }
+
+ // Update the overlay contents for the current app
+ mAppTitleView.setText(ssp.getBadgedApplicationLabel(activityInfo.applicationInfo, userId));
+ mAppTitleView.setTextColor(mTask.useLightOnPrimaryColor ?
+ mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
+ mAppIconView.setImageDrawable(ssp.getBadgedApplicationIcon(activityInfo.applicationInfo,
+ userId));
+ mAppInfoView.setImageDrawable(mTask.useLightOnPrimaryColor
+ ? mLightInfoIcon
+ : mDarkInfoIcon);
+ mAppOverlayView.setVisibility(View.VISIBLE);
+
+ int x = mIconView.getLeft() + mIconView.getWidth() / 2;
+ int y = mIconView.getTop() + mIconView.getHeight() / 2;
+ Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y, 0,
+ getWidth());
+ revealAnim.setDuration(OVERLAY_REVEAL_DURATION);
+ revealAnim.setInterpolator(mLinearOutSlowInInterpolator);
+ revealAnim.start();
+ }
+
+ /**
+ * Hide the application overlay.
+ */
+ private void hideAppOverlay(boolean immediate) {
+ // Skip if we haven't even loaded the overlay yet
+ if (mAppOverlayView == null) {
+ return;
+ }
+
+ if (immediate) {
+ mAppOverlayView.setVisibility(View.GONE);
+ } else {
+ int x = mIconView.getLeft() + mIconView.getWidth() / 2;
+ int y = mIconView.getTop() + mIconView.getHeight() / 2;
+ Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y,
+ getWidth(), 0);
+ revealAnim.setDuration(OVERLAY_REVEAL_DURATION);
+ revealAnim.setInterpolator(mLinearOutSlowInInterpolator);
+ revealAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAppOverlayView.setVisibility(View.GONE);
+ }
+ });
+ revealAnim.start();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
index 538c248..85b7c82 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -88,12 +88,39 @@
public float alpha = 1f;
public boolean visible = false;
- float p = 0f;
+
+ // This is the relative task progress of this task, relative to the stack scroll at which this
+ // transform was computed
+ public float p = 0f;
// This is a window-space rect used for positioning the task in the stack and freeform workspace
public RectF rect = new RectF();
/**
+ * Fills int this transform from the state of the given TaskView.
+ */
+ public void fillIn(TaskView tv) {
+ translationZ = tv.getTranslationZ();
+ scale = tv.getScaleX();
+ alpha = tv.getAlpha();
+ visible = true;
+ p = tv.getTaskProgress();
+ rect.set(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom());
+ }
+
+ /**
+ * Copies the transform state from another {@link TaskViewTransform}.
+ */
+ public void copyFrom(TaskViewTransform other) {
+ translationZ = other.translationZ;
+ scale = other.scale;
+ alpha = other.alpha;
+ visible = other.visible;
+ p = other.p;
+ rect.set(other.rect);
+ }
+
+ /**
* Resets the current transform.
*/
public void reset() {
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index c16703e8..9e83dcf 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -167,8 +167,7 @@
public boolean startDragging(boolean animate) {
mHandle.setTouching(true, animate);
mDockSide = mWindowManagerProxy.getDockSide();
- mSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(),
- mFlingAnimationUtils.getMinVelocityPxPerSecond(), mDisplayWidth,
+ mSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(), mDisplayWidth,
mDisplayHeight, mDividerSize, isHorizontalDivision(), mStableInsets);
if (mDockSide != WindowManager.DOCKED_INVALID) {
mWindowManagerProxy.setResizing(true);
@@ -180,9 +179,9 @@
}
}
- public void stopDragging(int position, float velocity) {
+ public void stopDragging(int position, float velocity, boolean avoidDismissStart) {
mHandle.setTouching(false, true /* animate */);
- fling(position, velocity);
+ fling(position, velocity, avoidDismissStart);
mWindowManager.setSlippery(true);
releaseBackground();
}
@@ -225,7 +224,7 @@
if (mMoving && mDockSide != WindowManager.DOCKED_INVALID) {
int position = calculatePosition(x, y);
SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(position,
- 0 /* velocity */);
+ 0 /* velocity */, false /* hardDismiss */);
resizeStack(calculatePosition(x, y), snapTarget.position, snapTarget);
}
break;
@@ -239,7 +238,7 @@
mVelocityTracker.computeCurrentVelocity(1000);
int position = calculatePosition(x, y);
stopDragging(position, isHorizontalDivision() ? mVelocityTracker.getYVelocity()
- : mVelocityTracker.getXVelocity());
+ : mVelocityTracker.getXVelocity(), false /* avoidDismissStart */);
mMoving = false;
break;
}
@@ -250,8 +249,12 @@
event.setLocation(event.getRawX(), event.getRawY());
}
- private void fling(int position, float velocity) {
- final SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(position, velocity);
+ private void fling(int position, float velocity, boolean avoidDismissStart) {
+ SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(position, velocity);
+ if (avoidDismissStart && snapTarget == mSnapAlgorithm.getDismissStartTarget()) {
+ snapTarget = mSnapAlgorithm.getFirstSplitTarget();
+ }
+ final SnapTarget finalTarget = snapTarget;
ValueAnimator anim = ValueAnimator.ofInt(position, snapTarget.position);
anim.addUpdateListener(new AnimatorUpdateListener() {
@@ -260,13 +263,13 @@
resizeStack((Integer) animation.getAnimatedValue(),
animation.getAnimatedFraction() == 1f
? TASK_POSITION_SAME
- : snapTarget.position, snapTarget);
+ : finalTarget.position, finalTarget);
}
});
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- commitSnapFlags(snapTarget);
+ commitSnapFlags(finalTarget);
mWindowManagerProxy.setResizing(false);
mDockSide = WindowManager.DOCKED_INVALID;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index da3cd54..38d24ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -176,26 +176,31 @@
};
@Override
- public boolean dispatchTouchEvent(MotionEvent event) {
- if (mDimmed && !mActivated) {
- return handleTouchEventDimmed(event);
- } else {
- return super.dispatchTouchEvent(event);
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (mDimmed && !mActivated
+ && ev.getActionMasked() == MotionEvent.ACTION_DOWN && disallowSingleClick(ev)) {
+ return true;
}
+ return super.onInterceptTouchEvent(ev);
+ }
+
+ protected boolean disallowSingleClick(MotionEvent ev) {
+ return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result;
- if (mDimmed && mActivated) {
+ if (mDimmed) {
+ boolean wasActivated = mActivated;
result = handleTouchEventDimmed(event);
+ if (wasActivated && result && event.getAction() == MotionEvent.ACTION_UP) {
+ mFalsingManager.onNotificationDoubleTap();
+ removeCallbacks(mTapTimeoutRunnable);
+ }
} else {
result = super.onTouchEvent(event);
}
- if (mActivated && result && event.getAction() == MotionEvent.ACTION_UP) {
- mFalsingManager.onNotificationDoubleTap();
- removeCallbacks(mTapTimeoutRunnable);
- }
return result;
}
@@ -608,8 +613,6 @@
onFinishedRunnable.run();
}
if (!mWasCancelled) {
- mAppearAnimationFraction = -1;
- setOutlineRect(null);
enableAppearDrawing(false);
}
}
@@ -630,6 +633,7 @@
private void cancelAppearAnimation() {
if (mAppearAnimator != null) {
mAppearAnimator.cancel();
+ mAppearAnimator = null;
}
}
@@ -735,6 +739,8 @@
mDrawingAppearAnimation = enable;
if (!enable) {
setContentAlpha(1.0f);
+ mAppearAnimationFraction = -1;
+ setOutlineRect(null);
}
invalidate();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index ce4eff5..2592486 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -1265,18 +1265,22 @@
}
/**
- * Called when the size of the notification panel changes
+ * Called when the notification panel layouts
*/
- public void onPanelHeightChanged() {
+ public void onPanelLaidOut() {
if (mState == StatusBarState.KEYGUARD) {
// Since the number of notifications is determined based on the height of the view, we
// need to update them.
- updateRowStates();
+ int maxBefore = getMaxKeyguardNotifications(false /* recompute */);
+ int maxNotifications = getMaxKeyguardNotifications(true /* recompute */);
+ if (maxBefore != maxNotifications) {
+ updateRowStates();
+ }
}
}
@Override
- public void onExpandClicked(View clickedView, boolean nowExpanded) {
+ public void onExpandClicked(Entry clickedEntry, boolean nowExpanded) {
}
protected class H extends Handler {
@@ -1359,6 +1363,7 @@
parent, false);
row.setExpansionLogger(this, entry.notification.getKey());
row.setGroupManager(mGroupManager);
+ row.setHeadsUpManager(mHeadsUpManager);
row.setRemoteInputController(mRemoteInputController);
row.setOnExpandClickListener(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
new file mode 100644
index 0000000..f71f092
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
@@ -0,0 +1,65 @@
+/*
+ * 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
+ */
+
+package com.android.systemui.statusbar;
+
+import android.view.View;
+
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
+
+/**
+ * A helper to fade views in and out.
+ */
+public class CrossFadeHelper {
+ public static final long ANIMATION_DURATION_LENGTH = 210;
+
+ public static void fadeOut(final View view, final Runnable endRunnable) {
+ view.animate().cancel();
+ view.animate()
+ .alpha(0f)
+ .setDuration(ANIMATION_DURATION_LENGTH)
+ .setInterpolator(PhoneStatusBar.ALPHA_OUT)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ if (endRunnable != null) {
+ endRunnable.run();
+ }
+ view.setVisibility(View.INVISIBLE);
+ }
+ });
+ if (view.hasOverlappingRendering()) {
+ view.animate().withLayer();
+ }
+
+ }
+
+ public static void fadeIn(final View view) {
+ view.animate().cancel();
+ if (view.getVisibility() == View.INVISIBLE) {
+ view.setAlpha(0.0f);
+ view.setVisibility(View.VISIBLE);
+ }
+ view.animate()
+ .alpha(1f)
+ .setDuration(ANIMATION_DURATION_LENGTH)
+ .setInterpolator(PhoneStatusBar.ALPHA_IN)
+ .withEndAction(null);
+ if (view.hasOverlappingRendering()) {
+ view.animate().withLayer();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 83853a2..93be009 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -25,8 +25,10 @@
import android.os.Build;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.NotificationHeaderView;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewStub;
import android.view.accessibility.AccessibilityEvent;
import android.widget.Chronometer;
@@ -35,7 +37,9 @@
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingManager;
+import com.android.systemui.statusbar.notification.NotificationViewWrapper;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
import com.android.systemui.statusbar.stack.StackScrollState;
import com.android.systemui.statusbar.stack.StackStateAnimator;
@@ -59,6 +63,11 @@
private boolean mHasUserChangedExpansion;
/** If {@link #mHasUserChangedExpansion}, has the user expanded this row */
private boolean mUserExpanded;
+
+ /**
+ * Has this notification been expanded while it was pinned
+ */
+ private boolean mExpandedWhenPinned;
/** Is the user touching this row */
private boolean mUserLocked;
/** Are we showing the "public" version */
@@ -103,6 +112,7 @@
private boolean mIsSystemChildExpanded;
private boolean mIsPinned;
private FalsingManager mFalsingManager;
+ private HeadsUpManager mHeadsUpManager;
private NotificationHeaderUtil mHeaderUtil = new NotificationHeaderUtil(this);
private boolean mJustClicked;
@@ -115,14 +125,19 @@
public void onClick(View v) {
if (!mShowingPublic && mGroupManager.isSummaryOfGroup(mStatusBarNotification)) {
mGroupManager.toggleGroupExpansion(mStatusBarNotification);
- mOnExpandClickListener.onExpandClicked(ExpandableNotificationRow.this,
+ mOnExpandClickListener.onExpandClicked(mEntry,
mGroupManager.isGroupExpanded(mStatusBarNotification));
} else {
- boolean nowExpanded = !isExpanded();
- setUserExpanded(nowExpanded);
+ boolean nowExpanded;
+ if (isPinned()) {
+ nowExpanded = !mExpandedWhenPinned;
+ mExpandedWhenPinned = nowExpanded;
+ } else {
+ nowExpanded = !isExpanded();
+ setUserExpanded(nowExpanded);
+ }
notifyHeightChanged(true);
- mOnExpandClickListener.onExpandClicked(ExpandableNotificationRow.this,
- nowExpanded);
+ mOnExpandClickListener.onExpandClicked(mEntry, nowExpanded);
}
}
};
@@ -303,6 +318,16 @@
}
@Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (event.getActionMasked() != MotionEvent.ACTION_DOWN
+ || !isChildInGroup() || isGroupExpanded()) {
+ return super.onTouchEvent(event);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
protected boolean shouldHideBackground() {
return super.shouldHideBackground() || mShowNoBackground;
}
@@ -358,9 +383,9 @@
}
public void startChildAnimation(StackScrollState finalState,
- StackStateAnimator stateAnimator, boolean withDelays, long delay, long duration) {
+ StackStateAnimator stateAnimator, long delay, long duration) {
if (mIsSummaryWithChildren) {
- mChildrenContainer.startAnimationToState(finalState, stateAnimator, withDelays, delay,
+ mChildrenContainer.startAnimationToState(finalState, stateAnimator, delay,
duration);
}
}
@@ -386,6 +411,12 @@
*/
public void setPinned(boolean pinned) {
mIsPinned = pinned;
+ if (pinned) {
+ setIconAnimationRunning(true);
+ mExpandedWhenPinned = false;
+ } else if (mExpandedWhenPinned) {
+ setUserExpanded(true);
+ }
setChronometerRunning(mLastChronometerRunning);
}
@@ -393,11 +424,22 @@
return mIsPinned;
}
- public int getHeadsUpHeight() {
+ /**
+ * @param atLeastMinHeight should the value returned be at least the minimum height.
+ * Used to avoid cyclic calls
+ * @return the height of the heads up notification when pinned
+ */
+ public int getPinnedHeadsUpHeight(boolean atLeastMinHeight) {
if (mIsSummaryWithChildren) {
return mChildrenContainer.getIntrinsicHeight();
}
- return mHeadsUpHeight;
+ if(mExpandedWhenPinned) {
+ return Math.max(getMaxExpandHeight(), mHeadsUpHeight);
+ } else if (atLeastMinHeight) {
+ return Math.max(getMinHeight(), mHeadsUpHeight);
+ } else {
+ return mHeadsUpHeight;
+ }
}
/**
@@ -461,6 +503,10 @@
mOnExpandClickListener = onExpandClickListener;
}
+ public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ mHeadsUpManager = headsUpManager;
+ }
+
public interface ExpansionLogger {
public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
}
@@ -517,8 +563,10 @@
protected void onFinishInflate() {
super.onFinishInflate();
mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
+ mPublicLayout.setContainingNotification(this);
mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
mPrivateLayout.setExpandClickListener(mExpandClickListener);
+ mPrivateLayout.setContainingNotification(this);
mPublicLayout.setExpandClickListener(mExpandClickListener);
mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@@ -551,13 +599,14 @@
private void updateChildrenVisibility() {
mPrivateLayout.setVisibility(!mShowingPublic && !mIsSummaryWithChildren ? VISIBLE
: INVISIBLE);
- if (mChildrenContainer == null) {
- return;
+ if (mChildrenContainer != null) {
+ mChildrenContainer.setVisibility(!mShowingPublic && mIsSummaryWithChildren ? VISIBLE
+ : INVISIBLE);
}
- mChildrenContainer.setVisibility(!mShowingPublic && mIsSummaryWithChildren ? VISIBLE
- : INVISIBLE);
- mNotificationHeader.setVisibility(!mShowingPublic && mIsSummaryWithChildren ? VISIBLE
- : INVISIBLE);
+ if (mNotificationHeader != null) {
+ mNotificationHeader.setVisibility(!mShowingPublic && mIsSummaryWithChildren ? VISIBLE
+ : INVISIBLE);
+ }
// The limits might have changed if the view suddenly became a group or vice versa
updateLimits();
}
@@ -601,6 +650,12 @@
mPrivateLayout.updateExpandButtons(isExpandable());
}
+ @Override
+ public void setClipToActualHeight(boolean clipToActualHeight) {
+ super.setClipToActualHeight(clipToActualHeight || isUserLocked());
+ getShowingLayout().setClipToActualHeight(clipToActualHeight || isUserLocked());
+ }
+
/**
* @return whether the user has changed the expansion state
*/
@@ -718,7 +773,6 @@
if (isUserLocked()) {
return getActualHeight();
}
- boolean inExpansionState = isExpanded();
if (mGuts != null && mGuts.areGutsExposed()) {
return mGuts.getHeight();
} else if ((isChildInGroup() && !isGroupExpanded())) {
@@ -728,12 +782,14 @@
} else if (mIsSummaryWithChildren && !mOnKeyguard) {
return mChildrenContainer.getIntrinsicHeight();
} else if (mIsHeadsUp) {
- if (inExpansionState) {
+ if (isPinned()) {
+ return getPinnedHeadsUpHeight(true /* atLeastMinHeight */);
+ } else if (isExpanded()) {
return Math.max(getMaxExpandHeight(), mHeadsUpHeight);
} else {
return Math.max(getMinHeight(), mHeadsUpHeight);
}
- } else if (inExpansionState) {
+ } else if (isExpanded()) {
return getMaxExpandHeight();
} else {
return getMinHeight();
@@ -818,6 +874,12 @@
}
}
+ @Override
+ public void notifyHeightChanged(boolean needsAnimation) {
+ super.notifyHeightChanged(needsAnimation);
+ getShowingLayout().requestSelectLayout(needsAnimation || isUserLocked());
+ }
+
public void setSensitive(boolean sensitive) {
mSensitive = sensitive;
}
@@ -961,8 +1023,12 @@
@Override
public int getMinHeight() {
- if (mIsSummaryWithChildren && !isGroupExpanded() && !mShowingPublic) {
+ if (mIsHeadsUp && mHeadsUpManager.isTrackingHeadsUp()) {
+ return getPinnedHeadsUpHeight(false /* atLeastMinHeight */);
+ } else if (mIsSummaryWithChildren && !isGroupExpanded() && !mShowingPublic) {
return mChildrenContainer.getMinHeight();
+ } else if (mIsHeadsUp) {
+ return mHeadsUpHeight;
}
NotificationContentView showingLayout = getShowingLayout();
return showingLayout.getMinHeight();
@@ -1001,7 +1067,7 @@
addView(mNotificationHeader, indexOfChild(mChildrenContainer) + 1);
} else {
header.reapply(getContext(), mNotificationHeader);
- mNotificationHeaderWrapper.notifyContentUpdated();
+ mNotificationHeaderWrapper.notifyContentUpdated(mEntry.notification);
}
updateHeaderExpandButton();
updateChildrenHeaderAppearance();
@@ -1059,6 +1125,17 @@
mLoggingKey = key;
}
+ @Override
+ protected boolean disallowSingleClick(MotionEvent event) {
+ float x = event.getX();
+ float y = event.getY();
+ NotificationHeaderView header = getNotificationHeader();
+ if (header != null) {
+ return header.isInTouchRect(x, y);
+ }
+ return super.disallowSingleClick(event);
+ }
+
private void logExpansionEvent(boolean userAction, boolean wasExpanded) {
final boolean nowExpanded = isExpanded();
if (wasExpanded != nowExpanded && mLogger != null) {
@@ -1067,6 +1144,6 @@
}
public interface OnExpandClickListener {
- void onExpandClicked(View clickedView, boolean nowExpanded);
+ void onExpandClicked(NotificationData.Entry clickedEntry, boolean nowExpanded);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index d6855a5..c190864 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -45,6 +45,7 @@
private static Rect mClipRect = new Rect();
private boolean mWillBeGone;
private int mMinClipTopAmount = 0;
+ private boolean mClipToActualHeight = true;
public ExpandableView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -326,12 +327,21 @@
}
private void updateClipping() {
- int top = mClipTopOptimization;
- if (top >= getActualHeight()) {
- top = getActualHeight() - 1;
+ if (mClipToActualHeight) {
+ int top = mClipTopOptimization;
+ if (top >= getActualHeight()) {
+ top = getActualHeight() - 1;
+ }
+ mClipRect.set(0, top, getWidth(), getActualHeight());
+ setClipBounds(mClipRect);
+ } else {
+ setClipBounds(null);
}
- mClipRect.set(0, top, getWidth(), getActualHeight());
- setClipBounds(mClipRect);
+ }
+
+ public void setClipToActualHeight(boolean clipToActualHeight) {
+ mClipToActualHeight = clipToActualHeight;
+ updateClipping();
}
public int getClipTopOptimization() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 02a39e7..36cf906 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -20,9 +20,6 @@
import android.app.RemoteInput;
import android.content.Context;
import android.graphics.Outline;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.os.Build;
import android.service.notification.StatusBarNotification;
@@ -32,13 +29,13 @@
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
-import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.HybridNotificationViewManager;
+import com.android.systemui.statusbar.notification.NotificationCustomViewWrapper;
+import com.android.systemui.statusbar.notification.NotificationViewWrapper;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.policy.RemoteInputView;
@@ -49,7 +46,6 @@
*/
public class NotificationContentView extends FrameLayout {
- private static final long ANIMATION_DURATION_LENGTH = 170;
private static final int VISIBLE_TYPE_CONTRACTED = 0;
private static final int VISIBLE_TYPE_EXPANDED = 1;
private static final int VISIBLE_TYPE_HEADSUP = 2;
@@ -57,9 +53,16 @@
private final Rect mClipBounds = new Rect();
private final int mRoundRectRadius;
- private final Interpolator mLinearInterpolator = new LinearInterpolator();
private final boolean mRoundRectClippingEnabled;
private final int mMinContractedHeight;
+ private final OnLayoutChangeListener mLayoutUpdater = new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft,
+ int oldTop, int oldRight, int oldBottom) {
+ selectLayout(false /* animate */, false /* force */);
+ }
+ };
private View mContractedChild;
@@ -76,7 +79,6 @@
private int mUnrestrictedContentHeight;
private int mVisibleType = VISIBLE_TYPE_CONTRACTED;
private boolean mDark;
- private final Paint mFadePaint = new Paint();
private boolean mAnimate;
private boolean mIsHeadsUp;
private boolean mShowingLegacyBackground;
@@ -108,11 +110,12 @@
private OnClickListener mExpandClickListener;
private boolean mBeforeN;
private boolean mExpandable;
+ private boolean mClipToActualHeight = true;
+ private ExpandableNotificationRow mContainingNotification;
public NotificationContentView(Context context, AttributeSet attrs) {
super(context, attrs);
mHybridViewManager = new HybridNotificationViewManager(getContext(), this);
- mFadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
mRoundRectRadius = getResources().getDimensionPixelSize(
R.dimen.notification_material_rounded_rect_radius);
mRoundRectClippingEnabled = getResources().getBoolean(
@@ -241,10 +244,12 @@
public void setContractedChild(View child) {
if (mContractedChild != null) {
mContractedChild.animate().cancel();
+ mContractedChild.removeOnLayoutChangeListener(mLayoutUpdater);
removeView(mContractedChild);
}
addView(child);
mContractedChild = child;
+ mContractedChild.addOnLayoutChangeListener(mLayoutUpdater);
mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child);
selectLayout(false /* animate */, true /* force */);
mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
@@ -254,10 +259,12 @@
public void setExpandedChild(View child) {
if (mExpandedChild != null) {
mExpandedChild.animate().cancel();
+ mExpandedChild.removeOnLayoutChangeListener(mLayoutUpdater);
removeView(mExpandedChild);
}
addView(child);
mExpandedChild = child;
+ mExpandedChild.addOnLayoutChangeListener(mLayoutUpdater);
mExpandedWrapper = NotificationViewWrapper.wrap(getContext(), child);
selectLayout(false /* animate */, true /* force */);
updateRoundRectClipping();
@@ -266,10 +273,12 @@
public void setHeadsUpChild(View child) {
if (mHeadsUpChild != null) {
mHeadsUpChild.animate().cancel();
+ mHeadsUpChild.removeOnLayoutChangeListener(mLayoutUpdater);
removeView(mHeadsUpChild);
}
addView(child);
mHeadsUpChild = child;
+ mHeadsUpChild.addOnLayoutChangeListener(mLayoutUpdater);
mHeadsUpWrapper = NotificationViewWrapper.wrap(getContext(), child);
selectLayout(false /* animate */, true /* force */);
updateRoundRectClipping();
@@ -357,8 +366,17 @@
}
private void updateClipping() {
- mClipBounds.set(0, mClipTopAmount, getWidth(), mContentHeight);
- setClipBounds(mClipBounds);
+ if (mClipToActualHeight) {
+ mClipBounds.set(0, mClipTopAmount, getWidth(), mContentHeight);
+ setClipBounds(mClipBounds);
+ } else {
+ setClipBounds(null);
+ }
+ }
+
+ public void setClipToActualHeight(boolean clipToActualHeight) {
+ mClipToActualHeight = clipToActualHeight;
+ updateClipping();
}
private void selectLayout(boolean animate, boolean force) {
@@ -371,7 +389,7 @@
|| (visibleType == VISIBLE_TYPE_HEADSUP && mHeadsUpChild != null)
|| (visibleType == VISIBLE_TYPE_SINGLELINE && mSingleLineView != null)
|| visibleType == VISIBLE_TYPE_CONTRACTED)) {
- runSwitchAnimation(visibleType);
+ animateToVisibleType(visibleType);
} else {
updateViewVisibilities(visibleType);
}
@@ -381,59 +399,55 @@
private void updateViewVisibilities(int visibleType) {
boolean contractedVisible = visibleType == VISIBLE_TYPE_CONTRACTED;
- mContractedChild.setVisibility(contractedVisible ? View.VISIBLE : View.INVISIBLE);
- mContractedChild.setAlpha(contractedVisible ? 1f : 0f);
- mContractedChild.setLayerType(LAYER_TYPE_NONE, null);
+ mContractedWrapper.setVisible(contractedVisible);
if (mExpandedChild != null) {
boolean expandedVisible = visibleType == VISIBLE_TYPE_EXPANDED;
- mExpandedChild.setVisibility(expandedVisible ? View.VISIBLE : View.INVISIBLE);
- mExpandedChild.setAlpha(expandedVisible ? 1f : 0f);
- mExpandedChild.setLayerType(LAYER_TYPE_NONE, null);
+ mExpandedWrapper.setVisible(expandedVisible);
}
if (mHeadsUpChild != null) {
boolean headsUpVisible = visibleType == VISIBLE_TYPE_HEADSUP;
- mHeadsUpChild.setVisibility(headsUpVisible ? View.VISIBLE : View.INVISIBLE);
- mHeadsUpChild.setAlpha(headsUpVisible ? 1f : 0f);
- mHeadsUpChild.setLayerType(LAYER_TYPE_NONE, null);
+ mHeadsUpWrapper.setVisible(headsUpVisible);
}
if (mSingleLineView != null) {
boolean singleLineVisible = visibleType == VISIBLE_TYPE_SINGLELINE;
- mSingleLineView.setVisibility(singleLineVisible ? View.VISIBLE : View.INVISIBLE);
- mSingleLineView.setAlpha(singleLineVisible ? 1f : 0f);
- mSingleLineView.setLayerType(LAYER_TYPE_NONE, null);
+ mSingleLineView.setVisible(singleLineVisible);
}
- setLayerType(LAYER_TYPE_NONE, null);
updateRoundRectClipping();
}
- private void runSwitchAnimation(int visibleType) {
- View shownView = getViewForVisibleType(visibleType);
- View hiddenView = getViewForVisibleType(mVisibleType);
- shownView.setVisibility(View.VISIBLE);
- hiddenView.setVisibility(View.VISIBLE);
- shownView.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
- hiddenView.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
- setLayerType(LAYER_TYPE_HARDWARE, null);
- hiddenView.animate()
- .alpha(0f)
- .setDuration(ANIMATION_DURATION_LENGTH)
- .setInterpolator(mLinearInterpolator)
- .withEndAction(null); // In case we have multiple changes in one frame.
- shownView.animate()
- .alpha(1f)
- .setDuration(ANIMATION_DURATION_LENGTH)
- .setInterpolator(mLinearInterpolator)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- updateViewVisibilities(mVisibleType);
- }
- });
+ private void animateToVisibleType(int visibleType) {
+ final TransformableView shownView = getTransformableViewForVisibleType(visibleType);
+ final TransformableView hiddenView = getTransformableViewForVisibleType(mVisibleType);
+ shownView.transformFrom(hiddenView);
+ getViewForVisibleType(visibleType).setVisibility(View.VISIBLE);
+ hiddenView.transformTo(shownView, new Runnable() {
+ @Override
+ public void run() {
+ hiddenView.setVisible(false);
+ }
+ });
updateRoundRectClipping();
}
/**
* @param visibleType one of the static enum types in this view
+ * @return the corresponding transformable view according to the given visible type
+ */
+ private TransformableView getTransformableViewForVisibleType(int visibleType) {
+ switch (visibleType) {
+ case VISIBLE_TYPE_EXPANDED:
+ return mExpandedWrapper;
+ case VISIBLE_TYPE_HEADSUP:
+ return mHeadsUpWrapper;
+ case VISIBLE_TYPE_SINGLELINE:
+ return mSingleLineView;
+ default:
+ return mContractedWrapper;
+ }
+ }
+
+ /**
+ * @param visibleType one of the static enum types in this view
* @return the corresponding view according to the given visible type
*/
private View getViewForVisibleType(int visibleType) {
@@ -455,7 +469,8 @@
private int calculateVisibleType() {
boolean noExpandedChild = mExpandedChild == null;
- if (!noExpandedChild && mContentHeight == mExpandedChild.getHeight()) {
+ int viewHeight = Math.min(mContentHeight, mContainingNotification.getIntrinsicHeight());
+ if (!noExpandedChild && viewHeight == mExpandedChild.getHeight()) {
return VISIBLE_TYPE_EXPANDED;
}
if (mIsChildInGroup && !isGroupExpanded()) {
@@ -463,13 +478,13 @@
}
if (mIsHeadsUp && mHeadsUpChild != null) {
- if (mContentHeight <= mHeadsUpChild.getHeight() || noExpandedChild) {
+ if (viewHeight <= mHeadsUpChild.getHeight() || noExpandedChild) {
return VISIBLE_TYPE_HEADSUP;
} else {
return VISIBLE_TYPE_EXPANDED;
}
} else {
- if (mContentHeight <= mContractedChild.getHeight() || noExpandedChild) {
+ if (viewHeight <= mContractedChild.getHeight() || noExpandedChild) {
return VISIBLE_TYPE_CONTRACTED;
} else {
return VISIBLE_TYPE_EXPANDED;
@@ -484,8 +499,17 @@
public void setDark(boolean dark, boolean fade, long delay) {
if (mDark == dark || mContractedChild == null) return;
mDark = dark;
- mContractedWrapper.setDark(dark && !mShowingLegacyBackground, fade, delay);
- if (mSingleLineView != null) {
+ dark = dark && !mShowingLegacyBackground;
+ if (mVisibleType == VISIBLE_TYPE_CONTRACTED) {
+ mContractedWrapper.setDark(dark, fade, delay);
+ }
+ if (mVisibleType == VISIBLE_TYPE_EXPANDED) {
+ mExpandedWrapper.setDark(dark, fade, delay);
+ }
+ if (mVisibleType == VISIBLE_TYPE_HEADSUP) {
+ mHeadsUpWrapper.setDark(dark, fade, delay);
+ }
+ if (mSingleLineView != null && mVisibleType == VISIBLE_TYPE_SINGLELINE) {
mSingleLineView.setDark(dark, fade, delay);
}
}
@@ -520,14 +544,14 @@
applyRemoteInput(entry);
selectLayout(false /* animate */, true /* force */);
if (mContractedChild != null) {
- mContractedWrapper.notifyContentUpdated();
+ mContractedWrapper.notifyContentUpdated(entry.notification);
mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
}
if (mExpandedChild != null) {
- mExpandedWrapper.notifyContentUpdated();
+ mExpandedWrapper.notifyContentUpdated(entry.notification);
}
if (mHeadsUpChild != null) {
- mHeadsUpWrapper.notifyContentUpdated();
+ mHeadsUpWrapper.notifyContentUpdated(entry.notification);
}
updateRoundRectClipping();
}
@@ -644,4 +668,12 @@
}
return header;
}
+
+ public void setContainingNotification(ExpandableNotificationRow containingNotification) {
+ mContainingNotification = containingNotification;
+ }
+
+ public void requestSelectLayout(boolean needsAnimation) {
+ selectLayout(needsAnimation, false);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
index 52326e3..e4cd7d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
@@ -29,6 +29,7 @@
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
import android.view.View;
+import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.SeekBar;
@@ -133,6 +134,10 @@
} catch (PackageManager.NameNotFoundException e) {
// unlikely.
}
+ if (systemApp) {
+ ((ImageView) row.findViewById(R.id.low_importance)).getDrawable().setTint(
+ mContext.getColor(R.color.notification_guts_disabled_icon_tint));
+ }
final int minProgress = systemApp ?
NotificationListenerService.Ranking.IMPORTANCE_LOW
: NotificationListenerService.Ranking.IMPORTANCE_NONE;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
index 859a330..98a37f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
@@ -110,6 +110,25 @@
sIconExtractor,
sGreyComparator,
mGreyApplicator));
+ mComparators.add(new HeaderProcessor(mRow,
+ com.android.internal.R.id.profile_badge,
+ null /* Extractor */,
+ new ViewComparator() {
+ @Override
+ public boolean compare(View parent, View child, Object parentData,
+ Object childData) {
+ return parent.getVisibility() == View.VISIBLE;
+ }
+
+ @Override
+ public boolean isEmpty(View view) {
+ if (view instanceof ImageView) {
+ return ((ImageView) view).getDrawable() == null;
+ }
+ return false;
+ }
+ },
+ sVisibilityApplicator));
mComparators.add(HeaderProcessor.forTextView(mRow,
com.android.internal.R.id.app_name_text));
mComparators.add(HeaderProcessor.forTextView(mRow,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
deleted file mode 100644
index 77e8c55..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar;
-
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.graphics.Color;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.ProgressBar;
-
-/**
- * Wraps a notification view inflated from a template.
- */
-public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapper {
-
- private static final int mDarkProgressTint = 0xffffffff;
-
- protected ImageView mPicture;
- private ProgressBar mProgressBar;
-
- protected NotificationTemplateViewWrapper(Context ctx, View view) {
- super(ctx, view);
- resolveTemplateViews();
- }
-
- private void resolveTemplateViews() {
- View mainColumn = mView.findViewById(com.android.internal.R.id.notification_main_column);
- mPicture = (ImageView) mView.findViewById(com.android.internal.R.id.right_icon);
- final View progress = mView.findViewById(com.android.internal.R.id.progress);
- if (progress instanceof ProgressBar) {
- mProgressBar = (ProgressBar) progress;
- } else {
- // It's still a viewstub
- mProgressBar = null;
- }
- if (mainColumn != null) {
- mInvertHelper.addTarget(mainColumn);
- }
- }
-
- @Override
- public void notifyContentUpdated() {
- super.notifyContentUpdated();
-
- // Reinspect the notification.
- resolveTemplateViews();
- }
-
- @Override
- public void setDark(boolean dark, boolean fade, long delay) {
- super.setDark(dark, fade, delay);
- setPictureGrayscale(dark, fade, delay);
- setProgressBarDark(dark, fade, delay);
- }
-
- private void setProgressBarDark(boolean dark, boolean fade, long delay) {
- if (mProgressBar != null) {
- if (fade) {
- fadeProgressDark(mProgressBar, dark, delay);
- } else {
- updateProgressDark(mProgressBar, dark);
- }
- }
- }
-
- private void fadeProgressDark(final ProgressBar target, final boolean dark, long delay) {
- startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float t = (float) animation.getAnimatedValue();
- updateProgressDark(target, t);
- }
- }, dark, delay, null /* listener */);
- }
-
- private void updateProgressDark(ProgressBar target, float intensity) {
- int color = interpolateColor(mColor, mDarkProgressTint, intensity);
- target.getIndeterminateDrawable().mutate().setTint(color);
- target.getProgressDrawable().mutate().setTint(color);
- }
-
- private void updateProgressDark(ProgressBar target, boolean dark) {
- updateProgressDark(target, dark ? 1f : 0f);
- }
-
- protected void setPictureGrayscale(boolean grayscale, boolean fade, long delay) {
- if (mPicture != null) {
- if (fade) {
- fadeGrayscale(mPicture, grayscale, delay);
- } else {
- updateGrayscale(mPicture, grayscale);
- }
- }
- }
-
- private static int interpolateColor(int source, int target, float t) {
- int aSource = Color.alpha(source);
- int rSource = Color.red(source);
- int gSource = Color.green(source);
- int bSource = Color.blue(source);
- int aTarget = Color.alpha(target);
- int rTarget = Color.red(target);
- int gTarget = Color.green(target);
- int bTarget = Color.blue(target);
- return Color.argb(
- (int) (aSource * (1f - t) + aTarget * t),
- (int) (rSource * (1f - t) + rTarget * t),
- (int) (gSource * (1f - t) + gTarget * t),
- (int) (bSource * (1f - t) + bTarget * t));
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScalingDrawableWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScalingDrawableWrapper.java
new file mode 100644
index 0000000..24277e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ScalingDrawableWrapper.java
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.statusbar;
+
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.DrawableWrapper;
+
+/**
+ * An extension of {@link DrawableWrapper} that will take a given Drawable and scale it by
+ * the given factor.
+ */
+class ScalingDrawableWrapper extends DrawableWrapper {
+ private float mScaleFactor;
+
+ public ScalingDrawableWrapper(Drawable drawable, float scaleFactor) {
+ super(drawable);
+ mScaleFactor = scaleFactor;
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return (int) (super.getIntrinsicWidth() * mScaleFactor);
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return (int) (super.getIntrinsicHeight() * mScaleFactor);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index cc30882..6801e5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -16,16 +16,18 @@
package com.android.systemui.statusbar;
+import android.annotation.DrawableRes;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.Resources;
import android.graphics.Color;
-import android.graphics.PorterDuff;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.telephony.SubscriptionInfo;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -86,10 +88,11 @@
View mWifiSignalSpacer;
LinearLayout mMobileSignalGroup;
- private int mWideTypeIconStartPadding;
- private int mSecondaryTelephonyPadding;
- private int mEndPadding;
- private int mEndPaddingNothingVisible;
+ private final int mWideTypeIconStartPadding;
+ private final int mSecondaryTelephonyPadding;
+ private final int mEndPadding;
+ private final int mEndPaddingNothingVisible;
+ private final float mIconScaleFactor;
private boolean mBlockAirplane;
private boolean mBlockMobile;
@@ -106,6 +109,17 @@
public SignalClusterView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+
+ Resources res = getResources();
+ mWideTypeIconStartPadding = res.getDimensionPixelSize(R.dimen.wide_type_icon_start_padding);
+ mSecondaryTelephonyPadding = res.getDimensionPixelSize(R.dimen.secondary_telephony_padding);
+ mEndPadding = res.getDimensionPixelSize(R.dimen.signal_cluster_battery_padding);
+ mEndPaddingNothingVisible = res.getDimensionPixelSize(
+ R.dimen.no_signal_cluster_battery_padding);
+
+ TypedValue typedValue = new TypedValue();
+ res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
+ mIconScaleFactor = typedValue.getFloat();
}
@Override
@@ -146,19 +160,6 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mWideTypeIconStartPadding = getContext().getResources().getDimensionPixelSize(
- R.dimen.wide_type_icon_start_padding);
- mSecondaryTelephonyPadding = getContext().getResources().getDimensionPixelSize(
- R.dimen.secondary_telephony_padding);
- mEndPadding = getContext().getResources().getDimensionPixelSize(
- R.dimen.signal_cluster_battery_padding);
- mEndPaddingNothingVisible = getContext().getResources().getDimensionPixelSize(
- R.dimen.no_signal_cluster_battery_padding);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
mVpn = (ImageView) findViewById(R.id.vpn);
mEthernetGroup = (ViewGroup) findViewById(R.id.ethernet_combo);
@@ -174,6 +175,32 @@
mWifiAirplaneSpacer = findViewById(R.id.wifi_airplane_spacer);
mWifiSignalSpacer = findViewById(R.id.wifi_signal_spacer);
mMobileSignalGroup = (LinearLayout) findViewById(R.id.mobile_signal_group);
+
+ maybeScaleVpnAndNoSimsIcons();
+ }
+
+ /**
+ * Extracts the icon off of the VPN and no sims views and maybe scale them by
+ * {@link #mIconScaleFactor}. Note that the other icons are not scaled here because they are
+ * dynamic. As such, they need to be scaled each time the icon changes in {@link #apply()}.
+ */
+ private void maybeScaleVpnAndNoSimsIcons() {
+ if (mIconScaleFactor == 1.f) {
+ return;
+ }
+
+ mVpn.setImageDrawable(new ScalingDrawableWrapper(mVpn.getDrawable(), mIconScaleFactor));
+
+ mNoSims.setImageDrawable(
+ new ScalingDrawableWrapper(mNoSims.getDrawable(), mIconScaleFactor));
+ mNoSimsDark.setImageDrawable(
+ new ScalingDrawableWrapper(mNoSimsDark.getDrawable(), mIconScaleFactor));
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
for (PhoneState state : mPhoneStates) {
mMobileSignalGroup.addView(state.mMobileGroup);
}
@@ -185,14 +212,7 @@
@Override
protected void onDetachedFromWindow() {
- mVpn = null;
- mEthernetGroup = null;
- mEthernet = null;
- mWifiGroup = null;
- mWifi = null;
- mAirplane = null;
mMobileSignalGroup.removeAllViews();
- mMobileSignalGroup = null;
TunerService.get(mContext).removeTunable(this);
super.onDetachedFromWindow();
@@ -351,10 +371,13 @@
for (PhoneState state : mPhoneStates) {
if (state.mMobile != null) {
+ state.maybeStopAnimatableDrawable(state.mMobile);
state.mMobile.setImageDrawable(null);
+ state.mLastMobileStrengthId = -1;
}
if (state.mMobileType != null) {
state.mMobileType.setImageDrawable(null);
+ state.mLastMobileTypeId = -1;
}
}
@@ -380,8 +403,8 @@
if (mEthernetVisible) {
if (mLastEthernetIconId != mEthernetIconId) {
- mEthernet.setImageResource(mEthernetIconId);
- mEthernetDark.setImageResource(mEthernetIconId);
+ setIconForView(mEthernet, mEthernetIconId);
+ setIconForView(mEthernetDark, mEthernetIconId);
mLastEthernetIconId = mEthernetIconId;
}
mEthernetGroup.setContentDescription(mEthernetDescription);
@@ -394,11 +417,10 @@
String.format("ethernet: %s",
(mEthernetVisible ? "VISIBLE" : "GONE")));
-
if (mWifiVisible) {
if (mWifiStrengthId != mLastWifiStrengthId) {
- mWifi.setImageResource(mWifiStrengthId);
- mWifiDark.setImageResource(mWifiStrengthId);
+ setIconForView(mWifi, mWifiStrengthId);
+ setIconForView(mWifiDark, mWifiStrengthId);
mLastWifiStrengthId = mWifiStrengthId;
}
mWifiGroup.setContentDescription(mWifiDescription);
@@ -425,7 +447,7 @@
if (mIsAirplaneMode) {
if (mLastAirplaneIconId != mAirplaneIconId) {
- mAirplane.setImageResource(mAirplaneIconId);
+ setIconForView(mAirplane, mAirplaneIconId);
mLastAirplaneIconId = mAirplaneIconId;
}
mAirplane.setContentDescription(mAirplaneContentDescription);
@@ -453,6 +475,21 @@
setPaddingRelative(0, 0, anythingVisible ? mEndPadding : mEndPaddingNothingVisible, 0);
}
+ /**
+ * Sets the given drawable id on the view. This method will also scale the icon by
+ * {@link #mIconScaleFactor} if appropriate.
+ */
+ private void setIconForView(ImageView imageView, @DrawableRes int iconId) {
+ // Using the imageView's context to retrieve the Drawable so that theme is preserved.
+ Drawable icon = imageView.getContext().getDrawable(iconId);
+
+ if (mIconScaleFactor == 1.f) {
+ imageView.setImageDrawable(icon);
+ } else {
+ imageView.setImageDrawable(new ScalingDrawableWrapper(icon, mIconScaleFactor));
+ }
+ }
+
public void setIconTint(int tint, float darkIntensity) {
boolean changed = tint != mIconTint || darkIntensity != mDarkIntensity;
mIconTint = tint;
@@ -486,6 +523,8 @@
private final int mSubId;
private boolean mMobileVisible = false;
private int mMobileStrengthId = 0, mMobileTypeId = 0;
+ private int mLastMobileStrengthId = -1;
+ private int mLastMobileTypeId = -1;
private boolean mIsMobileTypeIconWide;
private String mMobileDescription, mMobileTypeDescription;
@@ -508,25 +547,16 @@
public boolean apply(boolean isSecondaryIcon) {
if (mMobileVisible && !mIsAirplaneMode) {
- mMobile.setImageResource(mMobileStrengthId);
- Drawable mobileDrawable = mMobile.getDrawable();
- if (mobileDrawable instanceof Animatable) {
- Animatable ad = (Animatable) mobileDrawable;
- if (!ad.isRunning()) {
- ad.start();
- }
+ if (mLastMobileStrengthId != mMobileStrengthId) {
+ updateAnimatableIcon(mMobile, mMobileStrengthId);
+ updateAnimatableIcon(mMobileDark, mMobileStrengthId);
+ mLastMobileStrengthId = mMobileStrengthId;
}
- mMobileDark.setImageResource(mMobileStrengthId);
- Drawable mobileDarkDrawable = mMobileDark.getDrawable();
- if (mobileDarkDrawable instanceof Animatable) {
- Animatable ad = (Animatable) mobileDarkDrawable;
- if (!ad.isRunning()) {
- ad.start();
- }
+ if (mLastMobileTypeId != mMobileTypeId) {
+ mMobileType.setImageResource(mMobileTypeId);
+ mLastMobileTypeId = mMobileTypeId;
}
-
- mMobileType.setImageResource(mMobileTypeId);
mMobileGroup.setContentDescription(mMobileTypeDescription
+ " " + mMobileDescription);
mMobileGroup.setVisibility(View.VISIBLE);
@@ -550,6 +580,32 @@
return mMobileVisible;
}
+ private void updateAnimatableIcon(ImageView view, int resId) {
+ maybeStopAnimatableDrawable(view);
+ view.setImageResource(resId);
+ maybeStartAnimatableDrawable(view);
+ }
+
+ private void maybeStopAnimatableDrawable(ImageView view) {
+ Drawable drawable = view.getDrawable();
+ if (drawable instanceof Animatable) {
+ Animatable ad = (Animatable) drawable;
+ if (ad.isRunning()) {
+ ad.stop();
+ }
+ }
+ }
+
+ private void maybeStartAnimatableDrawable(ImageView view) {
+ Drawable drawable = view.getDrawable();
+ if (drawable instanceof Animatable) {
+ Animatable ad = (Animatable) drawable;
+ if (!ad.isRunning()) {
+ ad.start();
+ }
+ }
+ }
+
public void populateAccessibilityEvent(AccessibilityEvent event) {
if (mMobileVisible && mMobileGroup != null
&& mMobileGroup.getContentDescription() != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index de7a8db..5a7cf86 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -24,10 +24,13 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+import android.graphics.drawable.ScaleDrawable;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
import android.view.ViewDebug;
import android.view.accessibility.AccessibilityEvent;
import com.android.internal.statusbar.StatusBarIcon;
@@ -189,12 +192,24 @@
* @return Drawable for this item, or null if the package or item could not
* be found
*/
- public static Drawable getIcon(Context context, StatusBarIcon icon) {
- int userId = icon.user.getIdentifier();
+ public static Drawable getIcon(Context context, StatusBarIcon statusBarIcon) {
+ int userId = statusBarIcon.user.getIdentifier();
if (userId == UserHandle.USER_ALL) {
userId = UserHandle.USER_SYSTEM;
}
- return icon.icon.loadDrawableAsUser(context, userId);
+
+ Drawable icon = statusBarIcon.icon.loadDrawableAsUser(context, userId);
+
+ TypedValue typedValue = new TypedValue();
+ context.getResources().getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
+ float scaleFactor = typedValue.getFloat();
+
+ // No need to scale the icon, so return it as is.
+ if (scaleFactor == 1.f) {
+ return icon;
+ }
+
+ return new ScalingDrawableWrapper(icon, scaleFactor);
}
public StatusBarIcon getStatusBarIcon() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/TransformableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/TransformableView.java
new file mode 100644
index 0000000..38b6497
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/TransformableView.java
@@ -0,0 +1,55 @@
+/*
+ * 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
+ */
+
+package com.android.systemui.statusbar;
+
+import com.android.systemui.statusbar.notification.TransformState;
+
+/**
+ * A view that can be transformed to and from.
+ */
+public interface TransformableView {
+ int TRANSFORMING_VIEW_HEADER = 0;
+ int TRANSFORMING_VIEW_TITLE = 1;
+ int TRANSFORMING_VIEW_TEXT = 2;
+ int TRANSFORMING_VIEW_IMAGE = 3;
+ int TRANSFORMING_VIEW_PROGRESS = 4;
+
+ /**
+ * Get the current state of a view in a transform animation
+ * @param fadingView which view we are interested in
+ * @return the current transform state of this viewtype
+ */
+ TransformState getCurrentState(int fadingView);
+
+ /**
+ * Transform to the given view
+ * @param notification the view to transform to
+ */
+ void transformTo(TransformableView notification, Runnable endRunnable);
+
+ /**
+ * Transform to this view from the given view
+ * @param notification the view to transform from
+ */
+ void transformFrom(TransformableView notification);
+
+ /**
+ * Set this view to be fully visible or gone
+ * @param visible
+ */
+ void setVisible(boolean visible);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/UserGridView.java b/packages/SystemUI/src/com/android/systemui/statusbar/UserGridView.java
index 32caf9f..2f8bc2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/UserGridView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/UserGridView.java
@@ -137,7 +137,7 @@
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater)getContext().getSystemService
(Context.LAYOUT_INFLATER_SERVICE);
- convertView = inflater.inflate(R.layout.fullscreen_user_pod, null);
+ convertView = inflater.inflate(R.layout.car_fullscreen_user_pod, null);
}
UserSwitcherController.UserRecord record = getItem(position);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
new file mode 100644
index 0000000..63ff5aa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
@@ -0,0 +1,194 @@
+/*
+ * 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
+ */
+
+package com.android.systemui.statusbar;
+
+import android.os.Handler;
+import android.util.ArrayMap;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.TransformState;
+
+import java.util.Stack;
+
+/**
+ * A view that can be transformed to and from.
+ */
+public class ViewTransformationHelper implements TransformableView {
+
+ private static final int TAG_CONTAINS_TRANSFORMED_VIEW = R.id.contains_transformed_view;
+
+ private final Handler mHandler = new Handler();
+ private ArrayMap<Integer, View> mTransformedViews = new ArrayMap<>();
+ private ArrayMap<Integer, CustomTransformation> mCustomTransformations = new ArrayMap<>();
+
+ public void addTransformedView(int key, View transformedView) {
+ mTransformedViews.put(key, transformedView);
+ }
+
+ public void reset() {
+ mTransformedViews.clear();
+ }
+
+ public void setCustomTransformation(CustomTransformation transformation, int viewType) {
+ mCustomTransformations.put(viewType, transformation);
+ }
+
+ @Override
+ public TransformState getCurrentState(int fadingView) {
+ View view = mTransformedViews.get(fadingView);
+ if (view != null && view.getVisibility() != View.GONE) {
+ return TransformState.createFrom(view);
+ }
+ return null;
+ }
+
+ @Override
+ public void transformTo(TransformableView notification, Runnable endRunnable) {
+ Runnable runnable = endRunnable;
+ for (Integer viewType : mTransformedViews.keySet()) {
+ TransformState ownState = getCurrentState(viewType);
+ if (ownState != null) {
+ CustomTransformation customTransformation = mCustomTransformations.get(viewType);
+ if (customTransformation != null && customTransformation.transformTo(
+ ownState, notification, runnable)) {
+ ownState.recycle();
+ runnable = null;
+ continue;
+ }
+ TransformState otherState = notification.getCurrentState(viewType);
+ if (otherState != null) {
+ boolean run = ownState.transformViewTo(otherState, runnable);
+ otherState.recycle();
+ if (run) {
+ runnable = null;
+ }
+ } else {
+ // there's no other view available
+ CrossFadeHelper.fadeOut(mTransformedViews.get(viewType), runnable);
+ runnable = null;
+ }
+ ownState.recycle();
+ }
+ }
+ if (runnable != null) {
+ // We need to post, since the visible type is only set after the transformation is
+ // started
+ mHandler.post(runnable);
+ }
+ }
+
+ @Override
+ public void transformFrom(TransformableView notification) {
+ for (Integer viewType : mTransformedViews.keySet()) {
+ TransformState ownState = getCurrentState(viewType);
+ if (ownState != null) {
+ CustomTransformation customTransformation = mCustomTransformations.get(viewType);
+ if (customTransformation != null && customTransformation.transformFrom(
+ ownState, notification)) {
+ ownState.recycle();
+ continue;
+ }
+ TransformState otherState = notification.getCurrentState(viewType);
+ if (otherState != null) {
+ ownState.transformViewFrom(otherState);
+ otherState.recycle();
+ } else {
+ // There's no other view, lets fade us in
+ // Certain views need to prepare the fade in and make sure its children are
+ // completely visible. An example is the notification header.
+ ownState.prepareFadeIn();
+ CrossFadeHelper.fadeIn(mTransformedViews.get(viewType));
+ }
+ ownState.recycle();
+ }
+ }
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ for (Integer viewType : mTransformedViews.keySet()) {
+ TransformState ownState = getCurrentState(viewType);
+ if (ownState != null) {
+ ownState.setVisible(visible);
+ ownState.recycle();
+ }
+ }
+ }
+
+ /**
+ * Add the remaining transformation views such that all views are being transformed correctly
+ * @param viewRoot the root below which all elements need to be transformed
+ */
+ public void addRemainingTransformTypes(View viewRoot) {
+ // lets now tag the right views
+ int numValues = mTransformedViews.size();
+ for (int i = 0; i < numValues; i++) {
+ View view = mTransformedViews.valueAt(i);
+ while (view != viewRoot.getParent()) {
+ view.setTag(TAG_CONTAINS_TRANSFORMED_VIEW, true);
+ view = (View) view.getParent();
+ }
+ }
+ Stack<View> stack = new Stack<>();
+ // Add the right views now
+ stack.push(viewRoot);
+ while (!stack.isEmpty()) {
+ View child = stack.pop();
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+ Boolean containsView = (Boolean) child.getTag(TAG_CONTAINS_TRANSFORMED_VIEW);
+ if (containsView == null) {
+ // This one is unhandled, let's add it to our list.
+ int id = child.getId();
+ if (id != View.NO_ID) {
+ // We only fade views with an id
+ addTransformedView(id, child);
+ continue;
+ }
+ }
+ child.setTag(TAG_CONTAINS_TRANSFORMED_VIEW, null);
+ if (child instanceof ViewGroup && !mTransformedViews.containsValue(child)){
+ ViewGroup group = (ViewGroup) child;
+ for (int i = 0; i < group.getChildCount(); i++) {
+ stack.push(group.getChildAt(i));
+ }
+ }
+ }
+ }
+
+ public interface CustomTransformation {
+ /**
+ * Transform a state to the given view
+ * @param ownState the state to transform
+ * @param notification the view to transform to
+ * @return whether a custom transformation is performed
+ */
+ boolean transformTo(TransformState ownState, TransformableView notification,
+ Runnable endRunnable);
+
+ /**
+ * Transform to this state from the given view
+ * @param ownState the state to transform to
+ * @param notification the view to transform from
+ * @return whether a custom transformation is performed
+ */
+ boolean transformFrom(TransformState ownState, TransformableView notification);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeaderTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeaderTransformState.java
new file mode 100644
index 0000000..bf291d3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeaderTransformState.java
@@ -0,0 +1,149 @@
+/*
+ * 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
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.util.Pools;
+import android.view.NotificationHeaderView;
+import android.view.View;
+
+import com.android.systemui.statusbar.CrossFadeHelper;
+
+/**
+ * A transform state of a text view.
+*/
+public class HeaderTransformState extends TransformState {
+
+ private static Pools.SimplePool<HeaderTransformState> sInstancePool
+ = new Pools.SimplePool<>(40);
+ private View mExpandButton;
+
+ @Override
+ public void initFrom(View view) {
+ super.initFrom(view);
+ if (view instanceof NotificationHeaderView) {
+ NotificationHeaderView header = (NotificationHeaderView) view;
+ mExpandButton = header.getExpandButton();
+ }
+ }
+
+ @Override
+ public boolean transformViewTo(TransformState otherState, Runnable endRunnable) {
+ // if the transforming notification has a header, we have ensured that it looks the same
+ // but the expand button, so lets fade just that one.
+ if (!(mTransformedView instanceof NotificationHeaderView)) {
+ return false;
+ }
+ NotificationHeaderView header = (NotificationHeaderView) mTransformedView;
+ int childCount = header.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View headerChild = header.getChildAt(i);
+ if (headerChild.getVisibility() == View.GONE) {
+ continue;
+ }
+ if (headerChild != mExpandButton) {
+ headerChild.setVisibility(View.INVISIBLE);
+ } else {
+ CrossFadeHelper.fadeOut(mExpandButton, endRunnable);
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void transformViewFrom(TransformState otherState) {
+ // if the transforming notification has a header, we have ensured that it looks the same
+ // but the expand button, so lets fade just that one.
+ if (!(mTransformedView instanceof NotificationHeaderView)) {
+ return;
+ }
+ NotificationHeaderView header = (NotificationHeaderView) mTransformedView;
+ header.setVisibility(View.VISIBLE);
+ header.setAlpha(1.0f);
+ int childCount = header.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View headerChild = header.getChildAt(i);
+ if (headerChild.getVisibility() == View.GONE) {
+ continue;
+ }
+ if (headerChild != mExpandButton) {
+ headerChild.setVisibility(View.VISIBLE);
+ } else {
+ CrossFadeHelper.fadeIn(mExpandButton);
+ }
+ }
+ return;
+ }
+
+ public static HeaderTransformState obtain() {
+ HeaderTransformState instance = sInstancePool.acquire();
+ if (instance != null) {
+ return instance;
+ }
+ return new HeaderTransformState();
+ }
+
+ @Override
+ public void recycle() {
+ super.recycle();
+ sInstancePool.release(this);
+ }
+
+ @Override
+ protected void reset() {
+ super.reset();
+ mExpandButton = null;
+ }
+
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+ if (!(mTransformedView instanceof NotificationHeaderView)) {
+ return;
+ }
+ NotificationHeaderView header = (NotificationHeaderView) mTransformedView;
+ int childCount = header.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View headerChild = header.getChildAt(i);
+ if (headerChild.getVisibility() == View.GONE) {
+ continue;
+ }
+ headerChild.animate().cancel();
+ headerChild.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ if (headerChild == mExpandButton) {
+ headerChild.setAlpha(visible ? 1.0f : 0.0f);
+ }
+ }
+ }
+
+ @Override
+ public void prepareFadeIn() {
+ super.prepareFadeIn();
+ if (!(mTransformedView instanceof NotificationHeaderView)) {
+ return;
+ }
+ NotificationHeaderView header = (NotificationHeaderView) mTransformedView;
+ int childCount = header.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View headerChild = header.getChildAt(i);
+ if (headerChild.getVisibility() == View.GONE) {
+ continue;
+ }
+ headerChild.animate().cancel();
+ headerChild.setVisibility(View.VISIBLE);
+ headerChild.setAlpha(1.0f);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java
index 5fb6fec..5eed5ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java
@@ -18,18 +18,29 @@
import android.annotation.Nullable;
import android.content.Context;
+import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.AttributeSet;
+import android.view.View;
import android.widget.TextView;
import com.android.keyguard.AlphaOptimizedLinearLayout;
import com.android.systemui.R;
import com.android.systemui.ViewInvertHelper;
+import com.android.systemui.statusbar.CrossFadeHelper;
+import com.android.systemui.statusbar.TransformableView;
+import com.android.systemui.statusbar.ViewTransformationHelper;
import com.android.systemui.statusbar.phone.NotificationPanelView;
+import java.util.ArrayList;
+
/**
* A hybrid view which may contain information about one ore more notifications.
*/
-public class HybridNotificationView extends AlphaOptimizedLinearLayout {
+public class HybridNotificationView extends AlphaOptimizedLinearLayout
+ implements TransformableView {
+
+ private ViewTransformationHelper mTransformationHelper;
protected TextView mTitleView;
protected TextView mTextView;
@@ -58,6 +69,39 @@
mTitleView = (TextView) findViewById(R.id.notification_title);
mTextView = (TextView) findViewById(R.id.notification_text);
mInvertHelper = new ViewInvertHelper(this, NotificationPanelView.DOZE_ANIMATION_DURATION);
+ mTransformationHelper = new ViewTransformationHelper();
+ mTransformationHelper.setCustomTransformation(
+ new ViewTransformationHelper.CustomTransformation() {
+ @Override
+ public boolean transformTo(TransformState ownState, TransformableView notification,
+ Runnable endRunnable) {
+ // We want to transform to the same y location as the title
+ TransformState otherState = notification.getCurrentState(
+ TRANSFORMING_VIEW_TITLE);
+ CrossFadeHelper.fadeOut(mTextView, endRunnable);
+ if (otherState != null) {
+ ownState.animateViewVerticalTo(otherState, endRunnable);
+ otherState.recycle();
+ }
+ return true;
+ }
+
+ @Override
+ public boolean transformFrom(TransformState ownState,
+ TransformableView notification) {
+ // We want to transform from the same y location as the title
+ TransformState otherState = notification.getCurrentState(
+ TRANSFORMING_VIEW_TITLE);
+ CrossFadeHelper.fadeIn(mTextView);
+ if (otherState != null) {
+ ownState.animateViewVerticalFrom(otherState);
+ otherState.recycle();
+ }
+ return true;
+ }
+ }, TRANSFORMING_VIEW_TEXT);
+ mTransformationHelper.addTransformedView(TRANSFORMING_VIEW_TITLE, mTitleView);
+ mTransformationHelper.addTransformedView(TRANSFORMING_VIEW_TEXT, mTextView);
}
public void bind(CharSequence title) {
@@ -66,11 +110,38 @@
public void bind(CharSequence title, CharSequence text) {
mTitleView.setText(title);
+ if (TextUtils.isEmpty(title)) {
+ mTitleView.setVisibility(GONE);
+ }
mTextView.setText(text);
+ if (TextUtils.isEmpty(text)) {
+ mTextView.setVisibility(GONE);
+ }
requestLayout();
}
public void setDark(boolean dark, boolean fade, long delay) {
mInvertHelper.setInverted(dark, fade, delay);
}
+
+ @Override
+ public TransformState getCurrentState(int fadingView) {
+ return mTransformationHelper.getCurrentState(fadingView);
+ }
+
+ @Override
+ public void transformTo(TransformableView notification, Runnable endRunnable) {
+ mTransformationHelper.transformTo(notification, endRunnable);
+ }
+
+ @Override
+ public void transformFrom(TransformableView notification) {
+ mTransformationHelper.transformFrom(notification);
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ mTransformationHelper.setVisible(visible);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationViewManager.java
index b8adf5b..285d53f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationViewManager.java
@@ -52,16 +52,10 @@
public HybridNotificationView bindFromNotification(HybridNotificationView reusableView,
Notification notification) {
- CharSequence titleText = resolveTitle(notification);
- if (titleText == null) {
- if (reusableView != null) {
- mParent.removeView(reusableView);
- }
- return null;
- }
if (reusableView == null) {
reusableView = inflateHybridView();
}
+ CharSequence titleText = resolveTitle(notification);
CharSequence contentText = resolveText(notification);
reusableView.bind(titleText, contentText);
return reusableView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
new file mode 100644
index 0000000..e891a97
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
@@ -0,0 +1,80 @@
+/*
+ * 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
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.graphics.drawable.Icon;
+import android.util.Pools;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.systemui.R;
+
+/**
+ * A transform state of a image view.
+*/
+public class ImageTransformState extends TransformState {
+
+ public static final int ICON_TAG = R.id.image_icon_tag;
+ private static Pools.SimplePool<ImageTransformState> sInstancePool
+ = new Pools.SimplePool<>(40);
+ private Icon mIcon;
+
+ @Override
+ public void initFrom(View view) {
+ super.initFrom(view);
+ if (view instanceof ImageView) {
+ mIcon = (Icon) view.getTag(ICON_TAG);
+ }
+ }
+
+ @Override
+ protected boolean sameAs(TransformState otherState) {
+ if (otherState instanceof ImageTransformState) {
+ return mIcon != null && mIcon.sameAs(((ImageTransformState) otherState).getIcon());
+ }
+ return super.sameAs(otherState);
+ }
+
+ public Icon getIcon() {
+ return mIcon;
+ }
+
+ public static ImageTransformState obtain() {
+ ImageTransformState instance = sInstancePool.acquire();
+ if (instance != null) {
+ return instance;
+ }
+ return new ImageTransformState();
+ }
+
+ @Override
+ protected boolean animateScale() {
+ return true;
+ }
+
+ @Override
+ public void recycle() {
+ super.recycle();
+ sInstancePool.release(this);
+ }
+
+ @Override
+ protected void reset() {
+ super.reset();
+ mIcon = null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java
new file mode 100644
index 0000000..ce9540b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java
@@ -0,0 +1,49 @@
+/*
+ * 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
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.app.Notification;
+import android.content.Context;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.service.notification.StatusBarNotification;
+import android.view.View;
+
+/**
+ * Wraps a notification containing a big picture template
+ */
+public class NotificationBigPictureTemplateViewWrapper extends NotificationTemplateViewWrapper {
+
+ protected NotificationBigPictureTemplateViewWrapper(Context ctx, View view) {
+ super(ctx, view);
+ }
+
+ @Override
+ public void notifyContentUpdated(StatusBarNotification notification) {
+ super.notifyContentUpdated(notification);
+ updateImageTag(notification);
+ }
+
+ private void updateImageTag(StatusBarNotification notification) {
+ final Bundle extras = notification.getNotification().extras;
+ Icon overRiddenIcon = extras.getParcelable(Notification.EXTRA_LARGE_ICON_BIG);
+ if (overRiddenIcon != null) {
+ mPicture.setTag(ImageTransformState.ICON_TAG, overRiddenIcon);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/statusbar/NotificationCustomViewWrapper.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
index 6fd341b..fd65aac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationCustomViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.systemui.statusbar;
+package com.android.systemui.statusbar.notification;
import android.view.View;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
similarity index 76%
rename from packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderViewWrapper.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
index ddad2e0..4fd4cab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.systemui.statusbar;
+package com.android.systemui.statusbar.notification;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -27,17 +27,23 @@
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
+import android.service.notification.StatusBarNotification;
+import android.util.ArrayMap;
import android.view.NotificationHeaderView;
import android.view.View;
+import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.ImageView;
import com.android.systemui.R;
import com.android.systemui.ViewInvertHelper;
+import com.android.systemui.statusbar.TransformableView;
+import com.android.systemui.statusbar.ViewTransformationHelper;
import com.android.systemui.statusbar.phone.NotificationPanelView;
-import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Stack;
/**
* Wraps a notification header view.
@@ -52,6 +58,8 @@
protected final Interpolator mLinearOutSlowInInterpolator;
protected final ViewInvertHelper mInvertHelper;
+ protected final ViewTransformationHelper mTransformationHelper;
+
protected int mColor;
private ImageView mIcon;
@@ -64,7 +72,9 @@
mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(ctx,
android.R.interpolator.linear_out_slow_in);
mInvertHelper = new ViewInvertHelper(ctx, NotificationPanelView.DOZE_ANIMATION_DURATION);
+ mTransformationHelper = new ViewTransformationHelper();
resolveHeaderViews();
+ updateInvertHelper();
}
protected void resolveHeaderViews() {
@@ -73,12 +83,6 @@
mColor = resolveColor(mExpandButton);
mNotificationHeader = (NotificationHeaderView) mView.findViewById(
com.android.internal.R.id.notification_header);
- for (int i = 0; i < mNotificationHeader.getChildCount(); i++) {
- View child = mNotificationHeader.getChildAt(i);
- if (child != mIcon) {
- mInvertHelper.addTarget(child);
- }
- }
}
private int resolveColor(ImageView icon) {
@@ -92,10 +96,58 @@
}
@Override
- public void notifyContentUpdated() {
- mInvertHelper.clearTargets();
+ public void notifyContentUpdated(StatusBarNotification notification) {
// Reinspect the notification.
resolveHeaderViews();
+ updateInvertHelper();
+ updateTransformedTypes();
+ addRemainingTransformTypes();
+ updateCropToPaddingForImageViews();
+ }
+
+ /**
+ * Adds the remaining TransformTypes to the TransformHelper. This is done to make sure that each
+ * child is faded automatically and doesn't have to be manually added.
+ * The keys used for the views are the ids.
+ */
+ private void addRemainingTransformTypes() {
+ mTransformationHelper.addRemainingTransformTypes(mView);
+ }
+
+ /**
+ * Since we are deactivating the clipping when transforming the ImageViews don't get clipped
+ * anymore during these transitions. We can avoid that by using
+ * {@link ImageView#setCropToPadding(boolean)} on all ImageViews.
+ */
+ private void updateCropToPaddingForImageViews() {
+ Stack<View> stack = new Stack<>();
+ stack.push(mView);
+ while (!stack.isEmpty()) {
+ View child = stack.pop();
+ if (child instanceof ImageView) {
+ ((ImageView) child).setCropToPadding(true);
+ } else if (child instanceof ViewGroup){
+ ViewGroup group = (ViewGroup) child;
+ for (int i = 0; i < group.getChildCount(); i++) {
+ stack.push(group.getChildAt(i));
+ }
+ }
+ }
+ }
+
+ protected void updateInvertHelper() {
+ mInvertHelper.clearTargets();
+ for (int i = 0; i < mNotificationHeader.getChildCount(); i++) {
+ View child = mNotificationHeader.getChildAt(i);
+ if (child != mIcon) {
+ mInvertHelper.addTarget(child);
+ }
+ }
+ }
+
+ protected void updateTransformedTypes() {
+ mTransformationHelper.reset();
+ mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_HEADER, mNotificationHeader);
}
@Override
@@ -236,4 +288,25 @@
public NotificationHeaderView getNotificationHeader() {
return mNotificationHeader;
}
+
+ @Override
+ public TransformState getCurrentState(int fadingView) {
+ return mTransformationHelper.getCurrentState(fadingView);
+ }
+
+ @Override
+ public void transformTo(TransformableView notification, Runnable endRunnable) {
+ mTransformationHelper.transformTo(notification, endRunnable);
+ }
+
+ @Override
+ public void transformFrom(TransformableView notification) {
+ mTransformationHelper.transformFrom(notification);
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+ mTransformationHelper.setVisible(visible);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
new file mode 100644
index 0000000..a959e07
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Color;
+import android.service.notification.StatusBarNotification;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.android.systemui.statusbar.CrossFadeHelper;
+import com.android.systemui.statusbar.TransformableView;
+import com.android.systemui.statusbar.ViewTransformationHelper;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
+
+/**
+ * Wraps a notification view inflated from a template.
+ */
+public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapper {
+
+ private static final int mDarkProgressTint = 0xffffffff;
+
+ protected ImageView mPicture;
+ private ProgressBar mProgressBar;
+ private TextView mTitle;
+ private TextView mText;
+
+ protected NotificationTemplateViewWrapper(Context ctx, View view) {
+ super(ctx, view);
+ mTransformationHelper.setCustomTransformation(
+ new ViewTransformationHelper.CustomTransformation() {
+ @Override
+ public boolean transformTo(TransformState ownState,
+ TransformableView notification, final Runnable endRunnable) {
+ if (!(notification instanceof HybridNotificationView)) {
+ return false;
+ }
+ TransformState otherState = notification.getCurrentState(
+ TRANSFORMING_VIEW_TITLE);
+ CrossFadeHelper.fadeOut(mText, endRunnable);
+ if (otherState != null) {
+ int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
+ int[] ownPosition = ownState.getLaidOutLocationOnScreen();
+ mText.animate()
+ .translationY((otherStablePosition[1]
+ + otherState.getTransformedView().getHeight()
+ - ownPosition[1]) * 0.33f)
+ .setDuration(
+ StackStateAnimator.ANIMATION_DURATION_STANDARD)
+ .setInterpolator(TransformState.FAST_OUT_SLOW_IN)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ if (endRunnable != null) {
+ endRunnable.run();
+ }
+ TransformState.setClippingDeactivated(mText,
+ false);
+ }
+ });
+ TransformState.setClippingDeactivated(mText, true);
+ otherState.recycle();
+ }
+ return true;
+ }
+
+ @Override
+ public boolean transformFrom(TransformState ownState,
+ TransformableView notification) {
+ if (!(notification instanceof HybridNotificationView)) {
+ return false;
+ }
+ TransformState otherState = notification.getCurrentState(
+ TRANSFORMING_VIEW_TITLE);
+ boolean isVisible = mText.getVisibility() == View.VISIBLE;
+ CrossFadeHelper.fadeIn(mText);
+ if (otherState != null) {
+ int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
+ int[] ownStablePosition = ownState.getLaidOutLocationOnScreen();
+ if (!isVisible) {
+ mText.setTranslationY((otherStablePosition[1]
+ + otherState.getTransformedView().getHeight()
+ - ownStablePosition[1]) * 0.33f);
+ }
+ mText.animate()
+ .translationY(0)
+ .setDuration(
+ StackStateAnimator.ANIMATION_DURATION_STANDARD)
+ .setInterpolator(TransformState.FAST_OUT_SLOW_IN)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ TransformState.setClippingDeactivated(mText,
+ false);
+ }
+ });
+ TransformState.setClippingDeactivated(mText, true);
+ otherState.recycle();
+ }
+ return true;
+ }
+ }, TRANSFORMING_VIEW_TEXT);
+ }
+
+ private void resolveTemplateViews(StatusBarNotification notification) {
+ mPicture = (ImageView) mView.findViewById(com.android.internal.R.id.right_icon);
+ mPicture.setTag(ImageTransformState.ICON_TAG,
+ notification.getNotification().getLargeIcon());
+ mTitle = (TextView) mView.findViewById(com.android.internal.R.id.title);
+ mText = (TextView) mView.findViewById(com.android.internal.R.id.text);
+ final View progress = mView.findViewById(com.android.internal.R.id.progress);
+ if (progress instanceof ProgressBar) {
+ mProgressBar = (ProgressBar) progress;
+ } else {
+ // It's still a viewstub
+ mProgressBar = null;
+ }
+ }
+
+ @Override
+ public void notifyContentUpdated(StatusBarNotification notification) {
+ // Reinspect the notification. Before the super call, because the super call also updates
+ // the transformation types and we need to have our values set by then.
+ resolveTemplateViews(notification);
+ super.notifyContentUpdated(notification);
+ }
+
+ @Override
+ protected void updateInvertHelper() {
+ super.updateInvertHelper();
+ View mainColumn = mView.findViewById(com.android.internal.R.id.notification_main_column);
+ if (mainColumn != null) {
+ mInvertHelper.addTarget(mainColumn);
+ }
+ }
+
+ @Override
+ protected void updateTransformedTypes() {
+ // This also clears the existing types
+ super.updateTransformedTypes();
+ if (mTitle != null) {
+ mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE, mTitle);
+ }
+ if (mText != null) {
+ mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT, mText);
+ }
+ if (mPicture != null) {
+ mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_IMAGE, mPicture);
+ }
+ if (mProgressBar != null) {
+ mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_PROGRESS, mProgressBar);
+ }
+ }
+
+ @Override
+ public void setDark(boolean dark, boolean fade, long delay) {
+ super.setDark(dark, fade, delay);
+ setPictureGrayscale(dark, fade, delay);
+ setProgressBarDark(dark, fade, delay);
+ }
+
+ private void setProgressBarDark(boolean dark, boolean fade, long delay) {
+ if (mProgressBar != null) {
+ if (fade) {
+ fadeProgressDark(mProgressBar, dark, delay);
+ } else {
+ updateProgressDark(mProgressBar, dark);
+ }
+ }
+ }
+
+ private void fadeProgressDark(final ProgressBar target, final boolean dark, long delay) {
+ startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float t = (float) animation.getAnimatedValue();
+ updateProgressDark(target, t);
+ }
+ }, dark, delay, null /* listener */);
+ }
+
+ private void updateProgressDark(ProgressBar target, float intensity) {
+ int color = interpolateColor(mColor, mDarkProgressTint, intensity);
+ target.getIndeterminateDrawable().mutate().setTint(color);
+ target.getProgressDrawable().mutate().setTint(color);
+ }
+
+ private void updateProgressDark(ProgressBar target, boolean dark) {
+ updateProgressDark(target, dark ? 1f : 0f);
+ }
+
+ protected void setPictureGrayscale(boolean grayscale, boolean fade, long delay) {
+ if (mPicture != null) {
+ if (fade) {
+ fadeGrayscale(mPicture, grayscale, delay);
+ } else {
+ updateGrayscale(mPicture, grayscale);
+ }
+ }
+ }
+
+ private static int interpolateColor(int source, int target, float t) {
+ int aSource = Color.alpha(source);
+ int rSource = Color.red(source);
+ int gSource = Color.green(source);
+ int bSource = Color.blue(source);
+ int aTarget = Color.alpha(target);
+ int rTarget = Color.red(target);
+ int gTarget = Color.green(target);
+ int bTarget = Color.blue(target);
+ return Color.argb(
+ (int) (aSource * (1f - t) + aTarget * t),
+ (int) (rSource * (1f - t) + rTarget * t),
+ (int) (gSource * (1f - t) + gTarget * t),
+ (int) (bSource * (1f - t) + bTarget * t));
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
similarity index 68%
rename from packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index 61499de..0ceba78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -14,22 +14,29 @@
* limitations under the License
*/
-package com.android.systemui.statusbar;
+package com.android.systemui.statusbar.notification;
import android.content.Context;
+import android.service.notification.StatusBarNotification;
import android.view.NotificationHeaderView;
import android.view.View;
+import com.android.systemui.statusbar.CrossFadeHelper;
+import com.android.systemui.statusbar.TransformableView;
+
/**
* Wraps the actual notification content view; used to implement behaviors which are different for
* the individual templates and custom views.
*/
-public abstract class NotificationViewWrapper {
+public abstract class NotificationViewWrapper implements TransformableView {
protected final View mView;
public static NotificationViewWrapper wrap(Context ctx, View v) {
if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) {
+ if ("bigPicture".equals(v.getTag())) {
+ return new NotificationBigPictureTemplateViewWrapper(ctx, v);
+ }
return new NotificationTemplateViewWrapper(ctx, v);
} else if (v instanceof NotificationHeaderView) {
return new NotificationHeaderViewWrapper(ctx, v);
@@ -53,8 +60,9 @@
/**
* Notifies this wrapper that the content of the view might have changed.
+ * @param notification
*/
- public void notifyContentUpdated() {};
+ public void notifyContentUpdated(StatusBarNotification notification) {};
/**
* @return true if this template might need to be clipped with a round rect to make it look
@@ -78,4 +86,26 @@
public NotificationHeaderView getNotificationHeader() {
return null;
}
+
+ @Override
+ public TransformState getCurrentState(int fadingView) {
+ return null;
+ }
+
+ @Override
+ public void transformTo(TransformableView notification, Runnable endRunnable) {
+ // By default we are fading out completely
+ CrossFadeHelper.fadeOut(mView, endRunnable);
+ }
+
+ @Override
+ public void transformFrom(TransformableView notification) {
+ // By default we are fading in completely
+ CrossFadeHelper.fadeIn(mView);
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ProgressTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ProgressTransformState.java
new file mode 100644
index 0000000..bf78194
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ProgressTransformState.java
@@ -0,0 +1,50 @@
+/*
+ * 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
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.util.Pools;
+
+/**
+ * A transform state of a progress view.
+*/
+public class ProgressTransformState extends TransformState {
+
+ private static Pools.SimplePool<ProgressTransformState> sInstancePool
+ = new Pools.SimplePool<>(40);
+
+ @Override
+ protected boolean sameAs(TransformState otherState) {
+ if (otherState instanceof ProgressTransformState) {
+ return true;
+ }
+ return super.sameAs(otherState);
+ }
+
+ public static ProgressTransformState obtain() {
+ ProgressTransformState instance = sInstancePool.acquire();
+ if (instance != null) {
+ return instance;
+ }
+ return new ProgressTransformState();
+ }
+
+ @Override
+ public void recycle() {
+ super.recycle();
+ sInstancePool.release(this);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java
new file mode 100644
index 0000000..5ab441d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java
@@ -0,0 +1,70 @@
+/*
+ * 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
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.text.TextUtils;
+import android.util.Pools;
+import android.view.View;
+import android.widget.TextView;
+
+/**
+ * A transform state of a mText view.
+*/
+public class TextViewTransformState extends TransformState {
+
+ private static Pools.SimplePool<TextViewTransformState> sInstancePool
+ = new Pools.SimplePool<>(40);
+ private CharSequence mText;
+
+ @Override
+ public void initFrom(View view) {
+ super.initFrom(view);
+ if (view instanceof TextView) {
+ TextView txt = (TextView) view;
+ mText = txt.getText();
+ }
+ }
+
+ @Override
+ protected boolean sameAs(TransformState otherState) {
+ if (otherState instanceof TextViewTransformState) {
+ TextViewTransformState otherTvs = (TextViewTransformState) otherState;
+ return TextUtils.equals(otherTvs.mText, mText);
+ }
+ return super.sameAs(otherState);
+ }
+
+ public static TextViewTransformState obtain() {
+ TextViewTransformState instance = sInstancePool.acquire();
+ if (instance != null) {
+ return instance;
+ }
+ return new TextViewTransformState();
+ }
+
+ @Override
+ public void recycle() {
+ super.recycle();
+ sInstancePool.release(this);
+ }
+
+ @Override
+ protected void reset() {
+ super.reset();
+ mText = null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
new file mode 100644
index 0000000..388ba0e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -0,0 +1,316 @@
+/*
+ * 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
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.util.ArraySet;
+import android.util.Pools;
+import android.view.NotificationHeaderView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.CrossFadeHelper;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
+
+/**
+ * A transform state of a view.
+*/
+public class TransformState {
+
+ private static final int ANIMATE_X = 0x1;
+ private static final int ANIMATE_Y = 0x10;
+ private static final int ANIMATE_ALL = ANIMATE_X | ANIMATE_Y;
+ private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag;
+ private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag;
+ private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag;
+ public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+ private static Pools.SimplePool<TransformState> sInstancePool = new Pools.SimplePool<>(40);
+
+ protected View mTransformedView;
+ private int[] mOwnPosition = new int[2];
+
+ public void initFrom(View view) {
+ mTransformedView = view;
+ }
+
+ /**
+ * Transforms the {@link #mTransformedView} from the given transformviewstate
+ * @param otherState the state to transform from
+ */
+ public void transformViewFrom(TransformState otherState) {
+ mTransformedView.animate().cancel();
+ if (sameAs(otherState)) {
+ // We have the same content, lets show ourselves
+ mTransformedView.setAlpha(1.0f);
+ mTransformedView.setVisibility(View.VISIBLE);
+ } else {
+ CrossFadeHelper.fadeIn(mTransformedView);
+ }
+ animateViewFrom(otherState);
+ }
+
+ public void animateViewFrom(TransformState otherState) {
+ animateViewFrom(otherState, ANIMATE_ALL);
+ }
+
+ public void animateViewVerticalFrom(TransformState otherState) {
+ animateViewFrom(otherState, ANIMATE_Y);
+ }
+
+ private void animateViewFrom(TransformState otherState, int animationFlags) {
+ final View transformedView = mTransformedView;
+ // lets animate the positions correctly
+ int[] otherPosition = otherState.getLocationOnScreen();
+ int[] ownStablePosition = getLaidOutLocationOnScreen();
+ if ((animationFlags & ANIMATE_X) != 0) {
+ transformedView.setTranslationX(otherPosition[0] - ownStablePosition[0]);
+ transformedView.animate().translationX(0);
+ }
+ if ((animationFlags & ANIMATE_Y) != 0) {
+ transformedView.setTranslationY(otherPosition[1] - ownStablePosition[1]);
+ transformedView.animate().translationY(0);
+ }
+ if (animateScale()) {
+ // we also want to animate the scale if we're the same
+ View otherView = otherState.getTransformedView();
+ if (otherView.getWidth() != transformedView.getWidth()) {
+ float scaleX = (otherView.getWidth() * otherView.getScaleX()
+ / (float) transformedView.getWidth());
+ transformedView.setScaleX(scaleX);
+ transformedView.setPivotX(0);
+ transformedView.animate().scaleX(1.0f);
+ }
+ if (otherView.getHeight() != transformedView.getHeight()) {
+ float scaleY = (otherView.getHeight() * otherView.getScaleY()
+ / (float) transformedView.getHeight());
+ transformedView.setScaleY(scaleY);
+ transformedView.setPivotY(0);
+ transformedView.animate().scaleY(1.0f);
+ }
+ }
+ transformedView.animate()
+ .setInterpolator(TransformState.FAST_OUT_SLOW_IN)
+ .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ setClippingDeactivated(transformedView, false);
+ }
+ });
+ setClippingDeactivated(transformedView, true);
+ }
+
+ protected boolean animateScale() {
+ return false;
+ }
+
+ /**
+ * Transforms the {@link #mTransformedView} to the given transformviewstate
+ * @param otherState the state to transform from
+ * @param endRunnable a runnable to run at the end of the animation
+ * @return whether an animation was started
+ */
+ public boolean transformViewTo(TransformState otherState, final Runnable endRunnable) {
+ mTransformedView.animate().cancel();
+ if (sameAs(otherState)) {
+ // We have the same text, lets show ourselfs
+ mTransformedView.setAlpha(0.0f);
+ mTransformedView.setVisibility(View.INVISIBLE);
+ return false;
+ } else {
+ CrossFadeHelper.fadeOut(mTransformedView, endRunnable);
+ }
+ animateViewTo(otherState, endRunnable);
+ return true;
+ }
+
+ public void animateViewTo(TransformState otherState, Runnable endRunnable) {
+ animateViewTo(otherState, endRunnable, ANIMATE_ALL);
+ }
+
+ public void animateViewVerticalTo(TransformState otherState, Runnable endRunnable) {
+ animateViewTo(otherState, endRunnable, ANIMATE_Y);
+ }
+
+ private void animateViewTo(TransformState otherState, final Runnable endRunnable,
+ int animationFlags) {
+ // lets animate the positions correctly
+ int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
+ int[] ownPosition = getLaidOutLocationOnScreen();
+ final View transformedView = mTransformedView;
+ if ((animationFlags & ANIMATE_X) != 0) {
+ transformedView.animate()
+ .translationX(otherStablePosition[0] - ownPosition[0]);
+ }
+ if ((animationFlags & ANIMATE_Y) != 0) {
+ transformedView.animate()
+ .translationY(otherStablePosition[1] - ownPosition[1]);
+ }
+ transformedView.animate()
+ .setInterpolator(TransformState.FAST_OUT_SLOW_IN)
+ .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ if (endRunnable != null) {
+ endRunnable.run();
+ }
+ setClippingDeactivated(transformedView, false);
+ }
+ });
+ setClippingDeactivated(transformedView, true);
+ }
+
+ public static void setClippingDeactivated(final View transformedView, boolean deactivated) {
+ ViewGroup view = (ViewGroup) transformedView.getParent();
+ while (true) {
+ ArraySet<View> clipSet = (ArraySet<View>) view.getTag(CLIP_CLIPPING_SET);
+ if (clipSet == null) {
+ clipSet = new ArraySet<>();
+ view.setTag(CLIP_CLIPPING_SET, clipSet);
+ }
+ Boolean clipChildren = (Boolean) view.getTag(CLIP_CHILDREN_TAG);
+ if (clipChildren == null) {
+ clipChildren = view.getClipChildren();
+ view.setTag(CLIP_CHILDREN_TAG, clipChildren);
+ }
+ Boolean clipToPadding = (Boolean) view.getTag(CLIP_TO_PADDING);
+ if (clipToPadding == null) {
+ clipToPadding = view.getClipToPadding();
+ view.setTag(CLIP_TO_PADDING, clipToPadding);
+ }
+ ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
+ ? (ExpandableNotificationRow) view
+ : null;
+ if (!deactivated) {
+ clipSet.remove(transformedView);
+ if (clipSet.isEmpty()) {
+ view.setClipChildren(clipChildren);
+ view.setClipToPadding(clipToPadding);
+ view.setTag(CLIP_CLIPPING_SET, null);
+ if (row != null) {
+ row.setClipToActualHeight(true);
+ }
+ }
+ } else {
+ clipSet.add(transformedView);
+ view.setClipChildren(false);
+ view.setClipToPadding(false);
+ if (row != null && row.isChildInGroup()) {
+ // We still want to clip to the parent's height
+ row.setClipToActualHeight(false);
+ }
+ }
+ if (row != null && !row.isChildInGroup()) {
+ return;
+ }
+ final ViewParent parent = view.getParent();
+ if (parent instanceof ViewGroup) {
+ view = (ViewGroup) parent;
+ } else {
+ return;
+ }
+ }
+ }
+
+ public int[] getLaidOutLocationOnScreen() {
+ int[] location = getLocationOnScreen();
+ location[0] -= mTransformedView.getTranslationX();
+ location[1] -= mTransformedView.getTranslationY();
+ return location;
+ }
+
+ public int[] getLocationOnScreen() {
+ mTransformedView.getLocationOnScreen(mOwnPosition);
+ return mOwnPosition;
+ }
+
+ protected boolean sameAs(TransformState otherState) {
+ return false;
+ }
+
+ public static TransformState createFrom(View view) {
+ if (view instanceof TextView) {
+ TextViewTransformState result = TextViewTransformState.obtain();
+ result.initFrom(view);
+ return result;
+ }
+ if (view instanceof NotificationHeaderView) {
+ HeaderTransformState result = HeaderTransformState.obtain();
+ result.initFrom(view);
+ return result;
+ }
+ if (view instanceof ImageView) {
+ ImageTransformState result = ImageTransformState.obtain();
+ result.initFrom(view);
+ return result;
+ }
+ if (view instanceof ProgressBar) {
+ ProgressTransformState result = ProgressTransformState.obtain();
+ result.initFrom(view);
+ return result;
+ }
+ TransformState result = obtain();
+ result.initFrom(view);
+ return result;
+ }
+
+ public void recycle() {
+ reset();
+ if (getClass() == TransformState.class) {
+ sInstancePool.release(this);
+ }
+ }
+
+ protected void reset() {
+ mTransformedView = null;
+ }
+
+ public void setVisible(boolean visible) {
+ mTransformedView.animate().cancel();
+ mTransformedView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ mTransformedView.setAlpha(visible ? 1.0f : 0.0f);
+ if (visible) {
+ mTransformedView.setTranslationX(0);
+ mTransformedView.setTranslationY(0);
+ mTransformedView.setScaleX(1.0f);
+ mTransformedView.setScaleY(1.0f);
+ }
+ }
+
+ public void prepareFadeIn() {
+ }
+
+ public static TransformState obtain() {
+ TransformState instance = sInstancePool.acquire();
+ if (instance != null) {
+ return instance;
+ }
+ return new TransformState();
+ }
+
+ public View getTransformedView() {
+ return mTransformedView;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
new file mode 100644
index 0000000..8e3886b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
@@ -0,0 +1,168 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import com.android.systemui.statusbar.policy.KeyButtonView;
+
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.widget.ImageView;
+
+import java.util.ArrayList;
+
+/**
+ * Dispatches common view calls to multiple views. This is used to handle
+ * multiples of the same nav bar icon appearing.
+ */
+public class ButtonDispatcher {
+
+ private final ArrayList<View> mViews = new ArrayList<>();
+
+ private final int mId;
+
+ private View.OnClickListener mClickListener;
+ private View.OnTouchListener mTouchListener;
+ private View.OnLongClickListener mLongClickListener;
+ private Boolean mLongClickable;
+ private Integer mAlpha;
+ private Integer mVisibility = -1;
+ private int mImageResource = -1;
+ private Drawable mImageDrawable;
+ private View mCurrentView;
+
+ public ButtonDispatcher(int id) {
+ mId = id;
+ }
+
+ void clear() {
+ mViews.clear();
+ }
+
+ void addView(View view) {
+ mViews.add(view);
+ view.setOnClickListener(mClickListener);
+ view.setOnTouchListener(mTouchListener);
+ view.setOnLongClickListener(mLongClickListener);
+ if (mLongClickable != null) {
+ view.setLongClickable(mLongClickable);
+ }
+ if (mAlpha != null) {
+ view.setAlpha(mAlpha);
+ }
+ if (mVisibility != null) {
+ view.setVisibility(mVisibility);
+ }
+ if (mImageResource > 0) {
+ ((ImageView) view).setImageResource(mImageResource);
+ } else if (mImageDrawable != null) {
+ ((ImageView) view).setImageDrawable(mImageDrawable);
+ }
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ public int getVisibility() {
+ return mVisibility;
+ }
+
+ public float getAlpha() {
+ return mAlpha;
+ }
+
+ public void setImageDrawable(Drawable drawable) {
+ mImageDrawable = drawable;
+ mImageResource = -1;
+ final int N = mViews.size();
+ for (int i = 0; i < N; i++) {
+ ((ImageView) mViews.get(i)).setImageDrawable(mImageDrawable);
+ }
+ }
+
+ public void setImageResource(int resource) {
+ mImageResource = resource;
+ mImageDrawable = null;
+ final int N = mViews.size();
+ for (int i = 0; i < N; i++) {
+ ((ImageView) mViews.get(i)).setImageResource(mImageResource);
+ }
+ }
+
+ public void setVisibility(int visibility) {
+ if (mVisibility == visibility) return;
+ mVisibility = visibility;
+ final int N = mViews.size();
+ for (int i = 0; i < N; i++) {
+ mViews.get(i).setVisibility(mVisibility);
+ }
+ }
+
+ public void abortCurrentGesture() {
+ // This seems to be an instantaneous thing, so not going to persist it.
+ final int N = mViews.size();
+ for (int i = 0; i < N; i++) {
+ ((KeyButtonView) mViews.get(i)).abortCurrentGesture();
+ }
+ }
+
+ public void setAlpha(int alpha) {
+ mAlpha = alpha;
+ final int N = mViews.size();
+ for (int i = 0; i < N; i++) {
+ mViews.get(i).setAlpha(alpha);
+ }
+ }
+
+ public void setOnClickListener(View.OnClickListener clickListener) {
+ mClickListener = clickListener;
+ final int N = mViews.size();
+ for (int i = 0; i < N; i++) {
+ mViews.get(i).setOnClickListener(mClickListener);
+ }
+ }
+
+ public void setOnTouchListener(View.OnTouchListener touchListener) {
+ mTouchListener = touchListener;
+ final int N = mViews.size();
+ for (int i = 0; i < N; i++) {
+ mViews.get(i).setOnTouchListener(mTouchListener);
+ }
+ }
+
+ public void setLongClickable(boolean isLongClickable) {
+ mLongClickable = isLongClickable;
+ final int N = mViews.size();
+ for (int i = 0; i < N; i++) {
+ mViews.get(i).setLongClickable(mLongClickable);
+ }
+ }
+
+ public void setOnLongClickListener(View.OnLongClickListener longClickListener) {
+ mLongClickListener = longClickListener;
+ final int N = mViews.size();
+ for (int i = 0; i < N; i++) {
+ mViews.get(i).setOnLongClickListener(mLongClickListener);
+ }
+ }
+
+ public View getCurrentView() {
+ return mCurrentView;
+ }
+
+ public void setCurrentView(View currentView) {
+ mCurrentView = currentView.findViewById(mId);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index df8c7fa..26abc48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -110,12 +110,12 @@
mInitialTouchX = x;
mInitialTouchY = y;
int expandedHeight = mPickedChild.getActualHeight();
+ mHeadsUpManager.unpinAll();
mPanel.setPanelScrimMinFraction((float) expandedHeight
/ mPanel.getMaxPanelHeight());
mPanel.startExpandMotion(x, y, true /* startTracking */, expandedHeight
+ mNotificationsTopPadding);
mPanel.clearNotificattonEffects();
- mHeadsUpManager.unpinAll();
return true;
}
break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
index 2db0804..abe357a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -162,11 +162,18 @@
mDockWindowTouchSlopExceeded = false;
mTouchDownX = (int) event.getX();
mTouchDownY = (int) event.getY();
- View recentsButton = mNavigationBarView.getRecentsButton();
- mDownOnRecents = mTouchDownX >= recentsButton.getLeft()
- && mTouchDownX <= recentsButton.getRight()
- && mTouchDownY >= recentsButton.getTop()
- && mTouchDownY <= recentsButton.getBottom();
+
+ if (mNavigationBarView != null) {
+ View recentsButton = mNavigationBarView.getRecentsButton().getCurrentView();
+ if (recentsButton != null) {
+ mDownOnRecents = mTouchDownX >= recentsButton.getLeft()
+ && mTouchDownX <= recentsButton.getRight()
+ && mTouchDownY >= recentsButton.getTop()
+ && mTouchDownY <= recentsButton.getBottom();
+ } else {
+ mDownOnRecents = false;
+ }
+ }
}
private boolean handleDragActionMoveEvent(MotionEvent event) {
@@ -218,7 +225,7 @@
if (mDragMode == DRAG_MODE_DIVIDER) {
int position = !mIsVertical ? (int) event.getRawY() : (int) event.getRawX();
SnapTarget snapTarget = mDivider.getView().getSnapAlgorithm()
- .calculateSnapTarget(position, 0f /* velocity */);
+ .calculateSnapTarget(position, 0f /* velocity */, false /* hardDismiss */);
mDivider.getView().resizeStack(position, snapTarget.position, snapTarget);
} else if (mDragMode == DRAG_MODE_RECENTS) {
mRecentsComponent.onDraggingInRecents(event.getRawY());
@@ -237,7 +244,8 @@
: (int) event.getRawY(),
mIsVertical
? mVelocityTracker.getXVelocity()
- : mVelocityTracker.getYVelocity());
+ : mVelocityTracker.getYVelocity(),
+ true /* avoidDismissStart */);
} else if (mDragMode == DRAG_MODE_RECENTS) {
mRecentsComponent.onDraggingInRecentsEnded(mVelocityTracker.getYVelocity());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
new file mode 100644
index 0000000..b8ae81f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -0,0 +1,266 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.Space;
+import com.android.systemui.R;
+import com.android.systemui.tuner.TunerService;
+
+import java.util.Objects;
+
+public class NavigationBarInflaterView extends FrameLayout implements TunerService.Tunable {
+
+ private static final String TAG = "NavBarInflater";
+
+ public static final String NAV_BAR_VIEWS = "sysui_nav_bar";
+
+ private static final String MENU_IME = "menu_ime";
+ private static final String BACK = "back";
+ private static final String HOME = "home";
+ private static final String RECENT = "recent";
+ private static final String NAVSPACE = "space";
+
+ public static final String GRAVITY_SEPARATOR = ";";
+ public static final String BUTTON_SEPARATOR = ",";
+
+ private final LayoutInflater mLayoutInflater;
+ private final LayoutInflater mLandscapeInflater;
+
+ private FrameLayout mRot0;
+ private FrameLayout mRot90;
+ private SparseArray<ButtonDispatcher> mButtonDispatchers;
+ private String mCurrentLayout;
+
+ public NavigationBarInflaterView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mLayoutInflater = LayoutInflater.from(context);
+ Configuration landscape = new Configuration();
+ landscape.setTo(context.getResources().getConfiguration());
+ landscape.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mLandscapeInflater = LayoutInflater.from(context.createConfigurationContext(landscape));
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mRot0 = (FrameLayout) findViewById(R.id.rot0);
+ mRot90 = (FrameLayout) findViewById(R.id.rot90);
+ clearViews();
+ inflateLayout(getDefaultLayout());
+ }
+
+ private String getDefaultLayout() {
+ return mContext.getString(R.string.config_navBarLayout);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ TunerService.get(getContext()).addTunable(this, NAV_BAR_VIEWS);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ TunerService.get(getContext()).removeTunable(this);
+ super.onDetachedFromWindow();
+ }
+
+ @Override
+ public void onTuningChanged(String key, String newValue) {
+ if (NAV_BAR_VIEWS.equals(key)) {
+ if (newValue == null) {
+ newValue = getDefaultLayout();
+ }
+ if (!Objects.equals(mCurrentLayout, newValue)) {
+ clearViews();
+ inflateLayout(newValue);
+ }
+ }
+ }
+
+ public void setButtonDispatchers(SparseArray<ButtonDispatcher> buttonDisatchers) {
+ mButtonDispatchers = buttonDisatchers;
+ for (int i = 0; i < buttonDisatchers.size(); i++) {
+ initiallyFill(buttonDisatchers.valueAt(i));
+ }
+ }
+
+ private void initiallyFill(ButtonDispatcher buttonDispatcher) {
+ addAll(buttonDispatcher, (ViewGroup) mRot0.findViewById(R.id.start_group));
+ addAll(buttonDispatcher, (ViewGroup) mRot0.findViewById(R.id.center_group));
+ addAll(buttonDispatcher, (ViewGroup) mRot0.findViewById(R.id.end_group));
+ addAll(buttonDispatcher, (ViewGroup) mRot90.findViewById(R.id.start_group));
+ addAll(buttonDispatcher, (ViewGroup) mRot90.findViewById(R.id.center_group));
+ addAll(buttonDispatcher, (ViewGroup) mRot90.findViewById(R.id.end_group));
+ }
+
+ private void addAll(ButtonDispatcher buttonDispatcher, ViewGroup parent) {
+ for (int i = 0; i < parent.getChildCount(); i++) {
+ // Need to manually search for each id, just in case each group has more than one
+ // of a single id. It probably mostly a waste of time, but shouldn't take long
+ // and will only happen once.
+ if (parent.getChildAt(i).getId() == buttonDispatcher.getId()) {
+ buttonDispatcher.addView(parent.getChildAt(i));
+ } else if (parent.getChildAt(i) instanceof ViewGroup) {
+ addAll(buttonDispatcher, (ViewGroup) parent.getChildAt(i));
+ }
+ }
+ }
+
+ private void inflateLayout(String newLayout) {
+ mCurrentLayout = newLayout;
+ String[] sets = newLayout.split(GRAVITY_SEPARATOR);
+ String[] start = sets[0].split(BUTTON_SEPARATOR);
+ String[] center = sets[1].split(BUTTON_SEPARATOR);
+ String[] end = sets[2].split(BUTTON_SEPARATOR);
+ inflateButtons(start, (ViewGroup) mRot0.findViewById(R.id.start_group),
+ (ViewGroup) mRot0.findViewById(R.id.start_group_lightsout), false);
+ inflateButtons(start, (ViewGroup) mRot90.findViewById(R.id.start_group),
+ (ViewGroup) mRot90.findViewById(R.id.start_group_lightsout), true);
+ inflateButtons(center, (ViewGroup) mRot0.findViewById(R.id.center_group),
+ (ViewGroup) mRot0.findViewById(R.id.start_group_lightsout), false);
+ inflateButtons(center, (ViewGroup) mRot90.findViewById(R.id.center_group),
+ (ViewGroup) mRot90.findViewById(R.id.start_group_lightsout), true);
+ inflateButtons(end, (ViewGroup) mRot0.findViewById(R.id.end_group),
+ (ViewGroup) mRot0.findViewById(R.id.start_group_lightsout), false);
+ inflateButtons(end, (ViewGroup) mRot90.findViewById(R.id.end_group),
+ (ViewGroup) mRot90.findViewById(R.id.start_group_lightsout), true);
+ }
+
+ private void inflateButtons(String[] buttons, ViewGroup parent, ViewGroup lightsOutParent,
+ boolean landscape) {
+ for (int i = 0; i < buttons.length; i++) {
+ copyToLightsout(inflateButton(buttons[i], parent, landscape), lightsOutParent);
+ }
+ }
+
+ private void copyToLightsout(View view, ViewGroup lightsOutParent) {
+ if (view instanceof FrameLayout) {
+ // The only ViewGroup we support in here is a FrameLayout, so copy those manually.
+ FrameLayout original = (FrameLayout) view;
+ FrameLayout layout = new FrameLayout(view.getContext());
+ for (int i = 0; i < original.getChildCount(); i++) {
+ copyToLightsout(original.getChildAt(i), layout);
+ }
+ lightsOutParent.addView(layout, copy(view.getLayoutParams()));
+ } else if (view instanceof Space) {
+ lightsOutParent.addView(new Space(view.getContext()), copy(view.getLayoutParams()));
+ } else {
+ lightsOutParent.addView(generateLightsOutView(view), copy(view.getLayoutParams()));
+ }
+ }
+
+ private View generateLightsOutView(View view) {
+ ImageView imageView = new ImageView(view.getContext());
+ // Copy everything we can about the original view.
+ imageView.setPadding(view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(),
+ view.getPaddingBottom());
+ imageView.setContentDescription(view.getContentDescription());
+ imageView.setId(view.getId());
+ // Only home gets a big dot, everything else will be little.
+ imageView.setImageResource(view.getId() == R.id.home
+ ? R.drawable.ic_sysbar_lights_out_dot_large
+ : R.drawable.ic_sysbar_lights_out_dot_small);
+ return imageView;
+ }
+
+ private ViewGroup.LayoutParams copy(ViewGroup.LayoutParams layoutParams) {
+ return new LayoutParams(layoutParams.width, layoutParams.height);
+ }
+
+ private View inflateButton(String button, ViewGroup parent, boolean landscape) {
+ View v = null;
+ if (HOME.equals(button)) {
+ v = (landscape ? mLandscapeInflater : mLayoutInflater)
+ .inflate(R.layout.home, parent, false);
+ if (landscape && isSw600Dp()) {
+ setupLandButton(v);
+ }
+ } else if (BACK.equals(button)) {
+ v = (landscape ? mLandscapeInflater : mLayoutInflater)
+ .inflate(R.layout.back, parent, false);
+ if (landscape && isSw600Dp()) {
+ setupLandButton(v);
+ }
+ } else if (RECENT.equals(button)) {
+ v = (landscape ? mLandscapeInflater : mLayoutInflater)
+ .inflate(R.layout.recent_apps, parent, false);
+ if (landscape && isSw600Dp()) {
+ setupLandButton(v);
+ }
+ } else if (MENU_IME.equals(button)) {
+ v = (landscape ? mLandscapeInflater : mLayoutInflater)
+ .inflate(R.layout.menu_ime, parent, false);
+ } else if (NAVSPACE.equals(button)) {
+ v = (landscape ? mLandscapeInflater : mLayoutInflater)
+ .inflate(R.layout.nav_key_space, parent, false);
+ } else {
+ throw new IllegalArgumentException("Unknown button " + button);
+ }
+ parent.addView(v);
+ if (mButtonDispatchers != null) {
+ final int indexOfKey = mButtonDispatchers.indexOfKey(v.getId());
+ if (indexOfKey >= 0) {
+ mButtonDispatchers.valueAt(indexOfKey).addView(v);
+ }
+ }
+ return v;
+ }
+
+ private boolean isSw600Dp() {
+ Configuration configuration = mContext.getResources().getConfiguration();
+ return (configuration.smallestScreenWidthDp >= 600);
+ }
+
+ /**
+ * This manually sets the width of sw600dp landscape buttons because despite
+ * overriding the configuration from the overridden resources aren't loaded currently.
+ */
+ private void setupLandButton(View v) {
+ Resources res = mContext.getResources();
+ v.getLayoutParams().width = res.getDimensionPixelOffset(
+ R.dimen.navigation_key_width_sw600dp_land);
+ int padding = res.getDimensionPixelOffset(R.dimen.navigation_key_padding_sw600dp_land);
+ v.setPadding(padding, v.getPaddingTop(), padding, v.getPaddingBottom());
+ }
+
+ private void clearViews() {
+ if (mButtonDispatchers != null) {
+ for (int i = 0; i < mButtonDispatchers.size(); i++) {
+ mButtonDispatchers.valueAt(i).clear();
+ }
+ }
+ clearAllChildren((ViewGroup) mRot0.findViewById(R.id.nav_buttons));
+ clearAllChildren((ViewGroup) mRot0.findViewById(R.id.lights_out));
+ clearAllChildren((ViewGroup) mRot90.findViewById(R.id.nav_buttons));
+ clearAllChildren((ViewGroup) mRot90.findViewById(R.id.lights_out));
+ }
+
+ private void clearAllChildren(ViewGroup group) {
+ for (int i = 0; i < group.getChildCount(); i++) {
+ ((ViewGroup) group.getChildAt(i)).removeAllViews();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index efa8f5b9..839b579 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -21,6 +21,7 @@
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
+import android.annotation.Nullable;
import android.app.ActivityManagerNative;
import android.app.StatusBarManager;
import android.content.Context;
@@ -34,6 +35,7 @@
import android.os.RemoteException;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.SparseArray;
import android.view.Display;
import android.view.Gravity;
import android.view.IDockedStackListener.Stub;
@@ -44,15 +46,11 @@
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.inputmethod.InputMethodManager;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
import android.widget.LinearLayout;
-
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.policy.DeadZone;
-import com.android.systemui.statusbar.policy.KeyButtonView;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -100,6 +98,8 @@
private boolean mWakeAndUnlocking;
private boolean mCarMode = false;
+ private final SparseArray<ButtonDispatcher> mButtonDisatchers = new SparseArray<>();
+
private class NavTransitionListener implements TransitionListener {
private boolean mBackTransitioning;
private boolean mHomeAppearing;
@@ -131,12 +131,14 @@
}
public void onBackAltCleared() {
+ ButtonDispatcher backButton = getBackButton();
+
// When dismissing ime during unlock, force the back button to run the same appearance
// animation as home (if we catch this condition early enough).
- if (!mBackTransitioning && getBackButton().getVisibility() == VISIBLE
+ if (!mBackTransitioning && backButton.getVisibility() == VISIBLE
&& mHomeAppearing && getHomeButton().getAlpha() == 0) {
getBackButton().setAlpha(0);
- ValueAnimator a = ObjectAnimator.ofFloat(getBackButton(), "alpha", 0, 1);
+ ValueAnimator a = ObjectAnimator.ofFloat(backButton, "alpha", 0, 1);
a.setStartDelay(mStartDelay);
a.setDuration(mDuration);
a.setInterpolator(mInterpolator);
@@ -190,6 +192,12 @@
getIcons(context);
mBarTransitions = new NavigationBarTransitions(this);
+
+ mButtonDisatchers.put(R.id.back, new ButtonDispatcher(R.id.back));
+ mButtonDisatchers.put(R.id.home, new ButtonDispatcher(R.id.home));
+ mButtonDisatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
+ mButtonDisatchers.put(R.id.menu, new ButtonDispatcher(R.id.menu));
+ mButtonDisatchers.put(R.id.ime_switcher, new ButtonDispatcher(R.id.ime_switcher));
}
public BarTransitions getBarTransitions() {
@@ -235,26 +243,27 @@
return mRotatedViews;
}
- public KeyButtonView getRecentsButton() {
- return (KeyButtonView) getCurrentView().findViewById(R.id.recent_apps);
+ public ButtonDispatcher getRecentsButton() {
+ return mButtonDisatchers.get(R.id.recent_apps);
}
- public View getMenuButton() {
- return getCurrentView().findViewById(R.id.menu);
+ public ButtonDispatcher getMenuButton() {
+ return mButtonDisatchers.get(R.id.menu);
}
- public View getBackButton() {
- return getCurrentView().findViewById(R.id.back);
+ public ButtonDispatcher getBackButton() {
+ return mButtonDisatchers.get(R.id.back);
}
- public KeyButtonView getHomeButton() {
- return (KeyButtonView) getCurrentView().findViewById(R.id.home);
+ public ButtonDispatcher getHomeButton() {
+ return mButtonDisatchers.get(R.id.home);
}
- public View getImeSwitchButton() {
- return getCurrentView().findViewById(R.id.ime_switcher);
+ public ButtonDispatcher getImeSwitchButton() {
+ return mButtonDisatchers.get(R.id.ime_switcher);
}
+ @Nullable
public View getAppShelf() {
return getCurrentView().findViewById(R.id.app_shelf);
}
@@ -329,19 +338,20 @@
? getBackIconWithAlt(mCarMode, mVertical)
: getBackIcon(mCarMode, mVertical);
- ((ImageView) getBackButton()).setImageDrawable(backIcon);
+ getBackButton().setImageDrawable(backIcon);
- ((ImageView) getRecentsButton()).setImageDrawable(
+ getRecentsButton().setImageDrawable(
mVertical ? mRecentLandIcon : mRecentIcon);
if (mCarMode) {
- ((ImageView) getHomeButton()).setImageDrawable(mHomeCarModeIcon);
+ getHomeButton().setImageDrawable(mHomeCarModeIcon);
} else {
- ((ImageView) getHomeButton()).setImageDrawable(mHomeDefaultIcon);
+ getHomeButton().setImageDrawable(mHomeDefaultIcon);
}
final boolean showImeButton = ((hints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0);
getImeSwitchButton().setVisibility(showImeButton ? View.VISIBLE : View.INVISIBLE);
+
// Update menu button in case the IME state has changed.
setMenuVisibility(mShowMenu, true);
@@ -382,9 +392,9 @@
disableRecent = false;
}
- getBackButton() .setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
- getHomeButton() .setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
- getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
+ getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
+ getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
+ getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
// The app shelf, if it exists, follows the visibility of the home button.
View appShelf = getAppShelf();
@@ -475,6 +485,7 @@
// Only show Menu if IME switcher not shown.
final boolean shouldShow = mShowMenu &&
((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) == 0);
+
getMenuButton().setVisibility(shouldShow ? View.VISIBLE : View.INVISIBLE);
}
@@ -488,6 +499,11 @@
mRotatedViews[Surface.ROTATION_270] = mRotatedViews[Surface.ROTATION_90];
mCurrentView = mRotatedViews[Surface.ROTATION_0];
+ for (int i = 0; i < mButtonDisatchers.size(); i++) {
+ mButtonDisatchers.valueAt(i).setCurrentView(mCurrentView);
+ }
+ ((NavigationBarInflaterView) findViewById(R.id.navigation_inflater)).setButtonDispatchers(
+ mButtonDisatchers);
getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);
@@ -531,6 +547,9 @@
}
mCurrentView = mRotatedViews[rot];
mCurrentView.setVisibility(View.VISIBLE);
+ for (int i = 0; i < mButtonDisatchers.size(); i++) {
+ mButtonDisatchers.valueAt(i).setCurrentView(mCurrentView);
+ }
updateLayoutTransitionsEnabled();
getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);
@@ -621,33 +640,16 @@
// We swap all children of the 90 and 270 degree layouts, since they are vertical
View rotation90 = mRotatedViews[Surface.ROTATION_90];
- swapChildrenOrderIfVertical(rotation90.findViewById(R.id.nav_buttons));
- adjustExtraKeyGravity(rotation90, isLayoutRtl);
+ swapChildrenOrderIfVertical(rotation90);
View rotation270 = mRotatedViews[Surface.ROTATION_270];
if (rotation90 != rotation270) {
- swapChildrenOrderIfVertical(rotation270.findViewById(R.id.nav_buttons));
- adjustExtraKeyGravity(rotation270, isLayoutRtl);
+ swapChildrenOrderIfVertical(rotation270);
}
mIsLayoutRtl = isLayoutRtl;
}
}
- private void adjustExtraKeyGravity(View navBar, boolean isLayoutRtl) {
- View menu = navBar.findViewById(R.id.menu);
- View imeSwitcher = navBar.findViewById(R.id.ime_switcher);
- if (menu != null) {
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) menu.getLayoutParams();
- lp.gravity = isLayoutRtl ? Gravity.BOTTOM : Gravity.TOP;
- menu.setLayoutParams(lp);
- }
- if (imeSwitcher != null) {
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) imeSwitcher.getLayoutParams();
- lp.gravity = isLayoutRtl ? Gravity.BOTTOM : Gravity.TOP;
- imeSwitcher.setLayoutParams(lp);
- }
- }
-
/**
* Swaps the children order of a LinearLayout if it's orientation is Vertical
*
@@ -657,6 +659,11 @@
if (group instanceof LinearLayout) {
LinearLayout linearLayout = (LinearLayout) group;
if (linearLayout.getOrientation() == VERTICAL) {
+ if ((linearLayout.getGravity() & Gravity.TOP) != 0) {
+ linearLayout.setGravity(Gravity.BOTTOM);
+ } else if ((linearLayout.getGravity() & Gravity.BOTTOM) != 0) {
+ linearLayout.setGravity(Gravity.TOP);
+ }
int childCount = linearLayout.getChildCount();
ArrayList<View> childList = new ArrayList<>(childCount);
for (int i = 0; i < childCount; i++) {
@@ -667,6 +674,11 @@
linearLayout.addView(childList.get(i));
}
}
+ } else if (group instanceof ViewGroup) {
+ ViewGroup viewGroup = (ViewGroup) group;
+ for (int i = 0; i < viewGroup.getChildCount(); i++) {
+ swapChildrenOrderIfVertical(viewGroup.getChildAt(i));
+ }
}
}
@@ -754,13 +766,12 @@
pw.println(" }");
}
- private static void dumpButton(PrintWriter pw, String caption, View button) {
+ private static void dumpButton(PrintWriter pw, String caption, ButtonDispatcher button) {
pw.print(" " + caption + ": ");
if (button == null) {
pw.print("null");
} else {
- pw.print(PhoneStatusBar.viewInfo(button)
- + " " + visibilityToString(button.getVisibility())
+ pw.print(visibilityToString(button.getVisibility())
+ " alpha=" + button.getAlpha()
);
}
@@ -770,4 +781,5 @@
public interface OnVerticalChangedListener {
void onVerticalChanged(boolean isVertical);
}
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index f6fc259..3130eb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -16,12 +16,13 @@
package com.android.systemui.statusbar.phone;
-import android.app.Notification;
import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.util.HashMap;
import java.util.HashSet;
@@ -29,18 +30,19 @@
/**
* A class to handle notifications and their corresponding groups.
*/
-public class NotificationGroupManager {
+public class NotificationGroupManager implements HeadsUpManager.OnHeadsUpChangedListener {
private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>();
private OnGroupChangeListener mListener;
private int mBarState = -1;
+ private ArraySet<String> mHeadsUpedEntries = new ArraySet<>();
public void setOnGroupChangeListener(OnGroupChangeListener listener) {
mListener = listener;
}
public boolean isGroupExpanded(StatusBarNotification sbn) {
- NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+ NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
if (group == null) {
return false;
}
@@ -48,7 +50,7 @@
}
public void setGroupExpanded(StatusBarNotification sbn, boolean expanded) {
- NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+ NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
if (group == null) {
return;
}
@@ -75,8 +77,7 @@
*/
private void onEntryRemovedInternal(NotificationData.Entry removed,
final StatusBarNotification sbn) {
- Notification notif = sbn.getNotification();
- String groupKey = sbn.getGroupKey();
+ String groupKey = getGroupKey(sbn);
final NotificationGroup group = mGroupMap.get(groupKey);
if (group == null) {
// When an app posts 2 different notifications as summary of the same group, then a
@@ -85,7 +86,7 @@
// the close future. See b/23676310 for reference.
return;
}
- if (notif.isGroupChild()) {
+ if (isGroupChild(sbn)) {
group.children.remove(removed);
} else {
group.summary = null;
@@ -97,16 +98,16 @@
}
}
- public void onEntryAdded(NotificationData.Entry added) {
- StatusBarNotification sbn = added.notification;
- Notification notif = sbn.getNotification();
- String groupKey = sbn.getGroupKey();
+ public void onEntryAdded(final NotificationData.Entry added) {
+ final StatusBarNotification sbn = added.notification;
+ boolean isGroupChild = isGroupChild(sbn);
+ String groupKey = getGroupKey(sbn);
NotificationGroup group = mGroupMap.get(groupKey);
if (group == null) {
group = new NotificationGroup();
mGroupMap.put(groupKey, group);
}
- if (notif.isGroupChild()) {
+ if (isGroupChild) {
group.children.add(added);
} else {
group.summary = added;
@@ -119,17 +120,17 @@
public void onEntryUpdated(NotificationData.Entry entry,
StatusBarNotification oldNotification) {
- if (mGroupMap.get(oldNotification.getGroupKey()) != null) {
+ if (mGroupMap.get(getGroupKey(oldNotification)) != null) {
onEntryRemovedInternal(entry, oldNotification);
}
onEntryAdded(entry);
}
public boolean isVisible(StatusBarNotification sbn) {
- if (!sbn.getNotification().isGroupChild()) {
+ if (!isGroupChild(sbn)) {
return true;
}
- NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+ NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
if (group != null && (group.expanded || group.summary == null)) {
return true;
}
@@ -137,10 +138,10 @@
}
public boolean hasGroupChildren(StatusBarNotification sbn) {
- if (!sbn.getNotification().isGroupSummary()) {
+ if (!isGroupSummary(sbn)) {
return false;
}
- NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+ NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
if (group == null) {
return false;
}
@@ -165,10 +166,10 @@
* @return whether a given notification is a child in a group which has a summary
*/
public boolean isChildInGroupWithSummary(StatusBarNotification sbn) {
- if (!sbn.getNotification().isGroupChild()) {
+ if (!isGroupChild(sbn)) {
return false;
}
- NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+ NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
if (group == null || group.summary == null) {
return false;
}
@@ -179,10 +180,10 @@
* @return whether a given notification is a summary in a group which has children
*/
public boolean isSummaryOfGroup(StatusBarNotification sbn) {
- if (!sbn.getNotification().isGroupSummary()) {
+ if (!isGroupSummary(sbn)) {
return false;
}
- NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+ NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
if (group == null) {
return false;
}
@@ -190,24 +191,88 @@
}
public ExpandableNotificationRow getGroupSummary(StatusBarNotification sbn) {
- NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+ NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
return group == null ? null
: group.summary == null ? null
: group.summary.row;
}
- public void onEntryHeadsUped(NotificationData.Entry headsUp) {
- // TODO: handle this nicely
- }
-
public void toggleGroupExpansion(StatusBarNotification sbn) {
- NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+ NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
if (group == null) {
return;
}
setGroupExpanded(group, !group.expanded);
}
+ private boolean isIsolated(StatusBarNotification sbn) {
+ return mHeadsUpedEntries.contains(sbn.getKey()) && sbn.getNotification().isGroupChild();
+ }
+
+ private boolean isGroupSummary(StatusBarNotification sbn) {
+ if (isIsolated(sbn)) {
+ return true;
+ }
+ return sbn.getNotification().isGroupSummary();
+ }
+ private boolean isGroupChild(StatusBarNotification sbn) {
+ if (isIsolated(sbn)) {
+ return false;
+ }
+ return sbn.getNotification().isGroupChild();
+ }
+
+ private String getGroupKey(StatusBarNotification sbn) {
+ if (isIsolated(sbn)) {
+ return sbn.getKey();
+ }
+ return sbn.getGroupKey();
+ }
+
+ @Override
+ public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
+ }
+
+ @Override
+ public void onHeadsUpPinned(ExpandableNotificationRow headsUp) {
+ }
+
+ @Override
+ public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) {
+ }
+
+ @Override
+ public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
+ final StatusBarNotification sbn = entry.notification;
+ if (entry.row.isHeadsUp()) {
+ if (!mHeadsUpedEntries.contains(sbn.getKey())) {
+ final boolean groupChild = sbn.getNotification().isGroupChild();
+ if (groupChild) {
+ // We will be isolated now, so lets update the groups
+ onEntryRemovedInternal(entry, entry.notification);
+ }
+ mHeadsUpedEntries.add(sbn.getKey());
+ if (groupChild) {
+ onEntryAdded(entry);
+ mListener.onChildIsolationChanged();
+ }
+ }
+ } else {
+ if (mHeadsUpedEntries.contains(sbn.getKey())) {
+ boolean isolatedBefore = isIsolated(sbn);
+ if (isolatedBefore) {
+ // not isolated anymore, we need to update the groups
+ onEntryRemovedInternal(entry, entry.notification);
+ }
+ mHeadsUpedEntries.remove(sbn.getKey());
+ if (isolatedBefore) {
+ onEntryAdded(entry);
+ mListener.onChildIsolationChanged();
+ }
+ }
+ }
+ }
+
public static class NotificationGroup {
public final HashSet<NotificationData.Entry> children = new HashSet<>();
public NotificationData.Entry summary;
@@ -230,5 +295,10 @@
* @param group the group created
*/
void onGroupCreatedFromChildren(NotificationGroup group);
+
+ /**
+ * The isolation of a child has changed i.e it's group changes.
+ */
+ void onChildIsolationChanged();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index aa01bf2..f0b7894 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -81,7 +81,6 @@
private boolean mMotionAborted;
private boolean mUpwardsWhenTresholdReached;
private boolean mAnimatingOnDown;
- private int mLayoutHeight = 0;
private ValueAnimator mHeightAnimator;
private ObjectAnimator mPeekAnimator;
@@ -715,10 +714,7 @@
@Override
protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
- if (mLayoutHeight != getHeight()) {
- mLayoutHeight = getHeight();
- mStatusBar.onPanelHeightChanged();
- }
+ mStatusBar.onPanelLaidOut();
requestPanelHeightUpdate();
mHasLayoutedSinceDown = true;
if (mUpdateFlingOnLayout) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index bc869b5..ffc836c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -269,7 +269,7 @@
// These are no longer handled by the policy, because we need custom strategies for them
BluetoothControllerImpl mBluetoothController;
SecurityControllerImpl mSecurityController;
- BatteryController mBatteryController;
+ protected BatteryController mBatteryController;
LocationControllerImpl mLocationController;
NetworkControllerImpl mNetworkController;
HotspotControllerImpl mHotspotController;
@@ -293,7 +293,7 @@
Display mDisplay;
Point mCurrentDisplaySize = new Point();
- StatusBarWindowView mStatusBarWindow;
+ protected StatusBarWindowView mStatusBarWindow;
PhoneStatusBarView mStatusBarView;
private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
protected StatusBarWindowManager mStatusBarWindowManager;
@@ -673,8 +673,7 @@
updateDisplaySize(); // populates mDisplayMetrics
updateResources();
- mStatusBarWindow = (StatusBarWindowView) View.inflate(context,
- R.layout.super_status_bar, null);
+ inflateStatusBarWindow(context);
mStatusBarWindow.setService(this);
mStatusBarWindow.setOnTouchListener(new View.OnTouchListener() {
@Override
@@ -707,6 +706,7 @@
mHeadsUpManager.setBar(this);
mHeadsUpManager.addListener(this);
mHeadsUpManager.addListener(mNotificationPanel);
+ mHeadsUpManager.addListener(mGroupManager);
mNotificationPanel.setHeadsUpManager(mHeadsUpManager);
mNotificationData.setHeadsUpManager(mHeadsUpManager);
@@ -934,15 +934,13 @@
return mStatusBarView;
}
+ protected void inflateStatusBarWindow(Context context) {
+ mStatusBarWindow = (StatusBarWindowView) View.inflate(context,
+ R.layout.super_status_bar, null);
+ }
+
protected void createNavigationBarView(Context context) {
- // Optionally show app shortcuts in the nav bar "shelf" area.
- if (shouldShowAppShelf()) {
- mNavigationBarView = (NavigationBarView) View.inflate(
- context, R.layout.navigation_bar_with_apps, null);
- } else {
- mNavigationBarView = (NavigationBarView) View.inflate(
- context, R.layout.navigation_bar, null);
- }
+ inflateNavigationBarView(context);
mNavigationBarView.setDisabledFlags(mDisabled1);
mNavigationBarView.setComponents(mRecents, getComponent(Divider.class));
mNavigationBarView.setOnVerticalChangedListener(
@@ -963,7 +961,18 @@
}});
}
- private void initSignalCluster(View containerView) {
+ protected void inflateNavigationBarView(Context context) {
+ // Optionally show app shortcuts in the nav bar "shelf" area.
+ if (shouldShowAppShelf()) {
+ mNavigationBarView = (NavigationBarView) View.inflate(
+ context, R.layout.navigation_bar_with_apps, null);
+ } else {
+ mNavigationBarView = (NavigationBarView) View.inflate(
+ context, R.layout.navigation_bar, null);
+ }
+ }
+
+ protected void initSignalCluster(View containerView) {
SignalClusterView signalCluster =
(SignalClusterView) containerView.findViewById(R.id.signal_cluster);
if (signalCluster != null) {
@@ -1180,14 +1189,20 @@
private void prepareNavigationBarView() {
mNavigationBarView.reorient();
- mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener);
- mNavigationBarView.getRecentsButton().setOnTouchListener(mRecentsPreloadOnTouchListener);
- mNavigationBarView.getRecentsButton().setLongClickable(true);
- mNavigationBarView.getRecentsButton().setOnLongClickListener(mRecentsLongClickListener);
- mNavigationBarView.getBackButton().setLongClickable(true);
- mNavigationBarView.getBackButton().setOnLongClickListener(mLongPressBackListener);
- mNavigationBarView.getHomeButton().setOnTouchListener(mHomeActionListener);
- mNavigationBarView.getHomeButton().setOnLongClickListener(mLongPressHomeListener);
+ ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton();
+ recentsButton.setOnClickListener(mRecentsClickListener);
+ recentsButton.setOnTouchListener(mRecentsPreloadOnTouchListener);
+ recentsButton.setLongClickable(true);
+ recentsButton.setOnLongClickListener(mRecentsLongClickListener);
+
+ ButtonDispatcher backButton = mNavigationBarView.getBackButton();
+ backButton.setLongClickable(true);
+ backButton.setOnLongClickListener(mLongPressBackListener);
+
+ ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
+ homeButton.setOnTouchListener(mHomeActionListener);
+ homeButton.setOnLongClickListener(mLongPressHomeListener);
+
mAssistManager.onConfigurationChanged();
}
@@ -1392,16 +1407,21 @@
}
- ArrayList<View> toRemove = new ArrayList<>();
+ ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
for (int i=0; i< mStackScroller.getChildCount(); i++) {
View child = mStackScroller.getChildAt(i);
if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
- toRemove.add(child);
+ toRemove.add((ExpandableNotificationRow) child);
}
}
- for (View remove : toRemove) {
+ for (ExpandableNotificationRow remove : toRemove) {
+ if (mGroupManager.isChildInGroupWithSummary(remove.getStatusBarNotification())) {
+ // we are only transfering this notification to its parent, don't generate an animation
+ mStackScroller.setChildTransferInProgress(true);
+ }
mStackScroller.removeView(remove);
+ mStackScroller.setChildTransferInProgress(false);
}
for (int i=0; i<toShow.size(); i++) {
View v = toShow.get(i);
@@ -1557,6 +1577,10 @@
mIconController.updateNotificationIcons(mNotificationData);
}
+ public void requestNotificationUpdate() {
+ updateNotifications();
+ }
+
@Override
protected void updateRowStates() {
super.updateRowStates();
@@ -4019,9 +4043,10 @@
}
@Override
- public void onExpandClicked(View clickedView, boolean nowExpanded) {
+ public void onExpandClicked(Entry clickedEntry, boolean nowExpanded) {
+ mHeadsUpManager.setExpanded(clickedEntry, nowExpanded);
if (mState == StatusBarState.KEYGUARD && nowExpanded) {
- goToLockedShade(clickedView);
+ goToLockedShade(clickedEntry.row);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 813a167..902fd3d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -164,6 +164,9 @@
public void panelScrimMinFractionChanged(float minFraction) {
if (mMinFraction != minFraction) {
mMinFraction = minFraction;
+ if (minFraction != 0.0f) {
+ mScrimController.animateNextChange();
+ }
updateScrimFraction();
}
}
@@ -176,7 +179,7 @@
}
private void updateScrimFraction() {
- float scrimFraction = Math.max(mPanelFraction - mMinFraction / (1.0f - mMinFraction), 0);
+ float scrimFraction = Math.max(mPanelFraction, mMinFraction);
mScrimController.setPanelExpansion(scrimFraction);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index 29ad5d1c..7e27856 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -295,6 +295,7 @@
if (mTiles.containsKey(tileSpec)) {
QSTile<?> tile = mTiles.get(tileSpec);
if (DEBUG) Log.d(TAG, "Adding " + tile);
+ tile.removeCallbacks();
newTiles.put(tileSpec, tile);
} else {
if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java
new file mode 100644
index 0000000..da16954
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+/**
+ * Automatically reverses the order of children as they are added.
+ * Also reverse the width and height values of layout params
+ */
+public class ReverseLinearLayout extends LinearLayout {
+
+ public ReverseLinearLayout(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void addView(View child) {
+ reversParams(child.getLayoutParams());
+ super.addView(child, 0);
+ }
+
+ @Override
+ public void addView(View child, ViewGroup.LayoutParams params) {
+ reversParams(params);
+ super.addView(child, 0, params);
+ }
+
+ private void reversParams(ViewGroup.LayoutParams params) {
+ if (params == null) {
+ return;
+ }
+ int width = params.width;
+ params.width = params.height;
+ params.height = width;
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index b9e9292..403199a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -52,8 +52,8 @@
private static final float SCRIM_IN_FRONT_ALPHA = 0.75f;
private static final int TAG_KEY_ANIM = R.id.scrim;
private static final int TAG_KEY_ANIM_TARGET = R.id.scrim_target;
- private static final int TAG_HUN_START_ALPHA = R.id.hun_scrim_alpha_start;
- private static final int TAG_HUN_END_ALPHA = R.id.hun_scrim_alpha_end;
+ private static final int TAG_START_ALPHA = R.id.scrim_alpha_start;
+ private static final int TAG_END_ALPHA = R.id.scrim_alpha_end;
private final ScrimView mScrimBehind;
private final ScrimView mScrimInFront;
@@ -164,6 +164,10 @@
scheduleUpdate();
}
+ public void animateNextChange() {
+ mAnimateChange = true;
+ }
+
public void setDozing(boolean dozing) {
if (mDozing != dozing) {
mDozing = dozing;
@@ -271,21 +275,7 @@
}
private void setScrimColor(View scrim, float alpha) {
- ValueAnimator runningAnim = (ValueAnimator) scrim.getTag(TAG_KEY_ANIM);
- Float target = (Float) scrim.getTag(TAG_KEY_ANIM_TARGET);
- if (runningAnim != null && target != null) {
- if (alpha != target) {
- runningAnim.cancel();
- } else {
- return;
- }
- }
- if (mAnimateChange) {
- startScrimAnimation(scrim, alpha);
- } else {
- setCurrentScrimAlpha(scrim, alpha);
- updateScrimColor(scrim);
- }
+ updateScrim(mAnimateChange, scrim, alpha, getCurrentScrimAlpha(scrim));
}
private float getDozeAlpha(View scrim) {
@@ -428,41 +418,44 @@
}
private void updateHeadsUpScrim(boolean animate) {
- float alpha = calculateHeadsUpAlpha();
- ValueAnimator previousAnimator = StackStateAnimator.getChildTag(mHeadsUpScrim,
+ updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha(), mCurrentHeadsUpAlpha);
+ }
+
+ private void updateScrim(boolean animate, View scrim, float alpha, float currentAlpha) {
+ ValueAnimator previousAnimator = StackStateAnimator.getChildTag(scrim,
TAG_KEY_ANIM);
float animEndValue = -1;
if (previousAnimator != null) {
- if (animate || alpha == mCurrentHeadsUpAlpha) {
+ if (animate || alpha == currentAlpha) {
previousAnimator.cancel();
} else {
- animEndValue = StackStateAnimator.getChildTag(mHeadsUpScrim, TAG_HUN_END_ALPHA);
+ animEndValue = StackStateAnimator.getChildTag(scrim, TAG_END_ALPHA);
}
}
- if (alpha != mCurrentHeadsUpAlpha && alpha != animEndValue) {
+ if (alpha != currentAlpha && alpha != animEndValue) {
if (animate) {
- startScrimAnimation(mHeadsUpScrim, alpha);
- mHeadsUpScrim.setTag(TAG_HUN_START_ALPHA, mCurrentHeadsUpAlpha);
- mHeadsUpScrim.setTag(TAG_HUN_END_ALPHA, alpha);
+ startScrimAnimation(scrim, alpha);
+ scrim.setTag(TAG_START_ALPHA, currentAlpha);
+ scrim.setTag(TAG_END_ALPHA, alpha);
} else {
if (previousAnimator != null) {
- float previousStartValue = StackStateAnimator.getChildTag(mHeadsUpScrim,
- TAG_HUN_START_ALPHA);
- float previousEndValue = StackStateAnimator.getChildTag(mHeadsUpScrim,
- TAG_HUN_END_ALPHA);
+ float previousStartValue = StackStateAnimator.getChildTag(scrim,
+ TAG_START_ALPHA);
+ float previousEndValue = StackStateAnimator.getChildTag(scrim,
+ TAG_END_ALPHA);
// we need to increase all animation keyframes of the previous animator by the
// relative change to the end value
PropertyValuesHolder[] values = previousAnimator.getValues();
float relativeDiff = alpha - previousEndValue;
float newStartValue = previousStartValue + relativeDiff;
values[0].setFloatValues(newStartValue, alpha);
- mHeadsUpScrim.setTag(TAG_HUN_START_ALPHA, newStartValue);
- mHeadsUpScrim.setTag(TAG_HUN_END_ALPHA, alpha);
+ scrim.setTag(TAG_START_ALPHA, newStartValue);
+ scrim.setTag(TAG_END_ALPHA, alpha);
previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
} else {
// update the alpha directly
- setCurrentScrimAlpha(mHeadsUpScrim, alpha);
- updateScrimColor(mHeadsUpScrim);
+ setCurrentScrimAlpha(scrim, alpha);
+ updateScrimColor(scrim);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index b16c98e..64fb066 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -20,6 +20,7 @@
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.Icon;
import android.os.Bundle;
@@ -28,6 +29,7 @@
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArraySet;
+import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
@@ -122,7 +124,10 @@
notificationIconArea.addView(mNotificationIconAreaInner);
mStatusIconsKeyguard = (LinearLayout) keyguardStatusBar.findViewById(R.id.statusIcons);
+
mBatteryMeterView = (BatteryMeterView) statusBar.findViewById(R.id.battery);
+ maybeScaleBatteryMeterView(context);
+
mClock = (TextView) statusBar.findViewById(R.id.clock);
mLinearOutSlowIn = AnimationUtils.loadInterpolator(mContext,
android.R.interpolator.linear_out_slow_in);
@@ -136,6 +141,30 @@
TunerService.get(mContext).addTunable(this, ICON_BLACKLIST);
}
+ /**
+ * Looks up the scale factor for status bar icons and scales the battery view by that amount
+ * if appropriate.
+ */
+ private void maybeScaleBatteryMeterView(Context context) {
+ Resources res = context.getResources();
+ TypedValue typedValue = new TypedValue();
+
+ res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
+ float iconScaleFactor = typedValue.getFloat();
+
+ if (iconScaleFactor == 1.f) {
+ return;
+ }
+
+ float batteryHeight = res.getDimension(R.dimen.status_bar_battery_icon_height);
+ float batteryWidth = res.getDimension(R.dimen.status_bar_battery_icon_width);
+
+ LinearLayout.LayoutParams scaledLayoutParams = new LinearLayout.LayoutParams(
+ (int) (batteryWidth * iconScaleFactor), (int) (batteryHeight * iconScaleFactor));
+
+ mBatteryMeterView.setLayoutParams(scaledLayoutParams);
+ }
+
@Override
public void onTuningChanged(String key, String newValue) {
if (!ICON_BLACKLIST.equals(key)) {
@@ -174,8 +203,12 @@
boolean blocked = mIconBlacklist.contains(slot);
StatusBarIconView view = new StatusBarIconView(mContext, slot, null, blocked);
view.set(icon);
- mStatusIcons.addView(view, viewIndex, new LinearLayout.LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize));
+
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
+ lp.setMargins(mIconHPadding, 0, mIconHPadding, 0);
+ mStatusIcons.addView(view, viewIndex, lp);
+
view = new StatusBarIconView(mContext, slot, null, blocked);
view.set(icon);
mStatusIconsKeyguard.addView(view, viewIndex, new LinearLayout.LayoutParams(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index b38c3fe..dd7bd56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -190,7 +190,6 @@
if (alert) {
HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(headsUp.key);
headsUpEntry.updateEntry();
- mGroupManager.onEntryHeadsUped(headsUp);
setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUp));
}
}
@@ -384,10 +383,17 @@
for (HeadsUpEntry entry : mSortedEntries) {
ExpandableNotificationRow row = entry.entry.row;
if (row.isPinned()) {
+ if (row.isChildInGroup()) {
+ final ExpandableNotificationRow groupSummary
+ = mGroupManager.getGroupSummary(row.getStatusBarNotification());
+ if (groupSummary != null) {
+ row = groupSummary;
+ }
+ }
row.getLocationOnScreen(mTmpTwoArray);
minX = mTmpTwoArray[0];
maxX = mTmpTwoArray[0] + row.getWidth();
- maxY = row.getHeadsUpHeight();
+ maxY = row.getIntrinsicHeight();
break;
}
}
@@ -448,6 +454,8 @@
for (String key : mHeadsUpEntries.keySet()) {
HeadsUpEntry entry = mHeadsUpEntries.get(key);
setEntryPinned(entry, false /* isPinned */);
+ // maybe it got un sticky
+ entry.updateEntry(false /* updatePostTime */);
}
}
@@ -470,6 +478,10 @@
mTrackingHeadsUp = trackingHeadsUp;
}
+ public boolean isTrackingHeadsUp() {
+ return mTrackingHeadsUp;
+ }
+
public void setIsExpanded(boolean isExpanded) {
if (isExpanded != mIsExpanded) {
mIsExpanded = isExpanded;
@@ -482,9 +494,25 @@
}
}
- public int getTopHeadsUpHeight() {
+ /**
+ * @return the height of the top heads up notification when pinned. This is different from the
+ * intrinsic height, which also includes whether the notification is system expanded and
+ * is mainly used when dragging down from a heads up notification.
+ */
+ public int getTopHeadsUpPinnedHeight() {
HeadsUpEntry topEntry = getTopEntry();
- return topEntry != null ? topEntry.entry.row.getHeadsUpHeight() : 0;
+ if (topEntry == null || topEntry.entry == null) {
+ return 0;
+ }
+ ExpandableNotificationRow row = topEntry.entry.row;
+ if (row.isChildInGroup()) {
+ final ExpandableNotificationRow groupSummary
+ = mGroupManager.getGroupSummary(row.getStatusBarNotification());
+ if (groupSummary != null) {
+ row = groupSummary;
+ }
+ }
+ return row.getPinnedHeadsUpHeight(true /* atLeastMinHeight */);
}
/**
@@ -558,6 +586,22 @@
}
/**
+ * Set an entry to be expanded and therefore stick in the heads up area if it's pinned
+ * until it's collapsed again.
+ */
+ public void setExpanded(NotificationData.Entry entry, boolean expanded) {
+ HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key);
+ if (headsUpEntry != null && headsUpEntry.expanded != expanded) {
+ headsUpEntry.expanded = expanded;
+ if (expanded) {
+ headsUpEntry.removeAutoRemovalCallbacks();
+ } else {
+ headsUpEntry.updateEntry(false /* updatePostTime */);
+ }
+ }
+ }
+
+ /**
* This represents a notification and how long it is in a heads up mode. It also manages its
* lifecycle automatically when created.
*/
@@ -567,6 +611,7 @@
public long earliestRemovaltime;
private Runnable mRemoveHeadsUpRunnable;
public boolean remoteInputActive;
+ public boolean expanded;
public void setEntry(final NotificationData.Entry entry) {
this.entry = entry;
@@ -601,7 +646,7 @@
if (mEntriesToRemoveAfterExpand.contains(entry)) {
mEntriesToRemoveAfterExpand.remove(entry);
}
- if (!hasFullScreenIntent(entry) && !mRemoteInputActive) {
+ if (!isSticky()) {
long finishTime = postTime + mHeadsUpNotificationDecay;
long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime);
mHandler.postDelayed(mRemoveHeadsUpRunnable, removeDelay);
@@ -609,6 +654,11 @@
mSortedEntries.add(HeadsUpEntry.this);
}
+ private boolean isSticky() {
+ return (entry.row.isPinned() && expanded)
+ || remoteInputActive || hasFullScreenIntent(entry);
+ }
+
@Override
public int compareTo(HeadsUpEntry o) {
boolean selfFullscreen = hasFullScreenIntent(entry);
@@ -641,6 +691,8 @@
removeAutoRemovalCallbacks();
entry = null;
mRemoveHeadsUpRunnable = null;
+ expanded = false;
+ remoteInputActive = false;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index 3a97be6..a3f571e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -155,13 +155,7 @@
}
public int getInnerHeight() {
- return mLayoutHeight - mTopPadding - getTopHeadsUpPushIn();
- }
-
- private int getTopHeadsUpPushIn() {
- ExpandableNotificationRow topHeadsUpEntry = getTopHeadsUpEntry();
- return topHeadsUpEntry != null ? topHeadsUpEntry.getHeadsUpHeight()
- - topHeadsUpEntry.getMinHeight(): 0;
+ return mLayoutHeight - mTopPadding;
}
public boolean isShadeExpanded() {
@@ -180,11 +174,6 @@
return mMaxHeadsUpTranslation;
}
- public ExpandableNotificationRow getTopHeadsUpEntry() {
- HeadsUpManager.HeadsUpEntry topEntry = mHeadsUpManager.getTopEntry();
- return topEntry == null ? null : topEntry.entry.row;
- }
-
public void setDismissAllInProgress(boolean dismissAllInProgress) {
mDismissAllInProgress = dismissAllInProgress;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index baccd2c4..dc40fb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -24,6 +24,7 @@
import com.android.systemui.R;
import com.android.systemui.ViewInvertHelper;
+import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.HybridNotificationViewManager;
@@ -170,8 +171,15 @@
mChildren.remove(row);
removeView(row);
- View divider = mDividers.remove(childIndex);
+ final View divider = mDividers.remove(childIndex);
removeView(divider);
+ getOverlay().add(divider);
+ CrossFadeHelper.fadeOut(divider, new Runnable() {
+ @Override
+ public void run() {
+ getOverlay().remove(divider);
+ }
+ });
row.setSystemChildExpanded(false);
updateGroupOverflow();
@@ -403,31 +411,20 @@
}
public void startAnimationToState(StackScrollState state, StackStateAnimator stateAnimator,
- boolean withDelays, long baseDelay, long duration) {
+ long baseDelay, long duration) {
int childCount = mChildren.size();
ViewState tmpState = new ViewState();
- int delayIndex = 0;
- int maxAllowChildCount = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
for (int i = childCount - 1; i >= 0; i--) {
ExpandableNotificationRow child = mChildren.get(i);
StackViewState viewState = state.getViewStateForView(child);
- int difference = Math.min(StackStateAnimator.DELAY_EFFECT_MAX_INDEX_DIFFERENCE_CHILDREN,
- delayIndex);
- long delay = withDelays
- ? difference * StackStateAnimator.ANIMATION_DELAY_PER_ELEMENT_EXPAND_CHILDREN
- : 0;
- delay = (long) (delay * (mChildrenExpanded ? 1.0f : 0.5f) + baseDelay);
- stateAnimator.startStackAnimations(child, viewState, state, -1, delay);
+ stateAnimator.startStackAnimations(child, viewState, state, -1, baseDelay);
// layout the divider
View divider = mDividers.get(i);
tmpState.initFrom(divider);
tmpState.yTranslation = viewState.yTranslation - mDividerHeight;
tmpState.alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0;
- stateAnimator.startViewAnimations(divider, tmpState, delay, duration);
- if (i < maxAllowChildCount) {
- delayIndex++;
- }
+ stateAnimator.startViewAnimations(divider, tmpState, baseDelay, duration);
}
if (mGroupOverflowContainer != null) {
stateAnimator.startViewAnimations(mGroupOverflowContainer, mGroupOverFlowState,
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 0ed1527..36b2810 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -138,6 +138,7 @@
private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
private boolean mAnimationsEnabled;
private boolean mChangePositionInProgress;
+ private boolean mChildTransferInProgress;
/**
* The raw amount of the overScroll on the top, which is not rubber-banded.
@@ -363,14 +364,14 @@
updateContentHeight();
clampScrollPosition();
if (mRequestViewResizeAnimationOnLayout) {
- requestAnimationOnViewResize();
+ requestAnimationOnViewResize(null);
mRequestViewResizeAnimationOnLayout = false;
}
requestChildrenUpdate();
}
- private void requestAnimationOnViewResize() {
- if (mIsExpanded && mAnimationsEnabled) {
+ private void requestAnimationOnViewResize(ExpandableNotificationRow row) {
+ if (mAnimationsEnabled && (mIsExpanded || row != null && row.isPinned())) {
mNeedViewResizeAnimation = true;
mNeedsAnimation = true;
}
@@ -495,27 +496,28 @@
int stackHeight;
float paddingOffset;
boolean trackingHeadsUp = mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp();
- int normalUnfoldPositionStart = trackingHeadsUp ? mHeadsUpManager.getTopHeadsUpHeight()
+ int normalUnfoldPositionStart = trackingHeadsUp
+ ? mHeadsUpManager.getTopHeadsUpPinnedHeight()
: minStackHeight;
if (newStackHeight - mTopPadding - mTopPaddingOverflow >= normalUnfoldPositionStart
|| getNotGoneChildCount() == 0) {
paddingOffset = mTopPaddingOverflow;
stackHeight = newStackHeight;
} else {
-
- // We did not reach the position yet where we actually start growing,
- // so we translate the stack upwards.
- int translationY = (newStackHeight - minStackHeight);
- // A slight parallax effect is introduced in order for the stack to catch up with
- // the top card.
- float partiallyThere = (newStackHeight - mTopPadding - mTopPaddingOverflow)
- / minStackHeight;
- partiallyThere = Math.max(0, partiallyThere);
+ int translationY;
if (!trackingHeadsUp) {
+ // We did not reach the position yet where we actually start growing,
+ // so we translate the stack upwards.
+ translationY = (newStackHeight - minStackHeight);
+ // A slight parallax effect is introduced in order for the stack to catch up with
+ // the top card.
+ float partiallyThere = (newStackHeight - mTopPadding - mTopPaddingOverflow)
+ / minStackHeight;
+ partiallyThere = Math.max(0, partiallyThere);
translationY += (1 - partiallyThere) * (mBottomStackPeekSize +
mCollapseSecondCardPadding);
} else {
- translationY = (int) (height - mHeadsUpManager.getTopHeadsUpHeight());
+ translationY = (int) (height - normalUnfoldPositionStart);
}
paddingOffset = translationY - mTopPadding;
stackHeight = (int) (height - (translationY - mTopPadding));
@@ -729,7 +731,10 @@
if (slidingChild instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
- && mHeadsUpManager.getTopEntry().entry.row != row) {
+ && mHeadsUpManager.getTopEntry().entry.row != row
+ && mGroupManager.getGroupSummary(
+ mHeadsUpManager.getTopEntry().entry.row.getStatusBarNotification())
+ != row) {
continue;
}
return row.getViewAtPosition(touchY - childTop);
@@ -1506,7 +1511,7 @@
public int getMinStackHeight() {
final ExpandableView firstChild = getFirstChildNotGone();
- final int firstChildMinHeight = firstChild != null ? (int) firstChild.getMinHeight()
+ final int firstChildMinHeight = firstChild != null ? firstChild.getMinHeight()
: mCollapsedSize;
return firstChildMinHeight + mBottomStackPeekSize + mCollapseSecondCardPadding;
}
@@ -1628,12 +1633,16 @@
}
}
+ public void setChildTransferInProgress(boolean childTransferInProgress) {
+ mChildTransferInProgress = childTransferInProgress;
+ }
+
@Override
public void onViewRemoved(View child) {
super.onViewRemoved(child);
// we only call our internal methods if this is actually a removal and not just a
// notification which becomes a child notification
- if (!isChildInGroup(child)) {
+ if (!mChildTransferInProgress) {
onViewRemovedInternal(child);
}
}
@@ -2337,7 +2346,10 @@
clampScrollPosition();
notifyHeightChangeListener(view);
if (needsAnimation) {
- requestAnimationOnViewResize();
+ ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
+ ? (ExpandableNotificationRow) view
+ : null;
+ requestAnimationOnViewResize(row);
}
requestChildrenUpdate();
}
@@ -2801,7 +2813,7 @@
@Override
public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) {
- boolean animated = mAnimationsEnabled && mIsExpanded;
+ boolean animated = mAnimationsEnabled && (mIsExpanded || changedRow.isPinned());
if (animated) {
mExpandedGroupView = changedRow;
mNeedsAnimation = true;
@@ -2812,13 +2824,12 @@
@Override
public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
- for (NotificationData.Entry entry : group.children) {
- ExpandableNotificationRow row = entry.row;
- if (indexOfChild(row) != -1) {
- removeView(row);
- group.summary.row.addChildNotification(row);
- }
- }
+ mPhoneStatusBar.requestNotificationUpdate();
+ }
+
+ @Override
+ public void onChildIsolationChanged() {
+ mPhoneStatusBar.requestNotificationUpdate();
}
public void generateChildOrderChangedEvent() {
@@ -3113,7 +3124,7 @@
StackStateAnimator.ANIMATION_DURATION_STANDARD,
// ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
- StackStateAnimator.ANIMATION_DURATION_EXPAND_CLICKED,
+ StackStateAnimator.ANIMATION_DURATION_STANDARD,
// ANIMATION_TYPE_HEADS_UP_APPEAR
StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index 5496963..822012d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -410,10 +410,6 @@
// How far in is the element currently transitioning into the bottom stack.
float yPositionInScrollView = 0.0f;
- // If we have a heads-up higher than the collapsed height we need to add the difference to
- // the padding of all other elements, i.e push in the top stack slightly.
- ExpandableNotificationRow topHeadsUpEntry = ambientState.getTopHeadsUpEntry();
-
int childCount = algorithmState.visibleChildren.size();
int numberOfElementsCompletelyIn = algorithmState.partialInTop == 1.0f
? algorithmState.lastTopStackIndex
@@ -422,7 +418,7 @@
ExpandableView child = algorithmState.visibleChildren.get(i);
StackViewState childViewState = resultState.getViewStateForView(child);
childViewState.location = StackViewState.LOCATION_UNKNOWN;
- int childHeight = getMaxAllowedChildHeight(child, ambientState);
+ int childHeight = getMaxAllowedChildHeight(child);
int minHeight = child.getMinHeight();
float yPositionInScrollViewAfterElement = yPositionInScrollView
+ childHeight
@@ -504,11 +500,6 @@
currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements;
yPositionInScrollView = yPositionInScrollViewAfterElement;
- if (ambientState.isShadeExpanded() && topHeadsUpEntry != null
- && child != topHeadsUpEntry) {
- childViewState.yTranslation += topHeadsUpEntry.getHeadsUpHeight() -
- mFirstChildMinHeight;
- }
childViewState.yTranslation += ambientState.getTopPadding()
+ ambientState.getStackTranslation();
}
@@ -533,10 +524,6 @@
StackViewState childState = resultState.getViewStateForView(row);
boolean isTopEntry = topHeadsUpEntry == row;
if (mIsExpanded) {
- if (isTopEntry) {
- childState.height += row.getHeadsUpHeight() - mFirstChildMinHeight;
- }
- childState.height = Math.max(childState.height, row.getHeadsUpHeight());
// Ensure that the heads up is always visible even when scrolled off from the bottom
float bottomPosition = ambientState.getMaxHeadsUpTranslation() - childState.height;
childState.yTranslation = Math.min(childState.yTranslation,
@@ -545,12 +532,12 @@
if (row.isPinned()) {
childState.yTranslation = Math.max(childState.yTranslation,
mNotificationsTopPadding);
- childState.height = Math.max(row.getHeadsUpHeight(), childState.height);
+ childState.height = Math.max(row.getIntrinsicHeight(), childState.height);
if (!isTopEntry) {
// Ensure that a headsUp doesn't vertically extend further than the heads-up at
// the top most z-position
StackViewState topState = resultState.getViewStateForView(topHeadsUpEntry);
- childState.height = row.getHeadsUpHeight();
+ childState.height = row.getIntrinsicHeight();
childState.yTranslation = topState.yTranslation + topState.height
- childState.height;
}
@@ -608,16 +595,8 @@
mFirstChildMinHeight - childHeight);
}
- private int getMaxAllowedChildHeight(View child, AmbientState ambientState) {
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- if (ambientState == null && row.isHeadsUp()
- || ambientState != null && ambientState.getTopHeadsUpEntry() == child) {
- int extraSize = row.getIntrinsicHeight() - row.getHeadsUpHeight();
- return mFirstChildMinHeight + extraSize;
- }
- return row.getIntrinsicHeight();
- } else if (child instanceof ExpandableView) {
+ private int getMaxAllowedChildHeight(View child) {
+ if (child instanceof ExpandableView) {
ExpandableView expandableView = (ExpandableView) child;
return expandableView.getIntrinsicHeight();
}
@@ -744,7 +723,7 @@
for (int i = 0; i < childCount; i++) {
ExpandableView child = algorithmState.visibleChildren.get(i);
StackViewState childViewState = resultState.getViewStateForView(child);
- int childHeight = getMaxAllowedChildHeight(child, ambientState);
+ int childHeight = getMaxAllowedChildHeight(child);
float yPositionInScrollViewAfterElement = yPositionInScrollView
+ childHeight
+ mPaddingBetweenElements;
@@ -865,13 +844,6 @@
// current height or the end value of the animation.
mFirstChildMaxHeight = StackStateAnimator.getFinalActualHeight(
mFirstChildWhileExpanding);
- if (mFirstChildWhileExpanding instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row =
- (ExpandableNotificationRow) mFirstChildWhileExpanding;
- if (row.isHeadsUp()) {
- mFirstChildMaxHeight += mFirstChildMinHeight - row.getHeadsUpHeight();
- }
- }
} else {
updateFirstChildMaxSizeToMaxHeight();
}
@@ -893,7 +865,7 @@
int oldBottom) {
if (mFirstChildWhileExpanding != null) {
mFirstChildMaxHeight = getMaxAllowedChildHeight(
- mFirstChildWhileExpanding, null);
+ mFirstChildWhileExpanding);
} else {
mFirstChildMaxHeight = 0;
}
@@ -901,7 +873,7 @@
}
});
} else {
- mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding, null);
+ mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
index b4ab48a..9d5e072 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -43,17 +43,14 @@
public static final int ANIMATION_DURATION_STANDARD = 360;
public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448;
public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
- public static final int ANIMATION_DURATION_EXPAND_CLICKED = 360;
public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 650;
public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 230;
public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
- public static final int ANIMATION_DELAY_PER_ELEMENT_EXPAND_CHILDREN = 54;
public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
public static final int ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE = 48;
public static final int ANIMATION_DELAY_PER_ELEMENT_DARK = 24;
public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2;
- public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE_CHILDREN = 3;
public static final int ANIMATION_DELAY_HEADS_UP = 120;
private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag;
@@ -263,8 +260,7 @@
delay + duration);
} else if (child instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- row.startChildAnimation(finalState, this, child == mChildExpandingView, delay,
- duration);
+ row.startChildAnimation(finalState, this, delay, duration);
}
}
@@ -898,7 +894,7 @@
event.animationType == NotificationStackScrollLayout
.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) {
mHeadsUpDisappearChildren.add(changingView);
- if (mHostLayout.indexOfChild(changingView) == -1) {
+ if (changingView.getParent() == null) {
// This notification was actually removed, so we need to add it to the overlay
mHostLayout.getOverlay().add(changingView);
mTmpState.initFrom(changingView);
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/QSPagingSwitch.java b/packages/SystemUI/src/com/android/systemui/tuner/QSPagingSwitch.java
deleted file mode 100644
index d19a825..0000000
--- a/packages/SystemUI/src/com/android/systemui/tuner/QSPagingSwitch.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.android.systemui.tuner;
-
-import android.content.Context;
-import android.provider.Settings;
-import android.util.AttributeSet;
-
-import com.android.systemui.statusbar.phone.QSTileHost;
-
-public class QSPagingSwitch extends TunerSwitch {
-
- public static final String QS_PAGE_TILES =
- "dnd,cell,battery,user,rotation,flashlight,location,"
- + "hotspot,inversion,cast";
-
- public QSPagingSwitch(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected boolean persistBoolean(boolean value) {
- Settings.Secure.putString(getContext().getContentResolver(), QSTileHost.TILES_SETTING,
- value ? QS_PAGE_TILES : "default");
- return super.persistBoolean(value);
- }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
index 2aac69a..bed1e9e 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
@@ -29,6 +29,7 @@
import android.graphics.Rect;
import android.os.Handler;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.Log;
import java.util.ArrayList;
@@ -37,6 +38,9 @@
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
+
/**
* Manages the picture-in-picture (PIP) UI and states.
*/
@@ -46,10 +50,15 @@
private static PipManager sPipManager;
+ private static final int MAX_RUNNING_TASKS_COUNT = 10;
+
private static final int STATE_NO_PIP = 0;
private static final int STATE_PIP_OVERLAY = 1;
private static final int STATE_PIP_MENU = 2;
+ private static final int TASK_ID_NO_PIP = -1;
+ private static final int INVALID_RESOURCE_TYPE = -1;
+
private Context mContext;
private IActivityManager mActivityManager;
private int mState = STATE_NO_PIP;
@@ -58,6 +67,8 @@
private Rect mPipBound;
private Rect mMenuModePipBound;
private boolean mInitialized;
+ private int mPipTaskId = TASK_ID_NO_PIP;
+
private final Runnable mOnActivityPinnedRunnable = new Runnable() {
@Override
public void run() {
@@ -73,8 +84,8 @@
return;
}
if (DEBUG) Log.d(TAG, "PINNED_STACK:" + stackInfo);
- mState = STATE_PIP_OVERLAY;
- launchPipOverlayActivity();
+ mPipTaskId = stackInfo.taskIds[stackInfo.taskIds.length - 1];
+ showPipOverlay(false);
}
};
private final Runnable mOnTaskStackChanged = new Runnable() {
@@ -86,15 +97,27 @@
}
};
- private final BroadcastReceiver mPipButtonReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (DEBUG) Log.d(TAG, "PIP button pressed");
- if (!hasPipTasks()) {
- startPip();
- } else if (mState == STATE_PIP_OVERLAY) {
- showPipMenu();
+ String action = intent.getAction();
+ if (Intent.ACTION_PICTURE_IN_PICTURE_BUTTON.equals(action)) {
+ if (DEBUG) Log.d(TAG, "PIP button pressed");
+ if (!hasPipTasks()) {
+ startPip();
+ } else if (mState == STATE_PIP_OVERLAY) {
+ showPipMenu();
+ }
+ } else if (Intent.ACTION_MEDIA_RESOURCE_GRANTED.equals(action)) {
+ String[] packageNames = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
+ int resourceType = intent.getIntExtra(Intent.EXTRA_MEDIA_RESOURCE_TYPE,
+ INVALID_RESOURCE_TYPE);
+ if (mState != STATE_NO_PIP && packageNames != null && packageNames.length > 0
+ && resourceType == Intent.EXTRA_MEDIA_RESOURCE_TYPE_VIDEO_CODEC) {
+ handleMediaResourceGranted(packageNames);
+ }
}
+
}
};
@@ -125,7 +148,8 @@
}
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_PICTURE_IN_PICTURE_BUTTON);
- mContext.registerReceiver(mPipButtonReceiver, intentFilter);
+ intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED);
+ mContext.registerReceiver(mBroadcastReceiver, intentFilter);
}
private void startPip() {
@@ -142,6 +166,7 @@
*/
public void closePip() {
mState = STATE_NO_PIP;
+ mPipTaskId = TASK_ID_NO_PIP;
StackInfo stackInfo = null;
try {
stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
@@ -166,6 +191,7 @@
*/
public void movePipToFullscreen() {
mState = STATE_NO_PIP;
+ mPipTaskId = TASK_ID_NO_PIP;
for (int i = mListeners.size() - 1; i >= 0; --i) {
mListeners.get(i).onMoveToFullscreen();
}
@@ -181,17 +207,17 @@
* stack to the default PIP bound {@link com.android.internal.R.string
* .config_defaultPictureInPictureBounds}.
*/
- public void showPipOverlay() {
+ public void showPipOverlay(boolean resizeStack) {
if (DEBUG) Log.d(TAG, "showPipOverlay()");
- try {
- mActivityManager.resizeStack(PINNED_STACK_ID, mPipBound, false);
- } catch (Exception e) {
- Log.e(TAG, "resizeStack failed", e);
- closePip();
- return;
- }
mState = STATE_PIP_OVERLAY;
- launchPipOverlayActivity();
+ Intent intent = new Intent(mContext, PipOverlayActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchStackId(PINNED_STACK_ID);
+ if (resizeStack) {
+ options.setLaunchBounds(mPipBound);
+ }
+ mContext.startActivity(intent, options.toBundle());
}
/**
@@ -201,13 +227,6 @@
*/
public void showPipMenu() {
if (DEBUG) Log.d(TAG, "showPipMenu()");
- try {
- mActivityManager.resizeStack(PINNED_STACK_ID, mMenuModePipBound, false);
- } catch (Exception e) {
- Log.e(TAG, "resizeStack failed", e);
- closePip();
- return;
- }
mState = STATE_PIP_MENU;
for (int i = mListeners.size() - 1; i >= 0; --i) {
mListeners.get(i).onShowPipMenu();
@@ -216,6 +235,7 @@
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchStackId(PINNED_STACK_ID);
+ options.setLaunchBounds(mMenuModePipBound);
mContext.startActivity(intent, options.toBundle());
}
@@ -233,14 +253,6 @@
mListeners.remove(listener);
}
- private void launchPipOverlayActivity() {
- Intent intent = new Intent(mContext, PipOverlayActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- final ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchStackId(PINNED_STACK_ID);
- mContext.startActivity(intent, options.toBundle());
- }
-
private boolean hasPipTasks() {
try {
StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
@@ -251,6 +263,45 @@
}
}
+ private void handleMediaResourceGranted(String[] packageNames) {
+ StackInfo fullscreenStack = null;
+ try {
+ fullscreenStack = mActivityManager.getStackInfo(FULLSCREEN_WORKSPACE_STACK_ID);
+ } catch (RemoteException e) {
+ Log.e(TAG, "getStackInfo failed", e);
+ }
+ if (fullscreenStack == null) {
+ return;
+ }
+ int fullscreenTopTaskId = fullscreenStack.taskIds[fullscreenStack.taskIds.length - 1];
+ List<RunningTaskInfo> tasks = null;
+ try {
+ tasks = mActivityManager.getTasks(MAX_RUNNING_TASKS_COUNT, 0);
+ } catch (RemoteException e) {
+ Log.e(TAG, "getTasks failed", e);
+ }
+ if (tasks == null) {
+ return;
+ }
+ boolean wasGrantedInFullscreen = false;
+ boolean wasGrantedInPip = false;
+ for (int i = tasks.size() - 1; i >= 0; --i) {
+ RunningTaskInfo task = tasks.get(i);
+ for (int j = packageNames.length - 1; j >= 0; --j) {
+ if (task.topActivity.getPackageName().equals(packageNames[j])) {
+ if (task.id == fullscreenTopTaskId) {
+ wasGrantedInFullscreen = true;
+ } else if (task.id == mPipTaskId) {
+ wasGrantedInPip= true;
+ }
+ }
+ }
+ }
+ if (wasGrantedInFullscreen && !wasGrantedInPip) {
+ closePip();
+ }
+ }
+
private class TaskStackListener extends ITaskStackListener.Stub {
@Override
public void onTaskStackChanged() throws RemoteException {
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
index 1248321..97c70ed 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
@@ -55,7 +55,7 @@
findViewById(R.id.cancel).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- mPipManager.showPipOverlay();
+ mPipManager.showPipOverlay(true);
finish();
}
});
@@ -69,7 +69,7 @@
@Override
public void onBackPressed() {
- mPipManager.showPipOverlay();
+ mPipManager.showPipOverlay(true);
finish();
}
diff --git a/rs/java/android/renderscript/RenderScript.java b/rs/java/android/renderscript/RenderScript.java
index 7eb8005..3668ddd 100644
--- a/rs/java/android/renderscript/RenderScript.java
+++ b/rs/java/android/renderscript/RenderScript.java
@@ -751,6 +751,14 @@
rsnScriptReduce(mContext, id, slot, ain, aout, limits);
}
+ native void rsnScriptReduceNew(long con, long id, int slot, long[] ains,
+ long aout, int[] limits);
+ synchronized void nScriptReduceNew(long id, int slot, long ains[], long aout,
+ int[] limits) {
+ validate();
+ rsnScriptReduceNew(mContext, id, slot, ains, aout, limits);
+ }
+
native void rsnScriptInvokeV(long con, long id, int slot, byte[] params);
synchronized void nScriptInvokeV(long id, int slot, byte[] params) {
validate();
diff --git a/rs/java/android/renderscript/Script.java b/rs/java/android/renderscript/Script.java
index ed4c6c7..84f980d 100644
--- a/rs/java/android/renderscript/Script.java
+++ b/rs/java/android/renderscript/Script.java
@@ -284,7 +284,7 @@
}
/**
- * Only intended for use by generated reflected code.
+ * Only intended for use by generated reflected code. (Simple reduction)
*
* @hide
*/
@@ -312,6 +312,46 @@
mRS.nScriptReduce(getID(mRS), slot, in_id, out_id, limits);
}
+ /**
+ * Only intended for use by generated reflected code. (General reduction)
+ *
+ * @hide
+ */
+ protected void reduce(int slot, Allocation[] ains, Allocation aout, LaunchOptions sc) {
+ mRS.validate();
+ if (ains == null || ains.length < 1) {
+ throw new RSIllegalArgumentException(
+ "At least one input is required.");
+ }
+ if (aout == null) {
+ throw new RSIllegalArgumentException(
+ "aout is required to be non-null.");
+ }
+ for (Allocation ain : ains) {
+ mRS.validateObject(ain);
+ }
+
+ long[] in_ids = new long[ains.length];
+ for (int index = 0; index < ains.length; ++index) {
+ in_ids[index] = ains[index].getID(mRS);
+ }
+ long out_id = aout.getID(mRS);
+
+ int[] limits = null;
+ if (sc != null) {
+ limits = new int[6];
+
+ limits[0] = sc.xstart;
+ limits[1] = sc.xend;
+ limits[2] = sc.ystart;
+ limits[3] = sc.yend;
+ limits[4] = sc.zstart;
+ limits[5] = sc.zend;
+ }
+
+ mRS.nScriptReduceNew(getID(mRS), slot, in_ids, out_id, limits);
+ }
+
long[] mInIdsBuffer;
Script(long id, RenderScript rs) {
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index 113241d..3954070 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -1988,7 +1988,6 @@
if (sizeof(RsAllocation) == sizeof(jlong)) {
in_allocs = (RsAllocation*)in_ptr;
-
} else {
// Convert from 64-bit jlong types to the native pointer type.
@@ -2127,6 +2126,101 @@
}
}
+static void
+nScriptReduceNew(JNIEnv *_env, jobject _this, jlong con, jlong script, jint slot,
+ jlongArray ains, jlong aout, jintArray limits)
+{
+ if (kLogApi) {
+ ALOGD("nScriptReduceNew, con(%p), s(%p), slot(%i) ains(%p) aout(%" PRId64 ")", (RsContext)con, (void *)script, slot, ains, aout);
+ }
+
+ if (ains == nullptr) {
+ ALOGE("At least one input required.");
+ // TODO (b/20758983): Report back to Java and throw an exception
+ return;
+ }
+ jint in_len = _env->GetArrayLength(ains);
+ if (in_len > (jint)RS_KERNEL_MAX_ARGUMENTS) {
+ ALOGE("Too many arguments in kernel launch.");
+ // TODO (b/20758983): Report back to Java and throw an exception
+ return;
+ }
+
+ jlong *in_ptr = _env->GetLongArrayElements(ains, nullptr);
+ if (in_ptr == nullptr) {
+ ALOGE("Failed to get Java array elements");
+ // TODO (b/20758983): Report back to Java and throw an exception
+ return;
+ }
+
+ RsAllocation *in_allocs = nullptr;
+ if (sizeof(RsAllocation) == sizeof(jlong)) {
+ in_allocs = (RsAllocation*)in_ptr;
+ } else {
+ // Convert from 64-bit jlong types to the native pointer type.
+
+ in_allocs = (RsAllocation*)alloca(in_len * sizeof(RsAllocation));
+ if (in_allocs == nullptr) {
+ ALOGE("Failed launching kernel for lack of memory.");
+ // TODO (b/20758983): Report back to Java and throw an exception
+ _env->ReleaseLongArrayElements(ains, in_ptr, JNI_ABORT);
+ return;
+ }
+
+ for (int index = in_len; --index >= 0;) {
+ in_allocs[index] = (RsAllocation)in_ptr[index];
+ }
+ }
+
+ RsScriptCall sc, *sca = nullptr;
+ uint32_t sc_size = 0;
+
+ jint limit_len = 0;
+ jint *limit_ptr = nullptr;
+
+ if (limits != nullptr) {
+ limit_len = _env->GetArrayLength(limits);
+ limit_ptr = _env->GetIntArrayElements(limits, nullptr);
+ if (limit_ptr == nullptr) {
+ ALOGE("Failed to get Java array elements");
+ // TODO (b/20758983): Report back to Java and throw an exception
+ return;
+ }
+
+ assert(limit_len == 6);
+ UNUSED(limit_len); // As the assert might not be compiled.
+
+ sc.xStart = limit_ptr[0];
+ sc.xEnd = limit_ptr[1];
+ sc.yStart = limit_ptr[2];
+ sc.yEnd = limit_ptr[3];
+ sc.zStart = limit_ptr[4];
+ sc.zEnd = limit_ptr[5];
+ sc.strategy = RS_FOR_EACH_STRATEGY_DONT_CARE;
+ sc.arrayStart = 0;
+ sc.arrayEnd = 0;
+ sc.array2Start = 0;
+ sc.array2End = 0;
+ sc.array3Start = 0;
+ sc.array3End = 0;
+ sc.array4Start = 0;
+ sc.array4End = 0;
+
+ sca = ≻
+ sc_size = sizeof(sc);
+ }
+
+ rsScriptReduceNew((RsContext)con, (RsScript)script, slot,
+ in_allocs, in_len, (RsAllocation)aout,
+ sca, sc_size);
+
+ _env->ReleaseLongArrayElements(ains, in_ptr, JNI_ABORT);
+
+ if (limits != nullptr) {
+ _env->ReleaseIntArrayElements(limits, limit_ptr, JNI_ABORT);
+ }
+}
+
// -----------------------------------
static jlong
@@ -2755,6 +2849,7 @@
{"rsnScriptForEach", "(JJI[JJ[B[I)V", (void*)nScriptForEach },
{"rsnScriptReduce", "(JJIJJ[I)V", (void*)nScriptReduce },
+{"rsnScriptReduceNew", "(JJI[JJ[I)V", (void*)nScriptReduceNew },
{"rsnScriptSetVarI", "(JJII)V", (void*)nScriptSetVarI },
{"rsnScriptGetVarI", "(JJI)I", (void*)nScriptGetVarI },
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 28aeef7..dbbb189 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2179,6 +2179,24 @@
return true;
}
+ @Override
+ public void disableSelf() {
+ synchronized(mLock) {
+ UserState userState = getUserStateLocked(mUserId);
+ if (userState.mEnabledServices.remove(mComponentName)) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ persistComponentNamesToSettingLocked(
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ userState.mEnabledServices, mUserId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ onUserStateChangedLocked(userState);
+ }
+ }
+ }
+
public boolean canReceiveEventsLocked() {
return (mEventTypes != 0 && mFeedbackType != 0 && mService != null);
}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index aa15373..d63dd0c 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -27,9 +27,12 @@
import android.app.backup.BackupAgent;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupManager;
+import android.app.backup.BackupProgress;
import android.app.backup.BackupTransport;
import android.app.backup.FullBackup;
import android.app.backup.FullBackupDataOutput;
+import android.app.backup.IBackupObserver;
import android.app.backup.RestoreDescription;
import android.app.backup.RestoreSet;
import android.app.backup.IBackupManager;
@@ -217,6 +220,7 @@
private static final int MSG_RETRY_CLEAR = 12;
private static final int MSG_WIDGET_BROADCAST = 13;
private static final int MSG_RUN_FULL_TRANSPORT_BACKUP = 14;
+ private static final int MSG_REQUEST_BACKUP = 15;
// backup task state machine tick
static final int MSG_BACKUP_RESTORE_STEP = 20;
@@ -532,6 +536,25 @@
}
}
+ class BackupParams {
+ public IBackupTransport transport;
+ public String dirName;
+ public ArrayList<String> kvPackages;
+ public ArrayList<String> fullPackages;
+ public IBackupObserver observer;
+ public boolean userInitiated;
+
+ BackupParams(IBackupTransport transport, String dirName, ArrayList<String> kvPackages,
+ ArrayList<String> fullPackages, IBackupObserver observer, boolean userInitiated) {
+ this.transport = transport;
+ this.dirName = dirName;
+ this.kvPackages = kvPackages;
+ this.fullPackages = fullPackages;
+ this.observer = observer;
+ this.userInitiated = userInitiated;
+ }
+ }
+
// Bookkeeping of in-flight operations for timeout etc. purposes. The operation
// token is the index of the entry in the pending-operations list.
static final int OP_PENDING = 0;
@@ -653,8 +676,13 @@
return true;
}
+ // Checks if the app is in a stopped state, that means it won't receive broadcasts.
+ private static boolean appIsStopped(ApplicationInfo app) {
+ return ((app.flags & ApplicationInfo.FLAG_STOPPED) != 0);
+ }
+
/* does *not* check overall backup eligibility policy! */
- public static boolean appGetsFullBackup(PackageInfo pkg) {
+ private static boolean appGetsFullBackup(PackageInfo pkg) {
if (pkg.applicationInfo.backupAgentName != null) {
// If it has an agent, it gets full backups only if it says so
return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_FULL_BACKUP_ONLY) != 0;
@@ -719,7 +747,7 @@
try {
String dirName = transport.transportDirName();
PerformBackupTask pbt = new PerformBackupTask(transport, dirName,
- queue, oldJournal);
+ queue, oldJournal, null, null, false);
Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
sendMessage(pbtMessage);
} catch (RemoteException e) {
@@ -942,6 +970,26 @@
mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
break;
}
+
+ case MSG_REQUEST_BACKUP:
+ {
+ BackupParams params = (BackupParams)msg.obj;
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "MSG_REQUEST_BACKUP observer=" + params.observer);
+ }
+ ArrayList<BackupRequest> kvQueue = new ArrayList<>();
+ for (String packageName : params.kvPackages) {
+ kvQueue.add(new BackupRequest(packageName));
+ }
+ mBackupRunning = true;
+ mWakelock.acquire();
+
+ PerformBackupTask pbt = new PerformBackupTask(params.transport, params.dirName,
+ kvQueue, null, params.observer, params.fullPackages, true);
+ Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
+ sendMessage(pbtMessage);
+ break;
+ }
}
}
}
@@ -2330,6 +2378,63 @@
return token;
}
+ public int requestBackup(String[] packages, IBackupObserver observer) {
+ mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "requestBackup");
+
+ if (packages == null || packages.length < 1) {
+ Slog.e(TAG, "No packages named for backup request");
+ sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
+ throw new IllegalArgumentException("No packages are provided for backup");
+ }
+
+ IBackupTransport transport = getTransport(mCurrentTransport);
+ if (transport == null) {
+ sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
+ return BackupManager.ERROR_TRANSPORT_ABORTED;
+ }
+
+ ArrayList<String> fullBackupList = new ArrayList<>();
+ ArrayList<String> kvBackupList = new ArrayList<>();
+ for (String packageName : packages) {
+ try {
+ PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName,
+ PackageManager.GET_SIGNATURES);
+ if (!appIsEligibleForBackup(packageInfo.applicationInfo)) {
+ sendBackupOnResult(observer, packageName,
+ BackupManager.ERROR_BACKUP_NOT_ALLOWED);
+ continue;
+ }
+ if (appGetsFullBackup(packageInfo)) {
+ fullBackupList.add(packageInfo.packageName);
+ } else {
+ kvBackupList.add(packageInfo.packageName);
+ }
+ } catch (NameNotFoundException e) {
+ sendBackupOnResult(observer, packageName, BackupManager.ERROR_PACKAGE_NOT_FOUND);
+ }
+ }
+ EventLog.writeEvent(EventLogTags.BACKUP_REQUESTED, packages.length, kvBackupList.size(),
+ fullBackupList.size());
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Backup requested for " + packages.length + " packages, of them: " +
+ fullBackupList.size() + " full backups, " + kvBackupList.size() + " k/v backups");
+ }
+
+ String dirName;
+ try {
+ dirName = transport.transportDirName();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Transport became unavailable while attempting backup");
+ sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
+ return BackupManager.ERROR_TRANSPORT_ABORTED;
+ }
+ Message msg = mBackupHandler.obtainMessage(MSG_REQUEST_BACKUP);
+ msg.obj = new BackupParams(transport, dirName, kvBackupList, fullBackupList, observer,
+ true);
+ mBackupHandler.sendMessage(msg);
+ return BackupManager.SUCCESS;
+ }
+
// -----
// Interface and methods used by the asynchronous-with-timeout backup/restore operations
@@ -2429,6 +2534,8 @@
File mStateDir;
File mJournal;
BackupState mCurrentState;
+ ArrayList<String> mPendingFullBackups;
+ IBackupObserver mObserver;
// carried information about the current in-flight operation
IBackupAgent mAgentBinder;
@@ -2441,12 +2548,17 @@
ParcelFileDescriptor mNewState;
int mStatus;
boolean mFinished;
+ boolean mUserInitiated;
public PerformBackupTask(IBackupTransport transport, String dirName,
- ArrayList<BackupRequest> queue, File journal) {
+ ArrayList<BackupRequest> queue, File journal, IBackupObserver observer,
+ ArrayList<String> pendingFullBackups, boolean userInitiated) {
mTransport = transport;
mOriginalQueue = queue;
mJournal = journal;
+ mObserver = observer;
+ mPendingFullBackups = pendingFullBackups;
+ mUserInitiated = userInitiated;
mStateDir = new File(mBaseStateDir, dirName);
@@ -2498,9 +2610,10 @@
mStatus = BackupTransport.TRANSPORT_OK;
// Sanity check: if the queue is empty we have no work to do.
- if (mOriginalQueue.isEmpty()) {
+ if (mOriginalQueue.isEmpty() && mPendingFullBackups.isEmpty()) {
Slog.w(TAG, "Backup begun with an empty queue - nothing to do.");
addBackupTrace("queue empty at begin");
+ sendBackupFinished(mObserver, BackupManager.SUCCESS);
executeNextState(BackupState.FINAL);
return;
}
@@ -2584,6 +2697,8 @@
// if things went wrong at this point, we need to
// restage everything and try again later.
resetBackupState(mStateDir); // Just to make sure.
+ // In case of any other error, it's backup transport error.
+ sendBackupFinished(mObserver, BackupManager.ERROR_TRANSPORT_ABORTED);
executeNextState(BackupState.FINAL);
}
}
@@ -2625,6 +2740,10 @@
Slog.i(TAG, "Package " + request.packageName
+ " no longer supports backup; skipping");
addBackupTrace("skipping - not eligible, completion is noop");
+ // Shouldn't happen in case of requested backup, as pre-check was done in
+ // #requestBackup(), except to app update done concurrently
+ sendBackupOnResult(mObserver, mCurrentPackage.packageName,
+ BackupManager.ERROR_BACKUP_NOT_ALLOWED);
executeNextState(BackupState.RUNNING_QUEUE);
return;
}
@@ -2636,15 +2755,21 @@
Slog.i(TAG, "Package " + request.packageName
+ " requests full-data rather than key/value; skipping");
addBackupTrace("skipping - fullBackupOnly, completion is noop");
+ // Shouldn't happen in case of requested backup, as pre-check was done in
+ // #requestBackup()
+ sendBackupOnResult(mObserver, mCurrentPackage.packageName,
+ BackupManager.ERROR_BACKUP_NOT_ALLOWED);
executeNextState(BackupState.RUNNING_QUEUE);
return;
}
- if ((mCurrentPackage.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0) {
+ if (appIsStopped(mCurrentPackage.applicationInfo)) {
// The app has been force-stopped or cleared or just installed,
// and not yet launched out of that state, so just as it won't
// receive broadcasts, we won't run it for backup.
addBackupTrace("skipping - stopped");
+ sendBackupOnResult(mObserver, mCurrentPackage.packageName,
+ BackupManager.ERROR_BACKUP_NOT_ALLOWED);
executeNextState(BackupState.RUNNING_QUEUE);
return;
}
@@ -2692,10 +2817,14 @@
dataChangedImpl(request.packageName);
mStatus = BackupTransport.TRANSPORT_OK;
if (mQueue.isEmpty()) nextState = BackupState.FINAL;
+ sendBackupOnResult(mObserver, mCurrentPackage.packageName,
+ BackupManager.ERROR_AGENT_FAILURE);
} else if (mStatus == BackupTransport.AGENT_UNKNOWN) {
// Failed lookup of the app, so we couldn't bring up an agent, but
// we're otherwise fine. Just drop it and go on to the next as usual.
mStatus = BackupTransport.TRANSPORT_OK;
+ sendBackupOnResult(mObserver, mCurrentPackage.packageName,
+ BackupManager.ERROR_PACKAGE_NOT_FOUND);
} else {
// Transport-level failure means we reenqueue everything
revertAndEndBackup();
@@ -2750,9 +2879,37 @@
}
}
- // Only once we're entirely finished do we release the wakelock
clearBackupTrace();
- Slog.i(BackupManagerService.TAG, "Backup pass finished.");
+
+ if (mStatus == BackupTransport.TRANSPORT_OK &&
+ mPendingFullBackups != null && !mPendingFullBackups.isEmpty()) {
+ Slog.d(TAG, "Starting full backups for: " + mPendingFullBackups);
+ CountDownLatch latch = new CountDownLatch(1);
+ String[] fullBackups =
+ mPendingFullBackups.toArray(new String[mPendingFullBackups.size()]);
+ PerformFullTransportBackupTask task =
+ new PerformFullTransportBackupTask(/*fullBackupRestoreObserver*/ null,
+ fullBackups, /*updateSchedule*/ false, /*runningJob*/ null, latch,
+ mObserver, mUserInitiated);
+ // Acquiring wakelock for PerformFullTransportBackupTask before its start.
+ mWakelock.acquire();
+ (new Thread(task, "full-transport-requested")).start();
+ } else {
+ switch (mStatus) {
+ case BackupTransport.TRANSPORT_OK:
+ sendBackupFinished(mObserver, BackupManager.SUCCESS);
+ break;
+ case BackupTransport.TRANSPORT_NOT_INITIALIZED:
+ sendBackupFinished(mObserver, BackupManager.ERROR_TRANSPORT_ABORTED);
+ break;
+ case BackupTransport.TRANSPORT_ERROR:
+ default:
+ sendBackupFinished(mObserver, BackupManager.ERROR_TRANSPORT_ABORTED);
+ break;
+ }
+ }
+ Slog.i(BackupManagerService.TAG, "K/V backup pass finished.");
+ // Only once we're entirely finished do we release the wakelock for k/v backup.
mWakelock.release();
}
@@ -2959,6 +3116,8 @@
EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, pkgName,
"bad key");
mBackupHandler.removeMessages(MSG_TIMEOUT);
+ sendBackupOnResult(mObserver, pkgName,
+ BackupManager.ERROR_AGENT_FAILURE);
agentErrorCleanup();
// agentErrorCleanup() implicitly executes next state properly
return;
@@ -3003,7 +3162,8 @@
backupData = ParcelFileDescriptor.open(mBackupDataName,
ParcelFileDescriptor.MODE_READ_ONLY);
addBackupTrace("sending data to transport");
- mStatus = mTransport.performBackup(mCurrentPackage, backupData);
+ int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
+ mStatus = mTransport.performBackup(mCurrentPackage, backupData, flags);
}
// TODO - We call finishBackup() for each application backed up, because
@@ -3030,6 +3190,7 @@
// with the new state file it just created.
mBackupDataName.delete();
mNewStateName.renameTo(mSavedStateName);
+ sendBackupOnResult(mObserver, pkgName, BackupManager.SUCCESS);
EventLog.writeEvent(EventLogTags.BACKUP_PACKAGE, pkgName, size);
logBackupComplete(pkgName);
} else if (mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
@@ -3037,12 +3198,16 @@
// back but proceed with running the rest of the queue.
mBackupDataName.delete();
mNewStateName.delete();
+ sendBackupOnResult(mObserver, pkgName,
+ BackupManager.ERROR_TRANSPORT_PACKAGE_REJECTED);
EventLogTags.writeBackupAgentFailure(pkgName, "Transport rejected");
} else {
// Actual transport-level failure to communicate the data to the backend
+ sendBackupOnResult(mObserver, pkgName, BackupManager.ERROR_TRANSPORT_ABORTED);
EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, pkgName);
}
} catch (Exception e) {
+ sendBackupOnResult(mObserver, pkgName, BackupManager.ERROR_TRANSPORT_ABORTED);
Slog.e(TAG, "Transport error backing up " + pkgName, e);
EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, pkgName);
mStatus = BackupTransport.TRANSPORT_ERROR;
@@ -3290,6 +3455,8 @@
* or one of the other BackupTransport.* error codes as appropriate
*/
int preflightFullBackup(PackageInfo pkg, IBackupAgent agent);
+
+ long expectedSize();
};
class FullBackupEngine {
@@ -3997,45 +4164,46 @@
CountDownLatch mLatch;
AtomicBoolean mKeepRunning; // signal from job scheduler
FullBackupJob mJob; // if a scheduled job needs to be finished afterwards
+ IBackupObserver mBackupObserver;
+ boolean mUserInitiated;
PerformFullTransportBackupTask(IFullBackupRestoreObserver observer,
String[] whichPackages, boolean updateSchedule,
- FullBackupJob runningJob, CountDownLatch latch) {
+ FullBackupJob runningJob, CountDownLatch latch, IBackupObserver backupObserver,
+ boolean userInitiated) {
super(observer);
mUpdateSchedule = updateSchedule;
mLatch = latch;
mKeepRunning = new AtomicBoolean(true);
mJob = runningJob;
mPackages = new ArrayList<PackageInfo>(whichPackages.length);
+ mBackupObserver = backupObserver;
+ mUserInitiated = userInitiated;
for (String pkg : whichPackages) {
try {
PackageInfo info = mPackageManager.getPackageInfo(pkg,
PackageManager.GET_SIGNATURES);
- if ((info.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0
- || pkg.equals(SHARED_BACKUP_AGENT_PACKAGE)) {
+ if (!appIsEligibleForBackup(info.applicationInfo)) {
// Cull any packages that have indicated that backups are not permitted,
+ // that run as system-domain uids but do not define their own backup agents,
// as well as any explicit mention of the 'special' shared-storage agent
// package (we handle that one at the end).
if (MORE_DEBUG) {
- Slog.d(TAG, "Ignoring opted-out package " + pkg);
+ Slog.d(TAG, "Ignoring not eligible package " + pkg);
}
+ sendBackupOnResult(mBackupObserver, pkg,
+ BackupManager.ERROR_BACKUP_NOT_ALLOWED);
continue;
- } else if ((info.applicationInfo.uid < Process.FIRST_APPLICATION_UID)
- && (info.applicationInfo.backupAgentName == null)) {
- // Cull any packages that run as system-domain uids but do not define their
- // own backup agents
- if (MORE_DEBUG) {
- Slog.d(TAG, "Ignoring non-agent system package " + pkg);
- }
- continue;
- } else if ((info.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0) {
+ } else if (appIsStopped(info.applicationInfo)) {
// Cull any packages in the 'stopped' state: they've either just been
// installed or have explicitly been force-stopped by the user. In both
// cases we do not want to launch them for backup.
if (MORE_DEBUG) {
Slog.d(TAG, "Ignoring stopped package " + pkg);
}
+ sendBackupOnResult(mBackupObserver, pkg,
+ BackupManager.ERROR_BACKUP_NOT_ALLOWED);
continue;
}
mPackages.add(info);
@@ -4068,17 +4236,20 @@
+ " p=" + mProvisioned + "; ignoring");
}
mUpdateSchedule = false;
+ sendBackupFinished(mBackupObserver, BackupManager.ERROR_BACKUP_NOT_ALLOWED);
return;
}
IBackupTransport transport = getTransport(mCurrentTransport);
if (transport == null) {
Slog.w(TAG, "Transport not present; full data backup not performed");
+ sendBackupFinished(mBackupObserver, BackupManager.ERROR_TRANSPORT_ABORTED);
return;
}
// Set up to send data to the transport
final int N = mPackages.size();
+ final byte[] buffer = new byte[8192];
for (int i = 0; i < N; i++) {
currentPackage = mPackages.get(i);
if (DEBUG) {
@@ -4091,8 +4262,9 @@
transportPipes = ParcelFileDescriptor.createPipe();
// Tell the transport the data's coming
+ int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
int result = transport.performFullBackup(currentPackage,
- transportPipes[0]);
+ transportPipes[0], flags);
if (result == BackupTransport.TRANSPORT_OK) {
// The transport has its own copy of the read end of the pipe,
// so close ours now
@@ -4119,7 +4291,13 @@
enginePipes[0].getFileDescriptor());
FileOutputStream out = new FileOutputStream(
transportPipes[1].getFileDescriptor());
- byte[] buffer = new byte[8192];
+ long totalRead = 0;
+ final long expectedSize = backupRunner.expectedSize();
+ if (expectedSize < 0) {
+ result = BackupTransport.AGENT_ERROR;
+ sendBackupOnResult(mBackupObserver, currentPackage.packageName,
+ BackupManager.ERROR_AGENT_FAILURE);
+ }
int nRead = 0;
do {
if (!mKeepRunning.get()) {
@@ -4135,6 +4313,11 @@
if (nRead > 0) {
out.write(buffer, 0, nRead);
result = transport.sendBackupData(nRead);
+ totalRead += nRead;
+ if (mBackupObserver != null && expectedSize > 0) {
+ sendBackupOnUpdate(mBackupObserver, currentPackage.packageName,
+ new BackupProgress(expectedSize, totalRead));
+ }
}
} while (nRead > 0 && result == BackupTransport.TRANSPORT_OK);
@@ -4188,16 +4371,22 @@
}
EventLog.writeEvent(EventLogTags.FULL_BACKUP_AGENT_FAILURE,
currentPackage.packageName, "transport rejected");
+ sendBackupOnResult(mBackupObserver, currentPackage.packageName,
+ BackupManager.ERROR_TRANSPORT_PACKAGE_REJECTED);
// do nothing, clean up, and continue looping
} else if (result != BackupTransport.TRANSPORT_OK) {
Slog.w(TAG, "Transport failed; aborting backup: " + result);
EventLog.writeEvent(EventLogTags.FULL_BACKUP_TRANSPORT_FAILURE);
+ sendBackupOnResult(mBackupObserver, currentPackage.packageName,
+ BackupManager.ERROR_TRANSPORT_ABORTED);
return;
} else {
// Success!
EventLog.writeEvent(EventLogTags.FULL_BACKUP_SUCCESS,
currentPackage.packageName);
logBackupComplete(currentPackage.packageName);
+ sendBackupOnResult(mBackupObserver, currentPackage.packageName,
+ BackupManager.SUCCESS);
}
cleanUpPipes(transportPipes);
cleanUpPipes(enginePipes);
@@ -4207,8 +4396,10 @@
if (DEBUG) {
Slog.i(TAG, "Full backup completed.");
}
+ sendBackupFinished(mBackupObserver, BackupManager.SUCCESS);
} catch (Exception e) {
Slog.w(TAG, "Exception trying full transport backup", e);
+ sendBackupFinished(mBackupObserver, BackupManager.ERROR_TRANSPORT_ABORTED);
} finally {
cleanUpPipes(transportPipes);
cleanUpPipes(enginePipes);
@@ -4221,6 +4412,7 @@
mRunningFullBackupTask = null;
}
+
mLatch.countDown();
// Now that we're actually done with schedule-driven work, reschedule
@@ -4228,6 +4420,8 @@
if (mUpdateSchedule) {
scheduleNextFullBackupJob(backoff);
}
+ Slog.i(BackupManagerService.TAG, "Full data backup pass finished.");
+ mWakelock.release();
}
}
@@ -4316,7 +4510,16 @@
mResult.set(BackupTransport.AGENT_ERROR);
mLatch.countDown();
}
-
+
+ @Override
+ public long expectedSize() {
+ try {
+ mLatch.await();
+ return mResult.get();
+ } catch (InterruptedException e) {
+ return BackupTransport.NO_MORE_DATA;
+ }
+ }
}
class SinglePackageBackupRunner implements Runnable {
@@ -4351,6 +4554,10 @@
}
}
}
+
+ long expectedSize() {
+ return mPreflight.expectedSize();
+ }
}
}
@@ -4586,7 +4793,9 @@
CountDownLatch latch = new CountDownLatch(1);
String[] pkg = new String[] {entry.packageName};
mRunningFullBackupTask = new PerformFullTransportBackupTask(null, pkg, true,
- scheduledJob, latch);
+ scheduledJob, latch, null, false /* userInitiated */);
+ // Acquiring wakelock for PerformFullTransportBackupTask before its start.
+ mWakelock.acquire();
(new Thread(mRunningFullBackupTask)).start();
}
@@ -8744,8 +8953,10 @@
}
CountDownLatch latch = new CountDownLatch(1);
- PerformFullTransportBackupTask task =
- new PerformFullTransportBackupTask(null, pkgNames, false, null, latch);
+ PerformFullTransportBackupTask task = new PerformFullTransportBackupTask(null, pkgNames,
+ false, null, latch, null, false /* userInitiated */);
+ // Acquiring wakelock for PerformFullTransportBackupTask before its start.
+ mWakelock.acquire();
(new Thread(task, "full-transport-master")).start();
do {
try {
@@ -9322,6 +9533,32 @@
}
}
+ public boolean isAppEligibleForBackup(String packageName) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "isAppEligibleForBackup");
+ try {
+ PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName,
+ PackageManager.GET_SIGNATURES);
+ if (!appIsEligibleForBackup(packageInfo.applicationInfo) ||
+ appIsStopped(packageInfo.applicationInfo)) {
+ return false;
+ }
+ IBackupTransport transport = getTransport(mCurrentTransport);
+ if (transport != null) {
+ try {
+ return transport.isAppEligibleForBackup(packageInfo,
+ appGetsFullBackup(packageInfo));
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to contact transport");
+ }
+ }
+ // If transport is not present we couldn't tell that the package is not eligible.
+ return true;
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+ }
+
// ----- Restore session -----
class ActiveRestoreSession extends IRestoreSession.Stub {
@@ -9769,4 +10006,42 @@
}
}
}
+
+ private static void sendBackupOnUpdate(IBackupObserver observer, String packageName,
+ BackupProgress progress) {
+ if (observer != null) {
+ try {
+ observer.onUpdate(packageName, progress);
+ } catch (RemoteException e) {
+ if (DEBUG) {
+ Slog.w(TAG, "Backup observer went away: onUpdate");
+ }
+ }
+ }
+ }
+
+ private static void sendBackupOnResult(IBackupObserver observer, String packageName,
+ int status) {
+ if (observer != null) {
+ try {
+ observer.onResult(packageName, status);
+ } catch (RemoteException e) {
+ if (DEBUG) {
+ Slog.w(TAG, "Backup observer went away: onResult");
+ }
+ }
+ }
+ }
+
+ private static void sendBackupFinished(IBackupObserver observer, int status) {
+ if (observer != null) {
+ try {
+ observer.backupFinished(status);
+ } catch (RemoteException e) {
+ if (DEBUG) {
+ Slog.w(TAG, "Backup observer went away: backupFinished");
+ }
+ }
+ }
+ }
}
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index a51ab55..bbf881b 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -17,6 +17,7 @@
package com.android.server.backup;
import android.app.backup.IBackupManager;
+import android.app.backup.IBackupObserver;
import android.app.backup.IFullBackupRestoreObserver;
import android.app.backup.IRestoreSession;
import android.content.Context;
@@ -325,6 +326,18 @@
}
@Override
+ public boolean isAppEligibleForBackup(String packageName) {
+ BackupManagerService svc = mService;
+ return (svc != null) ? svc.isAppEligibleForBackup(packageName) : false;
+ }
+
+ @Override
+ public int requestBackup(String[] packages, IBackupObserver observer) throws RemoteException {
+ BackupManagerService svc = mService;
+ return (svc != null) ? svc.requestBackup(packages, observer) : null;
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index df20704..9927fd6c 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -171,6 +171,7 @@
private static final boolean VDBG = false;
private static final boolean LOGD_RULES = false;
+ private static final boolean LOGD_BLOCKED_NETWORKINFO = true;
// TODO: create better separation between radio types and network types
@@ -955,6 +956,21 @@
}
}
+ private void maybeLogBlockedNetworkInfo(NetworkInfo ni, int uid) {
+ if (ni == null || !LOGD_BLOCKED_NETWORKINFO) return;
+ boolean removed = false;
+ boolean added = false;
+ synchronized (mBlockedAppUids) {
+ if (ni.getDetailedState() == DetailedState.BLOCKED && mBlockedAppUids.add(uid)) {
+ added = true;
+ } else if (ni.isConnected() && mBlockedAppUids.remove(uid)) {
+ removed = true;
+ }
+ }
+ if (added) log("Returning blocked NetworkInfo to uid=" + uid);
+ else if (removed) log("Returning unblocked NetworkInfo to uid=" + uid);
+ }
+
/**
* Return a filtered {@link NetworkInfo}, potentially marked
* {@link DetailedState#BLOCKED} based on
@@ -965,10 +981,6 @@
// network is blocked; clone and override state
info = new NetworkInfo(info);
info.setDetailedState(DetailedState.BLOCKED, null, null);
- if (VDBG) {
- log("returning Blocked NetworkInfo for ifname=" +
- lp.getInterfaceName() + ", uid=" + uid);
- }
}
if (info != null && mLockdownTracker != null) {
info = mLockdownTracker.augmentNetworkInfo(info);
@@ -989,7 +1001,9 @@
enforceAccessPermission();
final int uid = Binder.getCallingUid();
NetworkState state = getUnfilteredActiveNetworkState(uid);
- return getFilteredNetworkInfo(state.networkInfo, state.linkProperties, uid);
+ NetworkInfo ni = getFilteredNetworkInfo(state.networkInfo, state.linkProperties, uid);
+ maybeLogBlockedNetworkInfo(ni, uid);
+ return ni;
}
@Override
@@ -3974,6 +3988,9 @@
private final HashMap<Messenger, NetworkAgentInfo> mNetworkAgentInfos =
new HashMap<Messenger, NetworkAgentInfo>();
+ @GuardedBy("mBlockedAppUids")
+ private final HashSet<Integer> mBlockedAppUids = new HashSet();
+
// Note: if mDefaultRequest is changed, NetworkMonitor needs to be updated.
private final NetworkRequest mDefaultRequest;
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 516e2f4..7bf1dea 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -107,6 +107,7 @@
2825 backup_success (Packages|1|1),(Time|1|3)
2826 backup_reset (Transport|3)
2827 backup_initialize
+2828 backup_requested (Total|1|1),(Key-Value|1|1),(Full|1|1)
2830 restore_start (Transport|3),(Source|2|5)
2831 restore_transport_failure
2832 restore_agent_failure (Package|3),(Message|3)
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 798a04a..4a123df 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -742,6 +742,31 @@
}
}
+ public static final class Lifecycle extends SystemService {
+ private InputMethodManagerService mService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ mService = new InputMethodManagerService(context);
+ }
+
+ @Override
+ public void onStart() {
+ LocalServices.addService(InputMethodManagerInternal.class,
+ new LocalServiceImpl(mService.mHandler));
+ publishBinderService(Context.INPUT_METHOD_SERVICE, mService);
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+ StatusBarManagerService statusBarService = (StatusBarManagerService) ServiceManager
+ .getService(Context.STATUS_BAR_SERVICE);
+ mService.systemRunning(statusBarService);
+ }
+ }
+ }
+
public InputMethodManagerService(Context context) {
mIPackageManager = AppGlobals.getPackageManager();
mContext = context;
@@ -894,7 +919,6 @@
}
}
}, filter);
- LocalServices.addService(InputMethodManagerInternal.class, new LocalServiceImpl(mHandler));
}
private void resetDefaultImeLocked(Context context) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ca33f9f..9c24271 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -141,6 +141,7 @@
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.Rect;
@@ -187,6 +188,7 @@
import android.os.storage.StorageManager;
import android.provider.Settings;
import android.service.voice.IVoiceInteractionSession;
+import android.service.voice.VoiceInteractionManagerInternal;
import android.service.voice.VoiceInteractionSession;
import android.text.format.DateUtils;
import android.text.format.Time;
@@ -269,6 +271,7 @@
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RTL;
import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
+import static android.provider.Settings.System.FONT_SCALE;
import static com.android.internal.util.XmlUtils.readBooleanAttribute;
import static com.android.internal.util.XmlUtils.readIntAttribute;
import static com.android.internal.util.XmlUtils.readLongAttribute;
@@ -1014,6 +1017,25 @@
CoreSettingsObserver mCoreSettingsObserver;
+ FontScaleSettingObserver mFontScaleSettingObserver;
+
+ private final class FontScaleSettingObserver extends ContentObserver {
+ private final Uri mFontScaleUri = Settings.System.getUriFor(FONT_SCALE);
+
+ public FontScaleSettingObserver() {
+ super(mHandler);
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(mFontScaleUri, false, this, UserHandle.USER_ALL);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (mFontScaleUri.equals(uri)) {
+ updateFontScaleIfNeeded();
+ }
+ }
+ }
+
/**
* Thread-local storage used to carry caller permissions over through
* indirect content-provider access.
@@ -1475,6 +1497,7 @@
final ServiceThread mHandlerThread;
final MainHandler mHandler;
final UiHandler mUiHandler;
+ final ProcessStartLogger mProcessStartLogger;
PackageManagerInternal mPackageManagerInt;
@@ -2452,6 +2475,8 @@
mHandler = new MainHandler(mHandlerThread.getLooper());
mUiHandler = new UiHandler();
+ mProcessStartLogger = new ProcessStartLogger();
+
mFgBroadcastQueue = new BroadcastQueue(this, mHandler,
"foreground", BROADCAST_FG_TIMEOUT, false);
mBgBroadcastQueue = new BroadcastQueue(this, mHandler,
@@ -2800,16 +2825,21 @@
} else {
r.appTimeTracker = null;
}
+ // TODO: VI Maybe r.task.voiceInteractor || r.voiceInteractor != null
+ // TODO: Probably not, because we don't want to resume voice on switching
+ // back to this activity
if (r.task.voiceInteractor != null) {
startRunningVoiceLocked(r.task.voiceSession, r.info.applicationInfo.uid);
} else {
finishRunningVoiceLocked();
- if (last != null && last.task.voiceSession != null) {
+ IVoiceInteractionSession session;
+ if (last != null && ((session = last.task.voiceSession) != null
+ || (session = last.voiceSession) != null)) {
// We had been in a voice interaction session, but now focused has
// move to something different. Just finish the session, we can't
// return to it and retain the proper state and synchronization with
// the voice interaction service.
- finishVoiceTask(last.task.voiceSession);
+ finishVoiceTask(session);
}
}
if (mStackSupervisor.moveActivityStackToFront(r, reason + " setFocusedActivity")) {
@@ -3552,6 +3582,8 @@
app.processName, hostingType,
hostingNameStr != null ? hostingNameStr : "");
+ mProcessStartLogger.logIfNeededLocked(app, startResult);
+
if (app.persistent) {
Watchdog.getInstance().processStarted(app.processName, startResult.pid);
}
@@ -4251,6 +4283,66 @@
}
@Override
+ public void startLocalVoiceInteraction(IBinder callingActivity, Bundle options)
+ throws RemoteException {
+ Slog.i(TAG, "Activity tried to startVoiceInteraction");
+ synchronized (this) {
+ ActivityRecord activity = getFocusedStack().topActivity();
+ if (ActivityRecord.forTokenLocked(callingActivity) != activity) {
+ throw new SecurityException("Only focused activity can call startVoiceInteraction");
+ }
+ if (mRunningVoice != null || activity.task.voiceSession != null
+ || activity.voiceSession != null) {
+ Slog.w(TAG, "Already in a voice interaction, cannot start new voice interaction");
+ return;
+ }
+ if (activity.pendingVoiceInteractionStart) {
+ Slog.w(TAG, "Pending start of voice interaction already.");
+ return;
+ }
+ activity.pendingVoiceInteractionStart = true;
+ }
+ LocalServices.getService(VoiceInteractionManagerInternal.class)
+ .startLocalVoiceInteraction(callingActivity, options);
+ }
+
+ @Override
+ public void stopLocalVoiceInteraction(IBinder callingActivity) throws RemoteException {
+ LocalServices.getService(VoiceInteractionManagerInternal.class)
+ .stopLocalVoiceInteraction(callingActivity);
+ }
+
+ @Override
+ public boolean supportsLocalVoiceInteraction() throws RemoteException {
+ return LocalServices.getService(VoiceInteractionManagerInternal.class)
+ .supportsLocalVoiceInteraction();
+ }
+
+ void onLocalVoiceInteractionStartedLocked(IBinder activity,
+ IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor) {
+ ActivityRecord activityToCallback = ActivityRecord.forTokenLocked(activity);
+ if (activityToCallback == null) return;
+ activityToCallback.setVoiceSessionLocked(voiceSession);
+
+ // Inform the activity
+ try {
+ activityToCallback.app.thread.scheduleLocalVoiceInteractionStarted(activity,
+ voiceInteractor);
+ long token = Binder.clearCallingIdentity();
+ try {
+ startRunningVoiceLocked(voiceSession, activityToCallback.appInfo.uid);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ // TODO: VI Should we cache the activity so that it's easier to find later
+ // rather than scan through all the stacks and activities?
+ } catch (RemoteException re) {
+ activityToCallback.clearVoiceSessionLocked();
+ // TODO: VI Should this terminate the voice session?
+ }
+ }
+
+ @Override
public void setVoiceKeepAwake(IVoiceInteractionSession session, boolean keepAwake) {
synchronized (this) {
if (mRunningVoice != null && mRunningVoice.asBinder() == session.asBinder()) {
@@ -4742,9 +4834,11 @@
@Override
public void finishVoiceTask(IVoiceInteractionSession session) {
- synchronized(this) {
+ synchronized (this) {
final long origId = Binder.clearCallingIdentity();
try {
+ // TODO: VI Consider treating local voice interactions and voice tasks
+ // differently here
mStackSupervisor.finishVoiceTask(session);
} finally {
Binder.restoreCallingIdentity(origId);
@@ -6634,6 +6728,8 @@
}
}, dumpheapFilter);
+ mProcessStartLogger.registerListener(mContext);
+
// Let system services know.
mSystemServiceManager.startBootPhase(SystemService.PHASE_BOOT_COMPLETED);
@@ -10841,6 +10937,7 @@
}
mCoreSettingsObserver = new CoreSettingsObserver(this);
+ mFontScaleSettingObserver = new FontScaleSettingObserver();
//mUsageStatsService.monitorPackages();
}
@@ -11100,6 +11197,7 @@
}
void finishRunningVoiceLocked() {
+ Slog.d(TAG, "finishRunningVoiceLocked() >>>>");
if (mRunningVoice != null) {
mRunningVoice = null;
mVoiceWakeLock.release();
@@ -11243,6 +11341,7 @@
}
void startRunningVoiceLocked(IVoiceInteractionSession session, int targetUid) {
+ Slog.d(TAG, "<<< startRunningVoiceLocked()");
mVoiceWakeLock.setWorkSource(new WorkSource(targetUid));
if (mRunningVoice == null || mRunningVoice.asBinder() != session.asBinder()) {
boolean wasRunningVoice = mRunningVoice != null;
@@ -18211,6 +18310,20 @@
}
}
+ private void updateFontScaleIfNeeded() {
+ final int currentUserId;
+ synchronized(this) {
+ currentUserId = mUserController.getCurrentUserIdLocked();
+ }
+ final float scaleFactor = Settings.System.getFloatForUser(mContext.getContentResolver(),
+ FONT_SCALE, 1.0f, currentUserId);
+ if (mConfiguration.fontScale != scaleFactor) {
+ final Configuration configuration = mWindowManager.computeNewConfiguration();
+ configuration.fontScale = scaleFactor;
+ updatePersistentConfiguration(configuration);
+ }
+ }
+
private void enforceWriteSettingsPermission(String func) {
int uid = Binder.getCallingUid();
if (uid == Process.ROOT_UID) {
@@ -20892,7 +21005,9 @@
}
public boolean isUserStopped(int userId) {
- return mUserController.getStartedUserStateLocked(userId) == null;
+ synchronized (this) {
+ return mUserController.getStartedUserStateLocked(userId) == null;
+ }
}
ActivityInfo getActivityInfoForUser(ActivityInfo aInfo, int userId) {
@@ -21050,6 +21165,15 @@
ActivityManagerService.this.onUserStoppedLocked(userId);
}
}
+
+ @Override
+ public void onLocalVoiceInteractionStarted(IBinder activity,
+ IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor) {
+ synchronized (ActivityManagerService.this) {
+ ActivityManagerService.this.onLocalVoiceInteractionStartedLocked(activity,
+ voiceSession, voiceInteractor);
+ }
+ }
}
private final class SleepTokenImpl extends SleepToken {
@@ -21136,7 +21260,12 @@
public void moveToFront() {
checkCaller();
// Will bring task to front if it already has a root activity.
- startActivityFromRecentsInner(mTaskId, null);
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ startActivityFromRecentsInner(mTaskId, null);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index b16bd2b..71008a9 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -54,6 +54,7 @@
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
+import android.service.voice.IVoiceInteractionSession;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
@@ -209,6 +210,9 @@
private int[] mHorizontalSizeConfigurations;
private int[] mSmallestSizeConfigurations;
+ boolean pendingVoiceInteractionStart; // Waiting for activity-invoked voice session
+ IVoiceInteractionSession voiceSession; // Voice interaction session for this activity
+
void dump(PrintWriter pw, String prefix) {
final long now = SystemClock.uptimeMillis();
pw.print(prefix); pw.print("packageName="); pw.print(packageName);
@@ -1274,6 +1278,16 @@
taskDescription = _taskDescription;
}
+ void setVoiceSessionLocked(IVoiceInteractionSession session) {
+ voiceSession = session;
+ pendingVoiceInteractionStart = false;
+ }
+
+ void clearVoiceSessionLocked() {
+ voiceSession = null;
+ pendingVoiceInteractionStart = false;
+ }
+
void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
out.attribute(null, ATTR_ID, String.valueOf(createTime));
out.attribute(null, ATTR_LAUNCHEDFROMUID, String.valueOf(launchedFromUid));
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index e3f4999a..4bac2d6 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1567,7 +1567,7 @@
behindTranslucentActivity = true;
}
} else {
- if (DEBUG_VISIBILITY || true) Slog.v(TAG_VISIBILITY, "Make invisible? " + r
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make invisible? " + r
+ " finishing=" + r.finishing + " state=" + r.state + " stackInvisible="
+ stackInvisible + " behindFullscreenActivity="
+ behindFullscreenActivity + " mLaunchTaskBehind="
@@ -3093,8 +3093,29 @@
didOne = true;
}
}
+ } else {
+ // Check if any of the activities are using voice
+ for (int activityNdx = tr.mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
+ ActivityRecord r = tr.mActivities.get(activityNdx);
+ if (r.voiceSession != null
+ && r.voiceSession.asBinder() == sessionBinder) {
+ // Inform of cancellation
+ r.clearVoiceSessionLocked();
+ try {
+ r.app.thread.scheduleLocalVoiceInteractionStarted((IBinder) r.appToken,
+ null);
+ } catch (RemoteException re) {
+ // Ok
+ }
+ // TODO: VI This is redundant in some cases
+ mService.finishRunningVoiceLocked();
+ break;
+ }
+ }
}
}
+ Slog.d(TAG, "ActivityStack.finishVoiceTask()");
+
if (didOne) {
mService.updateOomAdjLocked();
}
@@ -4243,6 +4264,14 @@
private int getTaskConfigurationChanges(ActivityRecord record, Configuration taskConfig,
Configuration oldTaskOverride) {
+
+ // If we went from full-screen to non-full-screen, make sure to use the correct
+ // configuration task diff, so the diff stays as small as possible.
+ if (Configuration.EMPTY.equals(oldTaskOverride)
+ && !Configuration.EMPTY.equals(taskConfig)) {
+ oldTaskOverride = record.task.extractOverrideConfig(record.configuration);
+ }
+
// Determine what has changed. May be nothing, if this is a config
// that has come back from the app after going idle. In that case
// we just want to leave the official config object now in the
@@ -4678,6 +4707,7 @@
updateTaskMovement(task, true);
if (!moving && task.mActivities.isEmpty()) {
+ // TODO: VI what about activity?
final boolean isVoiceSession = task.voiceSession != null;
if (isVoiceSession) {
try {
@@ -4782,6 +4812,7 @@
void addConfigOverride(ActivityRecord r, TaskRecord task) {
final Rect bounds = task.updateOverrideConfigurationFromLaunchBounds();
+ // TODO: VI deal with activity
mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,
r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
(r.info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId, r.info.configChanges,
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index d9dd77d..1fc674b 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2251,7 +2251,10 @@
// If we didn't actual do a relaunch (indicated by kept==true meaning we kept the old
// window), we need to clear the replace window settings. Otherwise, we schedule a
// timeout to remove the old window if the replacing window is not coming in time.
- mWindowManager.scheduleClearReplacingWindowIfNeeded(topActivity.appToken, !kept);
+ // In case of the pinned stack we don't resize the task during the move, but we will
+ // resize the stack soon after so we want to retain the replacing window.
+ mWindowManager.scheduleClearReplacingWindowIfNeeded(topActivity.appToken,
+ !kept || stackId == PINNED_STACK_ID);
}
// The task might have already been running and its visibility needs to be synchronized with
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 77364ab..d847824 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -5,6 +5,7 @@
import static android.app.ActivityManager.START_CLASS_NOT_FOUND;
import static android.app.ActivityManager.START_DELIVERED_TO_TOP;
import static android.app.ActivityManager.START_FLAG_ONLY_IF_NEEDED;
+import static android.app.ActivityManager.START_PERMISSION_DENIED;
import static android.app.ActivityManager.START_RETURN_INTENT_TO_CALLER;
import static android.app.ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
import static android.app.ActivityManager.START_SUCCESS;
@@ -42,6 +43,7 @@
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP;
+import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
@@ -228,6 +230,17 @@
}
}
+ if (aInfo != null) {
+ if ((aInfo.applicationInfo.flags & FLAG_SUSPENDED) != 0) {
+ Slog.w(TAG, "Application \"" + aInfo.applicationInfo.packageName
+ + "\" is suspended. Refusing to start: " + intent.toString());
+ // TODO: show a dialog/activity informing the user that the application is suspended
+ // and redirect the launch to it. Do not return START_PERMISSION_DENIED because
+ // it is wrong.
+ err = ActivityManager.START_PERMISSION_DENIED;
+ }
+ }
+
final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0;
if (err == ActivityManager.START_SUCCESS) {
diff --git a/services/core/java/com/android/server/am/ProcessStartLogger.java b/services/core/java/com/android/server/am/ProcessStartLogger.java
new file mode 100644
index 0000000..d2aa966
--- /dev/null
+++ b/services/core/java/com/android/server/am/ProcessStartLogger.java
@@ -0,0 +1,151 @@
+package com.android.server.am;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.app.AppGlobals;
+import android.auditing.SecurityLog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.Process.ProcessStartResult;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+
+/**
+ * A class that logs process start information (including APK hash) to the security log.
+ */
+class ProcessStartLogger {
+ private static final String CLASS_NAME = "ProcessStartLogger";
+ private static final String TAG = TAG_WITH_CLASS_NAME ? CLASS_NAME : TAG_AM;
+
+ final HandlerThread mHandlerProcessLoggingThread;
+ Handler mHandlerProcessLogging;
+ // Should only access in mHandlerProcessLoggingThread
+ final HashMap<String, String> mProcessLoggingApkHashes;
+
+ ProcessStartLogger() {
+ mHandlerProcessLoggingThread = new HandlerThread(CLASS_NAME,
+ Process.THREAD_PRIORITY_BACKGROUND);
+ mProcessLoggingApkHashes = new HashMap();
+ }
+
+ void logIfNeededLocked(ProcessRecord app, ProcessStartResult startResult) {
+ if (!SecurityLog.isLoggingEnabled()) {
+ return;
+ }
+ if (!mHandlerProcessLoggingThread.isAlive()) {
+ mHandlerProcessLoggingThread.start();
+ mHandlerProcessLogging = new Handler(mHandlerProcessLoggingThread.getLooper());
+ }
+ mHandlerProcessLogging.post(new ProcessLoggingRunnable(app, startResult,
+ System.currentTimeMillis()));
+ }
+
+ void registerListener(Context context) {
+ IntentFilter packageChangedFilter = new IntentFilter();
+ packageChangedFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ packageChangedFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ context.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)
+ || Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) {
+ int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
+ getSendingUserId());
+ String packageName = intent.getData().getSchemeSpecificPart();
+ try {
+ ApplicationInfo info = AppGlobals.getPackageManager().getApplicationInfo(
+ packageName, 0, userHandle);
+ invaildateCache(info.sourceDir);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ }, packageChangedFilter);
+ }
+
+ private void invaildateCache(final String apkPath) {
+ if (mHandlerProcessLogging != null) {
+ mHandlerProcessLogging.post(new Runnable() {
+ @Override
+ public void run() {
+ mProcessLoggingApkHashes.remove(apkPath);
+ }
+ });
+ }
+ }
+
+ private class ProcessLoggingRunnable implements Runnable {
+
+ private final ProcessRecord app;
+ private final Process.ProcessStartResult startResult;
+ private final long startTimestamp;
+
+ public ProcessLoggingRunnable(ProcessRecord app, Process.ProcessStartResult startResult,
+ long startTimestamp){
+ this.app = app;
+ this.startResult = startResult;
+ this.startTimestamp = startTimestamp;
+ }
+
+ @Override
+ public void run() {
+ String apkHash = computeStringHashOfApk(app);
+ SecurityLog.writeEvent(SecurityLog.TAG_APP_PROCESS_START,
+ app.processName,
+ startTimestamp,
+ app.uid,
+ startResult.pid,
+ app.info.seinfo,
+ apkHash);
+ }
+
+ private String computeStringHashOfApk(ProcessRecord app){
+ final String apkFile = app.info.sourceDir;
+ if(apkFile == null) {
+ return "No APK";
+ }
+ String apkHash = mProcessLoggingApkHashes.get(apkFile);
+ if (apkHash == null) {
+ try {
+ byte[] hash = computeHashOfApkFile(apkFile);
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < hash.length; i++) {
+ sb.append(String.format("%02x", hash[i]));
+ }
+ apkHash = sb.toString();
+ mProcessLoggingApkHashes.put(apkFile, apkHash);
+ } catch (IOException | NoSuchAlgorithmException e) {
+ Slog.w(TAG, "computeStringHashOfApk() failed", e);
+ }
+ }
+ return apkHash != null ? apkHash : "Failed to count APK hash";
+ }
+
+ private byte[] computeHashOfApkFile(String packageArchiveLocation)
+ throws IOException, NoSuchAlgorithmException {
+ MessageDigest md = MessageDigest.getInstance("SHA-256");
+ FileInputStream input = new FileInputStream(new File(packageArchiveLocation));
+ byte[] buffer = new byte[65536];
+ int size;
+ while((size = input.read(buffer)) > 0) {
+ md.update(buffer, 0, size);
+ }
+ input.close();
+ return md.digest();
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java
index a3c26cb..3f0674d 100644
--- a/services/core/java/com/android/server/am/RecentTasks.java
+++ b/services/core/java/com/android/server/am/RecentTasks.java
@@ -77,8 +77,8 @@
}
/**
- * Loads the persistent recentTasks for {@code userId} into {@link #mRecentTasks} from
- * persistent storage. Does nothing if they are already loaded.
+ * Loads the persistent recentTasks for {@code userId} into this list from persistent storage.
+ * Does nothing if they are already loaded.
*
* @param userId the user Id
*/
@@ -405,6 +405,8 @@
int recentsCount = size();
// Quick case: never add voice sessions.
+ // TODO: VI what about if it's just an activity?
+ // Probably nothing to do here
if (task.voiceSession != null) {
if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
"addRecent: not adding voice interaction " + task);
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index cc86a62..4ce8b2f 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1291,24 +1291,7 @@
if (stack == null || StackId.persistTaskBounds(stack.mStackId)) {
mLastNonFullscreenBounds = mBounds;
}
-
- final Configuration serviceConfig = mService.mConfiguration;
- mOverrideConfig = new Configuration(Configuration.EMPTY);
- // TODO(multidisplay): Update Dp to that of display stack is on.
- final float density = serviceConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
- mOverrideConfig.screenWidthDp =
- Math.min((int)(mBounds.width() / density), serviceConfig.screenWidthDp);
- mOverrideConfig.screenHeightDp =
- Math.min((int)(mBounds.height() / density), serviceConfig.screenHeightDp);
- mOverrideConfig.smallestScreenWidthDp =
- Math.min(mOverrideConfig.screenWidthDp, mOverrideConfig.screenHeightDp);
- mOverrideConfig.orientation =
- (mOverrideConfig.screenWidthDp <= mOverrideConfig.screenHeightDp)
- ? Configuration.ORIENTATION_PORTRAIT
- : Configuration.ORIENTATION_LANDSCAPE;
- final int sl = Configuration.resetScreenLayout(serviceConfig.screenLayout);
- mOverrideConfig.screenLayout = Configuration.reduceScreenLayout(
- sl, mOverrideConfig.screenWidthDp, mOverrideConfig.screenHeightDp);
+ mOverrideConfig = calculateOverrideConfig(mBounds);
}
if (mFullscreen != oldFullscreen) {
@@ -1318,6 +1301,41 @@
return !mOverrideConfig.equals(oldConfig) ? mOverrideConfig : null;
}
+ Configuration calculateOverrideConfig(Rect bounds) {
+ final Configuration serviceConfig = mService.mConfiguration;
+ final Configuration config = new Configuration(Configuration.EMPTY);
+ // TODO(multidisplay): Update Dp to that of display stack is on.
+ final float density = serviceConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ config.screenWidthDp =
+ Math.min((int)(bounds.width() / density), serviceConfig.screenWidthDp);
+ config.screenHeightDp =
+ Math.min((int)(bounds.height() / density), serviceConfig.screenHeightDp);
+ config.smallestScreenWidthDp =
+ Math.min(config.screenWidthDp, config.screenHeightDp);
+ config.orientation = (config.screenWidthDp <= config.screenHeightDp)
+ ? Configuration.ORIENTATION_PORTRAIT
+ : Configuration.ORIENTATION_LANDSCAPE;
+ final int sl = Configuration.resetScreenLayout(serviceConfig.screenLayout);
+ config.screenLayout = Configuration.reduceScreenLayout(
+ sl, config.screenWidthDp, config.screenHeightDp);
+ return config;
+ }
+
+ /**
+ * Using the existing configuration {@param config}, creates a new task override config such
+ * that all the fields that are usually set in an override config are set to the ones in
+ * {@param config}.
+ */
+ Configuration extractOverrideConfig(Configuration config) {
+ final Configuration extracted = new Configuration(Configuration.EMPTY);
+ extracted.screenWidthDp = config.screenWidthDp;
+ extracted.screenHeightDp = config.screenHeightDp;
+ extracted.smallestScreenWidthDp = config.smallestScreenWidthDp;
+ extracted.orientation = config.orientation;
+ extracted.screenLayout = config.screenLayout;
+ return extracted;
+ }
+
Rect updateOverrideConfigurationFromLaunchBounds() {
final Rect bounds = validateBounds(getLaunchBounds());
updateOverrideConfiguration(bounds);
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index ee91b63..573afd6 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -18,6 +18,7 @@
import android.annotation.Nullable;
import android.view.Display;
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
import com.android.internal.os.SomeArgs;
import com.android.internal.R;
import com.android.internal.util.XmlUtils;
@@ -67,6 +68,8 @@
import android.os.MessageQueue;
import android.os.Process;
import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCommand;
import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
@@ -97,6 +100,7 @@
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -157,8 +161,7 @@
private final ArrayList<InputDevice>
mTempFullKeyboards = new ArrayList<InputDevice>(); // handler thread only
private boolean mKeyboardLayoutNotificationShown;
- private PendingIntent mKeyboardLayoutIntent;
- private Toast mSwitchedKeyboardLayoutToast;
+ private InputMethodSubtypeHandle mCurrentImeHandle;
// State for vibrator tokens.
private Object mVibratorLock = new Object();
@@ -1297,6 +1300,82 @@
}
@Override // Binder call
+ @Nullable
+ public KeyboardLayout getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+ InputMethodInfo imeInfo, InputMethodSubtype imeSubtype) {
+ InputMethodSubtypeHandle handle = new InputMethodSubtypeHandle(imeInfo, imeSubtype);
+ String key = getLayoutDescriptor(identifier);
+ final String keyboardLayoutDescriptor;
+ synchronized (mDataStore) {
+ keyboardLayoutDescriptor = mDataStore.getKeyboardLayout(key, handle);
+ }
+
+ if (keyboardLayoutDescriptor == null) {
+ return null;
+ }
+
+ final KeyboardLayout[] result = new KeyboardLayout[1];
+ visitKeyboardLayout(keyboardLayoutDescriptor, new KeyboardLayoutVisitor() {
+ @Override
+ public void visitKeyboardLayout(Resources resources,
+ int keyboardLayoutResId, KeyboardLayout layout) {
+ result[0] = layout;
+ }
+ });
+ if (result[0] == null) {
+ Slog.w(TAG, "Could not get keyboard layout with descriptor '"
+ + keyboardLayoutDescriptor + "'.");
+ }
+ return result[0];
+ }
+
+ @Override
+ public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+ InputMethodInfo imeInfo, InputMethodSubtype imeSubtype,
+ String keyboardLayoutDescriptor) {
+ if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
+ "setKeyboardLayoutForInputDevice()")) {
+ throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
+ }
+ if (keyboardLayoutDescriptor == null) {
+ throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
+ }
+ if (imeInfo == null || imeSubtype == null) {
+ throw new IllegalArgumentException("imeInfo and imeSubtype must not be null");
+ }
+ InputMethodSubtypeHandle handle = new InputMethodSubtypeHandle(imeInfo, imeSubtype);
+ setKeyboardLayoutForInputDeviceInner(identifier, handle, keyboardLayoutDescriptor);
+ }
+
+ private void setKeyboardLayoutForInputDeviceInner(InputDeviceIdentifier identifier,
+ InputMethodSubtypeHandle imeHandle, String keyboardLayoutDescriptor) {
+ String key = getLayoutDescriptor(identifier);
+ synchronized (mDataStore) {
+ try {
+ if (mDataStore.setKeyboardLayout(key, imeHandle, keyboardLayoutDescriptor)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Set keyboard layout " + keyboardLayoutDescriptor +
+ " for subtype " + imeHandle + " and device " + identifier +
+ " using key " + key);
+ }
+ if (imeHandle.equals(mCurrentImeHandle)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Layout for current subtype changed, switching layout");
+ }
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = identifier;
+ args.arg2 = imeHandle;
+ mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, args).sendToTarget();
+ }
+ mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+ }
+ } finally {
+ mDataStore.saveIfNeeded();
+ }
+ }
+ }
+
+ @Override // Binder call
public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
String keyboardLayoutDescriptor) {
if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
@@ -1315,8 +1394,7 @@
oldLayout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
}
if (mDataStore.addKeyboardLayout(key, keyboardLayoutDescriptor)
- && !Objects.equal(oldLayout,
- mDataStore.getCurrentKeyboardLayout(key))) {
+ && !Objects.equal(oldLayout, mDataStore.getCurrentKeyboardLayout(key))) {
mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
}
} finally {
@@ -1366,45 +1444,44 @@
Slog.i(TAG, "InputMethodSubtype changed: userId=" + userId
+ " ime=" + inputMethodInfo + " subtype=" + subtype);
}
- }
-
- public void switchKeyboardLayout(int deviceId, int direction) {
- mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
+ if (inputMethodInfo == null) {
+ Slog.d(TAG, "No InputMethod is running, ignoring change");
+ return;
+ }
+ if (subtype != null && !"keyboard".equals(subtype.getMode())) {
+ Slog.d(TAG, "InputMethodSubtype changed to non-keyboard subtype, ignoring change");
+ return;
+ }
+ InputMethodSubtypeHandle handle = new InputMethodSubtypeHandle(inputMethodInfo, subtype);
+ if (!handle.equals(mCurrentImeHandle)) {
+ mCurrentImeHandle = handle;
+ handleSwitchKeyboardLayout(null, handle);
+ }
}
// Must be called on handler.
- private void handleSwitchKeyboardLayout(int deviceId, int direction) {
- final InputDevice device = getInputDevice(deviceId);
- if (device != null) {
- final boolean changed;
- final String keyboardLayoutDescriptor;
-
- String key = getLayoutDescriptor(device.getIdentifier());
- synchronized (mDataStore) {
- try {
- changed = mDataStore.switchKeyboardLayout(key, direction);
- keyboardLayoutDescriptor = mDataStore.getCurrentKeyboardLayout(
- key);
- } finally {
- mDataStore.saveIfNeeded();
+ private void handleSwitchKeyboardLayout(@Nullable InputDeviceIdentifier identifier,
+ InputMethodSubtypeHandle handle) {
+ synchronized (mInputDevicesLock) {
+ for (InputDevice device : mInputDevices) {
+ if (identifier != null && !device.getIdentifier().equals(identifier) ||
+ !device.isFullKeyboard()) {
+ continue;
}
- }
-
- if (changed) {
- if (mSwitchedKeyboardLayoutToast != null) {
- mSwitchedKeyboardLayoutToast.cancel();
- mSwitchedKeyboardLayoutToast = null;
- }
- if (keyboardLayoutDescriptor != null) {
- KeyboardLayout keyboardLayout = getKeyboardLayout(keyboardLayoutDescriptor);
- if (keyboardLayout != null) {
- mSwitchedKeyboardLayoutToast = Toast.makeText(
- mContext, keyboardLayout.getLabel(), Toast.LENGTH_SHORT);
- mSwitchedKeyboardLayoutToast.show();
+ String key = getLayoutDescriptor(device.getIdentifier());
+ boolean changed = false;
+ synchronized (mDataStore) {
+ try {
+ if (mDataStore.switchKeyboardLayout(key, handle)) {
+ changed = true;
+ }
+ } finally {
+ mDataStore.saveIfNeeded();
}
}
-
- reloadKeyboardLayouts();
+ if (changed) {
+ reloadKeyboardLayouts();
+ }
}
}
}
@@ -1616,7 +1693,7 @@
}
@Override
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
pw.println("Permission Denial: can't dump InputManager from from pid="
@@ -1630,8 +1707,48 @@
if (dumpStr != null) {
pw.println(dumpStr);
}
+ pw.println(" Keyboard Layouts:");
+ visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
+ @Override
+ public void visitKeyboardLayout(Resources resources,
+ int keyboardLayoutResId, KeyboardLayout layout) {
+ pw.println(" \"" + layout + "\": " + layout.getDescriptor());
+ }
+ });
+ pw.println();
+ synchronized(mDataStore) {
+ mDataStore.dump(pw, " ");
+ }
}
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out,
+ FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
+ (new Shell()).exec(this, in, out, err, args, resultReceiver);
+ }
+
+ public int onShellCommand(Shell shell, String cmd) {
+ if (TextUtils.isEmpty(cmd)) {
+ shell.onHelp();
+ return 1;
+ }
+ if (cmd.equals("setlayout")) {
+ if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
+ "onShellCommand()")) {
+ throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
+ }
+ InputMethodSubtypeHandle handle = new InputMethodSubtypeHandle(
+ shell.getNextArgRequired(), Integer.parseInt(shell.getNextArgRequired()));
+ String descriptor = shell.getNextArgRequired();
+ int vid = Integer.decode(shell.getNextArgRequired());
+ int pid = Integer.decode(shell.getNextArgRequired());
+ InputDeviceIdentifier id = new InputDeviceIdentifier(descriptor, vid, pid);
+ setKeyboardLayoutForInputDeviceInner(id, handle, shell.getNextArgRequired());
+ }
+ return 0;
+ }
+
+
private boolean checkCallingPermission(String permission, String func) {
// Quick check: if the calling permission is me, it's all okay.
if (Binder.getCallingPid() == Process.myPid()) {
@@ -1937,9 +2054,12 @@
case MSG_DELIVER_INPUT_DEVICES_CHANGED:
deliverInputDevicesChanged((InputDevice[])msg.obj);
break;
- case MSG_SWITCH_KEYBOARD_LAYOUT:
- handleSwitchKeyboardLayout(msg.arg1, msg.arg2);
+ case MSG_SWITCH_KEYBOARD_LAYOUT: {
+ SomeArgs args = (SomeArgs)msg.obj;
+ handleSwitchKeyboardLayout((InputDeviceIdentifier)args.arg1,
+ (InputMethodSubtypeHandle)args.arg2);
break;
+ }
case MSG_RELOAD_KEYBOARD_LAYOUTS:
reloadKeyboardLayouts();
break;
@@ -2106,6 +2226,25 @@
}
}
+ private class Shell extends ShellCommand {
+ @Override
+ public int onCommand(String cmd) {
+ return onShellCommand(this, cmd);
+ }
+
+ @Override
+ public void onHelp() {
+ final PrintWriter pw = getOutPrintWriter();
+ pw.println("Input manager commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println("");
+ pw.println(" setlayout IME_ID IME_SUPTYPE_HASH_CODE"
+ + " DEVICE_DESCRIPTOR VENDOR_ID PRODUCT_ID KEYBOARD_DESCRIPTOR");
+ pw.println(" Sets a keyboard layout for a given IME subtype and input device pair");
+ }
+ }
+
private final class LocalService extends InputManagerInternal {
@Override
public void setDisplayViewports(
diff --git a/services/core/java/com/android/server/input/PersistentDataStore.java b/services/core/java/com/android/server/input/PersistentDataStore.java
index f6d7244..e97aca8 100644
--- a/services/core/java/com/android/server/input/PersistentDataStore.java
+++ b/services/core/java/com/android/server/input/PersistentDataStore.java
@@ -16,6 +16,7 @@
package com.android.server.input;
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
@@ -26,6 +27,8 @@
import android.view.Surface;
import android.hardware.input.TouchCalibration;
+import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.Xml;
@@ -37,10 +40,13 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -131,9 +137,26 @@
}
return state.getKeyboardLayouts();
}
+ public String getKeyboardLayout(String inputDeviceDescriptor,
+ InputMethodSubtypeHandle imeHandle) {
+ InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
+ if (state == null) {
+ return null;
+ }
+ return state.getKeyboardLayout(imeHandle);
+ }
- public boolean addKeyboardLayout(String inputDeviceDescriptor,
- String keyboardLayoutDescriptor) {
+ public boolean setKeyboardLayout(String inputDeviceDescriptor,
+ InputMethodSubtypeHandle imeHandle, String keyboardLayoutDescriptor) {
+ InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
+ if (state.setKeyboardLayout(imeHandle, keyboardLayoutDescriptor)) {
+ setDirty();
+ return true;
+ }
+ return false;
+ }
+
+ public boolean addKeyboardLayout(String inputDeviceDescriptor, String keyboardLayoutDescriptor) {
InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
if (state.addKeyboardLayout(keyboardLayoutDescriptor)) {
setDirty();
@@ -152,9 +175,10 @@
return false;
}
- public boolean switchKeyboardLayout(String inputDeviceDescriptor, int direction) {
+ public boolean switchKeyboardLayout(String inputDeviceDescriptor,
+ InputMethodSubtypeHandle imeHandle) {
InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
- if (state != null && state.switchKeyboardLayout(direction)) {
+ if (state != null && state.switchKeyboardLayout(imeHandle)) {
setDirty();
return true;
}
@@ -301,13 +325,26 @@
serializer.endDocument();
}
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "PersistentDataStore");
+ pw.println(prefix + " mLoaded=" + mLoaded);
+ pw.println(prefix + " mDirty=" + mDirty);
+ pw.println(prefix + " InputDeviceStates:");
+ int i = 0;
+ for (Map.Entry<String, InputDeviceState> entry : mInputDevices.entrySet()) {
+ pw.println(prefix + " " + i++ + ": " + entry.getKey());
+ entry.getValue().dump(pw, prefix + " ");
+ }
+ }
+
private static final class InputDeviceState {
private static final String[] CALIBRATION_NAME = { "x_scale",
"x_ymix", "x_offset", "y_xmix", "y_scale", "y_offset" };
private TouchCalibration[] mTouchCalibration = new TouchCalibration[4];
private String mCurrentKeyboardLayout;
- private ArrayList<String> mKeyboardLayouts = new ArrayList<String>();
+ private List<String> mUnassociatedKeyboardLayouts = new ArrayList<>();
+ private ArrayMap<InputMethodSubtypeHandle, String> mKeyboardLayouts = new ArrayMap<>();
public TouchCalibration getTouchCalibration(int surfaceRotation) {
try {
@@ -345,18 +382,34 @@
}
public String[] getKeyboardLayouts() {
- if (mKeyboardLayouts.isEmpty()) {
+ if (mUnassociatedKeyboardLayouts.isEmpty()) {
return (String[])ArrayUtils.emptyArray(String.class);
}
- return mKeyboardLayouts.toArray(new String[mKeyboardLayouts.size()]);
+ return mUnassociatedKeyboardLayouts.toArray(
+ new String[mUnassociatedKeyboardLayouts.size()]);
+ }
+
+ public String getKeyboardLayout(InputMethodSubtypeHandle handle) {
+ return mKeyboardLayouts.get(handle);
+ }
+
+ public boolean setKeyboardLayout(InputMethodSubtypeHandle imeHandle,
+ String keyboardLayout) {
+ String existingLayout = mKeyboardLayouts.get(imeHandle);
+ if (TextUtils.equals(existingLayout, keyboardLayout)) {
+ return false;
+ }
+ mKeyboardLayouts.put(imeHandle, keyboardLayout);
+ return true;
}
public boolean addKeyboardLayout(String keyboardLayout) {
- int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout);
+ int index = Collections.binarySearch(
+ mUnassociatedKeyboardLayouts, keyboardLayout);
if (index >= 0) {
return false;
}
- mKeyboardLayouts.add(-index - 1, keyboardLayout);
+ mUnassociatedKeyboardLayouts.add(-index - 1, keyboardLayout);
if (mCurrentKeyboardLayout == null) {
mCurrentKeyboardLayout = keyboardLayout;
}
@@ -364,11 +417,11 @@
}
public boolean removeKeyboardLayout(String keyboardLayout) {
- int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout);
+ int index = Collections.binarySearch(mUnassociatedKeyboardLayouts, keyboardLayout);
if (index < 0) {
return false;
}
- mKeyboardLayouts.remove(index);
+ mUnassociatedKeyboardLayouts.remove(index);
updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, index);
return true;
}
@@ -376,41 +429,34 @@
private void updateCurrentKeyboardLayoutIfRemoved(
String removedKeyboardLayout, int removedIndex) {
if (Objects.equal(mCurrentKeyboardLayout, removedKeyboardLayout)) {
- if (!mKeyboardLayouts.isEmpty()) {
+ if (!mUnassociatedKeyboardLayouts.isEmpty()) {
int index = removedIndex;
- if (index == mKeyboardLayouts.size()) {
+ if (index == mUnassociatedKeyboardLayouts.size()) {
index = 0;
}
- mCurrentKeyboardLayout = mKeyboardLayouts.get(index);
+ mCurrentKeyboardLayout = mUnassociatedKeyboardLayouts.get(index);
} else {
mCurrentKeyboardLayout = null;
}
}
}
- public boolean switchKeyboardLayout(int direction) {
- final int size = mKeyboardLayouts.size();
- if (size < 2) {
- return false;
+ public boolean switchKeyboardLayout(InputMethodSubtypeHandle imeHandle) {
+ final String layout = mKeyboardLayouts.get(imeHandle);
+ if (layout != null && !TextUtils.equals(mCurrentKeyboardLayout, layout)) {
+ mCurrentKeyboardLayout = layout;
+ return true;
}
- int index = Collections.binarySearch(mKeyboardLayouts, mCurrentKeyboardLayout);
- assert index >= 0;
- if (direction > 0) {
- index = (index + 1) % size;
- } else {
- index = (index + size - 1) % size;
- }
- mCurrentKeyboardLayout = mKeyboardLayouts.get(index);
- return true;
+ return false;
}
public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) {
boolean changed = false;
- for (int i = mKeyboardLayouts.size(); i-- > 0; ) {
- String keyboardLayout = mKeyboardLayouts.get(i);
+ for (int i = mUnassociatedKeyboardLayouts.size(); i-- > 0; ) {
+ String keyboardLayout = mUnassociatedKeyboardLayouts.get(i);
if (!availableKeyboardLayouts.contains(keyboardLayout)) {
Slog.i(TAG, "Removing uninstalled keyboard layout " + keyboardLayout);
- mKeyboardLayouts.remove(i);
+ mUnassociatedKeyboardLayouts.remove(i);
updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, i);
changed = true;
}
@@ -428,13 +474,8 @@
throw new XmlPullParserException(
"Missing descriptor attribute on keyboard-layout.");
}
- String current = parser.getAttributeValue(null, "current");
- if (mKeyboardLayouts.contains(descriptor)) {
- throw new XmlPullParserException(
- "Found duplicate keyboard layout.");
- }
- mKeyboardLayouts.add(descriptor);
+ String current = parser.getAttributeValue(null, "current");
if (current != null && current.equals("true")) {
if (mCurrentKeyboardLayout != null) {
throw new XmlPullParserException(
@@ -442,6 +483,32 @@
}
mCurrentKeyboardLayout = descriptor;
}
+
+ String inputMethodId = parser.getAttributeValue(null, "input-method-id");
+ String inputMethodSubtypeId =
+ parser.getAttributeValue(null, "input-method-subtype-id");
+ if (inputMethodId == null && inputMethodSubtypeId != null
+ || inputMethodId != null && inputMethodSubtypeId == null) {
+ throw new XmlPullParserException(
+ "Found an incomplete input method description");
+ }
+
+ if (inputMethodSubtypeId != null) {
+ InputMethodSubtypeHandle handle = new InputMethodSubtypeHandle(
+ inputMethodId, Integer.parseInt(inputMethodSubtypeId));
+ if (mKeyboardLayouts.containsKey(handle)) {
+ throw new XmlPullParserException(
+ "Found duplicate subtype to keyboard layout mapping: "
+ + handle);
+ }
+ mKeyboardLayouts.put(handle, descriptor);
+ } else {
+ if (mUnassociatedKeyboardLayouts.contains(descriptor)) {
+ throw new XmlPullParserException(
+ "Found duplicate unassociated keyboard layout: " + descriptor);
+ }
+ mUnassociatedKeyboardLayouts.add(descriptor);
+ }
} else if (parser.getName().equals("calibration")) {
String format = parser.getAttributeValue(null, "format");
String rotation = parser.getAttributeValue(null, "rotation");
@@ -492,19 +559,31 @@
}
// Maintain invariant that layouts are sorted.
- Collections.sort(mKeyboardLayouts);
+ Collections.sort(mUnassociatedKeyboardLayouts);
// Maintain invariant that there is always a current keyboard layout unless
// there are none installed.
- if (mCurrentKeyboardLayout == null && !mKeyboardLayouts.isEmpty()) {
- mCurrentKeyboardLayout = mKeyboardLayouts.get(0);
+ if (mCurrentKeyboardLayout == null && !mUnassociatedKeyboardLayouts.isEmpty()) {
+ mCurrentKeyboardLayout = mUnassociatedKeyboardLayouts.get(0);
}
}
public void saveToXml(XmlSerializer serializer) throws IOException {
- for (String layout : mKeyboardLayouts) {
+ for (String layout : mUnassociatedKeyboardLayouts) {
serializer.startTag(null, "keyboard-layout");
serializer.attribute(null, "descriptor", layout);
+ serializer.endTag(null, "keyboard-layout");
+ }
+
+ final int N = mKeyboardLayouts.size();
+ for (int i = 0; i < N; i++) {
+ final InputMethodSubtypeHandle handle = mKeyboardLayouts.keyAt(i);
+ final String layout = mKeyboardLayouts.valueAt(i);
+ serializer.startTag(null, "keyboard-layout");
+ serializer.attribute(null, "descriptor", layout);
+ serializer.attribute(null, "input-method-id", handle.getInputMethodId());
+ serializer.attribute(null, "input-method-subtype-id",
+ Integer.toString(handle.getSubtypeId()));
if (layout.equals(mCurrentKeyboardLayout)) {
serializer.attribute(null, "current", "true");
}
@@ -529,6 +608,22 @@
}
}
+ private void dump(final PrintWriter pw, final String prefix) {
+ pw.println(prefix + "CurrentKeyboardLayout=" + mCurrentKeyboardLayout);
+ pw.println(prefix + "UnassociatedKeyboardLayouts=" + mUnassociatedKeyboardLayouts);
+ pw.println(prefix + "TouchCalibration=" + Arrays.toString(mTouchCalibration));
+ pw.println(prefix + "Subtype to Layout Mappings:");
+ final int N = mKeyboardLayouts.size();
+ if (N != 0) {
+ for (int i = 0; i < N; i++) {
+ pw.println(prefix + " " + mKeyboardLayouts.keyAt(i) + ": "
+ + mKeyboardLayouts.valueAt(i));
+ }
+ } else {
+ pw.println(prefix + " <none>");
+ }
+ }
+
private static String surfaceRotationToString(int surfaceRotation) {
switch (surfaceRotation) {
case Surface.ROTATION_0: return "0";
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 38ebc80..1d7d24b 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -31,6 +31,9 @@
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_WIMAX;
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
import static android.net.ConnectivityManager.isNetworkTypeMobile;
import static android.net.NetworkPolicy.CYCLE_NONE;
import static android.net.NetworkPolicy.LIMIT_DISABLED;
@@ -124,6 +127,7 @@
import android.os.INetworkManagementService;
import android.os.IPowerManager;
import android.os.Message;
+import android.os.ResultReceiver;
import android.os.MessageQueue.IdleHandler;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
@@ -1873,6 +1877,20 @@
}
@Override
+ public int getRestrictBackgroundByCaller() {
+ mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG);
+ final int uid = Binder.getCallingUid();
+ synchronized (mRulesLock) {
+ if (!mRestrictBackground) {
+ return RESTRICT_BACKGROUND_STATUS_DISABLED;
+ }
+ return mRestrictBackgroundWhitelistUids.get(uid)
+ ? RESTRICT_BACKGROUND_STATUS_WHITELISTED
+ : RESTRICT_BACKGROUND_STATUS_ENABLED;
+ }
+ }
+
+ @Override
public boolean getRestrictBackground() {
mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
@@ -2102,6 +2120,13 @@
}
@Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ResultReceiver resultReceiver) throws RemoteException {
+ (new NetworkPolicyManagerShellCommand(this)).exec(
+ this, in, out, err, args, resultReceiver);
+ }
+
+ @Override
public boolean isUidForeground(int uid) {
mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java b/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java
new file mode 100644
index 0000000..7b1acca
--- /dev/null
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java
@@ -0,0 +1,230 @@
+/*
+ * 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.
+ */
+
+package com.android.server.net;
+
+import java.io.PrintWriter;
+
+import android.content.Intent;
+import android.net.INetworkPolicyManager;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+
+public class NetworkPolicyManagerShellCommand extends ShellCommand {
+
+ final INetworkPolicyManager mInterface;
+
+ NetworkPolicyManagerShellCommand(NetworkPolicyManagerService service) {
+ mInterface = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ final PrintWriter pw = getOutPrintWriter();
+ try {
+ switch(cmd) {
+ case "get":
+ return runGet();
+ case "set":
+ return runSet();
+ case "list":
+ return runList();
+ case "add":
+ return runAdd();
+ case "remove":
+ return runRemove();
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ } catch (RemoteException e) {
+ pw.println("Remote exception: " + e);
+ }
+ return -1;
+ }
+
+ @Override
+ public void onHelp() {
+ final PrintWriter pw = getOutPrintWriter();
+ pw.println("Network policy manager (netpolicy) commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println("");
+ pw.println(" get restrict-background");
+ pw.println(" Gets the global restrict background usage status.");
+ pw.println(" set restrict-background BOOLEAN");
+ pw.println(" Sets the global restrict background usage status.");
+ pw.println(" list restrict-background-whitelist");
+ pw.println(" Prints UID that are whitelisted for restrict background usage.");
+ pw.println(" add restrict-background-whitelist UID");
+ pw.println(" Adds a UID to the whitelist for restrict background usage.");
+ pw.println(" remove restrict-background-whitelist UID");
+ pw.println(" Removes a UID from the whitelist for restrict background usage.");
+ }
+
+ private int runGet() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final String type = getNextArg();
+ if (type == null) {
+ pw.println("Error: didn't specify type of data to get");
+ return -1;
+ }
+ switch(type) {
+ case "restrict-background":
+ return getRestrictBackgroundWhitelist();
+ }
+ pw.println("Error: unknown get type '" + type + "'");
+ return -1;
+ }
+
+ private int runSet() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final String type = getNextArg();
+ if (type == null) {
+ pw.println("Error: didn't specify type of data to set");
+ return -1;
+ }
+ switch(type) {
+ case "restrict-background":
+ return setRestrictBackgroundWhitelist();
+ }
+ pw.println("Error: unknown set type '" + type + "'");
+ return -1;
+ }
+
+ private int runList() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final String type = getNextArg();
+ if (type == null) {
+ pw.println("Error: didn't specify type of data to list");
+ return -1;
+ }
+ switch(type) {
+ case "restrict-background-whitelist":
+ return runListRestrictBackgroundWhitelist();
+ }
+ pw.println("Error: unknown list type '" + type + "'");
+ return -1;
+ }
+
+ private int runAdd() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final String type = getNextArg();
+ if (type == null) {
+ pw.println("Error: didn't specify type of data to add");
+ return -1;
+ }
+ switch(type) {
+ case "restrict-background-whitelist":
+ return addRestrictBackgroundWhitelist();
+ }
+ pw.println("Error: unknown add type '" + type + "'");
+ return -1;
+ }
+
+ private int runRemove() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final String type = getNextArg();
+ if (type == null) {
+ pw.println("Error: didn't specify type of data to remove");
+ return -1;
+ }
+ switch(type) {
+ case "restrict-background-whitelist":
+ return removeRestrictBackgroundWhitelist();
+ }
+ pw.println("Error: unknown remove type '" + type + "'");
+ return -1;
+ }
+
+ private int runListRestrictBackgroundWhitelist() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final int[] uids = mInterface.getRestrictBackgroundWhitelistedUids();
+ pw.print("Restrict background whitelisted UIDs: ");
+ if (uids.length == 0) {
+ pw.println("none");
+ } else {
+ for (int i = 0; i < uids.length; i++) {
+ int uid = uids[i];
+ pw.print(uid);
+ pw.print(' ');
+ }
+ }
+ pw.println();
+ return 0;
+ }
+
+ private int getRestrictBackgroundWhitelist() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ pw.print("Restrict background status: ");
+ pw.println(mInterface.getRestrictBackground() ? "enabled" : "disabled");
+ return 0;
+ }
+
+ private int setRestrictBackgroundWhitelist() throws RemoteException {
+ final int enabled = getNextBooleanArg();
+ if (enabled < 0) {
+ return enabled;
+ }
+ mInterface.setRestrictBackground(enabled > 0);
+ return 0;
+ }
+
+ private int addRestrictBackgroundWhitelist() throws RemoteException {
+ final int uid = getUidFromNextArg();
+ if (uid < 0) {
+ return uid;
+ }
+ mInterface.addRestrictBackgroundWhitelistedUid(uid);
+ return 0;
+ }
+
+ private int removeRestrictBackgroundWhitelist() throws RemoteException {
+ final int uid = getUidFromNextArg();
+ if (uid < 0) {
+ return uid;
+ }
+ mInterface.removeRestrictBackgroundWhitelistedUid(uid);
+ return 0;
+ }
+
+ private int getNextBooleanArg() {
+ final PrintWriter pw = getOutPrintWriter();
+ final String arg = getNextArg();
+ if (arg == null) {
+ pw.println("Error: didn't specify BOOLEAN");
+ return -1;
+ }
+ return Boolean.valueOf(arg) ? 1 : 0;
+ }
+
+ private int getUidFromNextArg() {
+ final PrintWriter pw = getOutPrintWriter();
+ final String arg = getNextArg();
+ if (arg == null) {
+ pw.println("Error: didn't specify UID");
+ return -1;
+ }
+ try {
+ return Integer.parseInt(arg);
+ } catch (NumberFormatException e) {
+ pw.println("Error: UID (" + arg + ") should be a number");
+ return -2;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index f360dc2..f5da52e 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -297,7 +297,6 @@
checkType(guest.service);
if (registerServiceImpl(guest) != null) {
onServiceAdded(guest);
- onServiceAdded(guest);
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b1fe68c..2ee74db 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -223,6 +223,8 @@
private WorkerHandler mHandler;
private final HandlerThread mRankingThread = new HandlerThread("ranker",
Process.THREAD_PRIORITY_BACKGROUND);
+ private final HandlerThread mAssistantThread = new HandlerThread("assistant",
+ Process.THREAD_PRIORITY_BACKGROUND);
private Light mNotificationLight;
Light mAttentionLight;
@@ -295,6 +297,7 @@
private static final int MY_UID = Process.myUid();
private static final int MY_PID = Process.myPid();
private RankingHandler mRankingHandler;
+ private Handler mAssistantHandler;
private static class Archive {
final int mBufferSize;
@@ -878,6 +881,7 @@
mHandler = new WorkerHandler();
mRankingThread.start();
+ mAssistantThread.start();
String[] extractorNames;
try {
extractorNames = resources.getStringArray(R.array.config_notificationSignalExtractors);
@@ -886,6 +890,7 @@
}
mUsageStats = new NotificationUsageStats(getContext());
mRankingHandler = new RankingHandlerWorker(mRankingThread.getLooper());
+ mAssistantHandler = new Handler(mAssistantThread.getLooper());
mRankingHelper = new RankingHelper(getContext(),
mRankingHandler,
mUsageStats,
@@ -1957,7 +1962,7 @@
@Override
public void setImportanceFromAssistant(INotificationListener token, String key,
- int importance, CharSequence explanation) {
+ int importance, CharSequence explanation) throws RemoteException {
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mNotificationList) {
@@ -2249,115 +2254,145 @@
+ " id=" + id + " notification=" + notification);
}
- mHandler.post(new Runnable() {
- @Override
- public void run() {
+ // Sanitize inputs
+ notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN,
+ Notification.PRIORITY_MAX);
- synchronized (mNotificationList) {
-
- // Sanitize inputs
- notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN,
- Notification.PRIORITY_MAX);
-
- // setup local book-keeping
- final StatusBarNotification n = new StatusBarNotification(
- pkg, opPkg, id, tag, callingUid, callingPid, 0, notification,
- user);
- NotificationRecord r = new NotificationRecord(getContext(), n);
- NotificationRecord old = mNotificationsByKey.get(n.getKey());
- if (old != null) {
- // Retain ranking information from previous record
- r.copyRankingInformation(old);
- }
-
- // Handle grouped notifications and bail out early if we
- // can to avoid extracting signals.
- handleGroupedNotificationLocked(r, old, callingUid, callingPid);
- boolean ignoreNotification =
- removeUnusedGroupedNotificationLocked(r, old, callingUid, callingPid);
-
- // This conditional is a dirty hack to limit the logging done on
- // behalf of the download manager without affecting other apps.
- if (!pkg.equals("com.android.providers.downloads")
- || Log.isLoggable("DownloadManager", Log.VERBOSE)) {
- int enqueueStatus = EVENTLOG_ENQUEUE_STATUS_NEW;
- if (ignoreNotification) {
- enqueueStatus = EVENTLOG_ENQUEUE_STATUS_IGNORED;
- } else if (old != null) {
- enqueueStatus = EVENTLOG_ENQUEUE_STATUS_UPDATE;
- }
- EventLogTags.writeNotificationEnqueue(callingUid, callingPid,
- pkg, id, tag, userId, notification.toString(),
- enqueueStatus);
- }
-
- if (ignoreNotification) {
- return;
- }
-
- mRankingHelper.extractSignals(r);
- savePolicyFile();
-
- // blocked apps/topics
- if (r.getImportance() == NotificationListenerService.Ranking.IMPORTANCE_NONE
- || !noteNotificationOp(pkg, callingUid)) {
- if (!isSystemNotification) {
- Slog.e(TAG, "Suppressing notification from package " + pkg
- + " by user request.");
- mUsageStats.registerBlocked(r);
- return;
- }
- }
-
- int index = indexOfNotificationLocked(n.getKey());
- if (index < 0) {
- mNotificationList.add(r);
- mUsageStats.registerPostedByApp(r);
- } else {
- old = mNotificationList.get(index);
- mNotificationList.set(index, r);
- mUsageStats.registerUpdatedByApp(r, old);
- // Make sure we don't lose the foreground service state.
- notification.flags |=
- old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE;
- r.isUpdate = true;
- }
-
- mNotificationsByKey.put(n.getKey(), r);
-
- // Ensure if this is a foreground service that the proper additional
- // flags are set.
- if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
- notification.flags |= Notification.FLAG_ONGOING_EVENT
- | Notification.FLAG_NO_CLEAR;
- }
-
- applyZenModeLocked(r);
- mRankingHelper.sort(mNotificationList);
-
- if (notification.getSmallIcon() != null) {
- StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
- mListeners.notifyPostedLocked(n, oldSbn);
- } else {
- Slog.e(TAG, "Not posting notification without small icon: " + notification);
- if (old != null && !old.isCanceled) {
- mListeners.notifyRemovedLocked(n);
- }
- // ATTENTION: in a future release we will bail out here
- // so that we do not play sounds, show lights, etc. for invalid
- // notifications
- Slog.e(TAG, "WARNING: In a future release this will crash the app: "
- + n.getPackageName());
- }
-
- buzzBeepBlinkLocked(r);
- }
- }
- });
+ // setup local book-keeping
+ final StatusBarNotification n = new StatusBarNotification(
+ pkg, opPkg, id, tag, callingUid, callingPid, 0, notification,
+ user);
+ final NotificationRecord r = new NotificationRecord(getContext(), n);
+ mHandler.post(new EnqueueNotificationRunnable(userId, r));
idOut[0] = id;
}
+ private class EnqueueNotificationRunnable implements Runnable {
+ private final NotificationRecord r;
+ private final int userId;
+
+ EnqueueNotificationRunnable(int userId, NotificationRecord r) {
+ this.userId = userId;
+ this.r = r;
+ };
+
+ @Override
+ public void run() {
+
+ synchronized (mNotificationList) {
+ final StatusBarNotification n = r.sbn;
+ Slog.d(TAG, "EnqueueNotificationRunnable.run for: " + n.getKey());
+ NotificationRecord old = mNotificationsByKey.get(n.getKey());
+ if (old != null) {
+ // Retain ranking information from previous record
+ r.copyRankingInformation(old);
+ }
+
+ final int callingUid = n.getUid();
+ final int callingPid = n.getInitialPid();
+ final Notification notification = n.getNotification();
+ final String pkg = n.getPackageName();
+ final int id = n.getId();
+ final String tag = n.getTag();
+ final boolean isSystemNotification = isUidSystem(callingUid) ||
+ ("android".equals(pkg));
+
+ // Handle grouped notifications and bail out early if we
+ // can to avoid extracting signals.
+ handleGroupedNotificationLocked(r, old, callingUid, callingPid);
+ boolean ignoreNotification =
+ removeUnusedGroupedNotificationLocked(r, old, callingUid, callingPid);
+ Slog.d(TAG, "ignoreNotification is " + ignoreNotification);
+
+ // This conditional is a dirty hack to limit the logging done on
+ // behalf of the download manager without affecting other apps.
+ if (!pkg.equals("com.android.providers.downloads")
+ || Log.isLoggable("DownloadManager", Log.VERBOSE)) {
+ int enqueueStatus = EVENTLOG_ENQUEUE_STATUS_NEW;
+ if (ignoreNotification) {
+ enqueueStatus = EVENTLOG_ENQUEUE_STATUS_IGNORED;
+ } else if (old != null) {
+ enqueueStatus = EVENTLOG_ENQUEUE_STATUS_UPDATE;
+ }
+ EventLogTags.writeNotificationEnqueue(callingUid, callingPid,
+ pkg, id, tag, userId, notification.toString(),
+ enqueueStatus);
+ }
+
+ if (ignoreNotification) {
+ return;
+ }
+
+ mRankingHelper.extractSignals(r);
+
+ // why is this here?
+ savePolicyFile();
+
+ // blocked apps/topics
+ if (r.getImportance() == NotificationListenerService.Ranking.IMPORTANCE_NONE
+ || !noteNotificationOp(pkg, callingUid)) {
+ if (!isSystemNotification) {
+ Slog.e(TAG, "Suppressing notification from package " + pkg
+ + " by user request.");
+ mUsageStats.registerBlocked(r);
+ return;
+ }
+ }
+
+ // tell the assistant about the notification
+ if (mAssistant.isEnabled()) {
+ mAssistant.onNotificationEnqueued(r);
+ // TODO delay the code below here for 100ms or until there is an answer
+ }
+
+
+ int index = indexOfNotificationLocked(n.getKey());
+ if (index < 0) {
+ mNotificationList.add(r);
+ mUsageStats.registerPostedByApp(r);
+ } else {
+ old = mNotificationList.get(index);
+ mNotificationList.set(index, r);
+ mUsageStats.registerUpdatedByApp(r, old);
+ // Make sure we don't lose the foreground service state.
+ notification.flags |=
+ old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE;
+ r.isUpdate = true;
+ }
+
+ mNotificationsByKey.put(n.getKey(), r);
+
+ // Ensure if this is a foreground service that the proper additional
+ // flags are set.
+ if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
+ notification.flags |= Notification.FLAG_ONGOING_EVENT
+ | Notification.FLAG_NO_CLEAR;
+ }
+
+ applyZenModeLocked(r);
+ mRankingHelper.sort(mNotificationList);
+
+ if (notification.getSmallIcon() != null) {
+ StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
+ mListeners.notifyPostedLocked(n, oldSbn);
+ } else {
+ Slog.e(TAG, "Not posting notification without small icon: " + notification);
+ if (old != null && !old.isCanceled) {
+ mListeners.notifyRemovedLocked(n);
+ }
+ // ATTENTION: in a future release we will bail out here
+ // so that we do not play sounds, show lights, etc. for invalid
+ // notifications
+ Slog.e(TAG, "WARNING: In a future release this will crash the app: "
+ + n.getPackageName());
+ }
+
+ buzzBeepBlinkLocked(r);
+ }
+ }
+ }
+
/**
* Ensures that grouped notification receive their special treatment.
*
@@ -3391,6 +3426,30 @@
return true;
}
+ private class TrimCache {
+ StatusBarNotification heavy;
+ StatusBarNotification sbnClone;
+ StatusBarNotification sbnCloneLight;
+
+ TrimCache(StatusBarNotification sbn) {
+ heavy = sbn;
+ }
+
+ StatusBarNotification ForListener(ManagedServiceInfo info) {
+ if (mListeners.getOnNotificationPostedTrim(info) == TRIM_LIGHT) {
+ if (sbnCloneLight == null) {
+ sbnCloneLight = heavy.cloneLight();
+ }
+ return sbnCloneLight;
+ } else {
+ if (sbnClone == null) {
+ sbnClone = heavy.clone();
+ }
+ return sbnClone;
+ }
+ }
+ }
+
public class NotificationAssistant extends ManagedServices {
public NotificationAssistant() {
@@ -3428,6 +3487,46 @@
protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
mListeners.unregisterService(removed.service, removed.userid);
}
+
+ public void onNotificationEnqueued(final NotificationRecord r) {
+ final StatusBarNotification sbn = r.sbn;
+ TrimCache trimCache = new TrimCache(sbn);
+
+ // mServices is the list inside ManagedServices of all the assistants,
+ // There should be only one, but it's a list, so while we enforce
+ // singularity elsewhere, we keep it general here, to avoid surprises.
+ for (final ManagedServiceInfo info : NotificationAssistant.this.mServices) {
+ boolean sbnVisible = isVisibleToListener(sbn, info);
+ if (!sbnVisible) {
+ continue;
+ }
+
+ final int importance = r.getImportance();
+ final boolean fromUser = r.isImportanceFromUser();
+ final StatusBarNotification sbnToPost = trimCache.ForListener(info);
+ mAssistantHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ notifyEnqueued(info, sbnToPost, importance, fromUser);
+ }
+ });
+ }
+ }
+
+ private void notifyEnqueued(final ManagedServiceInfo info,
+ final StatusBarNotification sbn, int importance, boolean fromUser) {
+ final INotificationListener assistant = (INotificationListener) info.service;
+ StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
+ try {
+ assistant.onNotificationEnqueued(sbnHolder, importance, fromUser);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "unable to notify assistant (enqueued): " + assistant, ex);
+ }
+ }
+
+ public boolean isEnabled() {
+ return !mServices.isEmpty();
+ }
}
public class NotificationListeners extends ManagedServices {
@@ -3476,7 +3575,6 @@
}
}
-
@Override
protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
if (mListenersDisablingEffects.remove(removed)) {
@@ -3508,8 +3606,7 @@
*/
public void notifyPostedLocked(StatusBarNotification sbn, StatusBarNotification oldSbn) {
// Lazily initialized snapshots of the notification.
- StatusBarNotification sbnClone = null;
- StatusBarNotification sbnCloneLight = null;
+ TrimCache trimCache = new TrimCache(sbn);
for (final ManagedServiceInfo info : mServices) {
boolean sbnVisible = isVisibleToListener(sbn, info);
@@ -3532,16 +3629,7 @@
continue;
}
- final int trim = mListeners.getOnNotificationPostedTrim(info);
-
- if (trim == TRIM_LIGHT && sbnCloneLight == null) {
- sbnCloneLight = sbn.cloneLight();
- } else if (trim == TRIM_FULL && sbnClone == null) {
- sbnClone = sbn.clone();
- }
- final StatusBarNotification sbnToPost =
- (trim == TRIM_FULL) ? sbnClone : sbnCloneLight;
-
+ final StatusBarNotification sbnToPost = trimCache.ForListener(info);
mHandler.post(new Runnable() {
@Override
public void run() {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 0be2edd..490e890 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -15,6 +15,7 @@
*/
package com.android.server.notification;
+import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED;
import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_DEFAULT;
import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_HIGH;
import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_LOW;
@@ -88,8 +89,8 @@
private int mAuthoritativeRank;
private String mGlobalSortKey;
private int mPackageVisibility;
- private int mTopicImportance = NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED;
- private int mImportance = NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED;
+ private int mTopicImportance = IMPORTANCE_UNSPECIFIED;
+ private int mImportance = IMPORTANCE_UNSPECIFIED;
private CharSequence mImportanceExplanation = null;
private int mSuppressedVisualEffects = 0;
@@ -510,4 +511,8 @@
public String getGroupKey() {
return sbn.getGroupKey();
}
+
+ public boolean isImportanceFromUser() {
+ return mImportance == mTopicImportance;
+ }
}
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 06f828e..89e89b0 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -93,7 +93,8 @@
// skip previously failing package
continue;
}
- if (!pm.performDexOpt(pkg, /* instruction set */ null, useJitProfiles)) {
+ if (!pm.performDexOpt(pkg, /* instruction set */ null, useJitProfiles,
+ /* extractOnly */ false)) {
// there was a problem running dexopt,
// remember this so we do not keep retrying.
sFailedPackageNames.add(pkg);
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 366edf9..cf876ee 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -47,8 +47,8 @@
public static final int DEXOPT_DEBUGGABLE = 1 << 3;
/** The system boot has finished */
public static final int DEXOPT_BOOTCOMPLETE = 1 << 4;
- /** Run the application with the JIT compiler */
- public static final int DEXOPT_USEJIT = 1 << 5;
+ /** Do not compile, only extract bytecode into an OAT file */
+ public static final int DEXOPT_EXTRACTONLY = 1 << 5;
/** @hide */
@IntDef(flag = true, value = {
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 18618d5..d82bb3d 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
import android.content.pm.ILauncherApps;
import android.content.pm.IOnAppsChangedListener;
import android.content.pm.IPackageManager;
@@ -246,6 +247,25 @@
}
@Override
+ public ApplicationInfo getApplicationInfo(String packageName, int flags, UserHandle user)
+ throws RemoteException {
+ ensureInUserProfiles(user, "Cannot check package for unrelated profile " + user);
+ if (!isUserEnabled(user)) {
+ return null;
+ }
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ IPackageManager pm = AppGlobals.getPackageManager();
+ ApplicationInfo info = pm.getApplicationInfo(packageName, flags,
+ user.getIdentifier());
+ return info;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
public boolean isActivityEnabled(ComponentName component, UserHandle user)
throws RemoteException {
ensureInUserProfiles(user, "Cannot check component for unrelated profile " + user);
@@ -467,6 +487,44 @@
super.onPackagesUnavailable(packages);
}
+ @Override
+ public void onPackagesSuspended(String[] packages) {
+ UserHandle user = new UserHandle(getChangingUserId());
+ final int n = mListeners.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+ UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i);
+ if (!isEnabledProfileOf(user, listeningUser, "onPackagesSuspended")) continue;
+ try {
+ listener.onPackagesSuspended(user, packages);
+ } catch (RemoteException re) {
+ Slog.d(TAG, "Callback failed ", re);
+ }
+ }
+ mListeners.finishBroadcast();
+
+ super.onPackagesSuspended(packages);
+ }
+
+ @Override
+ public void onPackagesUnsuspended(String[] packages) {
+ UserHandle user = new UserHandle(getChangingUserId());
+ final int n = mListeners.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+ UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i);
+ if (!isEnabledProfileOf(user, listeningUser, "onPackagesUnsuspended")) continue;
+ try {
+ listener.onPackagesUnsuspended(user, packages);
+ } catch (RemoteException re) {
+ Slog.d(TAG, "Callback failed ", re);
+ }
+ }
+ mListeners.finishBroadcast();
+
+ super.onPackagesUnsuspended(packages);
+ }
+
}
class PackageCallbackList<T extends IInterface> extends RemoteCallbackList<T> {
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 44b51d4..bce7223 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -41,7 +41,7 @@
import static com.android.server.pm.Installer.DEXOPT_DEBUGGABLE;
import static com.android.server.pm.Installer.DEXOPT_PUBLIC;
import static com.android.server.pm.Installer.DEXOPT_SAFEMODE;
-import static com.android.server.pm.Installer.DEXOPT_USEJIT;
+import static com.android.server.pm.Installer.DEXOPT_EXTRACTONLY;
import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
@@ -82,7 +82,7 @@
* {@link PackageManagerService#mInstallLock}.
*/
int performDexOpt(PackageParser.Package pkg, String[] instructionSets,
- boolean inclDependencies, String volumeUuid, boolean useProfiles) {
+ boolean inclDependencies, boolean useProfiles, boolean extractOnly) {
ArraySet<String> done;
if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) {
done = new ArraySet<String>();
@@ -97,7 +97,8 @@
mDexoptWakeLock.acquire();
}
try {
- return performDexOptLI(pkg, instructionSets, done, volumeUuid, useProfiles);
+ return performDexOptLI(pkg, instructionSets, done, useProfiles,
+ extractOnly);
} finally {
if (useLock) {
mDexoptWakeLock.release();
@@ -107,7 +108,7 @@
}
private int performDexOptLI(PackageParser.Package pkg, String[] targetInstructionSets,
- ArraySet<String> done, String volumeUuid, boolean useProfiles) {
+ ArraySet<String> done, boolean useProfiles, boolean extractOnly) {
final String[] instructionSets = targetInstructionSets != null ?
targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
@@ -173,29 +174,32 @@
Log.i(TAG, "Running dexopt (" + dexoptType + ") on: " + path + " pkg="
+ pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet
+ " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable
- + " oatDir = " + oatDir);
+ + " extractOnly=" + extractOnly + " oatDir = " + oatDir);
final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
final int dexFlags =
(!pkg.isForwardLocked() ? DEXOPT_PUBLIC : 0)
| (vmSafeMode ? DEXOPT_SAFEMODE : 0)
| (debuggable ? DEXOPT_DEBUGGABLE : 0)
+ | (extractOnly ? DEXOPT_EXTRACTONLY : 0)
| DEXOPT_BOOTCOMPLETE;
try {
mPackageManagerService.mInstaller.dexopt(path, sharedGid,
pkg.packageName, dexCodeInstructionSet, dexoptNeeded, oatDir,
- dexFlags, volumeUuid, useProfiles);
+ dexFlags, pkg.volumeUuid, useProfiles);
performedDexOpt = true;
} catch (InstallerException e) {
Slog.w(TAG, "Failed to dexopt", e);
}
}
- // At this point we haven't failed dexopt and we haven't deferred dexopt. We must
- // either have either succeeded dexopt, or have had getDexOptNeeded tell us
- // it isn't required. We therefore mark that this package doesn't need dexopt unless
- // it's forced. performedDexOpt will tell us whether we performed dex-opt or skipped
- // it.
- pkg.mDexOptPerformed.add(dexCodeInstructionSet);
+ if (!extractOnly) {
+ // At this point we haven't failed dexopt and we haven't deferred dexopt. We must
+ // either have either succeeded dexopt, or have had getDexOptNeeded tell us
+ // it isn't required. We therefore mark that this package doesn't need dexopt unless
+ // it's forced. performedDexOpt will tell us whether we performed dex-opt or skipped
+ // it.
+ pkg.mDexOptPerformed.add(dexCodeInstructionSet);
+ }
}
// If we've gotten here, we're sure that no error occurred and that we haven't
@@ -248,8 +252,8 @@
if (libPkg != null && !done.contains(libName)) {
// TODO: Analyze and investigate if we (should) profile libraries.
// Currently this will do a full compilation of the library.
- performDexOptLI(libPkg, instructionSets, done,
- StorageManager.UUID_PRIVATE_INTERNAL, /*useProfiles*/ false);
+ performDexOptLI(libPkg, instructionSets, done, /*useProfiles*/ false,
+ /* extractOnly */ false);
}
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 26660df..d11da79 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -34,6 +34,7 @@
import static android.content.pm.PackageManager.INSTALL_EXTERNAL;
import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
import static android.content.pm.PackageManager.INSTALL_FAILED_CONFLICTING_PROVIDER;
+import static android.content.pm.PackageManager.INSTALL_FAILED_DEXOPT;
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;
@@ -627,6 +628,9 @@
// List of packages names to keep cached, even if they are uninstalled for all users
private List<String> mKeepUninstalledPackages;
+ private boolean mUseJitProfiles =
+ SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
+
private static class IFVerificationParams {
PackageParser.Package pkg;
boolean replacing;
@@ -3513,7 +3517,7 @@
/**
* Checks if the request is from the system or an app that has INTERACT_ACROSS_USERS
* or INTERACT_ACROSS_USERS_FULL permissions, if the userid is not for the caller.
- * @param checkShell TODO(yamasani):
+ * @param checkShell whether to prevent shell from access if there's a debugging restriction
* @param message the message to log on security exception
*/
void enforceCrossUserPermission(int callingUid, int userId, boolean requireFullPermission,
@@ -6624,6 +6628,23 @@
}
}
+ @Override
+ public void extractPackagesIfNeeded() {
+ enforceSystemOrRoot("Only the system can request package extraction");
+
+ // Extract pacakges only if profile-guided compilation is enabled because
+ // otherwise BackgroundDexOptService will not dexopt them later.
+ if (mUseJitProfiles) {
+ ArraySet<String> pkgs = getOptimizablePackages();
+ if (pkgs != null) {
+ for (String pkg : pkgs) {
+ performDexOpt(pkg, null /* instructionSet */, false /* useProfiles */,
+ true /* extractOnly */);
+ }
+ }
+ }
+ }
+
private ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) {
List<ResolveInfo> ris = null;
try {
@@ -6654,25 +6675,27 @@
// TODO: this is not used nor needed. Delete it.
@Override
public boolean performDexOptIfNeeded(String packageName, String instructionSet) {
- return performDexOptTraced(packageName, instructionSet, false);
+ return performDexOptTraced(packageName, instructionSet, false /* useProfiles */,
+ false /* extractOnly */);
}
- public boolean performDexOpt(String packageName, String instructionSet, boolean useProfiles) {
- return performDexOptTraced(packageName, instructionSet, useProfiles);
+ public boolean performDexOpt(String packageName, String instructionSet, boolean useProfiles,
+ boolean extractOnly) {
+ return performDexOptTraced(packageName, instructionSet, useProfiles, extractOnly);
}
private boolean performDexOptTraced(String packageName, String instructionSet,
- boolean useProfiles) {
+ boolean useProfiles, boolean extractOnly) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
try {
- return performDexOptInternal(packageName, instructionSet, useProfiles);
+ return performDexOptInternal(packageName, instructionSet, useProfiles, extractOnly);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
private boolean performDexOptInternal(String packageName, String instructionSet,
- boolean useProfiles) {
+ boolean useProfiles, boolean extractOnly) {
PackageParser.Package p;
final String targetInstructionSet;
synchronized (mPackages) {
@@ -6694,7 +6717,7 @@
synchronized (mInstallLock) {
final String[] instructionSets = new String[] { targetInstructionSet };
int result = mPackageDexOptimizer.performDexOpt(p, instructionSets,
- true /* inclDependencies */, p.volumeUuid, useProfiles);
+ true /* inclDependencies */, useProfiles, extractOnly);
return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
}
} finally {
@@ -6739,7 +6762,8 @@
// Whoever is calling forceDexOpt wants a fully compiled package.
// Don't use profiles since that may cause compilation to be skipped.
final int res = mPackageDexOptimizer.performDexOpt(pkg, instructionSets,
- true /* inclDependencies */, pkg.volumeUuid, false /* useProfiles */);
+ true /* inclDependencies */, false /* useProfiles */,
+ false /* extractOnly */);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
@@ -10178,16 +10202,25 @@
enforceCrossUserPermission(Binder.getCallingUid(), userId, true, true,
"setPackageSuspended for user " + userId);
+ // TODO: investigate and add more restrictions for suspending crucial packages.
+ if (isPackageDeviceAdmin(packageName, userId)) {
+ Slog.w(TAG, "Not suspending/un-suspending package \"" + packageName
+ + "\": has active device admin");
+ return false;
+ }
+
long callingId = Binder.clearCallingIdentity();
try {
boolean changed = false;
boolean success = false;
+ int appId = -1;
synchronized (mPackages) {
final PackageSetting pkgSetting = mSettings.mPackages.get(packageName);
if (pkgSetting != null) {
if (pkgSetting.getSuspended(userId) != suspended) {
pkgSetting.setSuspended(suspended, userId);
mSettings.writePackageRestrictionsLPr(userId);
+ appId = pkgSetting.appId;
changed = true;
}
success = true;
@@ -10195,10 +10228,11 @@
}
if (changed) {
- // TODO:
- // * maybe kill application if suspended
- // * hide suspended app from recents
sendPackagesSuspendedForUser(new String[]{packageName}, userId, suspended);
+ if (suspended) {
+ killApplication(packageName, UserHandle.getUid(userId, appId),
+ "suspending package");
+ }
}
return success;
} finally {
@@ -12981,6 +13015,24 @@
res.setError(INSTALL_FAILED_INTERNAL_ERROR, "Error deriving application ABI");
return;
}
+
+ // Extract package to save the VM unzipping the APK in memory during
+ // launch. Only do this if profile-guided compilation is enabled because
+ // otherwise BackgroundDexOptService will not dexopt the package later.
+ if (mUseJitProfiles) {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
+ // Do not run PackageDexOptimizer through the local performDexOpt
+ // method because `pkg` is not in `mPackages` yet.
+ int result = mPackageDexOptimizer.performDexOpt(pkg, null /* instructionSets */,
+ false /* inclDependencies */, false /* useProfiles */,
+ true /* extractOnly */);
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ if (result == PackageDexOptimizer.DEX_OPT_FAILED) {
+ String msg = "Extracking package failed for " + pkgName;
+ res.setError(INSTALL_FAILED_DEXOPT, msg);
+ return;
+ }
+ }
}
if (!args.doRename(res.returnCode, pkg, oldCodePath)) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 3248fe6..5b9f0b3 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -109,7 +109,7 @@
*/
public class UserManagerService extends IUserManager.Stub {
private static final String LOG_TAG = "UserManagerService";
- static final boolean DBG = true; // DO NOT SUBMIT WITH TRUE
+ static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
private static final boolean DBG_WITH_STACKTRACE = false; // DO NOT SUBMIT WITH TRUE
private static final String TAG_NAME = "name";
@@ -730,23 +730,15 @@
@Override
public void setUserIcon(int userId, Bitmap bitmap) {
checkManageUsersPermission("update users");
- long ident = Binder.clearCallingIdentity();
- try {
- synchronized (mPackagesLock) {
- UserData userData = getUserDataNoChecks(userId);
- if (userData == null || userData.info.partial) {
- Slog.w(LOG_TAG, "setUserIcon: unknown user #" + userId);
- return;
- }
- writeBitmapLP(userData.info, bitmap);
- writeUserLP(userData);
- }
- sendUserInfoChangedBroadcast(userId);
- } finally {
- Binder.restoreCallingIdentity(ident);
+ if (hasUserRestriction(UserManager.DISALLOW_SET_USER_ICON, userId)) {
+ Log.w(LOG_TAG, "Cannot set user icon. DISALLOW_SET_USER_ICON is enabled.");
+ return;
}
+ mLocalService.setUserIcon(userId, bitmap);
}
+
+
private void sendUserInfoChangedBroadcast(int userId) {
Intent changedIntent = new Intent(Intent.ACTION_USER_INFO_CHANGED);
changedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
@@ -2901,6 +2893,25 @@
mIsUserManaged.put(userId, isManaged);
}
}
+
+ @Override
+ public void setUserIcon(int userId, Bitmap bitmap) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mPackagesLock) {
+ UserData userData = getUserDataNoChecks(userId);
+ if (userData == null || userData.info.partial) {
+ Slog.w(LOG_TAG, "setUserIcon: unknown user #" + userId);
+ return;
+ }
+ writeBitmapLP(userData.info, bitmap);
+ writeUserLP(userData);
+ }
+ sendUserInfoChangedBroadcast(userId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
}
private class Shell extends ShellCommand {
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index f0ed790..87f505d 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -92,7 +92,8 @@
UserManager.DISALLOW_RECORD_AUDIO,
UserManager.DISALLOW_CAMERA,
UserManager.DISALLOW_RUN_IN_BACKGROUND,
- UserManager.DISALLOW_DATA_ROAMING
+ UserManager.DISALLOW_DATA_ROAMING,
+ UserManager.DISALLOW_SET_USER_ICON
);
/**
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 3b61817..0b1354a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3136,15 +3136,6 @@
hideRecentApps(true, false);
}
- // Handle keyboard layout switching.
- // TODO: Deprecate this behavior when we fully migrate to IME subtype-based layout rotation.
- if (down && repeatCount == 0 && keyCode == KeyEvent.KEYCODE_SPACE
- && ((metaState & KeyEvent.META_CTRL_MASK) != 0)) {
- int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
- mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction);
- return -1;
- }
-
// Handle input method switching.
if (down && repeatCount == 0
&& (keyCode == KeyEvent.KEYCODE_LANGUAGE_SWITCH
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index 9a55e7f..2deb0d5 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -34,7 +34,6 @@
*/
public class VrManagerService extends SystemService {
- public static final boolean DEBUG = false;
public static final String TAG = "VrManagerService";
private final Object mLock = new Object();
@@ -88,7 +87,8 @@
synchronized (mLock) {
if (mVrModeEnabled != enabled) {
mVrModeEnabled = enabled;
- if (DEBUG) Slog.d(TAG, "VR mode " + ((mVrModeEnabled) ? "enabled" : "disabled"));
+ // Log mode change event.
+ Slog.i(TAG, "VR mode " + ((mVrModeEnabled) ? "enabled" : "disabled"));
onVrModeChangedLocked();
}
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 87d0700..39983dd 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -92,6 +92,8 @@
import com.android.internal.R;
import com.android.server.EventLogTags;
+import libcore.io.IoUtils;
+
public class WallpaperManagerService extends IWallpaperManager.Stub {
static final String TAG = "WallpaperManagerService";
static final boolean DEBUG = false;
@@ -170,6 +172,12 @@
WallpaperData mLastWallpaper;
/**
+ * ID of the current wallpaper, changed every time anything sets a wallpaper.
+ * This is used for external detection of wallpaper update activity.
+ */
+ int mWallpaperId;
+
+ /**
* Name of the component used to display bitmap wallpapers from either the gallery or
* built-in wallpapers.
*/
@@ -205,6 +213,11 @@
*/
ComponentName nextWallpaperComponent;
+ /**
+ * The ID of this wallpaper
+ */
+ int wallpaperId;
+
WallpaperConnection connection;
long lastDiedTime;
boolean wallpaperUpdating;
@@ -227,6 +240,13 @@
}
}
+ int makeWallpaperIdLocked() {
+ do {
+ ++mWallpaperId;
+ } while (mWallpaperId == 0);
+ return mWallpaperId;
+ }
+
class WallpaperConnection extends IWallpaperConnection.Stub
implements ServiceConnection {
final WallpaperInfo mInfo;
@@ -333,7 +353,7 @@
public ParcelFileDescriptor setWallpaper(String name) {
synchronized (mLock) {
if (mWallpaper.connection == this) {
- return updateWallpaperBitmapLocked(name, mWallpaper);
+ return updateWallpaperBitmapLocked(name, mWallpaper, null);
}
return null;
}
@@ -848,18 +868,26 @@
}
}
- public ParcelFileDescriptor setWallpaper(String name, String callingPackage) {
+ @Override
+ public ParcelFileDescriptor setWallpaper(String name, String callingPackage, Bundle extras,
+ int which) {
checkPermission(android.Manifest.permission.SET_WALLPAPER);
+
+ if (which == 0) {
+ return null;
+ }
+
if (!isWallpaperSupported(callingPackage)) {
return null;
}
+
synchronized (mLock) {
if (DEBUG) Slog.v(TAG, "setWallpaper");
int userId = UserHandle.getCallingUserId();
WallpaperData wallpaper = getWallpaperSafeLocked(userId);
final long ident = Binder.clearCallingIdentity();
try {
- ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name, wallpaper);
+ ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name, wallpaper, extras);
if (pfd != null) {
wallpaper.imageWallpaperPending = true;
}
@@ -870,7 +898,8 @@
}
}
- ParcelFileDescriptor updateWallpaperBitmapLocked(String name, WallpaperData wallpaper) {
+ ParcelFileDescriptor updateWallpaperBitmapLocked(String name, WallpaperData wallpaper,
+ Bundle extras) {
if (name == null) name = "";
try {
File dir = getWallpaperDir(wallpaper.userId);
@@ -888,6 +917,14 @@
return null;
}
wallpaper.name = name;
+ wallpaper.wallpaperId = makeWallpaperIdLocked();
+ if (extras != null) {
+ extras.putInt(WallpaperManager.EXTRA_NEW_WALLPAPER_ID, wallpaper.wallpaperId);
+ }
+ if (DEBUG) {
+ Slog.v(TAG, "updateWallpaperBitmapLocked() : id=" + wallpaper.wallpaperId
+ + " name=" + name);
+ }
return fd;
} catch (FileNotFoundException e) {
Slog.w(TAG, "Error setting wallpaper", e);
@@ -1156,6 +1193,7 @@
out.startDocument(null, true);
out.startTag(null, "wp");
+ out.attribute(null, "id", Integer.toString(wallpaper.wallpaperId));
out.attribute(null, "width", Integer.toString(wallpaper.width));
out.attribute(null, "height", Integer.toString(wallpaper.height));
if (wallpaper.padding.left != 0) {
@@ -1184,13 +1222,7 @@
stream.close();
journal.commit();
} catch (IOException e) {
- try {
- if (stream != null) {
- stream.close();
- }
- } catch (IOException ex) {
- // Ignore
- }
+ IoUtils.closeQuietly(stream);
journal.rollback();
}
}
@@ -1259,6 +1291,16 @@
if (type == XmlPullParser.START_TAG) {
String tag = parser.getName();
if ("wp".equals(tag)) {
+ final String idString = parser.getAttributeValue(null, "id");
+ if (idString != null) {
+ final int id = wallpaper.wallpaperId = Integer.parseInt(idString);
+ if (id > mWallpaperId) {
+ mWallpaperId = id;
+ }
+ } else {
+ wallpaper.wallpaperId = makeWallpaperIdLocked();
+ }
+
wallpaper.width = Integer.parseInt(parser.getAttributeValue(null, "width"));
wallpaper.height = Integer.parseInt(parser
.getAttributeValue(null, "height"));
@@ -1301,19 +1343,21 @@
} catch (IndexOutOfBoundsException e) {
Slog.w(TAG, "failed parsing " + file + " " + e);
}
- try {
- if (stream != null) {
- stream.close();
- }
- } catch (IOException e) {
- // Ignore
- }
+ IoUtils.closeQuietly(stream);
if (!success) {
wallpaper.width = -1;
wallpaper.height = -1;
wallpaper.padding.set(0, 0, 0, 0);
wallpaper.name = "";
+ } else {
+ if (wallpaper.wallpaperId <= 0) {
+ wallpaper.wallpaperId = makeWallpaperIdLocked();
+ if (DEBUG) {
+ Slog.w(TAG, "Didn't set wallpaper id in loadSettingsLocked(" + userId
+ + "); now " + wallpaper.wallpaperId);
+ }
+ }
}
// We always want to have some reasonable width hint.
@@ -1346,6 +1390,7 @@
synchronized (mLock) {
loadSettingsLocked(0);
wallpaper = mWallpaperMap.get(0);
+ wallpaper.wallpaperId = makeWallpaperIdLocked(); // always bump id at restore
if (wallpaper.nextWallpaperComponent != null
&& !wallpaper.nextWallpaperComponent.equals(mImageWallpaper)) {
if (!bindWallpaperComponentLocked(wallpaper.nextWallpaperComponent, false, false,
@@ -1366,7 +1411,8 @@
if (DEBUG) Slog.v(TAG, "settingsRestored: attempting to restore named resource");
success = restoreNamedResourceLocked(wallpaper);
}
- if (DEBUG) Slog.v(TAG, "settingsRestored: success=" + success);
+ if (DEBUG) Slog.v(TAG, "settingsRestored: success=" + success
+ + " id=" + wallpaper.wallpaperId);
if (success) {
bindWallpaperComponentLocked(wallpaper.nextWallpaperComponent, false, false,
wallpaper, null);
@@ -1442,16 +1488,10 @@
} catch (IOException e) {
Slog.e(TAG, "IOException while restoring wallpaper ", e);
} finally {
- if (res != null) {
- try {
- res.close();
- } catch (IOException ex) {}
- }
+ IoUtils.closeQuietly(res);
if (fos != null) {
FileUtils.sync(fos);
- try {
- fos.close();
- } catch (IOException ex) {}
+ IoUtils.closeQuietly(fos);
}
}
}
@@ -1474,7 +1514,8 @@
pw.println("Current Wallpaper Service state:");
for (int i = 0; i < mWallpaperMap.size(); i++) {
WallpaperData wallpaper = mWallpaperMap.valueAt(i);
- pw.println(" User " + wallpaper.userId + ":");
+ pw.print(" User "); pw.print(wallpaper.userId);
+ pw.print(": id="); pw.println(wallpaper.wallpaperId);
pw.print(" mWidth=");
pw.print(wallpaper.width);
pw.print(" mHeight=");
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 0622fad..552af03 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -732,7 +732,8 @@
float scaleW = appWidth / thumbWidth;
float unscaledHeight = thumbHeight * scaleW;
getNextAppTransitionStartRect(taskId, mTmpRect);
- float unscaledStartY = mTmpRect.top - (unscaledHeight - thumbHeight) / 2f;
+ final float unscaledStartY = mTmpRect.top - (unscaledHeight - thumbHeight) / 2f;
+ final float toY = appRect.top + mNextAppTransitionInsets.top + -unscaledStartY;
if (mNextAppTransitionScaleUp) {
// Animation up from the thumbnail to the full screen
Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW,
@@ -744,7 +745,6 @@
alpha.setDuration(THUMBNAIL_APP_TRANSITION_ALPHA_DURATION);
final float toX = appRect.left + appRect.width() / 2 -
(mTmpRect.left + thumbWidth / 2);
- final float toY = appRect.top + mNextAppTransitionInsets.top + -unscaledStartY;
Animation translate = new TranslateAnimation(0, toX, 0, toY);
translate.setInterpolator(mTouchResponseInterpolator);
translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
@@ -764,8 +764,9 @@
Animation alpha = new AlphaAnimation(0f, 1f);
alpha.setInterpolator(mThumbnailFadeInInterpolator);
alpha.setDuration(THUMBNAIL_APP_TRANSITION_ALPHA_DURATION);
- Animation translate = new TranslateAnimation(0, 0, -unscaledStartY +
- mNextAppTransitionInsets.top, 0);
+ final float toX = appRect.left + appRect.width() / 2 -
+ (mTmpRect.left + thumbWidth / 2);
+ Animation translate = new TranslateAnimation(toX, 0, toY, 0);
translate.setInterpolator(mTouchResponseInterpolator);
translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 785f166..2732821 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -302,6 +302,13 @@
}
}
+ void markSurfacesExiting() {
+ for (int i = allAppWindows.size() - 1; i >= 0; i--) {
+ WindowState win = allAppWindows.get(i);
+ win.mExiting = true;
+ }
+ }
+
/**
* Checks whether we should save surfaces for this app.
*
@@ -329,15 +336,14 @@
if (!hasSavedSurface()) {
return;
}
-
- if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG_WM,
- "Restoring saved surfaces: " + this + ", allDrawn=" + allDrawn);
-
mAnimatingWithSavedSurface = true;
for (int i = windows.size() - 1; i >= 0; i--) {
WindowState ws = windows.get(i);
ws.restoreSavedSurface();
}
+ // Mark the app allDrawn since it must be allDrawn at the time
+ // it was first saved.
+ allDrawn = true;
}
void destroySavedSurfaces() {
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 7b0a8d7..c4653cf 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -28,6 +28,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
+import android.hardware.input.InputManager;
import android.os.IBinder;
import android.os.Message;
import android.os.Process;
@@ -36,6 +37,8 @@
import android.view.Display;
import android.view.DragEvent;
import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.PointerIcon;
import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowManager;
@@ -72,6 +75,7 @@
int mUid;
ClipData mData;
ClipDescription mDataDescription;
+ int mTouchSource;
boolean mDragResult;
float mOriginalAlpha;
float mOriginalX, mOriginalY;
@@ -342,6 +346,7 @@
private void cleanUpDragLw() {
broadcastDragEndedLw();
+ restorePointerIconLw();
// stop intercepting input
unregister();
@@ -360,8 +365,6 @@
mCurrentX = x;
mCurrentY = y;
- final int myPid = Process.myPid();
-
// Move the surface to the given touch
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
TAG_WM, ">>> OPEN TRANSACTION notifyMoveLw");
@@ -376,7 +379,10 @@
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
TAG_WM, "<<< CLOSE TRANSACTION notifyMoveLw");
}
+ notifyLocationLw(x, y);
+ }
+ void notifyLocationLw(float x, float y) {
// Tell the affected window
WindowState touchedWin = getTouchedWinAtPointLw(x, y);
if (touchedWin == null) {
@@ -392,6 +398,8 @@
}
}
try {
+ final int myPid = Process.myPid();
+
// have we dragged over a new window?
if ((touchedWin != mTargetWindow) && (mTargetWindow != null)) {
if (DEBUG_DRAG) {
@@ -399,7 +407,7 @@
}
// force DRAG_EXITED_EVENT if appropriate
DragEvent evt = obtainDragEvent(mTargetWindow, DragEvent.ACTION_DRAG_EXITED,
- x, y, null, null, null, null, false);
+ 0, 0, null, null, null, null, false);
mTargetWindow.mClient.dispatchDragEvent(evt);
if (myPid != mTargetWindow.mSession.mPid) {
evt.recycle();
@@ -573,4 +581,21 @@
set.start(); // Will start on the first call to getTransformation.
return set;
}
+
+ private boolean isFromSource(int source) {
+ return (mTouchSource & source) == source;
+ }
+
+ void overridePointerIconLw(int touchSource) {
+ mTouchSource = touchSource;
+ if (isFromSource(InputDevice.SOURCE_MOUSE)) {
+ InputManager.getInstance().setPointerIconShape(PointerIcon.STYLE_GRAB);
+ }
+ }
+
+ private void restorePointerIconLw() {
+ if (isFromSource(InputDevice.SOURCE_MOUSE)) {
+ InputManager.getInstance().setPointerIconShape(PointerIcon.STYLE_DEFAULT);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 8aaf430..a8d974f 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -283,7 +283,7 @@
}
public boolean performDrag(IWindow window, IBinder dragToken,
- float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+ int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
ClipData data) {
if (DEBUG_DRAG) {
Slog.d(TAG_WM, "perform drag: win=" + window + " data=" + data);
@@ -336,6 +336,7 @@
mService.mDragState.mData = data;
mService.mDragState.broadcastDragStartedLw(touchX, touchY);
+ mService.mDragState.overridePointerIconLw(touchSource);
// remember the thumb offsets for later
mService.mDragState.mThumbOffsetX = thumbCenterX;
@@ -357,6 +358,8 @@
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
TAG_WM, "<<< CLOSE TRANSACTION performDrag");
}
+
+ mService.mDragState.notifyLocationLw(touchX, touchY);
}
return true; // success!
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index a75f2c9..2833b35 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -299,8 +299,7 @@
final int orientation = mService.mCurConfiguration.orientation;
mService.mPolicy.getStableInsetsLw(rotation, displayWidth, displayHeight, outBounds);
final DividerSnapAlgorithm algorithm = new DividerSnapAlgorithm(
- mService.mContext.getResources(),
- 0 /* minFlingVelocityPxPerSecond */, displayWidth, displayHeight,
+ mService.mContext.getResources(), displayWidth, displayHeight,
dividerSize, orientation == Configuration.ORIENTATION_PORTRAIT, outBounds);
final SnapTarget target = algorithm.calculateNonDismissingSnapTarget(dividerPosition);
@@ -552,7 +551,6 @@
mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
mTmpRect2);
final int position = new DividerSnapAlgorithm(mService.mContext.getResources(),
- 0 /* minFlingVelocityPxPerSecond */,
di.logicalWidth,
di.logicalHeight,
dockDividerWidth,
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 62d4f36..722c3b4d 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -281,8 +281,7 @@
final int flags = win.mAttrs.flags;
boolean canBeForceHidden = mPolicy.canBeForceHidden(win, win.mAttrs);
boolean shouldBeForceHidden = shouldForceHide(win);
- if (winAnimator.mSurfaceController != null
- && winAnimator.mSurfaceController.hasSurface()) {
+ if (winAnimator.hasSurface()) {
final boolean wasAnimating = winAnimator.mWasAnimating;
final boolean nowAnimating = winAnimator.stepAnimationLocked(mCurrentTime);
winAnimator.mWasAnimating = nowAnimating;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4b0015e..1021411 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2679,20 +2679,11 @@
} else {
winAnimator.mEnterAnimationPending = false;
winAnimator.mEnteringAnimation = false;
- if (winAnimator.mSurfaceController != null &&
- winAnimator.mSurfaceController.hasSurface()) {
+ if (winAnimator.hasSurface() && !win.mExiting) {
if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Relayout invis " + win
+ ": mExiting=" + win.mExiting);
- // If we are using a saved surface to do enter animation, just let the
- // animation run and don't destroy the surface. This could happen when
- // the app sets visibility to invisible for the first time after resume,
- // or when the user exits immediately after a resume. In both cases, we
- // don't want to destroy the saved surface.
// If we are not currently running the exit animation, we
// need to see about starting one.
- final boolean notExitingOrAnimating =
- !win.mExiting && !win.isAnimatingWithSavedSurface();
- result |= notExitingOrAnimating ? RELAYOUT_RES_SURFACE_CHANGED : 0;
// We don't want to animate visibility of windows which are pending
// replacement. In the case of activity relaunch child windows
// could request visibility changes as they are detached from the main
@@ -2700,11 +2691,11 @@
// these visibility changes though, we would cause a visual glitch
// hiding the window before it's replacement was available.
// So we just do nothing on our side.
- if (notExitingOrAnimating && win.mWillReplaceWindow == false) {
- focusMayChange = tryStartingAnimation(win, winAnimator, isDefaultDisplay,
- focusMayChange);
-
+ if (!win.mWillReplaceWindow) {
+ focusMayChange = tryStartExitingAnimation(
+ win, winAnimator, isDefaultDisplay, focusMayChange);
}
+ result |= RELAYOUT_RES_SURFACE_CHANGED;
}
outSurface.release();
@@ -2787,7 +2778,7 @@
return result;
}
- private boolean tryStartingAnimation(WindowState win, WindowStateAnimator winAnimator,
+ private boolean tryStartExitingAnimation(WindowState win, WindowStateAnimator winAnimator,
boolean isDefaultDisplay, boolean focusMayChange) {
// Try starting an animation; if there isn't one, we
// can destroy the surface right away.
@@ -4240,6 +4231,7 @@
}
}
} else {
+ wtoken.markSurfacesExiting();
mClosingApps.add(wtoken);
wtoken.mEnteringAnimation = false;
}
@@ -5316,12 +5308,6 @@
// Called by window manager policy. Not exposed externally.
@Override
- public void switchKeyboardLayout(int deviceId, int direction) {
- mInputManager.switchKeyboardLayout(deviceId, direction);
- }
-
- // Called by window manager policy. Not exposed externally.
- @Override
public void switchInputMethod(boolean forwardDirection) {
final InputMethodManagerInternal inputMethodManagerInternal =
LocalServices.getService(InputMethodManagerInternal.class);
@@ -10278,10 +10264,6 @@
return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, displayMetrics);
}
- void scheduleSurfaceDestroy(WindowState win) {
- mDestroySurface.add(win);
- }
-
@Override
public void registerDockedStackListener(IDockedStackListener listener) {
if (!checkCallingPermission(android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS,
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index e8a02b0..dca7735 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1768,45 +1768,69 @@
return mAppToken != null && mAppToken.mAnimatingWithSavedSurface;
}
- // Returns true if the surface is saved.
- boolean destroyOrSaveSurface() {
- Task task = getTask();
+ private boolean shouldSaveSurface() {
if (ActivityManager.isLowRamDeviceStatic()) {
// Don't save surfaces on Svelte devices.
- mSurfaceSaved = false;
- } else if (task == null || task.inHomeStack()
- || task.getTopVisibleAppToken() != mAppToken) {
+ return false;
+ }
+
+ if (isChildWindow()) {
+ return false;
+ }
+
+ Task task = getTask();
+ if (task == null || task.inHomeStack()) {
// Don't save surfaces for home stack apps. These usually resume and draw
// first frame very fast. Saving surfaces are mostly a waste of memory.
- // Don't save if the window is not the topmost window.
- mSurfaceSaved = false;
- } else if (isChildWindow()) {
- mSurfaceSaved = false;
- } else {
- mSurfaceSaved = mAppToken.shouldSaveSurface();
+ return false;
}
- if (mSurfaceSaved == false) {
+
+ final AppWindowToken taskTop = task.getTopVisibleAppToken();
+ if (taskTop != null && taskTop != mAppToken) {
+ // Don't save if the window is not the topmost window.
+ return false;
+ }
+
+ return mAppToken.shouldSaveSurface();
+ }
+
+ void destroyOrSaveSurface() {
+ mSurfaceSaved = shouldSaveSurface();
+ if (mSurfaceSaved) {
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
+ Slog.v(TAG, "Saving surface: " + this);
+ }
+
+ mWinAnimator.hide("saved surface");
+ mWinAnimator.mDrawState = WindowStateAnimator.NO_SURFACE;
+ setHasSurface(false);
+ } else {
mWinAnimator.destroySurfaceLocked();
}
- return mSurfaceSaved;
}
public void destroySavedSurface() {
- if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, "Destroying saved surface: " + this);
if (mSurfaceSaved) {
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
+ Slog.v(TAG, "Destroying saved surface: " + this);
+ }
mWinAnimator.destroySurfaceLocked();
}
}
+ public void restoreSavedSurface() {
+ mSurfaceSaved = false;
+ setHasSurface(true);
+ mWinAnimator.mDrawState = WindowStateAnimator.READY_TO_SHOW;
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
+ Slog.v(TAG, "Restoring saved surface: " + this);
+ }
+ }
+
public boolean hasSavedSurface() {
return mSurfaceSaved;
}
- public void restoreSavedSurface() {
- mSurfaceSaved = false;
- mWinAnimator.mDrawState = WindowStateAnimator.READY_TO_SHOW;
- }
-
@Override
public boolean isDefaultDisplay() {
final DisplayContent displayContent = getDisplayContent();
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index da4e191..cffcc5d 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -468,7 +468,7 @@
if (WindowManagerService.localLOGV) Slog.v(
TAG, "Exit animation finished in " + this
+ ": remove=" + mWin.mRemoveOnExit);
- if (mSurfaceController != null && mSurfaceController.hasSurface()) {
+ if (hasSurface()) {
mService.mDestroySurface.add(mWin);
mWin.mDestroying = true;
hide("finishExit");
@@ -734,6 +734,11 @@
mTmpSize.bottom += scale * (attrs.surfaceInsets.top + attrs.surfaceInsets.bottom);
}
+ boolean hasSurface() {
+ return !mWin.mSurfaceSaved
+ && mSurfaceController != null && mSurfaceController.hasSurface();
+ }
+
void destroySurfaceLocked() {
final AppWindowToken wtoken = mWin.mAppToken;
if (wtoken != null) {
@@ -1229,7 +1234,7 @@
void prepareSurfaceLocked(final boolean recoveringMemory) {
final WindowState w = mWin;
- if (mSurfaceController == null || !mSurfaceController.hasSurface()) {
+ if (!hasSurface()) {
if (w.mOrientationChanging) {
if (DEBUG_ORIENTATION) {
Slog.v(TAG, "Orientation change skips hidden " + w);
@@ -1311,7 +1316,7 @@
w.mOrientationChanging = false;
}
}
- if (mSurfaceController != null && mSurfaceController.hasSurface()) {
+ if (hasSurface()) {
w.mToken.hasVisible = true;
}
} else {
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index ab6667a..761d6e9 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -27,6 +27,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerService.H.*;
import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD;
import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
@@ -67,6 +68,7 @@
* surfaces according to these frames. Z layer is still assigned withing WindowManagerService.
*/
class WindowSurfacePlacer {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowSurfacePlacer" : TAG_WM;
private final WindowManagerService mService;
private final WallpaperController mWallpaperControllerLocked;
@@ -160,7 +162,7 @@
if (DEBUG) {
throw new RuntimeException("Recursive call!");
}
- Slog.w(TAG_WM, "performLayoutAndPlaceSurfacesLocked called while in layout. Callers="
+ Slog.w(TAG, "performLayoutAndPlaceSurfacesLocked called while in layout. Callers="
+ Debug.getCallers(3));
return;
}
@@ -186,11 +188,10 @@
// Wait a little bit for things to settle down, and off we go.
while (!mService.mForceRemoves.isEmpty()) {
WindowState ws = mService.mForceRemoves.remove(0);
- Slog.i(TAG_WM, "Force removing: " + ws);
+ Slog.i(TAG, "Force removing: " + ws);
mService.removeWindowInnerLocked(ws);
}
- Slog.w(TAG_WM,
- "Due to memory failure, waiting a bit for next layout");
+ Slog.w(TAG, "Due to memory failure, waiting a bit for next layout");
Object tmp = new Object();
synchronized (tmp) {
try {
@@ -209,7 +210,7 @@
if (++mLayoutRepeatCount < 6) {
requestTraversal();
} else {
- Slog.e(TAG_WM, "Performed 6 layouts in a row. Skipping");
+ Slog.e(TAG, "Performed 6 layouts in a row. Skipping");
mLayoutRepeatCount = 0;
}
} else {
@@ -222,7 +223,7 @@
}
} catch (RuntimeException e) {
mInLayout = false;
- Slog.wtf(TAG_WM, "Unhandled exception while laying out windows", e);
+ Slog.wtf(TAG, "Unhandled exception while laying out windows", e);
}
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
@@ -230,18 +231,15 @@
void debugLayoutRepeats(final String msg, int pendingLayoutChanges) {
if (mLayoutRepeatCount >= LAYOUT_REPEAT_THRESHOLD) {
- Slog.v(TAG_WM, "Layouts looping: " + msg +
+ Slog.v(TAG, "Layouts looping: " + msg +
", mPendingLayoutChanges = 0x" + Integer.toHexString(pendingLayoutChanges));
}
}
// "Something has changed! Let's make it correct now."
private void performSurfacePlacementInner(boolean recoveringMemory) {
- if (DEBUG_WINDOW_TRACE) {
- Slog.v(TAG_WM,
- "performSurfacePlacementInner: entry. Called by "
- + Debug.getCallers(3));
- }
+ if (DEBUG_WINDOW_TRACE) Slog.v(TAG, "performSurfacePlacementInner: entry. Called by "
+ + Debug.getCallers(3));
int i;
boolean updateInputWindowsNeeded = false;
@@ -283,16 +281,16 @@
final int defaultDw = defaultInfo.logicalWidth;
final int defaultDh = defaultInfo.logicalHeight;
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
+ if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces");
SurfaceControl.openTransaction();
try {
applySurfaceChangesTransaction(recoveringMemory, numDisplays, defaultDw, defaultDh);
} catch (RuntimeException e) {
- Slog.wtf(TAG_WM, "Unhandled exception in Window Manager", e);
+ Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {
SurfaceControl.closeTransaction();
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
+ if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
"<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
}
@@ -339,7 +337,7 @@
if (mWallpaperMayChange) {
if (DEBUG_WALLPAPER_LIGHT)
- Slog.v(TAG_WM, "Wallpaper may change! Adjusting");
+ Slog.v(TAG, "Wallpaper may change! Adjusting");
defaultDisplay.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("WallpaperMayChange",
defaultDisplay.pendingLayoutChanges);
@@ -374,10 +372,8 @@
mService.mResizingWindows.remove(i);
}
- if (DEBUG_ORIENTATION && mService.mDisplayFrozen)
- Slog.v(TAG_WM,
- "With display frozen, orientationChangeComplete="
- + mOrientationChangeComplete);
+ if (DEBUG_ORIENTATION && mService.mDisplayFrozen) Slog.v(TAG,
+ "With display frozen, orientationChangeComplete=" + mOrientationChangeComplete);
if (mOrientationChangeComplete) {
if (mService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
@@ -435,9 +431,8 @@
// soon as their animations are complete
token.mAppAnimator.clearAnimation();
token.mAppAnimator.animating = false;
- if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT)
- Slog.v(TAG_WM,
- "performLayout: App token exiting now removed" + token);
+ if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
+ "performLayout: App token exiting now removed" + token);
token.removeAppFromTaskLocked();
}
}
@@ -482,7 +477,7 @@
|| Settings.Global.getInt(mService.mContext.getContentResolver(),
Settings.Global.THEATER_MODE_ON, 0) == 0) {
if (DEBUG_VISIBILITY || DEBUG_POWER) {
- Slog.v(TAG_WM, "Turning screen on after layout!");
+ Slog.v(TAG, "Turning screen on after layout!");
}
mService.mPowerManager.wakeUp(SystemClock.uptimeMillis(),
"android.server.wm:TURN_ON");
@@ -491,8 +486,7 @@
}
if (mUpdateRotation) {
- if (DEBUG_ORIENTATION) Slog.d(TAG_WM,
- "Performing post-rotate rotation");
+ if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-rotate rotation");
if (mService.updateRotationUncheckedLocked(false)) {
mService.mH.sendEmptyMessage(SEND_NEW_CONFIGURATION);
} else {
@@ -545,7 +539,7 @@
mService.scheduleAnimationLocked();
- if (DEBUG_WINDOW_TRACE) Slog.e(TAG_WM,
+ if (DEBUG_WINDOW_TRACE) Slog.e(TAG,
"performSurfacePlacementInner exit: animating=" + mService.mAnimator.isAnimating());
}
@@ -589,7 +583,7 @@
do {
repeats++;
if (repeats > 6) {
- Slog.w(TAG_WM, "Animation repeat aborted after too many iterations");
+ Slog.w(TAG, "Animation repeat aborted after too many iterations");
displayContent.layoutNeeded = false;
break;
}
@@ -605,7 +599,7 @@
if (isDefaultDisplay
&& (displayContent.pendingLayoutChanges & FINISH_LAYOUT_REDO_CONFIG) != 0) {
- if (DEBUG_LAYOUT) Slog.v(TAG_WM, "Computing new config from layout");
+ if (DEBUG_LAYOUT) Slog.v(TAG, "Computing new config from layout");
if (mService.updateOrientationFromAppTokensLocked(true)) {
displayContent.layoutNeeded = true;
mService.mH.sendEmptyMessage(SEND_NEW_CONFIGURATION);
@@ -621,7 +615,7 @@
performLayoutLockedInner(displayContent, repeats == 1,
false /* updateInputWindows */);
} else {
- Slog.w(TAG_WM, "Layout repeat skipped after too many iterations");
+ Slog.w(TAG, "Layout repeat skipped after too many iterations");
}
// FIRST AND ONE HALF LOOP: Make WindowManagerPolicy think
@@ -707,7 +701,7 @@
}
}
- //Slog.i(TAG_WM, "Window " + this + " clearing mContentChanged - done placing");
+ //Slog.i(TAG, "Window " + this + " clearing mContentChanged - done placing");
w.mContentChanged = false;
w.mMovedByResize = false;
@@ -730,7 +724,7 @@
}
if ((w.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
if (DEBUG_WALLPAPER_LIGHT)
- Slog.v(TAG_WM, "First draw done in potential wallpaper target " + w);
+ Slog.v(TAG, "First draw done in potential wallpaper target " + w);
mWallpaperMayChange = true;
displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
if (DEBUG_LAYOUT_REPEATS) {
@@ -753,7 +747,7 @@
final AppWindowToken atoken = w.mAppToken;
if (DEBUG_STARTING_WINDOW && atoken != null && w == atoken.startingWindow) {
- Slog.d(TAG_WM, "updateWindows: starting " + w
+ Slog.d(TAG, "updateWindows: starting " + w
+ " isOnScreen=" + w.isOnScreen() + " allDrawn=" + atoken.allDrawn
+ " freezingScreen=" + atoken.mAppAnimator.freezingScreen);
}
@@ -767,11 +761,11 @@
|| winAnimator.mAttrType == TYPE_BASE_APPLICATION)
&& !w.mExiting && !w.mDestroying) {
if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) {
- Slog.v(TAG_WM, "Eval win " + w + ": isDrawn="
+ Slog.v(TAG, "Eval win " + w + ": isDrawn="
+ w.isDrawnLw()
+ ", isAnimating=" + winAnimator.isAnimating());
if (!w.isDrawnLw()) {
- Slog.v(TAG_WM, "Not displayed: s="
+ Slog.v(TAG, "Not displayed: s="
+ winAnimator.mSurfaceController
+ " pv=" + w.mPolicyVisibility
+ " mDrawState=" + winAnimator.drawStateToString()
@@ -786,7 +780,7 @@
if (w.isDrawnLw()) {
atoken.numDrawnWindows++;
if (DEBUG_VISIBILITY || DEBUG_ORIENTATION)
- Slog.v(TAG_WM, "tokenMayBeDrawn: " + atoken
+ Slog.v(TAG, "tokenMayBeDrawn: " + atoken
+ " freezingScreen="
+ atoken.mAppAnimator.freezingScreen
+ " mAppFreezing=" + w.mAppFreezing);
@@ -854,8 +848,8 @@
int i;
if (DEBUG_LAYOUT) {
- Slog.v(TAG_WM, "-------------------------------------");
- Slog.v(TAG_WM, "performLayout: needed="
+ Slog.v(TAG, "-------------------------------------");
+ Slog.v(TAG, "performLayout: needed="
+ displayContent.layoutNeeded + " dw=" + dw + " dh=" + dh);
}
@@ -889,18 +883,18 @@
|| win.isGoneForLayoutLw();
if (DEBUG_LAYOUT && !win.mLayoutAttached) {
- Slog.v(TAG_WM, "1ST PASS " + win
+ Slog.v(TAG, "1ST PASS " + win
+ ": gone=" + gone + " mHaveFrame=" + win.mHaveFrame
+ " mLayoutAttached=" + win.mLayoutAttached
+ " screen changed=" + win.isConfigChanged());
final AppWindowToken atoken = win.mAppToken;
- if (gone) Slog.v(TAG_WM, " GONE: mViewVisibility="
+ if (gone) Slog.v(TAG, " GONE: mViewVisibility="
+ win.mViewVisibility + " mRelayoutCalled="
+ win.mRelayoutCalled + " hidden="
+ win.mRootToken.hidden + " hiddenRequested="
+ (atoken != null && atoken.hiddenRequested)
+ " mAttachedHidden=" + win.mAttachedHidden);
- else Slog.v(TAG_WM, " VIS: mViewVisibility="
+ else Slog.v(TAG, " VIS: mViewVisibility="
+ win.mViewVisibility + " mRelayoutCalled="
+ win.mRelayoutCalled + " hidden="
+ win.mRootToken.hidden + " hiddenRequested="
@@ -920,7 +914,7 @@
win.mAppToken.layoutConfigChanges)))) {
if (!win.mLayoutAttached) {
if (initial) {
- //Slog.i(TAG_WM, "Window " + this + " clearing mContentChanged - initial");
+ //Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
win.mContentChanged = false;
}
if (win.mAttrs.type == TYPE_DREAM) {
@@ -940,7 +934,7 @@
displayContent.mDimLayerController.updateDimLayer(task);
}
- if (DEBUG_LAYOUT) Slog.v(TAG_WM,
+ if (DEBUG_LAYOUT) Slog.v(TAG,
" LAYOUT: mFrame="
+ win.mFrame + " mContainingFrame="
+ win.mContainingFrame + " mDisplayFrame="
@@ -961,7 +955,7 @@
final WindowState win = windows.get(i);
if (win.mLayoutAttached) {
- if (DEBUG_LAYOUT) Slog.v(TAG_WM,
+ if (DEBUG_LAYOUT) Slog.v(TAG,
"2ND PASS " + win + " mHaveFrame=" + win.mHaveFrame + " mViewVisibility="
+ win.mViewVisibility + " mRelayoutCalled=" + win.mRelayoutCalled);
// If this view is GONE, then skip it -- keep the current
@@ -975,14 +969,14 @@
if ((win.mViewVisibility != View.GONE && win.mRelayoutCalled)
|| !win.mHaveFrame || win.mLayoutNeeded) {
if (initial) {
- //Slog.i(TAG_WM, "Window " + this + " clearing mContentChanged - initial");
+ //Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
win.mContentChanged = false;
}
win.mLayoutNeeded = false;
win.prelayout();
mService.mPolicy.layoutWindowLw(win, win.mAttachedWindow);
win.mLayoutSeq = seq;
- if (DEBUG_LAYOUT) Slog.v(TAG_WM,
+ if (DEBUG_LAYOUT) Slog.v(TAG,
" LAYOUT: mFrame=" + win.mFrame + " mContainingFrame="
+ win.mContainingFrame + " mDisplayFrame=" + win.mDisplayFrame);
}
@@ -1013,7 +1007,7 @@
if (!transitionGoodToGo(appsCount)) {
return 0;
}
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "**** GOOD TO GO");
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO");
int transit = mService.mAppTransition.getAppTransition();
if (mService.mSkipAppTransitionAnimation) {
transit = AppTransition.TRANSIT_UNSET;
@@ -1105,7 +1099,7 @@
// example, when this transition is being done behind
// the lock screen.
if (!mService.mPolicy.allowAppAnimationsLw()) {
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"Animations disallowed by keyguard or dream.");
animLp = null;
}
@@ -1154,13 +1148,16 @@
for (int i = 0; i < appsCount; i++) {
AppWindowToken wtoken = mService.mOpeningApps.valueAt(i);
final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Now opening app" + wtoken);
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now opening app" + wtoken);
if (!appAnimator.usingTransferredAnimation) {
appAnimator.clearThumbnail();
appAnimator.animation = null;
}
wtoken.inPendingTransaction = false;
+
+ wtoken.restoreSavedSurfaces();
+
if (!mService.setTokenVisibilityLocked(
wtoken, animLp, true, transit, false, voiceInteraction)){
// This token isn't going to be animating. Add it to the list of tokens to
@@ -1176,14 +1173,14 @@
for (int j = 0; j < windowsCount; j++) {
appAnimator.mAllAppWinAnimators.add(wtoken.allAppWindows.get(j).mWinAnimator);
}
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
+ if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
">>> OPEN TRANSACTION handleAppTransitionReadyLocked()");
SurfaceControl.openTransaction();
try {
mService.mAnimator.orAnimating(appAnimator.showAllWindowsLocked());
} finally {
SurfaceControl.closeTransaction();
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
+ if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
"<<< CLOSE TRANSACTION handleAppTransitionReadyLocked()");
}
mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
@@ -1217,8 +1214,6 @@
if (mService.mAppTransition.isNextAppTransitionThumbnailUp()) {
createThumbnailAppAnimator(transit, wtoken, topOpeningLayer, topClosingLayer);
}
-
- wtoken.restoreSavedSurfaces();
}
return topOpeningApp;
}
@@ -1230,7 +1225,7 @@
for (int i = 0; i < appsCount; i++) {
AppWindowToken wtoken = mService.mClosingApps.valueAt(i);
final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Now closing app " + wtoken);
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now closing app " + wtoken);
appAnimator.clearThumbnail();
appAnimator.animation = null;
wtoken.inPendingTransaction = false;
@@ -1269,14 +1264,14 @@
}
private boolean transitionGoodToGo(int appsCount) {
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"Checking " + appsCount + " opening apps (frozen="
+ mService.mDisplayFrozen + " timeout="
+ mService.mAppTransition.isTimeout() + ")...");
if (!mService.mAppTransition.isTimeout()) {
for (int i = 0; i < appsCount; i++) {
AppWindowToken wtoken = mService.mOpeningApps.valueAt(i);
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"Check opening app=" + wtoken + ": allDrawn="
+ wtoken.allDrawn + " startingDisplayed="
+ wtoken.startingDisplayed + " startingMoved="
@@ -1292,7 +1287,7 @@
// We also need to wait for the specs to be fetched, if needed.
if (mService.mAppTransition.isFetchingAppTransitionsSpecs()) {
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "isFetchingAppTransitionSpecs=true");
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "isFetchingAppTransitionSpecs=true");
return false;
}
@@ -1313,7 +1308,7 @@
? null : wallpaperTarget;
final ArraySet<AppWindowToken> openingApps = mService.mOpeningApps;
final ArraySet<AppWindowToken> closingApps = mService.mClosingApps;
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"New wallpaper target=" + wallpaperTarget
+ ", oldWallpaper=" + oldWallpaper
+ ", lower target=" + lowerWallpaperTarget
@@ -1323,7 +1318,7 @@
mService.mAnimateWallpaperWithTarget = false;
if (closingAppHasWallpaper && openingAppHasWallpaper) {
if (DEBUG_APP_TRANSITIONS)
- Slog.v(TAG_WM, "Wallpaper animation!");
+ Slog.v(TAG, "Wallpaper animation!");
switch (transit) {
case AppTransition.TRANSIT_ACTIVITY_OPEN:
case AppTransition.TRANSIT_TASK_OPEN:
@@ -1336,14 +1331,14 @@
transit = AppTransition.TRANSIT_WALLPAPER_INTRA_CLOSE;
break;
}
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"New transit: " + AppTransition.appTransitionToString(transit));
} else if (oldWallpaper != null && !mService.mOpeningApps.isEmpty()
&& !openingApps.contains(oldWallpaper.mAppToken)
&& closingApps.contains(oldWallpaper.mAppToken)) {
// We are transitioning from an activity with a wallpaper to one without.
transit = AppTransition.TRANSIT_WALLPAPER_CLOSE;
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"New transit away from wallpaper: "
+ AppTransition.appTransitionToString(transit));
} else if (wallpaperTarget != null && wallpaperTarget.isVisibleLw() &&
@@ -1351,7 +1346,7 @@
// We are transitioning from an activity without
// a wallpaper to now showing the wallpaper
transit = AppTransition.TRANSIT_WALLPAPER_OPEN;
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"New transit into wallpaper: "
+ AppTransition.appTransitionToString(transit));
} else {
@@ -1446,7 +1441,7 @@
int numInteresting = wtoken.numInterestingWindows;
if (numInteresting > 0 && wtoken.numDrawnWindows >= numInteresting) {
if (DEBUG_VISIBILITY)
- Slog.v(TAG_WM, "allDrawn: " + wtoken
+ Slog.v(TAG, "allDrawn: " + wtoken
+ " interesting=" + numInteresting
+ " drawn=" + wtoken.numDrawnWindows);
wtoken.allDrawn = true;
@@ -1475,7 +1470,7 @@
final AppWindowToken wtoken = win.mAppToken;
final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
if (DEBUG_APP_TRANSITIONS)
- Slog.v(TAG_WM, "Now animating app in place " + wtoken);
+ Slog.v(TAG, "Now animating app in place " + wtoken);
appAnimator.clearThumbnail();
appAnimator.animation = null;
mService.updateTokenInPlaceLocked(wtoken, transit);
@@ -1501,7 +1496,7 @@
final int taskId = appToken.mTask.mTaskId;
Bitmap thumbnailHeader = mService.mAppTransition.getAppTransitionThumbnailHeader(taskId);
if (thumbnailHeader == null || thumbnailHeader.getConfig() == Bitmap.Config.ALPHA_8) {
- if (DEBUG_APP_TRANSITIONS) Slog.d(TAG_WM, "No thumbnail header bitmap for: " + taskId);
+ if (DEBUG_APP_TRANSITIONS) Slog.d(TAG, "No thumbnail header bitmap for: " + taskId);
return;
}
// This thumbnail animation is very special, we need to have
@@ -1519,7 +1514,7 @@
PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
surfaceControl.setLayerStack(display.getLayerStack());
if (SHOW_TRANSACTIONS) {
- Slog.i(TAG_WM, " THUMBNAIL " + surfaceControl + ": CREATE");
+ Slog.i(TAG, " THUMBNAIL " + surfaceControl + ": CREATE");
}
// Draw the thumbnail onto the surface
@@ -1562,7 +1557,7 @@
openingAppAnimator.thumbnailX = mTmpStartRect.left;
openingAppAnimator.thumbnailY = mTmpStartRect.top;
} catch (Surface.OutOfResourcesException e) {
- Slog.e(TAG_WM, "Can't allocate thumbnail/Canvas surface w="
+ Slog.e(TAG, "Can't allocate thumbnail/Canvas surface w="
+ dirty.width() + " h=" + dirty.height(), e);
openingAppAnimator.clearThumbnail();
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 1ada0ac..f68a299 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -67,6 +67,7 @@
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.graphics.Bitmap;
+import android.graphics.Color;
import android.media.AudioManager;
import android.media.IAudioService;
import android.net.ConnectivityManager;
@@ -487,6 +488,7 @@
private static final String TAG_SHORT_SUPPORT_MESSAGE = "short-support-message";
private static final String TAG_LONG_SUPPORT_MESSAGE = "long-support-message";
private static final String TAG_PARENT_ADMIN = "parent-admin";
+ private static final String TAG_ORGANIZATION_COLOR = "organization-color";
final DeviceAdminInfo info;
@@ -580,6 +582,10 @@
String shortSupportMessage = null;
String longSupportMessage = null;
+ // Background color of confirm credentials screen. Default: gray.
+ static final int DEF_ORGANIZATION_COLOR = Color.GRAY;
+ int organizationColor = DEF_ORGANIZATION_COLOR;
+
ActiveAdmin(DeviceAdminInfo _info, boolean parent) {
info = _info;
isParent = parent;
@@ -787,6 +793,11 @@
parentAdmin.writeToXml(out);
out.endTag(null, TAG_PARENT_ADMIN);
}
+ if (organizationColor != DEF_ORGANIZATION_COLOR) {
+ out.startTag(null, TAG_ORGANIZATION_COLOR);
+ out.attribute(null, ATTR_VALUE, Integer.toString(organizationColor));
+ out.endTag(null, TAG_ORGANIZATION_COLOR);
+ }
}
void writePackageListToXml(XmlSerializer out, String outerTag,
@@ -920,6 +931,9 @@
} else if (TAG_PARENT_ADMIN.equals(tag)) {
parentAdmin = new ActiveAdmin(info, /* parent */ true);
parentAdmin.readFromXml(parser);
+ } else if (TAG_ORGANIZATION_COLOR.equals(tag)) {
+ organizationColor = Integer.parseInt(
+ parser.getAttributeValue(null, ATTR_VALUE));
} else {
Slog.w(LOG_TAG, "Unknown admin tag: " + tag);
XmlUtils.skipCurrentTag(parser);
@@ -2237,6 +2251,7 @@
case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
+ case DevicePolicyManager.PASSWORD_QUALITY_MANAGED:
return;
}
throw new IllegalArgumentException("Invalid quality constant: 0x"
@@ -3467,6 +3482,9 @@
}
}
quality = getPasswordQuality(null, userHandle, false);
+ if (quality == DevicePolicyManager.PASSWORD_QUALITY_MANAGED) {
+ quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+ }
if (quality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
int realQuality = LockPatternUtils.computePasswordQuality(password);
if (realQuality < quality
@@ -7028,7 +7046,7 @@
int userId = UserHandle.getCallingUserId();
long id = mInjector.binderClearCallingIdentity();
try {
- mUserManager.setUserIcon(userId, icon);
+ mUserManagerInternal.setUserIcon(userId, icon);
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
@@ -7681,4 +7699,46 @@
}
return null;
}
+
+ @Override
+ public void setOrganizationColor(@NonNull ComponentName who, int color) {
+ final int userHandle = mInjector.userHandleGetCallingUserId();
+ if (!mHasFeature || !isManagedProfile(userHandle)) {
+ return;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ synchronized (this) {
+ ActiveAdmin admin = getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ admin.organizationColor = color;
+ saveSettingsLocked(userHandle);
+ }
+ }
+
+ @Override
+ public int getOrganizationColor(@NonNull ComponentName who) {
+ final int userHandle = mInjector.userHandleGetCallingUserId();
+ if (!mHasFeature || !isManagedProfile(userHandle)) {
+ return ActiveAdmin.DEF_ORGANIZATION_COLOR;
+ }
+ synchronized (this) {
+ ActiveAdmin admin = getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ return admin.organizationColor;
+ }
+ }
+
+ @Override
+ public int getOrganizationColorForUser(int userHandle) {
+ if (!mHasFeature || !isManagedProfile(userHandle)) {
+ return ActiveAdmin.DEF_ORGANIZATION_COLOR;
+ }
+ enforceCrossUsersPermission(userHandle);
+ synchronized (this) {
+ ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userHandle);
+ return (profileOwner != null)
+ ? profileOwner.organizationColor
+ : ActiveAdmin.DEF_ORGANIZATION_COLOR;
+ }
+ }
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b5330e4..c186a12 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -602,7 +602,6 @@
StatusBarManagerService statusBar = null;
INotificationManager notification = null;
- InputMethodManagerService imm = null;
WallpaperManagerService wallpaper = null;
LocationManagerService location = null;
CountryDetectorService countryDetector = null;
@@ -613,14 +612,7 @@
// Bring up services needed for UI.
if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
- traceBeginAndSlog("StartInputMethodManagerService");
- try {
- imm = new InputMethodManagerService(context);
- ServiceManager.addService(Context.INPUT_METHOD_SERVICE, imm);
- } catch (Throwable e) {
- reportWtf("starting Input Manager Service", e);
- }
- Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ mSystemServiceManager.startService(InputMethodManagerService.Lifecycle.class);
traceBeginAndSlog("StartAccessibilityManagerService");
try {
@@ -667,6 +659,14 @@
}
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "ExtractPackagesIfNeeded");
+ try {
+ mPackageManagerService.extractPackagesIfNeeded();
+ } catch (Throwable e) {
+ reportWtf("extract packages", e);
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+
try {
ActivityManagerNative.getDefault().showBootMessage(
context.getResources().getText(
@@ -1199,7 +1199,6 @@
final ConnectivityService connectivityF = connectivity;
final NetworkScoreService networkScoreF = networkScore;
final WallpaperManagerService wallpaperF = wallpaper;
- final InputMethodManagerService immF = imm;
final LocationManagerService locationF = location;
final CountryDetectorService countryDetectorF = countryDetector;
final NetworkTimeUpdateService networkTimeUpdaterF = networkTimeUpdater;
@@ -1296,11 +1295,6 @@
reportWtf("Notifying WallpaperService running", e);
}
try {
- if (immF != null) immF.systemRunning(statusBarF);
- } catch (Throwable e) {
- reportWtf("Notifying InputMethodService running", e);
- }
- try {
if (locationF != null) locationF.systemRunning();
} catch (Throwable e) {
reportWtf("Notifying Location Service running", e);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 4253cd4..f2949bf4 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -558,6 +558,7 @@
}
mRealTimeSnapshot = actualRealtime;
mSystemTimeSnapshot = actualSystemTime;
+ postCheckIdleStates(UserHandle.USER_ALL);
}
return actualSystemTime;
}
@@ -602,7 +603,7 @@
|| event.mEventType == Event.SYSTEM_INTERACTION
|| event.mEventType == Event.USER_INTERACTION)) {
if (previouslyIdle) {
- // Slog.d(TAG, "Informing listeners of out-of-idle " + event.mPackage);
+ //Slog.d(TAG, "Informing listeners of out-of-idle " + event.mPackage);
mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
/* idle = */ 0, event.mPackage));
notifyBatteryStats(event.mPackage, userId, false);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 8fee91f..2aef109 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -18,6 +18,8 @@
import android.Manifest;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerNative;
import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -45,6 +47,7 @@
import android.provider.Settings;
import android.service.voice.IVoiceInteractionService;
import android.service.voice.IVoiceInteractionSession;
+import android.service.voice.VoiceInteractionManagerInternal;
import android.service.voice.VoiceInteractionService;
import android.service.voice.VoiceInteractionServiceInfo;
import android.service.voice.VoiceInteractionSession;
@@ -71,12 +74,13 @@
*/
public class VoiceInteractionManagerService extends SystemService {
static final String TAG = "VoiceInteractionManagerService";
- static final boolean DEBUG = false;
+ static final boolean DEBUG = true;
final Context mContext;
final ContentResolver mResolver;
final DatabaseHelper mDbHelper;
final SoundTriggerHelper mSoundTriggerHelper;
+ final ActivityManagerInternal mAmInternal;
public VoiceInteractionManagerService(Context context) {
super(context);
@@ -85,6 +89,7 @@
mDbHelper = new DatabaseHelper(context);
mSoundTriggerHelper = new SoundTriggerHelper(context);
mServiceStub = new VoiceInteractionManagerServiceStub();
+ mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
PackageManagerInternal packageManagerInternal = LocalServices.getService(
PackageManagerInternal.class);
@@ -105,6 +110,7 @@
@Override
public void onStart() {
publishBinderService(Context.VOICE_INTERACTION_MANAGER_SERVICE, mServiceStub);
+ publishLocalService(VoiceInteractionManagerInternal.class, new LocalService());
}
@Override
@@ -124,6 +130,31 @@
mServiceStub.switchUser(userHandle);
}
+ class LocalService extends VoiceInteractionManagerInternal {
+ @Override
+ public void startLocalVoiceInteraction(IBinder callingActivity, Bundle options) {
+ if (DEBUG) {
+ Slog.i(TAG, "startLocalVoiceInteraction " + callingActivity);
+ }
+ VoiceInteractionManagerService.this.mServiceStub.startLocalVoiceInteraction(
+ callingActivity, options);
+ }
+
+ @Override
+ public boolean supportsLocalVoiceInteraction() {
+ return VoiceInteractionManagerService.this.mServiceStub.supportsLocalVoiceInteraction();
+ }
+
+ @Override
+ public void stopLocalVoiceInteraction(IBinder callingActivity) {
+ if (DEBUG) {
+ Slog.i(TAG, "stopLocalVoiceInteraction " + callingActivity);
+ }
+ VoiceInteractionManagerService.this.mServiceStub.stopLocalVoiceInteraction(
+ callingActivity);
+ }
+ }
+
// implementation entry point and binder service
private final VoiceInteractionManagerServiceStub mServiceStub;
@@ -139,6 +170,49 @@
mEnableService = shouldEnableService(mContext.getResources());
}
+ // TODO: VI Make sure the caller is the current user or profile
+ void startLocalVoiceInteraction(final IBinder token, Bundle options) {
+ if (mImpl == null) return;
+
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ mImpl.showSessionLocked(options,
+ VoiceInteractionSession.SHOW_SOURCE_ACTIVITY,
+ new IVoiceInteractionSessionShowCallback.Stub() {
+ @Override
+ public void onFailed() {
+ }
+
+ @Override
+ public void onShown() {
+ mAmInternal.onLocalVoiceInteractionStarted(token,
+ mImpl.mActiveSession.mSession,
+ mImpl.mActiveSession.mInteractor);
+ }
+ },
+ token);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+
+ public void stopLocalVoiceInteraction(IBinder callingActivity) {
+ if (mImpl == null) return;
+
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ mImpl.finishLocked(callingActivity, true);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+
+ public boolean supportsLocalVoiceInteraction() {
+ if (mImpl == null) return false;
+
+ return mImpl.supportsLocalVoiceInteraction();
+ }
+
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
@@ -568,7 +642,7 @@
}
final long caller = Binder.clearCallingIdentity();
try {
- mImpl.finishLocked(token);
+ mImpl.finishLocked(token, false);
} finally {
Binder.restoreCallingIdentity(caller);
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 3efd0fb..1544723 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -215,12 +215,12 @@
}
}
- public void finishLocked(IBinder token) {
- if (mActiveSession == null || token != mActiveSession.mToken) {
+ public void finishLocked(IBinder token, boolean finishTask) {
+ if (mActiveSession == null || (!finishTask && token != mActiveSession.mToken)) {
Slog.w(TAG, "finish does not match active session");
return;
}
- mActiveSession.cancelLocked();
+ mActiveSession.cancelLocked(finishTask);
mActiveSession = null;
}
@@ -251,6 +251,10 @@
return mActiveSession != null ? mActiveSession.getUserDisabledShowContextLocked() : 0;
}
+ public boolean supportsLocalVoiceInteraction() {
+ return mInfo.getSupportsLocalInteraction();
+ }
+
public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!mValid) {
pw.print(" NOT VALID: ");
@@ -308,7 +312,7 @@
// If there is an active session, cancel it to allow it to clean up its window and other
// state.
if (mActiveSession != null) {
- mActiveSession.cancelLocked();
+ mActiveSession.cancelLocked(false);
mActiveSession = null;
}
try {
@@ -343,7 +347,7 @@
@Override
public void sessionConnectionGone(VoiceInteractionSessionConnection connection) {
synchronized (mLock) {
- finishLocked(connection.mToken);
+ finishLocked(connection.mToken, false);
}
}
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index 1788e88..e04f312 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -418,7 +418,7 @@
return false;
}
- public void cancelLocked() {
+ public void cancelLocked(boolean finishTask) {
hideLocked();
mCanceled = true;
if (mBound) {
@@ -429,7 +429,7 @@
Slog.w(TAG, "Voice interation session already dead");
}
}
- if (mSession != null) {
+ if (finishTask && mSession != null) {
try {
mAm.finishVoiceTask(mSession);
} catch (RemoteException e) {
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index d45b26f..497864e8 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -298,6 +298,16 @@
public static final String METADATA_IN_CALL_SERVICE_UI = "android.telecom.IN_CALL_SERVICE_UI";
/**
+ * A boolean meta-data value indicating whether an {@link InCallService} implements an
+ * in-call user interface to be used while the device is in car-mode (see
+ * {@link android.content.res.Configuration.UI_MODE_TYPE_CAR}).
+ *
+ * @hide
+ */
+ public static final String METADATA_IN_CALL_SERVICE_CAR_MODE_UI =
+ "android.telecom.IN_CALL_SERVICE_CAR_MODE_UI";
+
+ /**
* The dual tone multi-frequency signaling character sent to indicate the dialing system should
* pause for a predefined period.
*/
@@ -387,27 +397,23 @@
/**
* Broadcast intent action for letting custom component know to show the missed call
- * notification.
- * @hide
+ * notification. If no custom component exists then this is sent to the default dialer which
+ * should post a missed-call notification.
*/
- @SystemApi
public static final String ACTION_SHOW_MISSED_CALLS_NOTIFICATION =
"android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION";
/**
- * The number of calls associated with the notification.
- * @hide
+ * The number of calls associated with the notification. If the number is zero then the missed
+ * call notification should be dismissed.
*/
- @SystemApi
public static final String EXTRA_NOTIFICATION_COUNT =
"android.telecom.extra.NOTIFICATION_COUNT";
/**
* The number associated with the missed calls. This number is only relevant
* when EXTRA_NOTIFICATION_COUNT is 1.
- * @hide
*/
- @SystemApi
public static final String EXTRA_NOTIFICATION_PHONE_NUMBER =
"android.telecom.extra.NOTIFICATION_PHONE_NUMBER";
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 9eff591..320d274 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -357,6 +357,34 @@
public static final String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL =
"always_show_emergency_alert_onoff_bool";
+ /**
+ * The data call APN retry configuration for default type APN.
+ * @hide
+ */
+ public static final String KEY_CARRIER_DATA_CALL_RETRY_CONFIG_DEFAULT_STRING =
+ "carrier_data_call_retry_config_default_string";
+
+ /**
+ * The data call APN retry configuration for other type APNs.
+ * @hide
+ */
+ public static final String KEY_CARRIER_DATA_CALL_RETRY_CONFIG_OTHERS_STRING =
+ "carrier_data_call_retry_config_others_string";
+
+ /**
+ * Delay between trying APN from the pool
+ * @hide
+ */
+ public static final String KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG =
+ "carrier_data_call_apn_delay_default_long";
+
+ /**
+ * Faster delay between trying APN from the pool
+ * @hide
+ */
+ public static final String KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG =
+ "carrier_data_call_apn_delay_faster_long";
+
/* The following 3 fields are related to carrier visual voicemail. */
/**
@@ -619,6 +647,13 @@
sDefaults.putBoolean(KEY_ALLOW_ADDING_APNS_BOOL, true);
sDefaults.putBoolean(KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL, false);
sDefaults.putBoolean(KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL, false);
+ sDefaults.putString(KEY_CARRIER_DATA_CALL_RETRY_CONFIG_DEFAULT_STRING,
+ "default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000,"
+ + "320000:5000,640000:5000,1280000:5000,1800000:5000");
+ sDefaults.putString(KEY_CARRIER_DATA_CALL_RETRY_CONFIG_OTHERS_STRING,
+ "max_retries=3, 5000, 5000, 5000");
+ sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG, 20000);
+ sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG, 3000);
sDefaults.putStringArray(KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY, null);
sDefaults.putStringArray(KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY, null);
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index a4e9486..fcb967f 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -44,7 +44,9 @@
CONNECTED,
DISCONNECTING,
FAILED,
- RETRYING
+ RETRYING // After moving retry manager to ApnContext, we'll never enter this state!
+ // Todo: Remove this state and other places that use this state and then
+ // rename SCANNING to RETRYING.
}
public enum Activity {
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 81aa6c6..5296d4d 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -532,6 +532,12 @@
throw new UnsupportedOperationException();
}
+ /** @hide */
+ @Override
+ public Drawable getUserBadgeForDensityNoBackground(UserHandle user, int density) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) {
throw new UnsupportedOperationException();
diff --git a/tests/VoiceInteraction/AndroidManifest.xml b/tests/VoiceInteraction/AndroidManifest.xml
index fe17c6e..cbc6c76 100644
--- a/tests/VoiceInteraction/AndroidManifest.xml
+++ b/tests/VoiceInteraction/AndroidManifest.xml
@@ -61,5 +61,13 @@
<category android:name="android.intent.category.VOICE" />
</intent-filter>
</activity>
+ <activity android:name="StartVoiceInteractionActivity" android:label="In-Activity Voice"
+ android:theme="@android:style/Theme.Material.Light">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/tests/VoiceInteraction/res/layout/local_interaction_app.xml b/tests/VoiceInteraction/res/layout/local_interaction_app.xml
new file mode 100644
index 0000000..9694133
--- /dev/null
+++ b/tests/VoiceInteraction/res/layout/local_interaction_app.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:padding="8dp"
+ >
+
+ <TextView android:id="@+id/log"
+ android:layout_width="match_parent"
+ android:layout_height="0px"
+ android:layout_weight="1"
+ android:layout_marginTop="16dp"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ />
+
+ <LinearLayout android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:orientation="horizontal">
+
+ <Button android:id="@+id/start"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/startFromActivity"
+ />
+
+ </LinearLayout>
+
+ <LinearLayout android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:orientation="horizontal">
+
+ <Button android:id="@+id/stop"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/stopFromActivity"
+ android:enabled="false"
+ />
+
+ </LinearLayout>
+
+ <LinearLayout android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:orientation="horizontal">
+
+ <Button android:id="@+id/command"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/commandVoice"
+ />
+
+ <Button android:id="@+id/pick"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/pickVoice"
+ />
+
+ </LinearLayout>
+
+ <LinearLayout android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:layout_marginBottom="16dp"
+ android:orientation="horizontal">
+
+ <Button android:id="@+id/cancel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/cancelVoice"
+ />
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/tests/VoiceInteraction/res/values/strings.xml b/tests/VoiceInteraction/res/values/strings.xml
index c665c23..64f8bc5 100644
--- a/tests/VoiceInteraction/res/values/strings.xml
+++ b/tests/VoiceInteraction/res/values/strings.xml
@@ -31,6 +31,8 @@
<string name="pickVoice">Pick Voice</string>
<string name="cancelVoice">Cancel</string>
<string name="jumpOut">Jump out</string>
+ <string name="startFromActivity">Start voice interaction</string>
+ <string name="stopFromActivity">Stop voice interaction</string>
<string name="largetext">This is a bunch of text that we will use to show how we handle it
when reporting it for assist data. We need many many lines of text, like\n
diff --git a/tests/VoiceInteraction/res/xml/interaction_service.xml b/tests/VoiceInteraction/res/xml/interaction_service.xml
index c015ad2..f0c88a2 100644
--- a/tests/VoiceInteraction/res/xml/interaction_service.xml
+++ b/tests/VoiceInteraction/res/xml/interaction_service.xml
@@ -21,4 +21,5 @@
android:sessionService="com.android.test.voiceinteraction.MainInteractionSessionService"
android:recognitionService="com.android.test.voiceinteraction.MainRecognitionService"
android:settingsActivity="com.android.test.voiceinteraction.SettingsActivity"
- android:supportsAssist="true" />
+ android:supportsAssist="true"
+ android:supportsLocalInteraction="true" />
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
index 6e3694b..450334c 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
@@ -73,6 +73,7 @@
CharSequence mPendingPrompt;
Request mPendingRequest;
int mCurrentTask = -1;
+ int mShowFlags;
MainInteractionSession(Context context) {
super(context);
@@ -88,6 +89,7 @@
@Override
public void onShow(Bundle args, int showFlags) {
super.onShow(args, showFlags);
+ mShowFlags = showFlags;
Log.i(TAG, "onShow: flags=0x" + Integer.toHexString(showFlags) + " args=" + args);
mState = STATE_IDLE;
mStartIntent = args != null ? (Intent)args.getParcelable("intent") : null;
@@ -311,6 +313,8 @@
if (mState != STATE_IDLE) {
outInsets.contentInsets.top = mBottomContent.getTop();
outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_CONTENT;
+ } else if ((mShowFlags & SHOW_SOURCE_ACTIVITY) != 0) {
+ outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_CONTENT;
}
}
@@ -355,7 +359,7 @@
mPendingPrompt = prompt.getVisualPrompt();
}
}
-
+
@Override
public void onRequestConfirmation(ConfirmationRequest request) {
Log.i(TAG, "onConfirm: prompt=" + request.getVoicePrompt() + " extras="
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/StartVoiceInteractionActivity.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/StartVoiceInteractionActivity.java
new file mode 100644
index 0000000..41058c9
--- /dev/null
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/StartVoiceInteractionActivity.java
@@ -0,0 +1,208 @@
+/*
+ * 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.
+ */
+
+package com.android.test.voiceinteraction;
+
+import android.app.Activity;
+import android.app.VoiceInteractor;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+public class StartVoiceInteractionActivity extends Activity implements View.OnClickListener {
+ static final String TAG = "LocalVoiceInteractionActivity";
+
+ static final String REQUEST_ABORT = "abort";
+ static final String REQUEST_COMPLETE = "complete";
+ static final String REQUEST_COMMAND = "command";
+ static final String REQUEST_PICK = "pick";
+ static final String REQUEST_CONFIRM = "confirm";
+
+ VoiceInteractor mInteractor;
+ VoiceInteractor.Request mCurrentRequest = null;
+ TextView mLog;
+ Button mCommandButton;
+ Button mPickButton;
+ Button mCancelButton;
+ Button mStartButton;
+ Button mStopButton;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.local_interaction_app);
+
+ mLog = (TextView)findViewById(R.id.log);
+ mCommandButton = (Button)findViewById(R.id.command);
+ mCommandButton.setOnClickListener(this);
+ mPickButton = (Button)findViewById(R.id.pick);
+ mPickButton.setOnClickListener(this);
+ mCancelButton = (Button)findViewById(R.id.cancel);
+ mCancelButton.setOnClickListener(this);
+ mStartButton = (Button) findViewById(R.id.start);
+ mStartButton.setOnClickListener(this);
+ mStopButton = (Button) findViewById(R.id.stop);
+ mStopButton.setOnClickListener(this);
+
+ mLog.append("Local Voice Interaction Supported = " + isLocalVoiceInteractionSupported());
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v == mCommandButton) {
+ VoiceInteractor.CommandRequest req = new TestCommand("Some arg");
+ mInteractor.submitRequest(req, REQUEST_COMMAND);
+ } else if (v == mPickButton) {
+ VoiceInteractor.PickOptionRequest.Option[] options =
+ new VoiceInteractor.PickOptionRequest.Option[5];
+ options[0] = new VoiceInteractor.PickOptionRequest.Option("One");
+ options[1] = new VoiceInteractor.PickOptionRequest.Option("Two");
+ options[2] = new VoiceInteractor.PickOptionRequest.Option("Three");
+ options[3] = new VoiceInteractor.PickOptionRequest.Option("Four");
+ options[4] = new VoiceInteractor.PickOptionRequest.Option("Five");
+ VoiceInteractor.PickOptionRequest req = new TestPickOption(options);
+ mInteractor.submitRequest(req, REQUEST_PICK);
+ } else if (v == mCancelButton && mCurrentRequest != null) {
+ Log.i(TAG, "Cancel request");
+ mCurrentRequest.cancel();
+ } else if (v == mStartButton) {
+ Bundle args = new Bundle();
+ args.putString("Foo", "Bar");
+ startLocalVoiceInteraction(args);
+ } else if (v == mStopButton) {
+ stopLocalVoiceInteraction();
+ }
+ }
+
+ @Override
+ public void onLocalVoiceInteractionStarted() {
+ mInteractor = getVoiceInteractor();
+ mLog.append("\nLocalVoiceInteraction started!");
+ mStopButton.setEnabled(true);
+ }
+
+ @Override
+ public void onLocalVoiceInteractionStopped() {
+ mInteractor = getVoiceInteractor();
+ mLog.append("\nLocalVoiceInteraction stopped!");
+ mStopButton.setEnabled(false);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ }
+
+ static class TestAbortVoice extends VoiceInteractor.AbortVoiceRequest {
+ public TestAbortVoice() {
+ super(new VoiceInteractor.Prompt("Dammit, we suck :("), null);
+ }
+ @Override public void onCancel() {
+ Log.i(TAG, "Canceled!");
+ ((StartVoiceInteractionActivity)getActivity()).mLog.append("Canceled abort\n");
+ }
+ @Override public void onAbortResult(Bundle result) {
+ Log.i(TAG, "Abort result: result=" + result);
+ ((StartVoiceInteractionActivity)getActivity()).mLog.append(
+ "Abort: result=" + result + "\n");
+ getActivity().finish();
+ }
+ }
+
+ static class TestCompleteVoice extends VoiceInteractor.CompleteVoiceRequest {
+ public TestCompleteVoice() {
+ super(new VoiceInteractor.Prompt("Woohoo, completed!"), null);
+ }
+ @Override public void onCancel() {
+ Log.i(TAG, "Canceled!");
+ ((StartVoiceInteractionActivity)getActivity()).mLog.append("Canceled complete\n");
+ }
+ @Override public void onCompleteResult(Bundle result) {
+ Log.i(TAG, "Complete result: result=" + result);
+ ((StartVoiceInteractionActivity)getActivity()).mLog.append("Complete: result="
+ + result + "\n");
+ getActivity().finish();
+ }
+ }
+
+ static class TestCommand extends VoiceInteractor.CommandRequest {
+ public TestCommand(String arg) {
+ super("com.android.test.voiceinteraction.COMMAND", makeBundle(arg));
+ }
+ @Override public void onCancel() {
+ Log.i(TAG, "Canceled!");
+ ((StartVoiceInteractionActivity)getActivity()).mLog.append("Canceled command\n");
+ }
+ @Override
+ public void onCommandResult(boolean finished, Bundle result) {
+ Log.i(TAG, "Command result: finished=" + finished + " result=" + result);
+ StringBuilder sb = new StringBuilder();
+ if (finished) {
+ sb.append("Command final result: ");
+ } else {
+ sb.append("Command intermediate result: ");
+ }
+ if (result != null) {
+ result.getString("key");
+ }
+ sb.append(result);
+ sb.append("\n");
+ ((StartVoiceInteractionActivity)getActivity()).mLog.append(sb.toString());
+ }
+ static Bundle makeBundle(String arg) {
+ Bundle b = new Bundle();
+ b.putString("key", arg);
+ return b;
+ }
+ }
+
+ static class TestPickOption extends VoiceInteractor.PickOptionRequest {
+ public TestPickOption(Option[] options) {
+ super(new VoiceInteractor.Prompt("Need to pick something"), options, null);
+ }
+ @Override public void onCancel() {
+ Log.i(TAG, "Canceled!");
+ ((StartVoiceInteractionActivity)getActivity()).mLog.append("Canceled pick\n");
+ }
+ @Override
+ public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) {
+ Log.i(TAG, "Pick result: finished=" + finished + " selections=" + selections
+ + " result=" + result);
+ StringBuilder sb = new StringBuilder();
+ if (finished) {
+ sb.append("Pick final result: ");
+ } else {
+ sb.append("Pick intermediate result: ");
+ }
+ for (int i=0; i<selections.length; i++) {
+ if (i >= 1) {
+ sb.append(", ");
+ }
+ sb.append(selections[i].getLabel());
+ }
+ sb.append("\n");
+ ((StartVoiceInteractionActivity)getActivity()).mLog.append(sb.toString());
+ }
+ }
+}
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp
index d346731..3b01827 100644
--- a/tools/aapt/AaptAssets.cpp
+++ b/tools/aapt/AaptAssets.cpp
@@ -235,7 +235,7 @@
setRegion(part2.string());
} else if (part2.length() == 4 && isAlpha(part2)) {
setScript(part2.string());
- } else if (part2.length() >= 5 && part2.length() <= 8) {
+ } else if (part2.length() >= 4 && part2.length() <= 8) {
setVariant(part2.string());
} else {
valid = false;
@@ -250,7 +250,7 @@
if (((part3.length() == 2 && isAlpha(part3)) ||
(part3.length() == 3 && isNumber(part3))) && script[0]) {
setRegion(part3.string());
- } else if (part3.length() >= 5 && part3.length() <= 8) {
+ } else if (part3.length() >= 4 && part3.length() <= 8) {
setVariant(part3.string());
} else {
valid = false;
@@ -261,7 +261,7 @@
}
const String8& part4 = parts[3];
- if (part4.length() >= 5 && part4.length() <= 8) {
+ if (part4.length() >= 4 && part4.length() <= 8) {
setVariant(part4.string());
} else {
valid = false;
@@ -280,7 +280,7 @@
String8 part = parts[currentIndex];
if (part[0] == 'b' && part[1] == '+') {
- // This is a "modified" BCP-47 language tag. Same semantics as BCP-47 tags,
+ // This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags,
// except that the separator is "+" and not "-".
Vector<String8> subtags = AaptUtil::splitAndLowerCase(part, '+');
subtags.removeItemsAt(0);
@@ -296,8 +296,11 @@
setRegion(subtags[1]);
break;
case 4:
- setScript(subtags[1]);
- break;
+ if (isAlpha(subtags[1])) {
+ setScript(subtags[1]);
+ break;
+ }
+ // This is not alphabetical, so we fall through to variant
case 5:
case 6:
case 7:
@@ -305,7 +308,7 @@
setVariant(subtags[1]);
break;
default:
- fprintf(stderr, "ERROR: Invalid BCP-47 tag in directory name %s\n",
+ fprintf(stderr, "ERROR: Invalid BCP 47 tag in directory name %s\n",
part.string());
return -1;
}
@@ -322,13 +325,13 @@
setRegion(subtags[1]);
hasRegion = true;
} else {
- fprintf(stderr, "ERROR: Invalid BCP-47 tag in directory name %s\n", part.string());
+ fprintf(stderr, "ERROR: Invalid BCP 47 tag in directory name %s\n", part.string());
return -1;
}
// The third tag can either be a region code (if the second tag was
// a script), else a variant code.
- if (subtags[2].size() > 4) {
+ if (subtags[2].size() >= 4) {
setVariant(subtags[2]);
} else {
setRegion(subtags[2]);
@@ -339,7 +342,7 @@
setRegion(subtags[2]);
setVariant(subtags[3]);
} else {
- fprintf(stderr, "ERROR: Invalid BCP-47 tag in directory name: %s\n", part.string());
+ fprintf(stderr, "ERROR: Invalid BCP 47 tag in directory name: %s\n", part.string());
return -1;
}
@@ -370,7 +373,7 @@
void AaptLocaleValue::initFromResTable(const ResTable_config& config) {
config.unpackLanguage(language);
config.unpackRegion(region);
- if (config.localeScript[0]) {
+ if (config.localeScriptWasProvided) {
memcpy(script, config.localeScript, sizeof(config.localeScript));
}
@@ -385,6 +388,10 @@
if (script[0]) {
memcpy(out->localeScript, script, sizeof(out->localeScript));
+ out->localeScriptWasProvided = true;
+ } else {
+ out->computeScript();
+ out->localeScriptWasProvided = false;
}
if (variant[0]) {
diff --git a/tools/aapt2/Locale.cpp b/tools/aapt2/Locale.cpp
index 20a2d0c..0369156 100644
--- a/tools/aapt2/Locale.cpp
+++ b/tools/aapt2/Locale.cpp
@@ -96,7 +96,7 @@
setRegion(part2.c_str());
} else if (part2.length() == 4 && isAlpha(part2)) {
setScript(part2.c_str());
- } else if (part2.length() >= 5 && part2.length() <= 8) {
+ } else if (part2.length() >= 4 && part2.length() <= 8) {
setVariant(part2.c_str());
} else {
valid = false;
@@ -111,7 +111,7 @@
if (((part3.length() == 2 && isAlpha(part3)) ||
(part3.length() == 3 && isNumber(part3))) && script[0]) {
setRegion(part3.c_str());
- } else if (part3.length() >= 5 && part3.length() <= 8) {
+ } else if (part3.length() >= 4 && part3.length() <= 8) {
setVariant(part3.c_str());
} else {
valid = false;
@@ -122,7 +122,7 @@
}
const std::string& part4 = parts[3];
- if (part4.length() >= 5 && part4.length() <= 8) {
+ if (part4.length() >= 4 && part4.length() <= 8) {
setVariant(part4.c_str());
} else {
valid = false;
@@ -141,7 +141,7 @@
std::string& part = *iter;
if (part[0] == 'b' && part[1] == '+') {
- // This is a "modified" BCP-47 language tag. Same semantics as BCP-47 tags,
+ // This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags,
// except that the separator is "+" and not "-".
std::vector<std::string> subtags = util::splitAndLowercase(part, '+');
subtags.erase(subtags.begin());
@@ -157,8 +157,12 @@
setRegion(subtags[1].c_str());
break;
case 4:
- setScript(subtags[1].c_str());
- break;
+ if ('0' <= subtags[1][0] && subtags[1][0] <= '9') {
+ // This is a variant: fall through
+ } else {
+ setScript(subtags[1].c_str());
+ break;
+ }
case 5:
case 6:
case 7:
@@ -184,7 +188,7 @@
// The third tag can either be a region code (if the second tag was
// a script), else a variant code.
- if (subtags[2].size() > 4) {
+ if (subtags[2].size() >= 4) {
setVariant(subtags[2].c_str());
} else {
setRegion(subtags[2].c_str());
@@ -249,7 +253,7 @@
void LocaleValue::initFromResTable(const ResTable_config& config) {
config.unpackLanguage(language);
config.unpackRegion(region);
- if (config.localeScript[0]) {
+ if (config.localeScriptWasProvided) {
memcpy(script, config.localeScript, sizeof(config.localeScript));
}
@@ -264,6 +268,10 @@
if (script[0]) {
memcpy(out->localeScript, script, sizeof(out->localeScript));
+ out->localeScriptWasProvided = true;
+ } else {
+ out->computeScript();
+ out->localeScriptWasProvided = false;
}
if (variant[0]) {
diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp
index bf0f4aa..329dac9 100644
--- a/tools/aapt2/io/ZipArchive.cpp
+++ b/tools/aapt2/io/ZipArchive.cpp
@@ -75,11 +75,19 @@
std::unique_ptr<ZipFileCollection> ZipFileCollection::create(const StringPiece& path,
std::string* outError) {
+ constexpr static const int32_t kEmptyArchive = -6;
+
std::unique_ptr<ZipFileCollection> collection = std::unique_ptr<ZipFileCollection>(
new ZipFileCollection());
int32_t result = OpenArchive(path.data(), &collection->mHandle);
if (result != 0) {
+ // If a zip is empty, result will be an error code. This is fine and we should
+ // return an empty ZipFileCollection.
+ if (result == kEmptyArchive) {
+ return collection;
+ }
+
if (outError) *outError = ErrorCodeString(result);
return {};
}
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 51b9cec..f3a9ea3 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -56,6 +56,7 @@
Maybe<std::string> generateProguardRulesPath;
bool noAutoVersion = false;
bool staticLib = false;
+ bool generateNonFinalIds = false;
bool verbose = false;
bool outputToDirectory = false;
bool autoAddOverlay = false;
@@ -551,12 +552,13 @@
if (resourceFile) {
return mergeCompiledFile(file, std::move(resourceFile), override);
}
- } else {
- // Ignore non .flat files. This could be classes.dex or something else that happens
- // to be in an archive.
+
+ return false;
}
- return false;
+ // Ignore non .flat files. This could be classes.dex or something else that happens
+ // to be in an archive.
+ return true;
}
int run(const std::vector<std::string>& inputFiles) {
@@ -834,7 +836,7 @@
JavaClassGeneratorOptions options;
options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
- if (mOptions.staticLib) {
+ if (mOptions.staticLib || mOptions.generateNonFinalIds) {
options.useFinal = false;
}
@@ -932,6 +934,9 @@
.optionalFlag("--version-name", "Version name to inject into the AndroidManifest.xml "
"if none is present", &versionName)
.optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib)
+ .optionalSwitch("--non-final-ids", "Generates R.java without the final modifier.\n"
+ "This is implied when --static-lib is specified.",
+ &options.generateNonFinalIds)
.optionalFlag("--private-symbols", "Package name to use when generating R.java for "
"private symbols.\n"
"If not specified, public and private symbols will use the application's "
diff --git a/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java b/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java
index d62d4e1..1465f50 100644
--- a/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java
@@ -38,10 +38,15 @@
private float mLift;
+ private float mTranslationX;
+ private float mTranslationY;
+ private float mTranslationZ;
private float mRotation;
+ private float mScaleX = 1;
+ private float mScaleY = 1;
private float mPivotX;
private float mPivotY;
- private boolean mPivotExplicitelySet;
+ private boolean mPivotExplicitlySet;
private int mLeft;
private int mRight;
private int mTop;
@@ -81,6 +86,63 @@
}
@LayoutlibDelegate
+ /*package*/ static boolean nSetTranslationX(long renderNode, float translationX) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null && delegate.mTranslationX != translationX) {
+ delegate.mTranslationX = translationX;
+ return true;
+ }
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float nGetTranslationX(long renderNode) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null) {
+ return delegate.mTranslationX;
+ }
+ return 0f;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nSetTranslationY(long renderNode, float translationY) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null && delegate.mTranslationY != translationY) {
+ delegate.mTranslationY = translationY;
+ return true;
+ }
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float nGetTranslationY(long renderNode) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null) {
+ return delegate.mTranslationY;
+ }
+ return 0f;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nSetTranslationZ(long renderNode, float translationZ) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null && delegate.mTranslationZ != translationZ) {
+ delegate.mTranslationZ = translationZ;
+ return true;
+ }
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float nGetTranslationZ(long renderNode) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null) {
+ return delegate.mTranslationZ;
+ }
+ return 0f;
+ }
+
+ @LayoutlibDelegate
/*package*/ static boolean nSetRotation(long renderNode, float rotation) {
RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
if (delegate != null && delegate.mRotation != rotation) {
@@ -103,8 +165,17 @@
/*package*/ static void getMatrix(RenderNode renderNode, Matrix outMatrix) {
outMatrix.reset();
if (renderNode != null) {
- outMatrix.preRotate(renderNode.getRotation(), renderNode.getPivotX(),
- renderNode.getPivotY());
+ float rotation = renderNode.getRotation();
+ float translationX = renderNode.getTranslationX();
+ float translationY = renderNode.getTranslationY();
+ float pivotX = renderNode.getPivotX();
+ float pivotY = renderNode.getPivotY();
+ float scaleX = renderNode.getScaleX();
+ float scaleY = renderNode.getScaleY();
+
+ outMatrix.setTranslate(translationX, translationY);
+ outMatrix.preRotate(rotation, pivotX, pivotY);
+ outMatrix.preScale(scaleX, scaleY, pivotX, pivotY);
}
}
@@ -166,18 +237,15 @@
@LayoutlibDelegate
/*package*/ static boolean nIsPivotExplicitlySet(long renderNode) {
RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- if (delegate != null) {
- return delegate.mPivotExplicitelySet;
- }
- return false;
+ return delegate != null && delegate.mPivotExplicitlySet;
}
@LayoutlibDelegate
/*package*/ static boolean nSetPivotX(long renderNode, float pivotX) {
RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- if (delegate != null && delegate.mPivotX != pivotX) {
+ if (delegate != null) {
delegate.mPivotX = pivotX;
- delegate.mPivotExplicitelySet = true;
+ delegate.mPivotExplicitlySet = true;
return true;
}
return false;
@@ -187,7 +255,7 @@
/*package*/ static float nGetPivotX(long renderNode) {
RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
if (delegate != null) {
- if (delegate.mPivotExplicitelySet) {
+ if (delegate.mPivotExplicitlySet) {
return delegate.mPivotX;
} else {
return (delegate.mRight - delegate.mLeft) / 2.0f;
@@ -199,9 +267,9 @@
@LayoutlibDelegate
/*package*/ static boolean nSetPivotY(long renderNode, float pivotY) {
RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- if (delegate != null && delegate.mPivotY != pivotY) {
+ if (delegate != null) {
delegate.mPivotY = pivotY;
- delegate.mPivotExplicitelySet = true;
+ delegate.mPivotExplicitlySet = true;
return true;
}
return false;
@@ -211,7 +279,7 @@
/*package*/ static float nGetPivotY(long renderNode) {
RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
if (delegate != null) {
- if (delegate.mPivotExplicitelySet) {
+ if (delegate.mPivotExplicitlySet) {
return delegate.mPivotY;
} else {
return (delegate.mBottom - delegate.mTop) / 2.0f;
@@ -219,4 +287,42 @@
}
return 0f;
}
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nSetScaleX(long renderNode, float scaleX) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null && delegate.mScaleX != scaleX) {
+ delegate.mScaleX = scaleX;
+ return true;
+ }
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float nGetScaleX(long renderNode) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null) {
+ return delegate.mScaleX;
+ }
+ return 0f;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nSetScaleY(long renderNode, float scaleY) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null && delegate.mScaleY != scaleY) {
+ delegate.mScaleY = scaleY;
+ return true;
+ }
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float nGetScaleY(long renderNode) {
+ RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+ if (delegate != null) {
+ return delegate.mScaleY;
+ }
+ return 0f;
+ }
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
index 08258c9..037ce57 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
@@ -492,6 +492,11 @@
}
@Override
+ public Drawable getUserBadgeForDensityNoBackground(UserHandle user, int density) {
+ return null;
+ }
+
+ @Override
public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) {
return null;
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
index 769285f..7b8e29a 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
@@ -148,7 +148,7 @@
@Override
public boolean performDrag(IWindow window, IBinder dragToken,
- float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+ int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
ClipData data)
throws RemoteException {
// pass for now
diff --git a/tools/layoutlib/bridge/src/libcore/util/ZoneInfo_WallTime_Delegate.java b/tools/layoutlib/bridge/src/libcore/util/ZoneInfo_WallTime_Delegate.java
deleted file mode 100644
index f29c5c0..0000000
--- a/tools/layoutlib/bridge/src/libcore/util/ZoneInfo_WallTime_Delegate.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.util;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import java.util.GregorianCalendar;
-
-/**
- * Delegate used to provide alternate implementation of select methods in {@link ZoneInfo.WallTime}
- */
-public class ZoneInfo_WallTime_Delegate {
-
- @LayoutlibDelegate
- static GregorianCalendar createGregorianCalendar() {
- return new GregorianCalendar();
- }
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 183b729..d106592 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -192,6 +192,12 @@
"android.view.RenderNode#nDestroyRenderNode",
"android.view.RenderNode#nSetElevation",
"android.view.RenderNode#nGetElevation",
+ "android.view.RenderNode#nSetTranslationX",
+ "android.view.RenderNode#nGetTranslationX",
+ "android.view.RenderNode#nSetTranslationY",
+ "android.view.RenderNode#nGetTranslationY",
+ "android.view.RenderNode#nSetTranslationZ",
+ "android.view.RenderNode#nGetTranslationZ",
"android.view.RenderNode#nSetRotation",
"android.view.RenderNode#nGetRotation",
"android.view.RenderNode#getMatrix",
@@ -204,6 +210,10 @@
"android.view.RenderNode#nGetPivotX",
"android.view.RenderNode#nSetPivotY",
"android.view.RenderNode#nGetPivotY",
+ "android.view.RenderNode#nSetScaleX",
+ "android.view.RenderNode#nGetScaleX",
+ "android.view.RenderNode#nSetScaleY",
+ "android.view.RenderNode#nGetScaleY",
"android.view.RenderNode#nIsPivotExplicitlySet",
"android.view.ViewGroup#drawChild",
"android.widget.TimePickerClockDelegate#getAmOrPmKeyCode",
@@ -214,7 +224,6 @@
"libcore.io.MemoryMappedFile#mmapRO",
"libcore.io.MemoryMappedFile#close",
"libcore.io.MemoryMappedFile#bigEndianIterator",
- "libcore.util.ZoneInfo$WallTime#createGregorianCalendar",
};
/**
diff --git a/tools/localedata/extract_icu_data.py b/tools/localedata/extract_icu_data.py
new file mode 100755
index 0000000..b071093
--- /dev/null
+++ b/tools/localedata/extract_icu_data.py
@@ -0,0 +1,286 @@
+#!/usr/bin/env python
+#
+# Copyright 2016 The Android Open Source Project. All Rights Reserved.
+#
+# 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.
+#
+
+"""Generate a C++ data table containing locale data."""
+
+import collections
+import glob
+import os.path
+import sys
+
+
+def get_locale_parts(locale):
+ """Split a locale into three parts, for langauge, script, and region."""
+ parts = locale.split('_')
+ if len(parts) == 1:
+ return (parts[0], None, None)
+ elif len(parts) == 2:
+ if len(parts[1]) == 4: # parts[1] is a script
+ return (parts[0], parts[1], None)
+ else:
+ return (parts[0], None, parts[1])
+ else:
+ assert len(parts) == 3
+ return tuple(parts)
+
+
+def read_likely_subtags(input_file_name):
+ """Read and parse ICU's likelySubtags.txt."""
+ with open(input_file_name) as input_file:
+ likely_script_dict = {
+ # Android's additions for pseudo-locales. These internal codes make
+ # sure that the pseudo-locales would not match other English or
+ # Arabic locales. (We can't use private-use ISO 15924 codes, since
+ # they may be used by apps for other purposes.)
+ "en_XA": "~~~A",
+ "ar_XB": "~~~B",
+ }
+ representative_locales = {
+ # Android's additions
+ "en_Latn_GB", # representative for en_Latn_001
+ "es_Latn_MX", # representative for es_Latn_419
+ "es_Latn_US", # representative for es_Latn_419 (not the best idea,
+ # but Android has been shipping with it for quite a
+ # while. Fortunately, MX < US, so if both exist, MX
+ # would be chosen.)
+ }
+ for line in input_file:
+ line = unicode(line, 'UTF-8').strip(u' \n\uFEFF').encode('UTF-8')
+ if line.startswith('//'):
+ continue
+ if '{' in line and '}' in line:
+ from_locale = line[:line.index('{')]
+ to_locale = line[line.index('"')+1:line.rindex('"')]
+ from_lang, from_scr, from_region = get_locale_parts(from_locale)
+ _, to_scr, to_region = get_locale_parts(to_locale)
+ if from_lang == 'und':
+ continue # not very useful for our purposes
+ if from_region is None and to_region != '001':
+ representative_locales.add(to_locale)
+ if from_scr is None:
+ likely_script_dict[from_locale] = to_scr
+ return likely_script_dict, frozenset(representative_locales)
+
+
+# From packLanguageOrRegion() in ResourceTypes.cpp
+def pack_language_or_region(inp, base):
+ """Pack langauge or region in a two-byte tuple."""
+ if inp is None:
+ return (0, 0)
+ elif len(inp) == 2:
+ return ord(inp[0]), ord(inp[1])
+ else:
+ assert len(inp) == 3
+ base = ord(base)
+ first = ord(inp[0]) - base
+ second = ord(inp[1]) - base
+ third = ord(inp[2]) - base
+
+ return (0x80 | (third << 2) | (second >>3),
+ ((second << 5) | first) & 0xFF)
+
+
+# From packLanguage() in ResourceTypes.cpp
+def pack_language(language):
+ """Pack language in a two-byte tuple."""
+ return pack_language_or_region(language, 'a')
+
+
+# From packRegion() in ResourceTypes.cpp
+def pack_region(region):
+ """Pack region in a two-byte tuple."""
+ return pack_language_or_region(region, '0')
+
+
+def pack_to_uint32(locale):
+ """Pack language+region of locale into a 32-bit unsigned integer."""
+ lang, _, region = get_locale_parts(locale)
+ plang = pack_language(lang)
+ pregion = pack_region(region)
+ return (plang[0] << 24) | (plang[1] << 16) | (pregion[0] << 8) | pregion[1]
+
+
+def dump_script_codes(all_scripts):
+ """Dump the SCRIPT_CODES table."""
+ print 'const char SCRIPT_CODES[][4] = {'
+ for index, script in enumerate(all_scripts):
+ print " /* %-2d */ {'%c', '%c', '%c', '%c'}," % (
+ index, script[0], script[1], script[2], script[3])
+ print '};'
+ print
+
+
+def dump_script_data(likely_script_dict, all_scripts):
+ """Dump the script data."""
+ print
+ print 'const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({'
+ for locale in sorted(likely_script_dict.keys()):
+ script = likely_script_dict[locale]
+ print ' {0x%08Xu, %2du}, // %s -> %s' % (
+ pack_to_uint32(locale),
+ all_scripts.index(script),
+ locale.replace('_', '-'),
+ script)
+ print '});'
+
+
+def pack_to_uint64(locale):
+ """Pack a full locale into a 64-bit unsigned integer."""
+ _, script, _ = get_locale_parts(locale)
+ return ((pack_to_uint32(locale) << 32) |
+ (ord(script[0]) << 24) |
+ (ord(script[1]) << 16) |
+ (ord(script[2]) << 8) |
+ ord(script[3]))
+
+
+def dump_representative_locales(representative_locales):
+ """Dump the set of representative locales."""
+ print
+ print 'std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({'
+ for locale in sorted(representative_locales):
+ print ' 0x%08Xllu, // %s' % (
+ pack_to_uint64(locale),
+ locale)
+ print '});'
+
+
+def read_and_dump_likely_data(icu_data_dir):
+ """Read and dump the likely-script data."""
+ likely_subtags_txt = os.path.join(icu_data_dir, 'misc', 'likelySubtags.txt')
+ likely_script_dict, representative_locales = read_likely_subtags(
+ likely_subtags_txt)
+
+ all_scripts = list(set(likely_script_dict.values()))
+ assert len(all_scripts) <= 256
+ all_scripts.sort()
+
+ dump_script_codes(all_scripts)
+ dump_script_data(likely_script_dict, all_scripts)
+ dump_representative_locales(representative_locales)
+ return likely_script_dict
+
+
+def read_parent_data(icu_data_dir):
+ """Read locale parent data from ICU data files."""
+ all_icu_data_files = glob.glob(os.path.join(icu_data_dir, '*', '*.txt'))
+ parent_dict = {}
+ for data_file in all_icu_data_files:
+ locale = os.path.splitext(os.path.basename(data_file))[0]
+ with open(data_file) as input_file:
+ for line in input_file:
+ if '%%Parent' in line:
+ parent = line[line.index('"')+1:line.rindex('"')]
+ if locale in parent_dict:
+ # Different files shouldn't have different parent info
+ assert parent_dict[locale] == parent
+ else:
+ parent_dict[locale] = parent
+ elif locale.startswith('ar_') and 'default{"latn"}' in line:
+ # Arabic parent overrides for ASCII digits. Since
+ # Unicode extensions are not supported in ResourceTypes,
+ # we will use ar-015 (Arabic, Northern Africa) instead
+ # of the more correct ar-u-nu-latn.
+ parent_dict[locale] = 'ar_015'
+ return parent_dict
+
+
+def get_likely_script(locale, likely_script_dict):
+ """Find the likely script for a locale, given the likely-script dictionary.
+ """
+ if locale.count('_') == 2:
+ # it already has a script
+ return locale.split('_')[1]
+ elif locale in likely_script_dict:
+ return likely_script_dict[locale]
+ else:
+ language = locale.split('_')[0]
+ return likely_script_dict[language]
+
+
+def dump_parent_data(script_organized_dict):
+ """Dump information for parents of locales."""
+ sorted_scripts = sorted(script_organized_dict.keys())
+ print
+ for script in sorted_scripts:
+ parent_dict = script_organized_dict[script]
+ print ('const std::unordered_map<uint32_t, uint32_t> %s_PARENTS({'
+ % script.upper())
+ for locale in sorted(parent_dict.keys()):
+ parent = parent_dict[locale]
+ print ' {0x%08Xu, 0x%08Xu}, // %s -> %s' % (
+ pack_to_uint32(locale),
+ pack_to_uint32(parent),
+ locale.replace('_', '-'),
+ parent.replace('_', '-'))
+ print '});'
+ print
+
+ print 'const struct {'
+ print ' const char script[4];'
+ print ' const std::unordered_map<uint32_t, uint32_t>* map;'
+ print '} SCRIPT_PARENTS[] = {'
+ for script in sorted_scripts:
+ print " {{'%c', '%c', '%c', '%c'}, &%s_PARENTS}," % (
+ script[0], script[1], script[2], script[3],
+ script.upper())
+ print '};'
+
+
+def dump_parent_tree_depth(parent_dict):
+ """Find and dump the depth of the parent tree."""
+ max_depth = 1
+ for locale, _ in parent_dict.items():
+ depth = 1
+ while locale in parent_dict:
+ locale = parent_dict[locale]
+ depth += 1
+ max_depth = max(max_depth, depth)
+ assert max_depth < 5 # Our algorithms assume small max_depth
+ print
+ print 'const size_t MAX_PARENT_DEPTH = %d;' % max_depth
+
+
+def read_and_dump_parent_data(icu_data_dir, likely_script_dict):
+ """Read parent data from ICU and dump it."""
+ parent_dict = read_parent_data(icu_data_dir)
+ script_organized_dict = collections.defaultdict(dict)
+ for locale in parent_dict:
+ parent = parent_dict[locale]
+ if parent == 'root':
+ continue
+ script = get_likely_script(locale, likely_script_dict)
+ script_organized_dict[script][locale] = parent_dict[locale]
+ dump_parent_data(script_organized_dict)
+ dump_parent_tree_depth(parent_dict)
+
+
+def main():
+ """Read the data files from ICU and dump the output to a C++ file."""
+ source_root = sys.argv[1]
+ icu_data_dir = os.path.join(
+ source_root,
+ 'external', 'icu', 'icu4c', 'source', 'data')
+
+ print '// Auto-generated by %s' % sys.argv[0]
+ print
+ likely_script_dict = read_and_dump_likely_data(icu_data_dir)
+ read_and_dump_parent_data(icu_data_dir, likely_script_dict)
+
+
+if __name__ == '__main__':
+ main()