diff options
135 files changed, 4116 insertions, 1823 deletions
@@ -555,8 +555,6 @@ aidl_files := \ frameworks/base/core/java/android/service/chooser/ChooserTarget.aidl \ frameworks/base/core/java/android/speech/tts/Voice.aidl \ frameworks/base/core/java/android/app/usage/UsageEvents.aidl \ - frameworks/base/core/java/android/app/AssistStructure.aidl \ - frameworks/base/core/java/android/app/AssistContent.aidl \ frameworks/base/core/java/android/app/Notification.aidl \ frameworks/base/core/java/android/app/NotificationManager.aidl \ frameworks/base/core/java/android/app/WallpaperInfo.aidl \ @@ -566,6 +564,8 @@ aidl_files := \ frameworks/base/core/java/android/app/AlarmManager.aidl \ frameworks/base/core/java/android/app/SearchableInfo.aidl \ frameworks/base/core/java/android/app/VoiceInteractor.aidl \ + frameworks/base/core/java/android/app/assist/AssistContent.aidl \ + frameworks/base/core/java/android/app/assist/AssistStructure.aidl \ frameworks/base/core/java/android/app/job/JobParameters.aidl \ frameworks/base/core/java/android/app/job/JobInfo.aidl \ frameworks/base/core/java/android/appwidget/AppWidgetProviderInfo.aidl \ diff --git a/api/current.txt b/api/current.txt index 3c878c8..0467b69 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3445,7 +3445,7 @@ package android.app { method public void onPrepareNavigateUpTaskStack(android.app.TaskStackBuilder); method public boolean onPrepareOptionsMenu(android.view.Menu); method public boolean onPreparePanel(int, android.view.View, android.view.Menu); - method public void onProvideAssistContent(android.app.AssistContent); + method public void onProvideAssistContent(android.app.assist.AssistContent); method public void onProvideAssistData(android.os.Bundle); method public void onRequestPermissionsResult(int, java.lang.String[], int[]); method protected void onRestart(); @@ -3996,26 +3996,19 @@ package android.app { field public java.lang.String serviceDetails; } - public class AssistContent implements android.os.Parcelable { + public deprecated class AssistContent { ctor public AssistContent(); - method public int describeContents(); method public android.content.ClipData getClipData(); - method public android.content.Intent getIntent(); method public android.net.Uri getWebUri(); method public void setClipData(android.content.ClipData); method public void setIntent(android.content.Intent); method public void setWebUri(android.net.Uri); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.app.AssistContent> CREATOR; } - public final class AssistStructure implements android.os.Parcelable { - method public int describeContents(); + public deprecated class AssistStructure { + ctor public AssistStructure(); method public android.content.ComponentName getActivityComponent(); - method public android.app.AssistStructure.WindowNode getWindowNodeAt(int); method public int getWindowNodeCount(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.app.AssistStructure> CREATOR; } public static class AssistStructure.ViewNode { @@ -4384,6 +4377,7 @@ package android.app { method public void setSharedElementReturnTransition(android.transition.Transition); method public void setTargetFragment(android.app.Fragment, int); method public void setUserVisibleHint(boolean); + method public boolean shouldShowRequestPermissionRationale(java.lang.String); method public void startActivity(android.content.Intent); method public void startActivity(android.content.Intent, android.os.Bundle); method public void startActivityForResult(android.content.Intent, int); @@ -5899,6 +5893,26 @@ package android.app.admin { } +package android.app.assist { + + public final class AssistContent extends android.app.AssistContent implements android.os.Parcelable { + ctor public AssistContent(android.os.Parcel); + method public int describeContents(); + method public android.content.Intent getIntent(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.assist.AssistContent> CREATOR; + } + + public final class AssistStructure extends android.app.AssistStructure implements android.os.Parcelable { + ctor public AssistStructure(); + method public int describeContents(); + method public android.app.AssistStructure.WindowNode getWindowNodeAt(int); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.assist.AssistStructure> CREATOR; + } + +} + package android.app.backup { public abstract class BackupAgent extends android.content.ContextWrapper { @@ -13191,6 +13205,7 @@ package android.hardware.camera2 { method public abstract void close(); method public abstract android.hardware.camera2.CameraDevice getDevice(); method public abstract android.view.Surface getInputSurface(); + method public abstract boolean isConstrainedHighSpeed(); method public abstract boolean isReprocessable(); method public abstract void prepare(android.view.Surface) throws android.hardware.camera2.CameraAccessException; method public abstract int setRepeatingBurst(java.util.List<android.hardware.camera2.CaptureRequest>, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; @@ -13311,6 +13326,8 @@ package android.hardware.camera2 { 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 createConstrainedHighSpeedCaptureSession(java.util.List<android.view.Surface>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; + method public abstract java.util.List<android.hardware.camera2.CaptureRequest> createConstrainedHighSpeedRequestList(android.hardware.camera2.CaptureRequest) 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 java.lang.String getId(); @@ -13441,7 +13458,7 @@ package android.hardware.camera2 { field public static final int CONTROL_SCENE_MODE_FACE_PRIORITY = 1; // 0x1 field public static final int CONTROL_SCENE_MODE_FIREWORKS = 12; // 0xc field public static final int CONTROL_SCENE_MODE_HDR = 18; // 0x12 - field public static final int CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO = 17; // 0x11 + field public static final deprecated int CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO = 17; // 0x11 field public static final int CONTROL_SCENE_MODE_LANDSCAPE = 4; // 0x4 field public static final int CONTROL_SCENE_MODE_NIGHT = 5; // 0x5 field public static final int CONTROL_SCENE_MODE_NIGHT_PORTRAIT = 6; // 0x6 @@ -13488,6 +13505,7 @@ package android.hardware.camera2 { field public static final int NOISE_REDUCTION_MODE_OFF = 0; // 0x0 field public static final int REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE = 0; // 0x0 field public static final int REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE = 6; // 0x6 + field public static final int REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO = 9; // 0x9 field public static final int REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT = 8; // 0x8 field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING = 2; // 0x2 field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR = 1; // 0x1 @@ -28930,7 +28948,7 @@ package android.service.voice { method public android.view.View onCreateContentView(); method public void onDestroy(); method public boolean[] onGetSupportedCommands(java.lang.String[]); - method public void onHandleAssist(android.os.Bundle, android.app.AssistStructure, android.app.AssistContent); + method public void onHandleAssist(android.os.Bundle, android.app.assist.AssistStructure, android.app.assist.AssistContent); method public void onHide(); method public boolean onKeyDown(int, android.view.KeyEvent); method public boolean onKeyLongPress(int, android.view.KeyEvent); @@ -32454,7 +32472,7 @@ package android.text { method public android.text.StaticLayout.Builder setLineSpacing(float, float); method public android.text.StaticLayout.Builder setMaxLines(int); method public android.text.StaticLayout.Builder setText(java.lang.CharSequence); - method public android.text.StaticLayout.Builder setTextDir(android.text.TextDirectionHeuristic); + method public android.text.StaticLayout.Builder setTextDirection(android.text.TextDirectionHeuristic); } public abstract interface TextDirectionHeuristic { @@ -33984,6 +34002,7 @@ package android.util { method public void setTo(android.util.DisplayMetrics); method public void setToDefaults(); field public static final int DENSITY_280 = 280; // 0x118 + field public static final int DENSITY_360 = 360; // 0x168 field public static final int DENSITY_400 = 400; // 0x190 field public static final int DENSITY_560 = 560; // 0x230 field public static final int DENSITY_DEFAULT = 160; // 0xa0 diff --git a/api/system-current.txt b/api/system-current.txt index 65ca29a..d11fe85 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -3531,7 +3531,7 @@ package android.app { method public void onPrepareNavigateUpTaskStack(android.app.TaskStackBuilder); method public boolean onPrepareOptionsMenu(android.view.Menu); method public boolean onPreparePanel(int, android.view.View, android.view.Menu); - method public void onProvideAssistContent(android.app.AssistContent); + method public void onProvideAssistContent(android.app.assist.AssistContent); method public void onProvideAssistData(android.os.Bundle); method public void onRequestPermissionsResult(int, java.lang.String[], int[]); method protected void onRestart(); @@ -4092,26 +4092,19 @@ package android.app { field public java.lang.String serviceDetails; } - public class AssistContent implements android.os.Parcelable { + public deprecated class AssistContent { ctor public AssistContent(); - method public int describeContents(); method public android.content.ClipData getClipData(); - method public android.content.Intent getIntent(); method public android.net.Uri getWebUri(); method public void setClipData(android.content.ClipData); method public void setIntent(android.content.Intent); method public void setWebUri(android.net.Uri); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.app.AssistContent> CREATOR; } - public final class AssistStructure implements android.os.Parcelable { - method public int describeContents(); + public deprecated class AssistStructure { + ctor public AssistStructure(); method public android.content.ComponentName getActivityComponent(); - method public android.app.AssistStructure.WindowNode getWindowNodeAt(int); method public int getWindowNodeCount(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.app.AssistStructure> CREATOR; } public static class AssistStructure.ViewNode { @@ -4480,6 +4473,7 @@ package android.app { method public void setSharedElementReturnTransition(android.transition.Transition); method public void setTargetFragment(android.app.Fragment, int); method public void setUserVisibleHint(boolean); + method public boolean shouldShowRequestPermissionRationale(java.lang.String); method public void startActivity(android.content.Intent); method public void startActivity(android.content.Intent, android.os.Bundle); method public void startActivityForResult(android.content.Intent, int); @@ -6014,6 +6008,26 @@ package android.app.admin { } +package android.app.assist { + + public final class AssistContent extends android.app.AssistContent implements android.os.Parcelable { + ctor public AssistContent(android.os.Parcel); + method public int describeContents(); + method public android.content.Intent getIntent(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.assist.AssistContent> CREATOR; + } + + public final class AssistStructure extends android.app.AssistStructure implements android.os.Parcelable { + ctor public AssistStructure(); + method public int describeContents(); + method public android.app.AssistStructure.WindowNode getWindowNodeAt(int); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.assist.AssistStructure> CREATOR; + } + +} + package android.app.backup { public abstract class BackupAgent extends android.content.ContextWrapper { @@ -13509,6 +13523,7 @@ package android.hardware.camera2 { method public abstract void close(); method public abstract android.hardware.camera2.CameraDevice getDevice(); method public abstract android.view.Surface getInputSurface(); + method public abstract boolean isConstrainedHighSpeed(); method public abstract boolean isReprocessable(); method public abstract void prepare(android.view.Surface) throws android.hardware.camera2.CameraAccessException; method public abstract int setRepeatingBurst(java.util.List<android.hardware.camera2.CaptureRequest>, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; @@ -13629,6 +13644,8 @@ package android.hardware.camera2 { 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 createConstrainedHighSpeedCaptureSession(java.util.List<android.view.Surface>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; + method public abstract java.util.List<android.hardware.camera2.CaptureRequest> createConstrainedHighSpeedRequestList(android.hardware.camera2.CaptureRequest) 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 java.lang.String getId(); @@ -13759,7 +13776,7 @@ package android.hardware.camera2 { field public static final int CONTROL_SCENE_MODE_FACE_PRIORITY = 1; // 0x1 field public static final int CONTROL_SCENE_MODE_FIREWORKS = 12; // 0xc field public static final int CONTROL_SCENE_MODE_HDR = 18; // 0x12 - field public static final int CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO = 17; // 0x11 + field public static final deprecated int CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO = 17; // 0x11 field public static final int CONTROL_SCENE_MODE_LANDSCAPE = 4; // 0x4 field public static final int CONTROL_SCENE_MODE_NIGHT = 5; // 0x5 field public static final int CONTROL_SCENE_MODE_NIGHT_PORTRAIT = 6; // 0x6 @@ -13806,6 +13823,7 @@ package android.hardware.camera2 { field public static final int NOISE_REDUCTION_MODE_OFF = 0; // 0x0 field public static final int REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE = 0; // 0x0 field public static final int REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE = 6; // 0x6 + field public static final int REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO = 9; // 0x9 field public static final int REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT = 8; // 0x8 field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING = 2; // 0x2 field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR = 1; // 0x1 @@ -31063,7 +31081,7 @@ package android.service.voice { method public android.view.View onCreateContentView(); method public void onDestroy(); method public boolean[] onGetSupportedCommands(java.lang.String[]); - method public void onHandleAssist(android.os.Bundle, android.app.AssistStructure, android.app.AssistContent); + method public void onHandleAssist(android.os.Bundle, android.app.assist.AssistStructure, android.app.assist.AssistContent); method public void onHide(); method public boolean onKeyDown(int, android.view.KeyEvent); method public boolean onKeyLongPress(int, android.view.KeyEvent); @@ -34719,7 +34737,7 @@ package android.text { method public android.text.StaticLayout.Builder setLineSpacing(float, float); method public android.text.StaticLayout.Builder setMaxLines(int); method public android.text.StaticLayout.Builder setText(java.lang.CharSequence); - method public android.text.StaticLayout.Builder setTextDir(android.text.TextDirectionHeuristic); + method public android.text.StaticLayout.Builder setTextDirection(android.text.TextDirectionHeuristic); } public abstract interface TextDirectionHeuristic { @@ -36249,6 +36267,7 @@ package android.util { method public void setTo(android.util.DisplayMetrics); method public void setToDefaults(); field public static final int DENSITY_280 = 280; // 0x118 + field public static final int DENSITY_360 = 360; // 0x168 field public static final int DENSITY_400 = 400; // 0x190 field public static final int DENSITY_560 = 560; // 0x230 field public static final int DENSITY_DEFAULT = 160; // 0xa0 diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java index 250b002..cdf7429 100644 --- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java +++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java @@ -20,6 +20,7 @@ import android.content.ComponentName; import android.content.Context; import android.os.RemoteException; import android.os.ServiceManager; +import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.text.TextUtils; @@ -39,8 +40,10 @@ public final class Telecom extends BaseCommand { (new Telecom()).run(args); } - private static final String COMMAND_SET_PHONE_ACOUNT_ENABLED = "set-phone-account-enabled"; - private static final String COMMAND_SET_PHONE_ACOUNT_DISABLED = "set-phone-account-disabled"; + private static final String COMMAND_SET_PHONE_ACCOUNT_ENABLED = "set-phone-account-enabled"; + private static final String COMMAND_SET_PHONE_ACCOUNT_DISABLED = "set-phone-account-disabled"; + private static final String COMMAND_REGISTER_PHONE_ACCOUNT = "register-phone-account"; + private static final String COMMAND_UNREGISTER_PHONE_ACCOUNT = "unregister-phone-account"; private static final String COMMAND_SET_DEFAULT_DIALER = "set-default-dialer"; private static final String COMMAND_GET_DEFAULT_DIALER = "get-default-dialer"; @@ -54,6 +57,8 @@ public final class Telecom extends BaseCommand { "usage: telecom [subcommand] [options]\n" + "usage: telecom set-phone-account-enabled <COMPONENT> <ID>\n" + "usage: telecom set-phone-account-disabled <COMPONENT> <ID>\n" + + "usage: telecom register-phone-account <COMPONENT> <ID> <LABEL>\n" + + "usage: telecom unregister-phone-account <COMPONENT> <ID>\n" + "usage: telecom set-default-dialer <PACKAGE>\n" + "usage: telecom get-default-dialer <PACKAGE>\n" + "\n" + @@ -63,9 +68,9 @@ public final class Telecom extends BaseCommand { "telecom set-phone-account-disabled: Disables the given phone account, if it \n" + " has already been registered with telecom.\n" + "\n" + - "telecom set-default_dialer: Sets the default dialer to the given component. \n" + + "telecom set-default-dialer: Sets the default dialer to the given component. \n" + "\n" + - "telecom get-default_dialer: Displays the current default dialer. \n" + "telecom get-default-dialer: Displays the current default dialer. \n" ); } @@ -80,12 +85,18 @@ public final class Telecom extends BaseCommand { String command = nextArgRequired(); switch (command) { - case COMMAND_SET_PHONE_ACOUNT_ENABLED: + case COMMAND_SET_PHONE_ACCOUNT_ENABLED: runSetPhoneAccountEnabled(true); break; - case COMMAND_SET_PHONE_ACOUNT_DISABLED: + case COMMAND_SET_PHONE_ACCOUNT_DISABLED: runSetPhoneAccountEnabled(false); break; + case COMMAND_REGISTER_PHONE_ACCOUNT: + runRegisterPhoneAccount(); + break; + case COMMAND_UNREGISTER_PHONE_ACCOUNT: + runUnregisterPhoneAccount(); + break; case COMMAND_SET_DEFAULT_DIALER: runSetDefaultDialer(); break; @@ -98,9 +109,7 @@ public final class Telecom extends BaseCommand { } private void runSetPhoneAccountEnabled(boolean enabled) throws RemoteException { - final ComponentName component = parseComponentName(nextArgRequired()); - final String accountId = nextArgRequired(); - final PhoneAccountHandle handle = new PhoneAccountHandle(component, accountId); + final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs(); final boolean success = mTelecomService.enablePhoneAccount(handle, enabled); if (success) { System.out.println("Success - " + handle + (enabled ? " enabled." : " disabled.")); @@ -109,6 +118,21 @@ public final class Telecom extends BaseCommand { } } + private void runRegisterPhoneAccount() throws RemoteException { + final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs(); + final String label = nextArgRequired(); + PhoneAccount account = PhoneAccount.builder(handle, label) + .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER).build(); + mTelecomService.registerPhoneAccount(account); + System.out.println("Success - " + handle + " registered."); + } + + private void runUnregisterPhoneAccount() throws RemoteException { + final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs(); + mTelecomService.unregisterPhoneAccount(handle); + System.out.println("Success - " + handle + " unregistered."); + } + private void runSetDefaultDialer() throws RemoteException { final String packageName = nextArgRequired(); final boolean success = mTelecomService.setDefaultDialer(packageName); @@ -124,6 +148,12 @@ public final class Telecom extends BaseCommand { System.out.println(mTelecomService.getDefaultDialerPackage()); } + private PhoneAccountHandle getPhoneAccountHandleFromArgs() { + final ComponentName component = parseComponentName(nextArgRequired()); + final String accountId = nextArgRequired(); + return new PhoneAccountHandle(component, accountId); + } + private ComponentName parseComponentName(String component) { ComponentName cn = ComponentName.unflattenFromString(component); if (cn == null) { diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 073fefc..90567c7 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -38,6 +38,7 @@ import com.android.internal.app.ToolbarActionBar; import android.annotation.SystemApi; import android.app.admin.DevicePolicyManager; +import android.app.assist.AssistContent; import android.content.ComponentCallbacks2; import android.content.ComponentName; import android.content.ContentResolver; diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index e4def1e..b6cec60 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -17,6 +17,8 @@ package android.app; import android.app.ActivityManager.StackInfo; +import android.app.assist.AssistContent; +import android.app.assist.AssistStructure; import android.content.ComponentName; import android.content.IIntentReceiver; import android.content.IIntentSender; diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 2a98b6c..3224d41 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -16,6 +16,8 @@ package android.app; +import android.app.assist.AssistContent; +import android.app.assist.AssistStructure; import android.app.backup.BackupAgent; import android.content.BroadcastReceiver; import android.content.ComponentCallbacks2; diff --git a/core/java/android/app/AssistContent.java b/core/java/android/app/AssistContent.java index f271af1..4cb89a8 100644 --- a/core/java/android/app/AssistContent.java +++ b/core/java/android/app/AssistContent.java @@ -26,9 +26,11 @@ import android.os.Parcelable; /** * Holds information about the content an application is viewing, to hand to an * assistant at the user's request. This is filled in by - * {@link Activity#onProvideAssistContent Activity.onProvideAssistContent}. + * {@link android.app.Activity#onProvideAssistContent Activity.onProvideAssistContent}. + * @deprecated use {@link android.app.assist.AssistContent}. */ -public class AssistContent implements Parcelable { +@Deprecated +public class AssistContent { private Intent mIntent; private ClipData mClipData; private Uri mUri; @@ -36,16 +38,16 @@ public class AssistContent implements Parcelable { /** * @hide * Key name this data structure is stored in the Bundle generated by - * {@link Activity#onProvideAssistData}. + * {@link android.app.Activity#onProvideAssistData}. */ public static final String ASSIST_KEY = "android:assist_content"; /** * @hide * Retrieve the framework-generated AssistContent that is stored within - * the Bundle filled in by {@link Activity#onProvideAssistContent}. + * the Bundle filled in by {@link android.app.Activity#onProvideAssistContent}. */ - public static AssistContent getAssistContent(Bundle assistBundle) { + public static android.app.assist.AssistContent getAssistContent(Bundle assistBundle) { return assistBundle.getParcelable(ASSIST_KEY); } @@ -71,6 +73,7 @@ public class AssistContent implements Parcelable { /** * Return the current {@link #setIntent}, which you can modify in-place. + * @hide */ public Intent getIntent() { return mIntent; @@ -116,7 +119,8 @@ public class AssistContent implements Parcelable { return mUri; } - AssistContent(Parcel in) { + /** @hide */ + public AssistContent(Parcel in) { if (in.readInt() != 0) { mIntent = Intent.CREATOR.createFromParcel(in); } @@ -128,13 +132,8 @@ public class AssistContent implements Parcelable { } } - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { + /** @hide */ + public void writeToParcelInternal(Parcel dest, int flags) { if (mIntent != null) { dest.writeInt(1); mIntent.writeToParcel(dest, flags); @@ -154,15 +153,4 @@ public class AssistContent implements Parcelable { dest.writeInt(0); } } - - public static final Parcelable.Creator<AssistContent> CREATOR - = new Parcelable.Creator<AssistContent>() { - public AssistContent createFromParcel(Parcel in) { - return new AssistContent(in); - } - - public AssistContent[] newArray(int size) { - return new AssistContent[size]; - } - }; } diff --git a/core/java/android/app/AssistStructure.java b/core/java/android/app/AssistStructure.java index ca47a5e..ef7fde4 100644 --- a/core/java/android/app/AssistStructure.java +++ b/core/java/android/app/AssistStructure.java @@ -17,9 +17,7 @@ package android.app; import android.content.ComponentName; -import android.graphics.Paint; import android.graphics.Rect; -import android.graphics.Typeface; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; @@ -29,7 +27,6 @@ import android.os.PooledStringReader; import android.os.PooledStringWriter; import android.os.RemoteException; import android.os.SystemClock; -import android.text.TextPaint; import android.text.TextUtils; import android.util.Log; import android.view.View; @@ -42,19 +39,22 @@ import java.util.ArrayList; /** * Assist data automatically created by the platform's implementation - * of {@link Activity#onProvideAssistData}. + * of {@link android.app.Activity#onProvideAssistData}. + * @deprecated use {@link android.app.assist.AssistStructure}. */ -final public class AssistStructure implements Parcelable { +@Deprecated +public class AssistStructure { static final String TAG = "AssistStructure"; /** * @hide * Key name this data structure is stored in the Bundle generated by - * {@link Activity#onProvideAssistData}. + * {@link android.app.Activity#onProvideAssistData}. */ public static final String ASSIST_KEY = "android:assist_structure"; - boolean mHaveData; + /** @hide */ + public boolean mHaveData; ComponentName mActivityComponent; @@ -62,15 +62,18 @@ final public class AssistStructure implements Parcelable { final ArrayList<ViewNodeBuilder> mPendingAsyncChildren = new ArrayList<>(); - SendChannel mSendChannel; - IBinder mReceiveChannel; + /** @hide */ + public SendChannel mSendChannel; + /** @hide */ + public IBinder mReceiveChannel; Rect mTmpRect = new Rect(); static final int TRANSACTION_XFER = Binder.FIRST_CALL_TRANSACTION+1; static final String DESCRIPTOR = "android.app.AssistStructure"; - final class SendChannel extends Binder { + /** @hide */ + public final class SendChannel extends Binder { @Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { if (code == TRANSACTION_XFER) { @@ -702,7 +705,8 @@ final public class AssistStructure implements Parcelable { } } - AssistStructure(Activity activity) { + /** @hide */ + public AssistStructure(Activity activity) { mHaveData = true; mActivityComponent = activity.getComponentName(); ArrayList<ViewRootImpl> views = WindowManagerGlobal.getInstance().getRootViews( @@ -713,12 +717,13 @@ final public class AssistStructure implements Parcelable { } } - AssistStructure() { + public AssistStructure() { mHaveData = true; mActivityComponent = null; } - AssistStructure(Parcel in) { + /** @hide */ + public AssistStructure(Parcel in) { mReceiveChannel = in.readStrongBinder(); } @@ -792,7 +797,7 @@ final public class AssistStructure implements Parcelable { * Retrieve the framework-generated AssistStructure that is stored within * the Bundle filled in by {@link Activity#onProvideAssistData}. */ - public static AssistStructure getAssistStructure(Bundle assistBundle) { + public static android.app.assist.AssistStructure getAssistStructure(Bundle assistBundle) { return assistBundle.getParcelable(ASSIST_KEY); } @@ -812,16 +817,13 @@ final public class AssistStructure implements Parcelable { /** * Return one of the windows in the assist data. * @param index Which window to retrieve, may be 0 to {@link #getWindowNodeCount()}-1. + * @hide */ public WindowNode getWindowNodeAt(int index) { ensureData(); return mWindowNodes.get(index); } - public int describeContents() { - return 0; - } - /** @hide */ public void ensureData() { if (mHaveData) { @@ -880,29 +882,4 @@ final public class AssistStructure implements Parcelable { } //dump(); } - - public void writeToParcel(Parcel out, int flags) { - if (mHaveData) { - // This object holds its data. We want to write a send channel that the - // other side can use to retrieve that data. - if (mSendChannel == null) { - mSendChannel = new SendChannel(); - } - out.writeStrongBinder(mSendChannel); - } else { - // This object doesn't hold its data, so just propagate along its receive channel. - out.writeStrongBinder(mReceiveChannel); - } - } - - public static final Parcelable.Creator<AssistStructure> CREATOR - = new Parcelable.Creator<AssistStructure>() { - public AssistStructure createFromParcel(Parcel in) { - return new AssistStructure(in); - } - - public AssistStructure[] newArray(int size) { - return new AssistStructure[size]; - } - }; } diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java index 1fb88a9..02e26a5 100644 --- a/core/java/android/app/BackStackRecord.java +++ b/core/java/android/app/BackStackRecord.java @@ -1107,6 +1107,7 @@ final class BackStackRecord extends FragmentTransaction implements } if (enterTransition != null) { + enterTransition.removeTarget(state.nonExistentView); View view = inFragment.getView(); if (view != null) { view.captureTransitioningViews(enteringViews); @@ -1115,7 +1116,6 @@ final class BackStackRecord extends FragmentTransaction implements } enteringViews.add(state.nonExistentView); // We added this earlier to prevent any views being targeted. - enterTransition.removeTarget(state.nonExistentView); addTargets(enterTransition, enteringViews); } setSharedElementEpicenter(enterTransition, state); @@ -1170,7 +1170,7 @@ final class BackStackRecord extends FragmentTransaction implements Transition exitTransition, Transition sharedElementTransition, Fragment inFragment, boolean isBack) { boolean overlap = true; - if (enterTransition != null && exitTransition != null) { + if (enterTransition != null && exitTransition != null && inFragment != null) { overlap = isBack ? inFragment.getAllowReturnTransitionOverlap() : inFragment.getAllowEnterTransitionOverlap(); } @@ -1638,7 +1638,7 @@ final class BackStackRecord extends FragmentTransaction implements private void setBackNameOverrides(TransitionState state, ArrayMap<String, View> namedViews, boolean isEnd) { - int count = mSharedElementTargetNames.size(); + int count = mSharedElementTargetNames == null ? 0 : mSharedElementTargetNames.size(); for (int i = 0; i < count; i++) { String source = mSharedElementSourceNames.get(i); String originalTarget = mSharedElementTargetNames.get(i); @@ -1656,7 +1656,7 @@ final class BackStackRecord extends FragmentTransaction implements private void setNameOverrides(TransitionState state, ArrayMap<String, View> namedViews, boolean isEnd) { - int count = namedViews.size(); + int count = namedViews == null ? 0 : namedViews.size(); for (int i = 0; i < count; i++) { String source = namedViews.keyAt(i); String target = namedViews.valueAt(i).getTransitionName(); diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index 40c5c64..26d4fd4 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -1223,6 +1223,33 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene } /** + * Gets whether you should show UI with rationale for requesting a permission. + * You should do this only if you do not have the permission and the context in + * which the permission is requested does not clearly communicate to the user + * what would be the benefit from granting this permission. + * <p> + * For example, if you write a camera app, requesting the camera permission + * would be expected by the user and no rationale for why it is requested is + * needed. If however, the app needs location for tagging photos then a non-tech + * savvy user may wonder how location is related to taking photos. In this case + * you may choose to show UI with rationale of requesting this permission. + * </p> + * + * @param permission A permission your app wants to request. + * @return Whether you can show permission rationale UI. + * + * @see Context#checkSelfPermission(String) + * @see #requestPermissions(String[], int) + * @see #onRequestPermissionsResult(int, String[], int[]) + */ + public boolean shouldShowRequestPermissionRationale(@NonNull String permission) { + if (mHost != null) { + mHost.getContext().getPackageManager().shouldShowRequestPermissionRationale(permission); + } + return false; + } + + /** * @hide Hack so that DialogFragment can make its Dialog before creating * its views, and the view construction can use the dialog's context for * inflation. Maybe this should become a public API. Note sure. diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 0a425ae..249cdb2 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -19,6 +19,8 @@ package android.app; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityManager.RunningServiceInfo; import android.app.ActivityManager.StackInfo; +import android.app.assist.AssistContent; +import android.app.assist.AssistStructure; import android.content.ComponentName; import android.content.ContentProviderNative; import android.content.IContentProvider; diff --git a/core/java/android/app/AssistContent.aidl b/core/java/android/app/assist/AssistContent.aidl index a6321bf..24379bb 100644 --- a/core/java/android/app/AssistContent.aidl +++ b/core/java/android/app/assist/AssistContent.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package android.app; +package android.app.assist; parcelable AssistContent; diff --git a/core/java/android/app/assist/AssistContent.java b/core/java/android/app/assist/AssistContent.java new file mode 100644 index 0000000..c7e7330 --- /dev/null +++ b/core/java/android/app/assist/AssistContent.java @@ -0,0 +1,44 @@ +package android.app.assist; + +import android.content.Intent; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * New home for AssistContent. + */ +public final class AssistContent extends android.app.AssistContent implements Parcelable { + + /** @hide */ + public AssistContent() { + } + + public AssistContent(Parcel in) { + super(in); + } + + public Intent getIntent() { + return super.getIntent(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + public static final Parcelable.Creator<AssistContent> CREATOR + = new Parcelable.Creator<AssistContent>() { + public AssistContent createFromParcel(Parcel in) { + return new AssistContent(in); + } + + public AssistContent[] newArray(int size) { + return new AssistContent[size]; + } + }; +} diff --git a/core/java/android/app/AssistStructure.aidl b/core/java/android/app/assist/AssistStructure.aidl index 07fb2453..ae0a34c 100644 --- a/core/java/android/app/AssistStructure.aidl +++ b/core/java/android/app/assist/AssistStructure.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package android.app; +package android.app.assist; parcelable AssistStructure; diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java new file mode 100644 index 0000000..1677e95 --- /dev/null +++ b/core/java/android/app/assist/AssistStructure.java @@ -0,0 +1,56 @@ +package android.app.assist; + +import android.app.Activity; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * New home for AssistStructure. + */ +public final class AssistStructure extends android.app.AssistStructure implements Parcelable { + + public AssistStructure() { + } + + /** @hide */ + public AssistStructure(Activity activity) { + super(activity); + } + + AssistStructure(Parcel in) { + super(in); + } + + public WindowNode getWindowNodeAt(int index) { + return super.getWindowNodeAt(index); + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel out, int flags) { + if (mHaveData) { + // This object holds its data. We want to write a send channel that the + // other side can use to retrieve that data. + if (mSendChannel == null) { + mSendChannel = new SendChannel(); + } + out.writeStrongBinder(mSendChannel); + } else { + // This object doesn't hold its data, so just propagate along its receive channel. + out.writeStrongBinder(mReceiveChannel); + } + } + + public static final Parcelable.Creator<AssistStructure> CREATOR + = new Parcelable.Creator<AssistStructure>() { + public AssistStructure createFromParcel(Parcel in) { + return new AssistStructure(in); + } + + public AssistStructure[] newArray(int size) { + return new AssistStructure[size]; + } + }; +} diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java index c22ee5f..82d40d3 100644 --- a/core/java/android/hardware/camera2/CameraCaptureSession.java +++ b/core/java/android/hardware/camera2/CameraCaptureSession.java @@ -471,6 +471,17 @@ public abstract class CameraCaptureSession implements AutoCloseable { public abstract boolean isReprocessable(); /** + * Return if this capture session is constrained high speed session that is created by + * {@link CameraDevice#createConstrainedHighSpeedCaptureSession}. + * + * @return {@code true} if this session is constrained high speed capture session, + * {@code false} otherwise. + * + * @see CameraDevice#createConstrainedHighSpeedCaptureSession + */ + public abstract boolean isConstrainedHighSpeed(); + + /** * Get the input Surface associated with a reprocessable capture session. * * <p>Each reprocessable capture session has an input {@link Surface} where the reprocess diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index d5867a9..b69ca88 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -626,35 +626,54 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<Integer>("android.control.maxRegionsAf", int.class); /** - * <p>List of available high speed video size and fps range configurations - * supported by the camera device, in the format of (width, height, fps_min, fps_max).</p> - * <p>When HIGH_SPEED_VIDEO is supported in {@link CameraCharacteristics#CONTROL_AVAILABLE_SCENE_MODES android.control.availableSceneModes}, this metadata - * will list the supported high speed video size and fps range configurations. All the sizes - * listed in this configuration will be a subset of the sizes reported by {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputSizes } for processed - * non-stalling formats.</p> - * <p>For the high speed video use case, where the application will set - * {@link CaptureRequest#CONTROL_SCENE_MODE android.control.sceneMode} to HIGH_SPEED_VIDEO in capture requests, the application must + * <p>List of available high speed video size, fps range and max batch size configurations + * supported by the camera device, in the format of (width, height, fps_min, fps_max, batch_size_max).</p> + * <p>When CONSTRAINED_HIGH_SPEED_VIDEO is supported in android.control.availableCapabilities, + * this metadata will list the supported high speed video size, fps range and max batch size + * configurations. All the sizes listed in this configuration will be a subset of the sizes + * reported by {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputSizes } + * for processed non-stalling formats.</p> + * <p>For the high speed video use case, the application must * select the video size and fps range from this metadata to configure the recording and * preview streams and setup the recording requests. For example, if the application intends * to do high speed recording, it can select the maximum size reported by this metadata to * configure output streams. Once the size is selected, application can filter this metadata * by selected size and get the supported fps ranges, and use these fps ranges to setup the * recording requests. Note that for the use case of multiple output streams, application - * must select one unique size from this metadata to use. Otherwise a request error might - * occur.</p> - * <p>For normal video recording use case, where some application will NOT set - * {@link CaptureRequest#CONTROL_SCENE_MODE android.control.sceneMode} to HIGH_SPEED_VIDEO in capture requests, the fps ranges - * reported in this metadata must not be used to setup capture requests, or it will cause - * request error.</p> + * must select one unique size from this metadata to use (e.g., preview and recording streams + * must have the same size). Otherwise, the high speed capture session creation will fail.</p> + * <p>The min and max fps will be multiple times of 30fps.</p> + * <p>High speed video streaming extends significant performance pressue to camera hardware, + * to achieve efficient high speed streaming, the camera device may have to aggregate + * multiple frames together and send to camera device for processing where the request + * controls are same for all the frames in this batch. Max batch size indicates + * the max possible number of frames the camera device will group together for this high + * speed stream configuration. This max batch size will be used to generate a high speed + * recording request list by + * {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedRequestList }. + * The max batch size for each configuration will satisfy below conditions:</p> + * <ul> + * <li>Each max batch size will be a divisor of its corresponding fps_max / 30. For example, + * if max_fps is 300, max batch size will only be 1, 2, 5, or 10.</li> + * <li>The camera device may choose smaller internal batch size for each configuration, but + * the actual batch size will be a divisor of max batch size. For example, if the max batch + * size is 8, the actual batch size used by camera device will only be 1, 2, 4, or 8.</li> + * <li>The max batch size in each configuration entry must be no larger than 32.</li> + * </ul> + * <p>The camera device doesn't have to support batch mode to achieve high speed video recording, + * in such case, batch_size_max will be reported as 1 in each configuration entry.</p> + * <p>This fps ranges in this configuration list can only be used to create requests + * that are submitted to a high speed camera capture session created by + * {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedCaptureSession }. + * The fps ranges reported in this metadata must not be used to setup capture requests for + * normal capture session, or it will cause request error.</p> * <p><b>Range of valid values:</b><br></p> - * <p>For each configuration, the fps_max >= 60fps.</p> + * <p>For each configuration, the fps_max >= 120fps.</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * <p><b>Limited capability</b> - * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> * - * @see CameraCharacteristics#CONTROL_AVAILABLE_SCENE_MODES - * @see CaptureRequest#CONTROL_SCENE_MODE * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL * @hide */ @@ -1003,12 +1022,33 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <p>Position of the camera optical center.</p> - * <p>As measured in the device sensor coordinate system, the - * position of the camera device's optical center, as a - * three-dimensional vector <code>(x,y,z)</code>.</p> - * <p>To transform a world position to a camera-device centered - * coordinate system, the position must be translated by this - * vector and then rotated by {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}.</p> + * <p>The position of the camera device's lens optical center, + * as a three-dimensional vector <code>(x,y,z)</code>, relative to the + * optical center of the largest camera device facing in the + * same direction as this camera, in the {@link android.hardware.SensorEvent Android sensor coordinate + * axes}. Note that only the axis definitions are shared with + * the sensor coordinate system, but not the origin.</p> + * <p>If this device is the largest or only camera device with a + * given facing, then this position will be <code>(0, 0, 0)</code>; a + * camera device with a lens optical center located 3 cm from + * the main sensor along the +X axis (to the right from the + * user's perspective) will report <code>(0.03, 0, 0)</code>.</p> + * <p>To transform a pixel coordinates between two cameras + * facing the same direction, first the source camera + * android.lens.radialDistortion must be corrected for. Then + * the source camera android.lens.intrinsicCalibration needs + * to be applied, followed by the {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} + * of the source camera, the translation of the source camera + * relative to the destination camera, the + * {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} of the destination camera, and + * finally the inverse of android.lens.intrinsicCalibration + * of the destination camera. This obtains a + * radial-distortion-free coordinate in the destination + * camera pixel coordinates.</p> + * <p>To compare this against a real image from the destination + * camera, the destination camera image then needs to be + * corrected for radial distortion before comparison or + * sampling.</p> * <p><b>Units</b>: Meters</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * @@ -1308,7 +1348,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * the max pipeline depth.</p> * <p>A pipeline depth of X stages is equivalent to a pipeline latency of * X frame intervals.</p> - * <p>This value will be 8 or less.</p> + * <p>This value will normally be 8 or less, however, for high speed capture session, + * the max pipeline depth will be up to 8 x size of high speed capture request list.</p> * <p>This key is available on all devices.</p> * * @see CaptureResult#REQUEST_PIPELINE_DEPTH @@ -1371,6 +1412,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE BURST_CAPTURE}</li> * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING YUV_REPROCESSING}</li> * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT DEPTH_OUTPUT}</li> + * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO CONSTRAINED_HIGH_SPEED_VIDEO}</li> * </ul></p> * <p>This key is available on all devices.</p> * @@ -1384,6 +1426,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * @see #REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE * @see #REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING * @see #REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT + * @see #REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO */ @PublicKey public static final Key<int[]> REQUEST_AVAILABLE_CAPABILITIES = diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index d02f349..006030c 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -583,6 +583,147 @@ public abstract class CameraDevice implements AutoCloseable { throws CameraAccessException; /** + * <p>Create a new constrained high speed capture session.</p> + * + * <p>The application can use normal capture session (created via {@link #createCaptureSession}) + * for high speed capture if the desired high speed FPS ranges are advertised by + * {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES}, in which case all API + * semantics associated with normal capture sessions applies.</p> + * + * <p>The method creates a specialized capture session that is only targeted at high speed + * video recording (>=120fps) use case if the camera device supports high speed video + * capability (i.e., {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES} contains + * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO}). + * Therefore, it has special characteristics compared with a normal capture session:</p> + * + * <ul> + * + * <li>In addition to the output target Surface requirements specified by the + * {@link #createCaptureSession} method, an active high speed capture session will support up + * to 2 output Surfaces, though the application might choose to configure just one Surface + * (e.g., preview only). All Surfaces must be either video encoder surfaces (acquired by + * {@link android.media.MediaRecorder#getSurface} or + * {@link android.media.MediaCodec#createInputSurface}) or preview surfaces (obtained from + * {@link android.view.SurfaceView}, {@link android.graphics.SurfaceTexture} via + * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}). The Surface sizes + * must be one of the sizes reported by {@link StreamConfigurationMap#getHighSpeedVideoSizes}. + * When multiple Surfaces are configured, their size must be same.</li> + * + * <li>An active high speed capture session only accepts request lists created via + * {@link #createConstrainedHighSpeedRequestList}, and the request list can only be submitted + * to this session via {@link CameraCaptureSession#captureBurst captureBurst}, or + * {@link CameraCaptureSession#setRepeatingBurst setRepeatingBurst}.</li> + * + * <li>The FPS ranges being requested to this session must be selected from + * {@link StreamConfigurationMap#getHighSpeedVideoFpsRangesFor}. The application can still use + * {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE} to control the desired FPS range. + * Switching to an FPS range that has different + * {@link android.util.Range#getUpper() maximum FPS} may trigger some camera device + * reconfigurations, which may introduce extra latency. It is recommended that the + * application avoids unnecessary maximum target FPS changes as much as possible during high + * speed streaming.</li> + * + * <li>For the request lists submitted to this session, the camera device will override the + * {@link CaptureRequest#CONTROL_MODE control mode}, auto-exposure (AE), auto-white balance + * (AWB) and auto-focus (AF) to {@link CameraMetadata#CONTROL_MODE_AUTO}, + * {@link CameraMetadata#CONTROL_AE_MODE_ON}, {@link CameraMetadata#CONTROL_AWB_MODE_AUTO} + * and {@link CameraMetadata#CONTROL_AF_MODE_CONTINUOUS_VIDEO}, respectively. All + * post-processing block mode controls will be overridden to be FAST. Therefore, no manual + * control of capture and post-processing parameters is possible. Beside these, only a subset + * of controls will work, see + * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO} for + * more details.</li> + * + * </ul> + * + * @param outputs The new set of Surfaces that should be made available as + * targets for captured high speed image data. + * @param callback The callback to notify about the status of the new capture session. + * @param handler The handler on which the callback should be invoked, or {@code null} to use + * the current thread's {@link android.os.Looper looper}. + * + * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements, + * the callback is null, or the handler is null but the current + * thread has no looper, or the camera device doesn't support + * high speed video capability. + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera device has been closed + * + * @see #createCaptureSession + * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE + * @see StreamConfigurationMap#getHighSpeedVideoSizes + * @see StreamConfigurationMap#getHighSpeedVideoFpsRangesFor + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + * @see CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO + * @see CameraCaptureSession#captureBurst + * @see CameraCaptureSession#setRepeatingBurst + * @see #createConstrainedHighSpeedRequestList + */ + public abstract void createConstrainedHighSpeedCaptureSession(@NonNull List<Surface> outputs, + @NonNull CameraCaptureSession.StateCallback callback, + @Nullable Handler handler) + throws CameraAccessException; + + + /** + * <p>Create a unmodifiable list of requests that is suitable for constrained high speed capture + * session streaming.</p> + * + * <p>High speed video streaming creates significant performance pressue on the camera device, + * so to achieve efficient high speed streaming, the camera device may have to aggregate + * multiple frames together. This means requests must be sent in batched groups, with all + * requests sharing the same settings. This method takes the list of output target + * Surfaces (subject to the output Surface requirements specified by the contrained high speed + * session) and a {@link CaptureRequest request}, and generates a request list that has the same + * controls for each request. The input {@link CaptureRequest request} must contain the target + * output Surfaces and target high speed FPS range that is one of the + * {@link StreamConfigurationMap#getHighSpeedVideoFpsRangesFor} for the Surface size.</p> + * + * <p>If both preview and recording Surfaces are specified in the {@code request}, the + * {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE target FPS range} in the input + * {@link CaptureRequest request} must be a fixed framerate FPS range, where the + * {@link android.util.Range#getLower minimal FPS} == + * {@link android.util.Range#getUpper() maximum FPS}. The created request list will contain + * a interleaved request pattern such that the preview output FPS is at least 30fps, the + * recording output FPS is {@link android.util.Range#getUpper() maximum FPS} of the requested + * FPS range. The application can submit this request list directly to an active high speed + * capture session to achieve high speed video recording. When only preview or recording + * Surface is specified, this method will return a list of request that have the same controls + * and output targets for all requests.</p> + * + * <p>Submitting a request list created by this method to a normal capture session will result + * in an {@link IllegalArgumentException} if the high speed + * {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE FPS range} is not supported by + * {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES}.</p> + * + * @param request The high speed capture request that will be used to generate the high speed + * request list. + * @return A unmodifiable CaptureRequest list that is suitable for constrained high speed + * capture. + * + * @throws IllegalArgumentException if the set of output Surfaces in the request do not meet the + * high speed video capability requirements, or the camera + * device doesn't support high speed video capability, or the + * request doesn't meet the high speed video capability + * requirements, or the request doesn't contain the required + * controls for high speed capture. + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera device has been closed + * + * @see #createConstrainedHighSpeedCaptureSession + * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE + * @see StreamConfigurationMap#getHighSpeedVideoSizes + * @see StreamConfigurationMap#getHighSpeedVideoFpsRangesFor + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + * @see CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO + */ + @NonNull + public abstract List<CaptureRequest> createConstrainedHighSpeedRequestList( + @NonNull CaptureRequest request)throws CameraAccessException; + + /** * <p>Create a {@link CaptureRequest.Builder} for new capture requests, * initialized with template for a target use case. The settings are chosen * to be the best options for the specific camera device, so it is not diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 0f002a9..0fb6889 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -802,12 +802,9 @@ public final class CameraManager { */ public ICameraService getCameraService() { synchronized(mLock) { + connectCameraServiceLocked(); if (mCameraService == null) { - Log.i(TAG, "getCameraService: Reconnecting to camera service"); - connectCameraServiceLocked(); - if (mCameraService == null) { - Log.e(TAG, "Camera service is unavailable"); - } + Log.e(TAG, "Camera service is unavailable"); } return mCameraService; } @@ -815,11 +812,16 @@ public final class CameraManager { /** * Connect to the camera service if it's available, and set up listeners. + * If the service is already connected, do nothing. * * <p>Sets mCameraService to a valid pointer or null if the connection does not succeed.</p> */ private void connectCameraServiceLocked() { - mCameraService = null; + // Only reconnect if necessary + if (mCameraService != null) return; + + Log.i(TAG, "Connecting to camera service"); + IBinder cameraServiceBinder = ServiceManager.getService(CAMERA_SERVICE_BINDER_NAME); if (cameraServiceBinder == null) { // Camera service is now down, leave mCameraService as null @@ -1098,6 +1100,8 @@ public final class CameraManager { */ public void registerAvailabilityCallback(AvailabilityCallback callback, Handler handler) { synchronized (mLock) { + connectCameraServiceLocked(); + Handler oldHandler = mCallbackMap.put(callback, handler); // For new callbacks, provide initial availability information if (oldHandler == null) { @@ -1120,6 +1124,8 @@ public final class CameraManager { public void registerTorchCallback(TorchCallback callback, Handler handler) { synchronized(mLock) { + connectCameraServiceLocked(); + Handler oldHandler = mTorchCallbackMap.put(callback, handler); // For new callbacks, provide initial torch information if (oldHandler == null) { diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index ac29f80..f8db6d9 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -648,6 +648,100 @@ public abstract class CameraMetadata<TKey> { */ public static final int REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT = 8; + /** + * <p>The device supports constrained high speed video recording (frame rate >=120fps) + * use case. The camera device will support high speed capture session created by + * {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedCaptureSession }, which + * only accepts high speed request list created by + * {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedRequestList }.</p> + * <p>A camera device can still support high speed video streaming by advertising the high speed + * FPS ranges in {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES android.control.aeAvailableTargetFpsRanges}. For this case, all normal + * capture request per frame control and synchronization requirements will apply to + * the high speed fps ranges, the same as all other fps ranges. This capability describes + * the capability of a specialized operating mode with many limitations (see below), which + * is only targeted at high speed video recording.</p> + * <p>The supported high speed video sizes and fps ranges are specified in + * {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoFpsRanges }. + * To get desired output frame rates, the application is only allowed to select video size + * and FPS range combinations provided by + * {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoSizes }. + * The fps range can be controlled via {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange}.</p> + * <p>In this capability, the camera device will override aeMode, awbMode, and afMode to + * ON, ON, and CONTINUOUS_VIDEO, respectively. All post-processing block mode + * controls will be overridden to be FAST. Therefore, no manual control of capture + * and post-processing parameters is possible. All other controls operate the + * same as when {@link CaptureRequest#CONTROL_MODE android.control.mode} == AUTO. This means that all other + * android.control.* fields continue to work, such as</p> + * <ul> + * <li>{@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange}</li> + * <li>{@link CaptureRequest#CONTROL_AE_EXPOSURE_COMPENSATION android.control.aeExposureCompensation}</li> + * <li>{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock}</li> + * <li>{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock}</li> + * <li>{@link CaptureRequest#CONTROL_EFFECT_MODE android.control.effectMode}</li> + * <li>{@link CaptureRequest#CONTROL_AE_REGIONS android.control.aeRegions}</li> + * <li>{@link CaptureRequest#CONTROL_AF_REGIONS android.control.afRegions}</li> + * <li>{@link CaptureRequest#CONTROL_AWB_REGIONS android.control.awbRegions}</li> + * <li>{@link CaptureRequest#CONTROL_AF_TRIGGER android.control.afTrigger}</li> + * <li>{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger}</li> + * </ul> + * <p>Outside of android.control.*, the following controls will work:</p> + * <ul> + * <li>{@link CaptureRequest#FLASH_MODE android.flash.mode} (TORCH mode only, automatic flash for still capture will not + * work since aeMode is ON)</li> + * <li>{@link CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE android.lens.opticalStabilizationMode} (if it is supported)</li> + * <li>{@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}</li> + * <li>{@link CaptureRequest#STATISTICS_FACE_DETECT_MODE android.statistics.faceDetectMode} (if it is supported)</li> + * </ul> + * <p>For high speed recording use case, the actual maximum supported frame rate may + * be lower than what camera can output, depending on the destination Surfaces for + * the image data. For example, if the destination surface is from video encoder, + * the application need check if the video encoder is capable of supporting the + * high frame rate for a given video size, or it will end up with lower recording + * frame rate. If the destination surface is from preview window, the actual preview frame + * rate will be bounded by the screen refresh rate.</p> + * <p>The camera device will only support up to 2 high speed simultaneous output surfaces + * (preview and recording surfaces) + * in this mode. Above controls will be effective only if all of below conditions are true:</p> + * <ul> + * <li>The application creates a camera capture session with no more than 2 surfaces via + * {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedCaptureSession }. The + * targeted surfaces must be preview surface (either from + * {@link android.view.SurfaceView } or {@link android.graphics.SurfaceTexture }) or + * recording surface(either from {@link android.media.MediaRecorder#getSurface } or + * {@link android.media.MediaCodec#createInputSurface }).</li> + * <li>The stream sizes are selected from the sizes reported by + * {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoSizes }.</li> + * <li>The FPS ranges are selected from + * {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoFpsRanges }.</li> + * </ul> + * <p>When above conditions are NOT satistied, the + * {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedCaptureSession } + * and {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedRequestList } will fail.</p> + * <p>Switching to a FPS range that has different maximum FPS may trigger some camera device + * reconfigurations, which may introduce extra latency. It is recommended that + * the application avoids unnecessary maximum target FPS changes as much as possible + * during high speed streaming.</p> + * + * @see CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES + * @see CaptureRequest#CONTROL_AE_EXPOSURE_COMPENSATION + * @see CaptureRequest#CONTROL_AE_LOCK + * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER + * @see CaptureRequest#CONTROL_AE_REGIONS + * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE + * @see CaptureRequest#CONTROL_AF_REGIONS + * @see CaptureRequest#CONTROL_AF_TRIGGER + * @see CaptureRequest#CONTROL_AWB_LOCK + * @see CaptureRequest#CONTROL_AWB_REGIONS + * @see CaptureRequest#CONTROL_EFFECT_MODE + * @see CaptureRequest#CONTROL_MODE + * @see CaptureRequest#FLASH_MODE + * @see CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE + * @see CaptureRequest#SCALER_CROP_REGION + * @see CaptureRequest#STATISTICS_FACE_DETECT_MODE + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + */ + public static final int REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO = 9; + // // Enumeration values for CameraCharacteristics#SCALER_CROPPING_TYPE // @@ -1725,6 +1819,10 @@ public abstract class CameraMetadata<TKey> { public static final int CONTROL_SCENE_MODE_BARCODE = 16; /** + * <p>This is deprecated, please use + * {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedCaptureSession } + * and {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedRequestList } + * for high speed video recording.</p> * <p>Optimized for high speed video recording (frame rate >=60fps) use case.</p> * <p>The supported high speed video sizes and fps ranges are specified in * android.control.availableHighSpeedVideoConfigurations. To get desired @@ -1799,6 +1897,7 @@ public abstract class CameraMetadata<TKey> { * @see CaptureRequest#SCALER_CROP_REGION * @see CaptureRequest#STATISTICS_FACE_DETECT_MODE * @see CaptureRequest#CONTROL_SCENE_MODE + * @deprecated Please refer to this API documentation to find the alternatives */ public static final int CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO = 17; diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index df6c986..3bb2fdb 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -2584,12 +2584,33 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { /** * <p>Position of the camera optical center.</p> - * <p>As measured in the device sensor coordinate system, the - * position of the camera device's optical center, as a - * three-dimensional vector <code>(x,y,z)</code>.</p> - * <p>To transform a world position to a camera-device centered - * coordinate system, the position must be translated by this - * vector and then rotated by {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}.</p> + * <p>The position of the camera device's lens optical center, + * as a three-dimensional vector <code>(x,y,z)</code>, relative to the + * optical center of the largest camera device facing in the + * same direction as this camera, in the {@link android.hardware.SensorEvent Android sensor coordinate + * axes}. Note that only the axis definitions are shared with + * the sensor coordinate system, but not the origin.</p> + * <p>If this device is the largest or only camera device with a + * given facing, then this position will be <code>(0, 0, 0)</code>; a + * camera device with a lens optical center located 3 cm from + * the main sensor along the +X axis (to the right from the + * user's perspective) will report <code>(0.03, 0, 0)</code>.</p> + * <p>To transform a pixel coordinates between two cameras + * facing the same direction, first the source camera + * android.lens.radialDistortion must be corrected for. Then + * the source camera android.lens.intrinsicCalibration needs + * to be applied, followed by the {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} + * of the source camera, the translation of the source camera + * relative to the destination camera, the + * {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} of the destination camera, and + * finally the inverse of android.lens.intrinsicCalibration + * of the destination camera. This obtains a + * radial-distortion-free coordinate in the destination + * camera pixel coordinates.</p> + * <p>To compare this against a real image from the destination + * camera, the destination camera image then needs to be + * corrected for radial distortion before comparison or + * sampling.</p> * <p><b>Units</b>: Meters</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java index 7a39dd5..ab0f607 100644 --- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java @@ -721,4 +721,10 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { } } + @Override + public boolean isConstrainedHighSpeed() { + // TODO: to be implemented + return false; + } + } diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index e60e266..ad0cd0f 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -906,7 +906,6 @@ public class CameraDeviceImpl extends CameraDevice { } mRemoteDevice = null; - mInError = false; } } @@ -1889,13 +1888,13 @@ public class CameraDeviceImpl extends CameraDevice { } private void checkIfCameraClosedOrInError() throws CameraAccessException { + if (mRemoteDevice == null) { + throw new IllegalStateException("CameraDevice was already closed"); + } if (mInError) { throw new CameraAccessException(CameraAccessException.CAMERA_ERROR, "The camera device has encountered a serious error"); } - if (mRemoteDevice == null) { - throw new IllegalStateException("CameraDevice was already closed"); - } } /** Whether the camera device has started to close (may not yet have finished) */ @@ -1906,4 +1905,18 @@ public class CameraDeviceImpl extends CameraDevice { private CameraCharacteristics getCharacteristics() { return mCharacteristics; } + + @Override + public void createConstrainedHighSpeedCaptureSession(List<Surface> outputs, + android.hardware.camera2.CameraCaptureSession.StateCallback callback, Handler handler) + throws CameraAccessException { + // TODO: to be implemented + throw new UnsupportedOperationException("To be implemented!!!!"); + } + + @Override + public List<CaptureRequest> createConstrainedHighSpeedRequestList(CaptureRequest request) + throws CameraAccessException { + throw new UnsupportedOperationException("To be implemented!!!!"); + } } diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableHighSpeedVideoConfiguration.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableHighSpeedVideoConfiguration.java index c03144b..2449abe 100644 --- a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableHighSpeedVideoConfiguration.java +++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableHighSpeedVideoConfiguration.java @@ -33,7 +33,7 @@ import java.nio.ByteBuffer; */ public class MarshalQueryableHighSpeedVideoConfiguration implements MarshalQueryable<HighSpeedVideoConfiguration> { - private static final int SIZE = SIZEOF_INT32 * 4; + private static final int SIZE = SIZEOF_INT32 * 5; private class MarshalerHighSpeedVideoConfiguration extends Marshaler<HighSpeedVideoConfiguration> { @@ -49,6 +49,7 @@ public class MarshalQueryableHighSpeedVideoConfiguration buffer.putInt(value.getHeight()); buffer.putInt(value.getFpsMin()); buffer.putInt(value.getFpsMax()); + buffer.putInt(value.getBatchSizeMax()); } @Override @@ -57,8 +58,9 @@ public class MarshalQueryableHighSpeedVideoConfiguration int height = buffer.getInt(); int fpsMin = buffer.getInt(); int fpsMax = buffer.getInt(); + int batchSizeMax = buffer.getInt(); - return new HighSpeedVideoConfiguration(width, height, fpsMin, fpsMax); + return new HighSpeedVideoConfiguration(width, height, fpsMin, fpsMax, batchSizeMax); } @Override diff --git a/core/java/android/hardware/camera2/params/HighSpeedVideoConfiguration.java b/core/java/android/hardware/camera2/params/HighSpeedVideoConfiguration.java index 088049f..b469126 100644 --- a/core/java/android/hardware/camera2/params/HighSpeedVideoConfiguration.java +++ b/core/java/android/hardware/camera2/params/HighSpeedVideoConfiguration.java @@ -33,6 +33,7 @@ import android.util.Size; * @hide */ public final class HighSpeedVideoConfiguration { + static final private int HIGH_SPEED_MAX_MINIMAL_FPS = 120; /** * Create a new {@link HighSpeedVideoConfiguration}. @@ -48,15 +49,18 @@ public final class HighSpeedVideoConfiguration { * @hide */ public HighSpeedVideoConfiguration( - final int width, final int height, final int fpsMin, final int fpsMax) { - if (fpsMax < 60) { - throw new IllegalArgumentException("fpsMax must be at least 60"); + final int width, final int height, final int fpsMin, final int fpsMax, + final int batchSizeMax) { + if (fpsMax < HIGH_SPEED_MAX_MINIMAL_FPS) { + throw new IllegalArgumentException("fpsMax must be at least " + + HIGH_SPEED_MAX_MINIMAL_FPS); } mFpsMax = fpsMax; mWidth = checkArgumentPositive(width, "width must be positive"); mHeight = checkArgumentPositive(height, "height must be positive"); mFpsMin = checkArgumentPositive(fpsMin, "fpsMin must be positive"); mSize = new Size(mWidth, mHeight); + mBatchSizeMax = checkArgumentPositive(batchSizeMax, "batchSizeMax must be positive"); mFpsRange = new Range<Integer>(mFpsMin, mFpsMax); } @@ -106,9 +110,18 @@ public final class HighSpeedVideoConfiguration { } /** + * Convenience method to return the max batch size of this high speed video configuration. + * + * @return the maximal batch size for this high speed video configuration + */ + public int getBatchSizeMax() { + return mBatchSizeMax; + } + + /** * Convenience method to return the FPS range of this high speed video configuration. * - * @return a Range with high bound >= 60 + * @return a Range with high bound >= {@value #HIGH_SPEED_MAX_MINIMAL_FPS} */ public Range<Integer> getFpsRange() { return mFpsRange; @@ -135,7 +148,8 @@ public final class HighSpeedVideoConfiguration { return mWidth == other.mWidth && mHeight == other.mHeight && mFpsMin == other.mFpsMin && - mFpsMax == other.mFpsMax; + mFpsMax == other.mFpsMax && + mBatchSizeMax == other.mBatchSizeMax; } return false; } @@ -152,6 +166,7 @@ public final class HighSpeedVideoConfiguration { private final int mHeight; private final int mFpsMin; private final int mFpsMax; + private final int mBatchSizeMax; private final Size mSize; private final Range<Integer> mFpsRange; } diff --git a/core/java/android/security/IKeystoreService.aidl b/core/java/android/security/IKeystoreService.aidl index 2097d5a..409542d 100644 --- a/core/java/android/security/IKeystoreService.aidl +++ b/core/java/android/security/IKeystoreService.aidl @@ -67,7 +67,8 @@ interface IKeystoreService { OperationResult begin(IBinder appToken, String alias, int purpose, boolean pruneable, in KeymasterArguments params, in byte[] entropy); OperationResult update(IBinder token, in KeymasterArguments params, in byte[] input); - OperationResult finish(IBinder token, in KeymasterArguments params, in byte[] signature); + OperationResult finish(IBinder token, in KeymasterArguments params, in byte[] signature, + in byte[] entropy); int abort(IBinder handle); boolean isOperationAuthorized(IBinder token); int addAuthToken(in byte[] authToken); diff --git a/core/java/android/service/voice/IVoiceInteractionSession.aidl b/core/java/android/service/voice/IVoiceInteractionSession.aidl index 894edac..8fe84e1 100644 --- a/core/java/android/service/voice/IVoiceInteractionSession.aidl +++ b/core/java/android/service/voice/IVoiceInteractionSession.aidl @@ -16,8 +16,8 @@ package android.service.voice; -import android.app.AssistContent; -import android.app.AssistStructure; +import android.app.assist.AssistContent; +import android.app.assist.AssistStructure; import android.content.Intent; import android.graphics.Bitmap; import android.os.Bundle; diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index 48ad5a8..33fef62 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -16,11 +16,11 @@ package android.service.voice; -import android.app.AssistContent; -import android.app.AssistStructure; import android.app.Dialog; import android.app.Instrumentation; import android.app.VoiceInteractor; +import android.app.assist.AssistContent; +import android.app.assist.AssistStructure; import android.content.ComponentCallbacks2; import android.content.Context; import android.content.Intent; @@ -386,7 +386,7 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall } /** - * ASk the app to cancel this current request. + * ASk the app to cancelLocked this current request. */ public void cancel() { try { @@ -878,14 +878,34 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall show(null, 0); } - public void show(Bundle args, int showFlags) { + /** + * Show the UI for this session. This asks the system to go through the process of showing + * your UI, which will eventually culminate in {@link #onShow}. This is similar to calling + * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}. + * @param args Arbitrary arguments that will be propagated {@link #onShow}. + * @param flags Indicates additional optional behavior that should be performed. May + * be {@link VoiceInteractionSession#SHOW_WITH_ASSIST VoiceInteractionSession.SHOW_WITH_ASSIST} + * to request that the system generate and deliver assist data on the current foreground + * app as part of showing the session UI. + */ + public void show(Bundle args, int flags) { + if (mToken == null) { + throw new IllegalStateException("Can't call before onCreate()"); + } try { - mSystemService.showSessionFromSession(mToken, null, 0); + mSystemService.showSessionFromSession(mToken, args, flags); } catch (RemoteException e) { } } + /** + * Hide the session's UI, if currently shown. Call this when you are done with your + * user interaction. + */ public void hide() { + if (mToken == null) { + throw new IllegalStateException("Can't call before onCreate()"); + } try { mSystemService.hideSessionFromSession(mToken); } catch (RemoteException e) { @@ -964,6 +984,9 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall * {@link #startVoiceActivity}.</p> */ public void setKeepAwake(boolean keepAwake) { + if (mToken == null) { + throw new IllegalStateException("Can't call before onCreate()"); + } try { mSystemService.setKeepAwake(mToken, keepAwake); } catch (RemoteException e) { @@ -985,7 +1008,9 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall } /** - * Finish the session. + * Finish the session. This completely destroys the session -- the next time it is shown, + * an entirely new one will be created. You do not normally call this function; instead, + * use {@link #hide} and allow the system to destroy your session if it needs its RAM. */ public void finish() { if (mToken == null) { @@ -1114,7 +1139,7 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall * Called when the user presses the back button while focus is in the session UI. Note * that this will only happen if the session UI has requested input focus in its window; * otherwise, the back key will go to whatever window has focus and do whatever behavior - * it normally has there. + * it normally has there. The default implementation simply calls {@link #hide}. */ public void onBackPressed() { hide(); @@ -1123,7 +1148,7 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall /** * Sessions automatically watch for requests that all system UI be closed (such as when * the user presses HOME), which will appear here. The default implementation always - * calls {@link #finish}. + * calls {@link #hide}. */ public void onCloseSystemDialogs() { hide(); @@ -1287,7 +1312,7 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall } /** - * Called when the {@link android.app.VoiceInteractor} has asked to cancel a {@link Request} + * Called when the {@link android.app.VoiceInteractor} has asked to cancelLocked a {@link Request} * that was previously delivered to {@link #onRequestConfirmation}, * {@link #onRequestPickOption}, {@link #onRequestCompleteVoice}, {@link #onRequestAbortVoice}, * or {@link #onRequestCommand}. diff --git a/core/java/android/text/Annotation.java b/core/java/android/text/Annotation.java index dbc290b..bb5d3ea 100644 --- a/core/java/android/text/Annotation.java +++ b/core/java/android/text/Annotation.java @@ -38,6 +38,11 @@ public class Annotation implements ParcelableSpan { } public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + /** @hide */ + public int getSpanTypeIdInternal() { return TextUtils.ANNOTATION; } @@ -46,6 +51,11 @@ public class Annotation implements ParcelableSpan { } public void writeToParcel(Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + /** @hide */ + public void writeToParcelInternal(Parcel dest, int flags) { dest.writeString(mKey); dest.writeString(mValue); } diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java index e99a960..5b5cdd2 100644 --- a/core/java/android/text/DynamicLayout.java +++ b/core/java/android/text/DynamicLayout.java @@ -291,7 +291,7 @@ public class DynamicLayout extends Layout b.setText(text, where, where + after) .setPaint(getPaint()) .setWidth(getWidth()) - .setTextDir(getTextDirectionHeuristic()) + .setTextDirection(getTextDirectionHeuristic()) .setLineSpacing(getSpacingAdd(), getSpacingMultiplier()) .setEllipsizedWidth(mEllipsizedWidth) .setEllipsize(mEllipsizeAt) diff --git a/core/java/android/text/ParcelableSpan.java b/core/java/android/text/ParcelableSpan.java index 224511a..d7c1a4b 100644 --- a/core/java/android/text/ParcelableSpan.java +++ b/core/java/android/text/ParcelableSpan.java @@ -16,6 +16,7 @@ package android.text; +import android.os.Parcel; import android.os.Parcelable; /** @@ -27,5 +28,21 @@ public interface ParcelableSpan extends Parcelable { /** * Return a special type identifier for this span class. */ - public abstract int getSpanTypeId(); + int getSpanTypeId(); + + /** + * Internal implementation of {@link #getSpanTypeId()} that is not meant to + * be overridden outside of the framework. + * + * @hide + */ + int getSpanTypeIdInternal(); + + /** + * Internal implementation of {@link Parcelable#writeToParcel(Parcel, int)} + * that is not meant to be overridden outside of the framework. + * + * @hide + */ + void writeToParcelInternal(Parcel dest, int flags); } diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index d6d046b..464710b 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -184,7 +184,7 @@ public class StaticLayout extends Layout { * @param textDir text direction heuristic for resolving BiDi behavior. * @return this builder, useful for chaining */ - public Builder setTextDir(TextDirectionHeuristic textDir) { + public Builder setTextDirection(TextDirectionHeuristic textDir) { mTextDir = textDir; return this; } @@ -473,7 +473,7 @@ public class StaticLayout extends Layout { Builder b = Builder.obtain(source, bufstart, bufend, paint, outerwidth) .setAlignment(align) - .setTextDir(textDir) + .setTextDirection(textDir) .setLineSpacing(spacingadd, spacingmult) .setIncludePad(includepad) .setEllipsizedWidth(ellipsizedWidth) diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index 676986d..6c4d8fd 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -622,8 +622,7 @@ public class TextUtils { * Flatten a CharSequence and whatever styles can be copied across processes * into the parcel. */ - public static void writeToParcel(CharSequence cs, Parcel p, - int parcelableFlags) { + public static void writeToParcel(CharSequence cs, Parcel p, int parcelableFlags) { if (cs instanceof Spanned) { p.writeInt(0); p.writeString(cs.toString()); @@ -645,15 +644,15 @@ public class TextUtils { } if (prop instanceof ParcelableSpan) { - ParcelableSpan ps = (ParcelableSpan)prop; - int spanTypeId = ps.getSpanTypeId(); + final ParcelableSpan ps = (ParcelableSpan) prop; + final int spanTypeId = ps.getSpanTypeIdInternal(); if (spanTypeId < FIRST_SPAN || spanTypeId > LAST_SPAN) { - Log.e(TAG, "external class \"" + ps.getClass().getSimpleName() + Log.e(TAG, "External class \"" + ps.getClass().getSimpleName() + "\" is attempting to use the frameworks-only ParcelableSpan" + " interface"); } else { p.writeInt(spanTypeId); - ps.writeToParcel(p, parcelableFlags); + ps.writeToParcelInternal(p, parcelableFlags); writeWhere(p, sp, o); } } diff --git a/core/java/android/text/style/AbsoluteSizeSpan.java b/core/java/android/text/style/AbsoluteSizeSpan.java index 1214040..908ef55 100644 --- a/core/java/android/text/style/AbsoluteSizeSpan.java +++ b/core/java/android/text/style/AbsoluteSizeSpan.java @@ -49,6 +49,11 @@ public class AbsoluteSizeSpan extends MetricAffectingSpan implements ParcelableS } public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + /** @hide */ + public int getSpanTypeIdInternal() { return TextUtils.ABSOLUTE_SIZE_SPAN; } @@ -57,6 +62,11 @@ public class AbsoluteSizeSpan extends MetricAffectingSpan implements ParcelableS } public void writeToParcel(Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + /** @hide */ + public void writeToParcelInternal(Parcel dest, int flags) { dest.writeInt(mSize); dest.writeInt(mDip ? 1 : 0); } diff --git a/core/java/android/text/style/AlignmentSpan.java b/core/java/android/text/style/AlignmentSpan.java index b8a37da..6158309 100644 --- a/core/java/android/text/style/AlignmentSpan.java +++ b/core/java/android/text/style/AlignmentSpan.java @@ -22,10 +22,9 @@ import android.text.ParcelableSpan; import android.text.TextUtils; public interface AlignmentSpan extends ParagraphStyle { - public Layout.Alignment getAlignment(); + Layout.Alignment getAlignment(); - public static class Standard - implements AlignmentSpan, ParcelableSpan { + class Standard implements AlignmentSpan, ParcelableSpan { public Standard(Layout.Alignment align) { mAlignment = align; } @@ -35,6 +34,11 @@ public interface AlignmentSpan extends ParagraphStyle { } public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + /** @hide */ + public int getSpanTypeIdInternal() { return TextUtils.ALIGNMENT_SPAN; } @@ -43,6 +47,11 @@ public interface AlignmentSpan extends ParagraphStyle { } public void writeToParcel(Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + /** @hide */ + public void writeToParcelInternal(Parcel dest, int flags) { dest.writeString(mAlignment.name()); } diff --git a/core/java/android/text/style/BackgroundColorSpan.java b/core/java/android/text/style/BackgroundColorSpan.java index cda8015..de05f50 100644 --- a/core/java/android/text/style/BackgroundColorSpan.java +++ b/core/java/android/text/style/BackgroundColorSpan.java @@ -35,6 +35,11 @@ public class BackgroundColorSpan extends CharacterStyle } public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + /** @hide */ + public int getSpanTypeIdInternal() { return TextUtils.BACKGROUND_COLOR_SPAN; } @@ -43,6 +48,11 @@ public class BackgroundColorSpan extends CharacterStyle } public void writeToParcel(Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + /** @hide */ + public void writeToParcelInternal(Parcel dest, int flags) { dest.writeInt(mColor); } diff --git a/core/java/android/text/style/BulletSpan.java b/core/java/android/text/style/BulletSpan.java index 3f86b08..7408415 100644 --- a/core/java/android/text/style/BulletSpan.java +++ b/core/java/android/text/style/BulletSpan.java @@ -60,6 +60,11 @@ public class BulletSpan implements LeadingMarginSpan, ParcelableSpan { } public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + /** @hide */ + public int getSpanTypeIdInternal() { return TextUtils.BULLET_SPAN; } @@ -68,6 +73,11 @@ public class BulletSpan implements LeadingMarginSpan, ParcelableSpan { } public void writeToParcel(Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + /** @hide */ + public void writeToParcelInternal(Parcel dest, int flags) { dest.writeInt(mGapWidth); dest.writeInt(mWantColor ? 1 : 0); dest.writeInt(mColor); diff --git a/core/java/android/text/style/EasyEditSpan.java b/core/java/android/text/style/EasyEditSpan.java index 03b4f60..7af1c2c 100644 --- a/core/java/android/text/style/EasyEditSpan.java +++ b/core/java/android/text/style/EasyEditSpan.java @@ -91,12 +91,22 @@ public class EasyEditSpan implements ParcelableSpan { @Override public void writeToParcel(Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + /** @hide */ + public void writeToParcelInternal(Parcel dest, int flags) { dest.writeParcelable(mPendingIntent, 0); dest.writeByte((byte) (mDeleteEnabled ? 1 : 0)); } @Override public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + /** @hide */ + public int getSpanTypeIdInternal() { return TextUtils.EASY_EDIT_SPAN; } diff --git a/core/java/android/text/style/ForegroundColorSpan.java b/core/java/android/text/style/ForegroundColorSpan.java index f167aab..2bc6d54 100644 --- a/core/java/android/text/style/ForegroundColorSpan.java +++ b/core/java/android/text/style/ForegroundColorSpan.java @@ -36,14 +36,24 @@ public class ForegroundColorSpan extends CharacterStyle } public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + /** @hide */ + public int getSpanTypeIdInternal() { return TextUtils.FOREGROUND_COLOR_SPAN; } - + public int describeContents() { return 0; } public void writeToParcel(Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + /** @hide */ + public void writeToParcelInternal(Parcel dest, int flags) { dest.writeInt(mColor); } diff --git a/core/java/android/text/style/LeadingMarginSpan.java b/core/java/android/text/style/LeadingMarginSpan.java index 96a7cd9..339d885 100644 --- a/core/java/android/text/style/LeadingMarginSpan.java +++ b/core/java/android/text/style/LeadingMarginSpan.java @@ -125,6 +125,11 @@ extends ParagraphStyle } public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + /** @hide */ + public int getSpanTypeIdInternal() { return TextUtils.LEADING_MARGIN_SPAN; } @@ -133,6 +138,11 @@ extends ParagraphStyle } public void writeToParcel(Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + /** @hide */ + public void writeToParcelInternal(Parcel dest, int flags) { dest.writeInt(mFirst); dest.writeInt(mRest); } diff --git a/core/java/android/text/style/LocaleSpan.java b/core/java/android/text/style/LocaleSpan.java index a12c42f..d286231 100644 --- a/core/java/android/text/style/LocaleSpan.java +++ b/core/java/android/text/style/LocaleSpan.java @@ -44,6 +44,11 @@ public class LocaleSpan extends MetricAffectingSpan implements ParcelableSpan { @Override public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + /** @hide */ + public int getSpanTypeIdInternal() { return TextUtils.LOCALE_SPAN; } @@ -54,6 +59,11 @@ public class LocaleSpan extends MetricAffectingSpan implements ParcelableSpan { @Override public void writeToParcel(Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + /** @hide */ + public void writeToParcelInternal(Parcel dest, int flags) { dest.writeString(mLocale.getLanguage()); dest.writeString(mLocale.getCountry()); dest.writeString(mLocale.getVariant()); diff --git a/core/java/android/text/style/QuoteSpan.java b/core/java/android/text/style/QuoteSpan.java index 17748ca..0b0a82c 100644 --- a/core/java/android/text/style/QuoteSpan.java +++ b/core/java/android/text/style/QuoteSpan.java @@ -45,6 +45,11 @@ public class QuoteSpan implements LeadingMarginSpan, ParcelableSpan { } public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + /** @hide */ + public int getSpanTypeIdInternal() { return TextUtils.QUOTE_SPAN; } @@ -53,6 +58,11 @@ public class QuoteSpan implements LeadingMarginSpan, ParcelableSpan { } public void writeToParcel(Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + /** @hide */ + public void writeToParcelInternal(Parcel dest, int flags) { dest.writeInt(mColor); } diff --git a/core/java/android/text/style/RelativeSizeSpan.java b/core/java/android/text/style/RelativeSizeSpan.java index 632dbd4..95f048a 100644 --- a/core/java/android/text/style/RelativeSizeSpan.java +++ b/core/java/android/text/style/RelativeSizeSpan.java @@ -34,6 +34,11 @@ public class RelativeSizeSpan extends MetricAffectingSpan implements ParcelableS } public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + /** @hide */ + public int getSpanTypeIdInternal() { return TextUtils.RELATIVE_SIZE_SPAN; } @@ -42,6 +47,11 @@ public class RelativeSizeSpan extends MetricAffectingSpan implements ParcelableS } public void writeToParcel(Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + /** @hide */ + public void writeToParcelInternal(Parcel dest, int flags) { dest.writeFloat(mProportion); } diff --git a/core/java/android/text/style/ScaleXSpan.java b/core/java/android/text/style/ScaleXSpan.java index a22a5a1..d085018 100644 --- a/core/java/android/text/style/ScaleXSpan.java +++ b/core/java/android/text/style/ScaleXSpan.java @@ -34,6 +34,11 @@ public class ScaleXSpan extends MetricAffectingSpan implements ParcelableSpan { } public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + /** @hide */ + public int getSpanTypeIdInternal() { return TextUtils.SCALE_X_SPAN; } @@ -42,6 +47,11 @@ public class ScaleXSpan extends MetricAffectingSpan implements ParcelableSpan { } public void writeToParcel(Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + /** @hide */ + public void writeToParcelInternal(Parcel dest, int flags) { dest.writeFloat(mProportion); } diff --git a/core/java/android/text/style/SpellCheckSpan.java b/core/java/android/text/style/SpellCheckSpan.java index 0d8a103..10275c2 100644 --- a/core/java/android/text/style/SpellCheckSpan.java +++ b/core/java/android/text/style/SpellCheckSpan.java @@ -54,11 +54,21 @@ public class SpellCheckSpan implements ParcelableSpan { @Override public void writeToParcel(Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + /** @hide */ + public void writeToParcelInternal(Parcel dest, int flags) { dest.writeInt(mSpellCheckInProgress ? 1 : 0); } @Override public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + /** @hide */ + public int getSpanTypeIdInternal() { return TextUtils.SPELL_CHECK_SPAN; } } diff --git a/core/java/android/text/style/StrikethroughSpan.java b/core/java/android/text/style/StrikethroughSpan.java index 303e415..1389704 100644 --- a/core/java/android/text/style/StrikethroughSpan.java +++ b/core/java/android/text/style/StrikethroughSpan.java @@ -30,6 +30,11 @@ public class StrikethroughSpan extends CharacterStyle } public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + /** @hide */ + public int getSpanTypeIdInternal() { return TextUtils.STRIKETHROUGH_SPAN; } @@ -38,6 +43,11 @@ public class StrikethroughSpan extends CharacterStyle } public void writeToParcel(Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + /** @hide */ + public void writeToParcelInternal(Parcel dest, int flags) { } @Override diff --git a/core/java/android/text/style/StyleSpan.java b/core/java/android/text/style/StyleSpan.java index b08f70e..f900db5 100644 --- a/core/java/android/text/style/StyleSpan.java +++ b/core/java/android/text/style/StyleSpan.java @@ -50,6 +50,11 @@ public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan { } public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + /** @hide */ + public int getSpanTypeIdInternal() { return TextUtils.STYLE_SPAN; } @@ -58,6 +63,11 @@ public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan { } public void writeToParcel(Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + /** @hide */ + public void writeToParcelInternal(Parcel dest, int flags) { dest.writeInt(mStyle); } diff --git a/core/java/android/text/style/SubscriptSpan.java b/core/java/android/text/style/SubscriptSpan.java index de1d8b2..f1b0d38 100644 --- a/core/java/android/text/style/SubscriptSpan.java +++ b/core/java/android/text/style/SubscriptSpan.java @@ -29,6 +29,11 @@ public class SubscriptSpan extends MetricAffectingSpan implements ParcelableSpan } public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + /** @hide */ + public int getSpanTypeIdInternal() { return TextUtils.SUBSCRIPT_SPAN; } @@ -37,6 +42,11 @@ public class SubscriptSpan extends MetricAffectingSpan implements ParcelableSpan } public void writeToParcel(Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + /** @hide */ + public void writeToParcelInternal(Parcel dest, int flags) { } @Override diff --git a/core/java/android/text/style/SuggestionRangeSpan.java b/core/java/android/text/style/SuggestionRangeSpan.java index 2dbfc72..c1943d5 100644 --- a/core/java/android/text/style/SuggestionRangeSpan.java +++ b/core/java/android/text/style/SuggestionRangeSpan.java @@ -46,11 +46,21 @@ public class SuggestionRangeSpan extends CharacterStyle implements ParcelableSpa @Override public void writeToParcel(Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + /** @hide */ + public void writeToParcelInternal(Parcel dest, int flags) { dest.writeInt(mBackgroundColor); } @Override public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + /** @hide */ + public int getSpanTypeIdInternal() { return TextUtils.SUGGESTION_RANGE_SPAN; } diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java index 8b40953..6b449f9 100644 --- a/core/java/android/text/style/SuggestionSpan.java +++ b/core/java/android/text/style/SuggestionSpan.java @@ -248,6 +248,11 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { @Override public void writeToParcel(Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + /** @hide */ + public void writeToParcelInternal(Parcel dest, int flags) { dest.writeStringArray(mSuggestions); dest.writeInt(mFlags); dest.writeString(mLocaleString); @@ -264,6 +269,11 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { @Override public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + /** @hide */ + public int getSpanTypeIdInternal() { return TextUtils.SUGGESTION_SPAN; } diff --git a/core/java/android/text/style/SuperscriptSpan.java b/core/java/android/text/style/SuperscriptSpan.java index 285fe84..abcf688 100644 --- a/core/java/android/text/style/SuperscriptSpan.java +++ b/core/java/android/text/style/SuperscriptSpan.java @@ -29,6 +29,11 @@ public class SuperscriptSpan extends MetricAffectingSpan implements ParcelableSp } public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + /** @hide */ + public int getSpanTypeIdInternal() { return TextUtils.SUPERSCRIPT_SPAN; } @@ -37,6 +42,11 @@ public class SuperscriptSpan extends MetricAffectingSpan implements ParcelableSp } public void writeToParcel(Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + /** @hide */ + public void writeToParcelInternal(Parcel dest, int flags) { } @Override diff --git a/core/java/android/text/style/TextAppearanceSpan.java b/core/java/android/text/style/TextAppearanceSpan.java index ecbf4bc..abbd793 100644 --- a/core/java/android/text/style/TextAppearanceSpan.java +++ b/core/java/android/text/style/TextAppearanceSpan.java @@ -136,6 +136,11 @@ public class TextAppearanceSpan extends MetricAffectingSpan implements Parcelabl } public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + /** @hide */ + public int getSpanTypeIdInternal() { return TextUtils.TEXT_APPEARANCE_SPAN; } @@ -144,6 +149,11 @@ public class TextAppearanceSpan extends MetricAffectingSpan implements Parcelabl } public void writeToParcel(Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + /** @hide */ + public void writeToParcelInternal(Parcel dest, int flags) { dest.writeString(mTypeface); dest.writeInt(mStyle); dest.writeInt(mTextSize); diff --git a/core/java/android/text/style/TtsSpan.java b/core/java/android/text/style/TtsSpan.java index 342a183..c40f11f 100644 --- a/core/java/android/text/style/TtsSpan.java +++ b/core/java/android/text/style/TtsSpan.java @@ -495,12 +495,22 @@ public class TtsSpan implements ParcelableSpan { @Override public void writeToParcel(Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + /** @hide */ + public void writeToParcelInternal(Parcel dest, int flags) { dest.writeString(mType); dest.writePersistableBundle(mArgs); } @Override public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + /** @hide */ + public int getSpanTypeIdInternal() { return TextUtils.TTS_SPAN; } diff --git a/core/java/android/text/style/TypefaceSpan.java b/core/java/android/text/style/TypefaceSpan.java index f194060..aa622d8 100644 --- a/core/java/android/text/style/TypefaceSpan.java +++ b/core/java/android/text/style/TypefaceSpan.java @@ -42,6 +42,11 @@ public class TypefaceSpan extends MetricAffectingSpan implements ParcelableSpan } public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + /** @hide */ + public int getSpanTypeIdInternal() { return TextUtils.TYPEFACE_SPAN; } @@ -50,6 +55,11 @@ public class TypefaceSpan extends MetricAffectingSpan implements ParcelableSpan } public void writeToParcel(Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + /** @hide */ + public void writeToParcelInternal(Parcel dest, int flags) { dest.writeString(mFamily); } diff --git a/core/java/android/text/style/URLSpan.java b/core/java/android/text/style/URLSpan.java index 0669b6f..58239ef 100644 --- a/core/java/android/text/style/URLSpan.java +++ b/core/java/android/text/style/URLSpan.java @@ -40,6 +40,11 @@ public class URLSpan extends ClickableSpan implements ParcelableSpan { } public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + /** @hide */ + public int getSpanTypeIdInternal() { return TextUtils.URL_SPAN; } @@ -48,6 +53,11 @@ public class URLSpan extends ClickableSpan implements ParcelableSpan { } public void writeToParcel(Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + /** @hide */ + public void writeToParcelInternal(Parcel dest, int flags) { dest.writeString(mURL); } diff --git a/core/java/android/text/style/UnderlineSpan.java b/core/java/android/text/style/UnderlineSpan.java index 80b2427..9024dcd 100644 --- a/core/java/android/text/style/UnderlineSpan.java +++ b/core/java/android/text/style/UnderlineSpan.java @@ -30,6 +30,11 @@ public class UnderlineSpan extends CharacterStyle } public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + /** @hide */ + public int getSpanTypeIdInternal() { return TextUtils.UNDERLINE_SPAN; } @@ -38,6 +43,11 @@ public class UnderlineSpan extends CharacterStyle } public void writeToParcel(Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + /** @hide */ + public void writeToParcelInternal(Parcel dest, int flags) { } @Override diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java index d0e5b9e..a36e66c 100644 --- a/core/java/android/util/DisplayMetrics.java +++ b/core/java/android/util/DisplayMetrics.java @@ -79,6 +79,14 @@ public class DisplayMetrics { * This is not a density that applications should target, instead relying * on the system to scale their {@link #DENSITY_XXHIGH} assets for them. */ + public static final int DENSITY_360 = 360; + + /** + * Intermediate density for screens that sit somewhere between + * {@link #DENSITY_XHIGH} (320 dpi) and {@link #DENSITY_XXHIGH} (480 dpi). + * This is not a density that applications should target, instead relying + * on the system to scale their {@link #DENSITY_XXHIGH} assets for them. + */ public static final int DENSITY_400 = 400; /** diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index 6e2d110..664c02a 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -19,6 +19,7 @@ package android.view; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; +import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -158,7 +159,9 @@ final class AccessibilityInteractionController { try { mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; applyAppScaleAndMagnificationSpecIfNeeded(infos, spec); - if (spec != null) { + // Recycle if called from another process. Specs are cached in the + // system process and obtained from a pool when read from parcel. + if (spec != null && android.os.Process.myPid() != Binder.getCallingPid()) { spec.recycle(); } adjustIsVisibleToUserIfNeeded(infos, interactiveRegion); @@ -167,6 +170,12 @@ final class AccessibilityInteractionController { } catch (RemoteException re) { /* ignore - the other side will time out */ } + + // Recycle if called from the same process. Regions are obtained in + // the system process and instantiated when read from parcel. + if (interactiveRegion != null && android.os.Process.myPid() == Binder.getCallingPid()) { + interactiveRegion.recycle(); + } } } @@ -244,7 +253,9 @@ final class AccessibilityInteractionController { try { mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; applyAppScaleAndMagnificationSpecIfNeeded(infos, spec); - if (spec != null) { + // Recycle if called from another process. Specs are cached in the + // system process and obtained from a pool when read from parcel. + if (spec != null && android.os.Process.myPid() != Binder.getCallingPid()) { spec.recycle(); } adjustIsVisibleToUserIfNeeded(infos, interactiveRegion); @@ -252,6 +263,12 @@ final class AccessibilityInteractionController { } catch (RemoteException re) { /* ignore - the other side will time out */ } + + // Recycle if called from the same process. Regions are obtained in + // the system process and instantiated when read from parcel. + if (interactiveRegion != null && android.os.Process.myPid() == Binder.getCallingPid()) { + interactiveRegion.recycle(); + } } } @@ -354,7 +371,9 @@ final class AccessibilityInteractionController { try { mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; applyAppScaleAndMagnificationSpecIfNeeded(infos, spec); - if (spec != null) { + // Recycle if called from another process. Specs are cached in the + // system process and obtained from a pool when read from parcel. + if (spec != null && android.os.Process.myPid() != Binder.getCallingPid()) { spec.recycle(); } adjustIsVisibleToUserIfNeeded(infos, interactiveRegion); @@ -362,6 +381,12 @@ final class AccessibilityInteractionController { } catch (RemoteException re) { /* ignore - the other side will time out */ } + + // Recycle if called from the same process. Regions are obtained in + // the system process and instantiated when read from parcel. + if (interactiveRegion != null && android.os.Process.myPid() == Binder.getCallingPid()) { + interactiveRegion.recycle(); + } } } @@ -468,7 +493,9 @@ final class AccessibilityInteractionController { try { mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; applyAppScaleAndMagnificationSpecIfNeeded(focused, spec); - if (spec != null) { + // Recycle if called from another process. Specs are cached in the + // system process and obtained from a pool when read from parcel. + if (spec != null && android.os.Process.myPid() != Binder.getCallingPid()) { spec.recycle(); } adjustIsVisibleToUserIfNeeded(focused, interactiveRegion); @@ -476,6 +503,12 @@ final class AccessibilityInteractionController { } catch (RemoteException re) { /* ignore - the other side will time out */ } + + // Recycle if called from the same process. Regions are obtained in + // the system process and instantiated when read from parcel. + if (interactiveRegion != null && android.os.Process.myPid() == Binder.getCallingPid()) { + interactiveRegion.recycle(); + } } } @@ -545,7 +578,9 @@ final class AccessibilityInteractionController { try { mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; applyAppScaleAndMagnificationSpecIfNeeded(next, spec); - if (spec != null) { + // Recycle if called from another process. Specs are cached in the + // system process and obtained from a pool when read from parcel. + if (spec != null && android.os.Process.myPid() != Binder.getCallingPid()) { spec.recycle(); } adjustIsVisibleToUserIfNeeded(next, interactiveRegion); @@ -553,6 +588,12 @@ final class AccessibilityInteractionController { } catch (RemoteException re) { /* ignore - the other side will time out */ } + + // Recycle if called from the same process. Regions are obtained in + // the system process and instantiated when read from parcel. + if (interactiveRegion != null && android.os.Process.myPid() == Binder.getCallingPid()) { + interactiveRegion.recycle(); + } } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 774307f..126540f 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -17115,6 +17115,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Also calls {@link StateListAnimator#jumpToCurrentState()} if there is a StateListAnimator * attached to this view. */ + @CallSuper public void jumpDrawablesToCurrentState() { if (mBackground != null) { mBackground.jumpToCurrentState(); @@ -17564,13 +17565,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see Drawable#setTintMode(PorterDuff.Mode) */ public void setForegroundTintMode(@Nullable PorterDuff.Mode tintMode) { - if (mBackgroundTint == null) { - mBackgroundTint = new TintInfo(); + if (mForegroundInfo == null) { + mForegroundInfo = new ForegroundInfo(); } - mBackgroundTint.mTintMode = tintMode; - mBackgroundTint.mHasTintMode = true; + if (mForegroundInfo.mTintInfo == null) { + mForegroundInfo.mTintInfo = new TintInfo(); + } + mForegroundInfo.mTintInfo.mTintMode = tintMode; + mForegroundInfo.mTintInfo.mHasTintMode = true; - applyBackgroundTint(); + applyForegroundTint(); } /** @@ -17580,7 +17584,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @return the blending mode used to apply the tint to the foreground * drawable * @attr ref android.R.styleable#View_foregroundTintMode - * @see #setBackgroundTintMode(PorterDuff.Mode) + * @see #setForegroundTintMode(PorterDuff.Mode) */ @Nullable public PorterDuff.Mode getForegroundTintMode() { diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java index 886547a..e525474 100644 --- a/core/java/android/view/ViewStructure.java +++ b/core/java/android/view/ViewStructure.java @@ -18,7 +18,6 @@ package android.view; import android.graphics.Rect; import android.os.Bundle; -import android.text.TextPaint; /** * Container for storing additional per-view data generated by {@link View#onProvideStructure diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index 9e0719d..997e7e8 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -251,7 +251,7 @@ public final class WindowInsets { * @return true if any inset values are nonzero */ public boolean hasInsets() { - return hasSystemWindowInsets() || hasWindowDecorInsets(); + return hasSystemWindowInsets() || hasWindowDecorInsets() || hasStableInsets(); } /** diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index f7b6405..ca5f5ad 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -1058,6 +1058,13 @@ public interface WindowManagerPolicy { public boolean isKeyguardSecure(); /** + * Return whether the keyguard is on. + * + * @return true if in keyguard is on. + */ + public boolean isKeyguardShowingOrOccluded(); + + /** * inKeyguardRestrictedKeyInputMode * * if keyguard screen is showing or in restricted key input mode (i.e. in diff --git a/core/java/android/widget/ArrayAdapter.java b/core/java/android/widget/ArrayAdapter.java index 260854f..027f6d6 100644 --- a/core/java/android/widget/ArrayAdapter.java +++ b/core/java/android/widget/ArrayAdapter.java @@ -52,12 +52,6 @@ import java.util.List; */ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSpinnerAdapter { /** - * Contains the list of objects that represent the data of this ArrayAdapter. - * The content of this list is referred to as "the array" in the documentation. - */ - private List<T> mObjects; - - /** * Lock used to modify the content of {@link #mObjects}. Any write operation * performed on the array should be synchronized on this lock. This lock is also * used by the filter (see {@link #getFilter()} to make a synchronized copy of @@ -65,6 +59,14 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp */ private final Object mLock = new Object(); + private final LayoutInflater mInflater; + + /** + * Contains the list of objects that represent the data of this ArrayAdapter. + * The content of this list is referred to as "the array" in the documentation. + */ + private List<T> mObjects; + /** * The resource indicating what views to inflate to display the content of this * array adapter. @@ -97,8 +99,6 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp private ArrayList<T> mOriginalValues; private ArrayFilter mFilter; - private LayoutInflater mInflater; - /** Layout inflater used for {@link #getDropDownView(int, View, ViewGroup)}. */ private LayoutInflater mDropDownInflater; @@ -442,9 +442,6 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp return mDropDownInflater == null ? null : mDropDownInflater.getContext().getTheme(); } - /** - * {@inheritDoc} - */ @Override public View getDropDownView(int position, View convertView, ViewGroup parent) { final LayoutInflater inflater = mDropDownInflater == null ? mInflater : mDropDownInflater; diff --git a/core/java/android/widget/DatePickerCalendarDelegate.java b/core/java/android/widget/DatePickerCalendarDelegate.java index d38a225..7e542c9 100755 --- a/core/java/android/widget/DatePickerCalendarDelegate.java +++ b/core/java/android/widget/DatePickerCalendarDelegate.java @@ -115,7 +115,8 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate { R.styleable.DatePicker_internalLayout, R.layout.date_picker_material); // Set up and attach container. - mContainer = (ViewGroup) inflater.inflate(layoutResourceId, mDelegator); + mContainer = (ViewGroup) inflater.inflate(layoutResourceId, mDelegator, false); + mDelegator.addView(mContainer); // Set up header views. final ViewGroup header = (ViewGroup) mContainer.findViewById(R.id.date_picker_header); @@ -471,7 +472,11 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate { @Override public void setEnabled(boolean enabled) { - mContainer.setEnabled(false); + mContainer.setEnabled(enabled); + mDayPickerView.setEnabled(enabled); + mYearPickerView.setEnabled(enabled); + mHeaderYear.setEnabled(enabled); + mHeaderMonthDay.setEnabled(enabled); } @Override @@ -481,8 +486,7 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate { @Override public CalendarView getCalendarView() { - throw new UnsupportedOperationException( - "CalendarView does not exists for the new DatePicker"); + throw new UnsupportedOperationException("Not supported by calendar-mode DatePicker"); } @Override diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index bd57214..56f9b5c 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -1004,14 +1004,14 @@ public class Editor { stopSelectionActionMode(); } else { stopSelectionActionMode(); - startSelectionActionModeWithSelectionAndStartDrag(); + selectCurrentWordAndStartDrag(); } handled = true; } // Start a new selection if (!handled) { - handled = startSelectionActionModeWithSelectionAndStartDrag(); + handled = selectCurrentWordAndStartDrag(); } return handled; @@ -1724,22 +1724,9 @@ public class Editor { } /** - * Starts a Selection Action Mode with the current selection and enters drag mode. This should - * be used whenever the mode is started from a touch event. - * - * @return true if the selection mode was actually started. - */ - private boolean startSelectionActionModeWithSelectionAndStartDrag() { - boolean selectionStarted = startSelectionActionModeWithSelectionInternal(); - if (selectionStarted) { - getSelectionController().enterDrag(); - } - return selectionStarted; - } - - /** * Starts a Selection Action Mode with the current selection and ensures the selection handles - * are shown. This should be used when the mode is started from a non-touch event. + * are shown if there is a selection, otherwise the insertion handle is shown. This should be + * used when the mode is started from a non-touch event. * * @return true if the selection mode was actually started. */ @@ -1747,38 +1734,65 @@ public class Editor { boolean selectionStarted = startSelectionActionModeWithSelectionInternal(); if (selectionStarted) { getSelectionController().show(); + } else if (getInsertionController() != null) { + getInsertionController().show(); } return selectionStarted; } - private boolean startSelectionActionModeWithSelectionInternal() { + /** + * If the TextView allows text selection, selects the current word when no existing selection + * was available and starts a drag. + * + * @return true if the drag was started. + */ + private boolean selectCurrentWordAndStartDrag() { if (extractedTextModeWillBeStarted()) { // Cancel the single tap delayed runnable. if (mSelectionModeWithoutSelectionRunnable != null) { mTextView.removeCallbacks(mSelectionModeWithoutSelectionRunnable); } - return false; } - if (mSelectionActionMode != null) { - // Selection action mode is already started - mSelectionActionMode.invalidate(); + mSelectionActionMode.finish(); + } + if (!checkFieldAndSelectCurrentWord()) { return false; } + getSelectionController().enterDrag(); + return true; + } + /** + * Checks whether a selection can be performed on the current TextView and if so selects + * the current word. + * + * @return true if there already was a selection or if the current word was selected. + */ + private boolean checkFieldAndSelectCurrentWord() { if (!mTextView.canSelectText() || !mTextView.requestFocus()) { Log.w(TextView.LOG_TAG, - "TextView does not support text selection. Action mode cancelled."); + "TextView does not support text selection. Selection cancelled."); return false; } if (!mTextView.hasSelection()) { // There may already be a selection on device rotation - if (!selectCurrentWord()) { - // No word found under cursor or text selection not permitted. - return false; - } + return selectCurrentWord(); + } + return true; + } + + private boolean startSelectionActionModeWithSelectionInternal() { + if (mSelectionActionMode != null) { + // Selection action mode is already started + mSelectionActionMode.invalidate(); + return false; + } + + if (!checkFieldAndSelectCurrentWord()) { + return false; } boolean willExtract = extractedTextModeWillBeStarted(); @@ -1829,12 +1843,18 @@ public class Editor { } if (selectionStart == selectionEnd) { // Spans overlap the cursor. - return true; + for (int i = 0; i < suggestionSpans.length; i++) { + if (suggestionSpans[i].getSuggestions().length > 0) { + return true; + } + } + return false; } int minSpanStart = mTextView.getText().length(); int maxSpanEnd = 0; int unionOfSpansCoveringSelectionStartStart = mTextView.getText().length(); int unionOfSpansCoveringSelectionStartEnd = 0; + boolean hasValidSuggestions = false; for (int i = 0; i < suggestionSpans.length; i++) { final int spanStart = spannable.getSpanStart(suggestionSpans[i]); final int spanEnd = spannable.getSpanEnd(suggestionSpans[i]); @@ -1844,11 +1864,16 @@ public class Editor { // The span doesn't cover the current selection start point. continue; } + hasValidSuggestions = + hasValidSuggestions || suggestionSpans[i].getSuggestions().length > 0; unionOfSpansCoveringSelectionStartStart = Math.min(unionOfSpansCoveringSelectionStartStart, spanStart); unionOfSpansCoveringSelectionStartEnd = Math.max(unionOfSpansCoveringSelectionStartEnd, spanEnd); } + if (!hasValidSuggestions) { + return false; + } if (unionOfSpansCoveringSelectionStartStart >= unionOfSpansCoveringSelectionStartEnd) { // No spans cover the selection start point. return false; @@ -4387,7 +4412,7 @@ public class Editor { boolean stayedInArea = distanceSquared < doubleTapSlop * doubleTapSlop; if (stayedInArea && isPositionOnText(eventX, eventY)) { - startSelectionActionModeWithSelectionAndStartDrag(); + selectCurrentWordAndStartDrag(); mDiscardNextActionUp = true; } } @@ -4490,6 +4515,7 @@ public class Editor { mEndHandle.showAtLocation(endOffset); // No longer the first dragging motion, reset. + startSelectionActionModeWithSelection(); mDragAcceleratorActive = false; mStartOffset = -1; } diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java index 16dc26d..e7d9226 100644 --- a/core/java/android/widget/NumberPicker.java +++ b/core/java/android/widget/NumberPicker.java @@ -16,6 +16,7 @@ package android.widget; +import android.annotation.CallSuper; import android.annotation.IntDef; import android.annotation.Widget; import android.content.Context; @@ -608,7 +609,16 @@ public class NumberPicker extends LinearLayout { mSolidColor = attributesArray.getColor(R.styleable.NumberPicker_solidColor, 0); - mSelectionDivider = attributesArray.getDrawable(R.styleable.NumberPicker_selectionDivider); + final Drawable selectionDivider = attributesArray.getDrawable( + R.styleable.NumberPicker_selectionDivider); + if (selectionDivider != null) { + selectionDivider.setCallback(this); + selectionDivider.setLayoutDirection(getLayoutDirection()); + if (selectionDivider.isStateful()) { + selectionDivider.setState(getDrawableState()); + } + } + mSelectionDivider = selectionDivider; final int defSelectionDividerHeight = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT, @@ -1499,6 +1509,38 @@ public class NumberPicker extends LinearLayout { removeAllCallbacks(); } + @CallSuper + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + + final int[] state = getDrawableState(); + + if (mSelectionDivider != null && mSelectionDivider.isStateful()) { + mSelectionDivider.setState(state); + } + } + + @CallSuper + @Override + public void jumpDrawablesToCurrentState() { + super.jumpDrawablesToCurrentState(); + + if (mSelectionDivider != null) { + mSelectionDivider.jumpToCurrentState(); + } + } + + /** @hide */ + @Override + public void onResolveDrawables(@ResolvedLayoutDir int layoutDirection) { + super.onResolveDrawables(layoutDirection); + + if (mSelectionDivider != null) { + mSelectionDivider.setLayoutDirection(layoutDirection); + } + } + @Override protected void onDraw(Canvas canvas) { if (!mHasSelectorWheel) { diff --git a/core/java/android/widget/SimpleAdapter.java b/core/java/android/widget/SimpleAdapter.java index e7760ee..3bf9485 100644 --- a/core/java/android/widget/SimpleAdapter.java +++ b/core/java/android/widget/SimpleAdapter.java @@ -52,6 +52,8 @@ import java.util.Map; * If no appropriate binding can be found, an {@link IllegalStateException} is thrown. */ public class SimpleAdapter extends BaseAdapter implements Filterable, ThemedSpinnerAdapter { + private final LayoutInflater mInflater; + private int[] mTo; private String[] mFrom; private ViewBinder mViewBinder; @@ -60,7 +62,6 @@ public class SimpleAdapter extends BaseAdapter implements Filterable, ThemedSpin private int mResource; private int mDropDownResource; - private LayoutInflater mInflater; /** Layout inflater used for {@link #getDropDownView(int, View, ViewGroup)}. */ private LayoutInflater mDropDownInflater; @@ -174,8 +175,8 @@ public class SimpleAdapter extends BaseAdapter implements Filterable, ThemedSpin @Override public View getDropDownView(int position, View convertView, ViewGroup parent) { - return createViewFromResource( - mDropDownInflater, position, convertView, parent, mDropDownResource); + final LayoutInflater inflater = mDropDownInflater == null ? mInflater : mDropDownInflater; + return createViewFromResource(inflater, position, convertView, parent, mDropDownResource); } private void bindView(int position, View view) { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index e14e39c..3a85476 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -6680,7 +6680,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0, mHint.length(), mTextPaint, hintWidth) .setAlignment(alignment) - .setTextDir(mTextDir) + .setTextDirection(mTextDir) .setLineSpacing(mSpacingAdd, mSpacingMult) .setIncludePad(mIncludePad) .setBreakStrategy(mBreakStrategy) @@ -6771,7 +6771,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed, 0, mTransformed.length(), mTextPaint, wantWidth) .setAlignment(alignment) - .setTextDir(mTextDir) + .setTextDirection(mTextDir) .setLineSpacing(mSpacingAdd, mSpacingMult) .setIncludePad(mIncludePad) .setBreakStrategy(mBreakStrategy) diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java index ce94727..e607a3f 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.text.TextUtils; import android.util.Log; +import android.util.Printer; import android.util.Slog; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; @@ -314,6 +315,15 @@ public class InputMethodSubtypeSwitchingController { } return null; } + + protected void dump(final Printer pw, final String prefix) { + final int N = mImeSubtypeList.size(); + for (int i = 0; i < N; ++i) { + final int rank = i; + final ImeSubtypeListItem item = mImeSubtypeList.get(i); + pw.println(prefix + "rank=" + rank + " item=" + item); + } + } } private static class DynamicRotationList { @@ -388,6 +398,14 @@ public class InputMethodSubtypeSwitchingController { } return null; } + + protected void dump(final Printer pw, final String prefix) { + for (int i = 0; i < mUsageHistoryOfSubtypeListItemIndex.length; ++i) { + final int rank = mUsageHistoryOfSubtypeListItemIndex[i]; + final ImeSubtypeListItem item = mImeSubtypeList.get(i); + pw.println(prefix + "rank=" + rank + " item=" + item); + } + } } @VisibleForTesting @@ -478,6 +496,13 @@ public class InputMethodSubtypeSwitchingController { } return result; } + + protected void dump(final Printer pw) { + pw.println(" mSwitchingAwareRotationList:"); + mSwitchingAwareRotationList.dump(pw, " "); + pw.println(" mSwitchingUnawareRotationList:"); + mSwitchingUnawareRotationList.dump(pw, " "); + } } private final InputMethodSettings mSettings; @@ -526,4 +551,12 @@ public class InputMethodSubtypeSwitchingController { return mSubtypeList.getSortedInputMethodAndSubtypeList( showSubtypes, includingAuxiliarySubtypes, isScreenLocked); } + + public void dump(final Printer pw) { + if (mController != null) { + mController.dump(pw); + } else { + pw.println(" mController=null"); + } + } } diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java index e02d706..c77d614 100644 --- a/core/java/com/android/internal/widget/FloatingToolbar.java +++ b/core/java/com/android/internal/widget/FloatingToolbar.java @@ -37,6 +37,7 @@ import android.view.View.MeasureSpec; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.Window; +import android.view.WindowInsets; import android.view.WindowManager; import android.view.animation.Animation; import android.view.animation.AnimationSet; @@ -83,7 +84,7 @@ public final class FloatingToolbar { private final Rect mContentRect = new Rect(); private Menu mMenu; - private List<CharSequence> mShowingTitles = new ArrayList<CharSequence>(); + private List<Object> mShowingMenuItems = new ArrayList<Object>(); private MenuItem.OnMenuItemClickListener mMenuItemClickListener = NO_OP_MENUITEM_CLICK_LISTENER; private int mSuggestedWidth; @@ -155,7 +156,7 @@ public final class FloatingToolbar { if (!isCurrentlyShowing(menuItems) || mWidthChanged) { mPopup.dismiss(); mPopup.layoutMenuItems(menuItems, mMenuItemClickListener, mSuggestedWidth); - mShowingTitles = getMenuItemTitles(menuItems); + mShowingMenuItems = getShowingMenuItemsReferences(menuItems); } mPopup.updateCoordinates(mContentRect); if (!mPopup.isShowing()) { @@ -210,7 +211,7 @@ public final class FloatingToolbar { * Returns true if this floating toolbar is currently showing the specified menu items. */ private boolean isCurrentlyShowing(List<MenuItem> menuItems) { - return mShowingTitles.equals(getMenuItemTitles(menuItems)); + return mShowingMenuItems.equals(getShowingMenuItemsReferences(menuItems)); } /** @@ -233,12 +234,16 @@ public final class FloatingToolbar { return menuItems; } - private List<CharSequence> getMenuItemTitles(List<MenuItem> menuItems) { - List<CharSequence> titles = new ArrayList<CharSequence>(); + private List<Object> getShowingMenuItemsReferences(List<MenuItem> menuItems) { + List<Object> references = new ArrayList<Object>(); for (MenuItem menuItem : menuItems) { - titles.add(menuItem.getTitle()); + if (isIconOnlyMenuItem(menuItem)) { + references.add(menuItem.getIcon()); + } else { + references.add(menuItem.getTitle()); + } } - return titles; + return references; } @@ -323,6 +328,7 @@ public final class FloatingToolbar { } }; + private final Rect mViewPort = new Rect(); private final Point mCoords = new Point(); private final Region mTouchableRegion = new Region(); @@ -394,7 +400,7 @@ public final class FloatingToolbar { mMainPanel = new FloatingToolbarMainPanel(mParent.getContext(), mOpenOverflow); } List<MenuItem> overflowMenuItems = - mMainPanel.layoutMenuItems(menuItems, suggestedWidth); + mMainPanel.layoutMenuItems(menuItems, getToolbarWidth(suggestedWidth)); mMainPanel.setOnMenuItemClickListener(menuItemClickListener); if (!overflowMenuItems.isEmpty()) { if (mOverflowPanel == null) { @@ -430,7 +436,8 @@ public final class FloatingToolbar { // The "show" animation will make this visible. mContentContainer.setAlpha(0); } - updateOverflowHeight(contentRect.top - (mMarginVertical * 2)); + refreshViewPort(); + updateOverflowHeight(contentRect.top - (mMarginVertical * 2) - mViewPort.top); refreshCoordinatesAndOverflowDirection(contentRect); preparePopupContent(); mPopupWindow.showAtLocation(mParent, Gravity.NO_GRAVITY, mCoords.x, mCoords.y); @@ -494,6 +501,7 @@ public final class FloatingToolbar { } cancelOverflowAnimations(); + refreshViewPort(); refreshCoordinatesAndOverflowDirection(contentRect); preparePopupContent(); mPopupWindow.update(mCoords.x, mCoords.y, getWidth(), getHeight()); @@ -521,18 +529,24 @@ public final class FloatingToolbar { } private void refreshCoordinatesAndOverflowDirection(Rect contentRect) { + // NOTE: Ensure that mViewPort has been refreshed before this. + int x = contentRect.centerX() - getWidth() / 2; int y; - if (contentRect.top > getHeight()) { + if (contentRect.top - getHeight() > mViewPort.top) { y = contentRect.top - getHeight(); mOverflowDirection = FloatingToolbarPopup.OVERFLOW_DIRECTION_UP; - } else if (contentRect.top > getToolbarHeightWithVerticalMargin()) { + } else if (contentRect.top - getToolbarHeightWithVerticalMargin() > mViewPort.top) { y = contentRect.top - getToolbarHeightWithVerticalMargin(); mOverflowDirection = FloatingToolbarPopup.OVERFLOW_DIRECTION_DOWN; } else { y = contentRect.bottom; mOverflowDirection = FloatingToolbarPopup.OVERFLOW_DIRECTION_DOWN; } + + // Update x so that the toolbar isn't rendered behind the nav bar in landscape. + x = Math.max(0, Math.min(x, mViewPort.right - getWidth())); + mCoords.set(x, y); if (mOverflowPanel != null) { mOverflowPanel.setOverflowDirection(mOverflowDirection); @@ -821,6 +835,29 @@ public final class FloatingToolbar { mPopupWindow.setHeight(height + mMarginVertical * 2); } + + private void refreshViewPort() { + mParent.getGlobalVisibleRect(mViewPort); + WindowInsets windowInsets = mParent.getRootWindowInsets(); + mViewPort.set( + mViewPort.left + windowInsets.getStableInsetLeft(), + mViewPort.top + windowInsets.getStableInsetTop(), + mViewPort.right - windowInsets.getStableInsetRight(), + mViewPort.bottom - windowInsets.getStableInsetBottom()); + } + + private int getToolbarWidth(int suggestedWidth) { + int width = suggestedWidth; + refreshViewPort(); + int maximumWidth = mViewPort.width() - 2 * mParent.getResources() + .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin); + if (width <= 0) { + width = mParent.getResources() + .getDimensionPixelSize(R.dimen.floating_toolbar_preferred_width); + } + return Math.min(width, maximumWidth); + } + /** * Sets the touchable region of this popup to be zero. This means that all touch events on * this popup will go through to the surface behind it. @@ -906,12 +943,11 @@ public final class FloatingToolbar { * * @return The menu items that are not included in this main panel. */ - public List<MenuItem> layoutMenuItems(List<MenuItem> menuItems, int suggestedWidth) { + public List<MenuItem> layoutMenuItems(List<MenuItem> menuItems, int width) { Preconditions.checkNotNull(menuItems); - final int toolbarWidth = getAdjustedToolbarWidth(mContext, suggestedWidth) - // Reserve space for the "open overflow" button. - - getEstimatedOpenOverflowButtonWidth(mContext); + // Reserve space for the "open overflow" button. + final int toolbarWidth = width - getEstimatedOpenOverflowButtonWidth(mContext); int availableWidth = toolbarWidth; final LinkedList<MenuItem> remainingMenuItems = new LinkedList<MenuItem>(menuItems); diff --git a/core/jni/android_os_Trace.cpp b/core/jni/android_os_Trace.cpp index 52fd111..3fd3b3c 100644 --- a/core/jni/android_os_Trace.cpp +++ b/core/jni/android_os_Trace.cpp @@ -111,19 +111,19 @@ static JNINativeMethod gTraceMethods[] = { "()J", (void*)android_os_Trace_nativeGetEnabledTags }, { "nativeTraceCounter", - "(JLjava/lang/String;I)V", + "!(JLjava/lang/String;I)V", (void*)android_os_Trace_nativeTraceCounter }, { "nativeTraceBegin", - "(JLjava/lang/String;)V", + "!(JLjava/lang/String;)V", (void*)android_os_Trace_nativeTraceBegin }, { "nativeTraceEnd", - "(J)V", + "!(J)V", (void*)android_os_Trace_nativeTraceEnd }, { "nativeAsyncTraceBegin", - "(JLjava/lang/String;I)V", + "!(JLjava/lang/String;I)V", (void*)android_os_Trace_nativeAsyncTraceBegin }, { "nativeAsyncTraceEnd", - "(JLjava/lang/String;I)V", + "!(JLjava/lang/String;I)V", (void*)android_os_Trace_nativeAsyncTraceEnd }, { "nativeSetAppTracingAllowed", "(Z)V", diff --git a/core/res/res/drawable/number_picker_divider_material.xml b/core/res/res/drawable/number_picker_divider_material.xml new file mode 100644 index 0000000..2474be0 --- /dev/null +++ b/core/res/res/drawable/number_picker_divider_material.xml @@ -0,0 +1,21 @@ +<?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. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:tint="?attr/colorControlNormal" + android:shape="rectangle"> + <solid android:color="@color/black" /> +</shape> diff --git a/core/res/res/layout/number_picker_material.xml b/core/res/res/layout/number_picker_material.xml new file mode 100644 index 0000000..47edec4 --- /dev/null +++ b/core/res/res/layout/number_picker_material.xml @@ -0,0 +1,25 @@ +<?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. +--> + +<view xmlns:android="http://schemas.android.com/apk/res/android" + class="android.widget.NumberPicker$CustomEditText" + android:id="@+id/numberpicker_input" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:singleLine="true" + android:background="@null" + android:textAppearance="@style/TextAppearance.Material.Caption" /> diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml index f7a42fa..7782ed7 100644 --- a/core/res/res/values/styles_material.xml +++ b/core/res/res/values/styles_material.xml @@ -630,13 +630,13 @@ please see styles_device_defaults.xml. </style> <style name="Widget.Material.NumberPicker" parent="Widget.NumberPicker"> - <item name="internalLayout">@layout/number_picker_with_selector_wheel</item> + <item name="internalLayout">@layout/number_picker_material</item> <item name="solidColor">@color/transparent</item> - <item name="selectionDivider">@drawable/numberpicker_selection_divider</item> - <item name="selectionDividerHeight">2dip</item> - <item name="selectionDividersDistance">48dip</item> - <item name="internalMinWidth">64dip</item> - <item name="internalMaxHeight">180dip</item> + <item name="selectionDivider">@drawable/number_picker_divider_material</item> + <item name="selectionDividerHeight">2dp</item> + <item name="selectionDividersDistance">48dp</item> + <item name="internalMinWidth">64dp</item> + <item name="internalMaxHeight">180dp</item> <item name="virtualButtonPressedDrawable">?attr/selectableItemBackground</item> </style> diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index 37ed723..1a05104 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -514,15 +514,20 @@ public class KeyStore { } } - public OperationResult finish(IBinder token, KeymasterArguments arguments, byte[] signature) { + public OperationResult finish(IBinder token, KeymasterArguments arguments, byte[] signature, + byte[] entropy) { try { - return mBinder.finish(token, arguments, signature); + return mBinder.finish(token, arguments, signature, entropy); } catch (RemoteException e) { Log.w(TAG, "Cannot connect to keystore", e); return null; } } + public OperationResult finish(IBinder token, KeymasterArguments arguments, byte[] signature) { + return finish(token, arguments, signature, null); + } + public int abort(IBinder token) { try { return mBinder.abort(token); diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java index dc4c8a3..4d6178f 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java @@ -296,19 +296,33 @@ public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { int flags = 0; String keyAliasInKeystore = Credentials.USER_SECRET_KEY + spec.getKeystoreAlias(); KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics(); - int errorCode = mKeyStore.generateKey( - keyAliasInKeystore, args, additionalEntropy, flags, resultingKeyCharacteristics); - if (errorCode != KeyStore.NO_ERROR) { - throw new ProviderException( - "Keystore operation failed", KeyStore.getKeyStoreException(errorCode)); - } - @KeyProperties.KeyAlgorithmEnum String keyAlgorithmJCA; + boolean success = false; try { - keyAlgorithmJCA = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm( - mKeymasterAlgorithm, mKeymasterDigest); - } catch (IllegalArgumentException e) { - throw new ProviderException("Failed to obtain JCA secret key algorithm name", e); + Credentials.deleteAllTypesForAlias(mKeyStore, spec.getKeystoreAlias()); + int errorCode = mKeyStore.generateKey( + keyAliasInKeystore, + args, + additionalEntropy, + flags, + resultingKeyCharacteristics); + if (errorCode != KeyStore.NO_ERROR) { + throw new ProviderException( + "Keystore operation failed", KeyStore.getKeyStoreException(errorCode)); + } + @KeyProperties.KeyAlgorithmEnum String keyAlgorithmJCA; + try { + keyAlgorithmJCA = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm( + mKeymasterAlgorithm, mKeymasterDigest); + } catch (IllegalArgumentException e) { + throw new ProviderException("Failed to obtain JCA secret key algorithm name", e); + } + SecretKey result = new AndroidKeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmJCA); + success = true; + return result; + } finally { + if (!success) { + Credentials.deleteAllTypesForAlias(mKeyStore, spec.getKeystoreAlias()); + } } - return new AndroidKeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmJCA); } } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java index 4b45fd7..7b5ca3a 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -121,7 +121,6 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato public KeyPair generateKeyPair() { if (mKeyStore == null || mSpec == null) { throw new IllegalStateException("Not initialized"); - } final int flags = (mEncryptionAtRestRequired) ? KeyStore.FLAG_ENCRYPTED : 0; @@ -134,62 +133,65 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato final String alias = mSpec.getKeystoreAlias(); - Credentials.deleteAllTypesForAlias(mKeyStore, alias); - byte[][] args = getArgsForKeyType(mKeyType, mSpec.getAlgorithmParameterSpec()); final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias; - if (!mKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF, mKeyType, mKeySize, - flags, args)) { - throw new IllegalStateException("could not generate key in keystore"); - } + boolean success = false; + try { + Credentials.deleteAllTypesForAlias(mKeyStore, alias); + if (!mKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF, mKeyType, mKeySize, + flags, args)) { + throw new IllegalStateException("could not generate key in keystore"); + } - Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias); + final PrivateKey privKey; + final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore"); + try { + privKey = engine.getPrivateKeyById(privateKeyAlias); + } catch (InvalidKeyException e) { + throw new RuntimeException("Can't get key", e); + } - final PrivateKey privKey; - final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore"); - try { - privKey = engine.getPrivateKeyById(privateKeyAlias); - } catch (InvalidKeyException e) { - throw new RuntimeException("Can't get key", e); - } + final byte[] pubKeyBytes = mKeyStore.getPubkey(privateKeyAlias); - final byte[] pubKeyBytes = mKeyStore.getPubkey(privateKeyAlias); + final PublicKey pubKey; + try { + final KeyFactory keyFact = KeyFactory.getInstance(mKeyAlgorithm); + pubKey = keyFact.generatePublic(new X509EncodedKeySpec(pubKeyBytes)); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("Can't instantiate key generator", e); + } catch (InvalidKeySpecException e) { + throw new IllegalStateException("keystore returned invalid key encoding", e); + } - final PublicKey pubKey; - try { - final KeyFactory keyFact = KeyFactory.getInstance(mKeyAlgorithm); - pubKey = keyFact.generatePublic(new X509EncodedKeySpec(pubKeyBytes)); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("Can't instantiate key generator", e); - } catch (InvalidKeySpecException e) { - throw new IllegalStateException("keystore returned invalid key encoding", e); - } + final X509Certificate cert; + try { + cert = generateCertificate(privKey, pubKey); + } catch (Exception e) { + throw new IllegalStateException("Can't generate certificate", e); + } - final X509Certificate cert; - try { - cert = generateCertificate(privKey, pubKey); - } catch (Exception e) { - Credentials.deleteAllTypesForAlias(mKeyStore, alias); - throw new IllegalStateException("Can't generate certificate", e); - } + byte[] certBytes; + try { + certBytes = cert.getEncoded(); + } catch (CertificateEncodingException e) { + throw new IllegalStateException("Can't get encoding of certificate", e); + } - byte[] certBytes; - try { - certBytes = cert.getEncoded(); - } catch (CertificateEncodingException e) { - Credentials.deleteAllTypesForAlias(mKeyStore, alias); - throw new IllegalStateException("Can't get encoding of certificate", e); - } + if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, certBytes, KeyStore.UID_SELF, + flags)) { + throw new IllegalStateException("Can't store certificate in AndroidKeyStore"); + } - if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, certBytes, KeyStore.UID_SELF, - flags)) { - Credentials.deleteAllTypesForAlias(mKeyStore, alias); - throw new IllegalStateException("Can't store certificate in AndroidKeyStore"); + KeyPair result = new KeyPair(pubKey, privKey); + success = true; + return result; + } finally { + if (!success) { + Credentials.deleteAllTypesForAlias(mKeyStore, alias); + } } - - return new KeyPair(pubKey, privKey); } @SuppressWarnings("deprecation") diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java index 701bd67..05ddef6 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java @@ -674,12 +674,13 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { @Override public void engineDeleteEntry(String alias) throws KeyStoreException { - if (!isKeyEntry(alias) && !isCertificateEntry(alias)) { + if (!engineContainsAlias(alias)) { return; } + // At least one entry corresponding to this alias exists in keystore if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias)) { - throw new KeyStoreException("No such entry " + alias); + throw new KeyStoreException("Failed to delete entry: " + alias); } } @@ -849,9 +850,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { throw new KeyStoreException("entry == null"); } - if (engineContainsAlias(alias)) { - engineDeleteEntry(alias); - } + Credentials.deleteAllTypesForAlias(mKeyStore, alias); if (entry instanceof KeyStore.TrustedCertificateEntry) { KeyStore.TrustedCertificateEntry trE = (KeyStore.TrustedCertificateEntry) entry; diff --git a/media/java/android/media/ClosedCaptionRenderer.java b/media/java/android/media/ClosedCaptionRenderer.java index e3680e9..8403c1c 100644 --- a/media/java/android/media/ClosedCaptionRenderer.java +++ b/media/java/android/media/ClosedCaptionRenderer.java @@ -1044,42 +1044,26 @@ class CCParser { } /** - * @hide - * - * MutableBackgroundColorSpan - * - * This is a mutable version of BackgroundSpan to facilitate text - * rendering with edge styles. + * Mutable version of BackgroundSpan to facilitate text rendering with edge + * styles. * + * @hide */ -class MutableBackgroundColorSpan extends CharacterStyle - implements UpdateAppearance, ParcelableSpan { +class MutableBackgroundColorSpan extends CharacterStyle implements UpdateAppearance { private int mColor; public MutableBackgroundColorSpan(int color) { mColor = color; } - public MutableBackgroundColorSpan(Parcel src) { - mColor = src.readInt(); - } + public void setBackgroundColor(int color) { mColor = color; } + public int getBackgroundColor() { return mColor; } - @Override - public int getSpanTypeId() { - return TextUtils.BACKGROUND_COLOR_SPAN; - } - @Override - public int describeContents() { - return 0; - } - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mColor); - } + @Override public void updateDrawState(TextPaint ds) { ds.bgColor = mColor; diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java index 8441541..166ff38 100644 --- a/media/java/android/media/Ringtone.java +++ b/media/java/android/media/Ringtone.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.res.AssetFileDescriptor; import android.content.res.Resources.NotFoundException; import android.database.Cursor; +import android.media.MediaPlayer.OnCompletionListener; import android.net.Uri; import android.os.Binder; import android.os.RemoteException; @@ -29,6 +30,7 @@ import android.provider.Settings; import android.util.Log; import java.io.IOException; +import java.util.ArrayList; /** * Ringtone provides a quick method for playing a ringtone, notification, or @@ -49,6 +51,9 @@ public class Ringtone { MediaStore.Audio.Media.TITLE }; + // keep references on active Ringtones until stopped or completion listener called. + private static final ArrayList<Ringtone> sActiveRingtones = new ArrayList<Ringtone>(); + private final Context mContext; private final AudioManager mAudioManager; @@ -62,6 +67,7 @@ public class Ringtone { private final Binder mRemoteToken; private MediaPlayer mLocalPlayer; + private final MyOnCompletionListener mCompletionListener = new MyOnCompletionListener(); private Uri mUri; private String mTitle; @@ -247,7 +253,7 @@ public class Ringtone { // (typically because ringer mode is silent). if (mAudioManager.getStreamVolume( AudioAttributes.toLegacyStreamType(mAudioAttributes)) != 0) { - mLocalPlayer.start(); + startLocalPlayer(); } } else if (mAllowRemote && (mRemotePlayer != null)) { final Uri canonicalUri = mUri.getCanonicalUri(); @@ -285,7 +291,21 @@ public class Ringtone { mLocalPlayer.reset(); mLocalPlayer.release(); mLocalPlayer = null; + synchronized (sActiveRingtones) { + sActiveRingtones.remove(this); + } + } + } + + private void startLocalPlayer() { + if (mLocalPlayer == null) { + return; } + synchronized (sActiveRingtones) { + sActiveRingtones.add(this); + } + mLocalPlayer.setOnCompletionListener(mCompletionListener); + mLocalPlayer.start(); } /** @@ -330,7 +350,7 @@ public class Ringtone { } mLocalPlayer.setAudioAttributes(mAudioAttributes); mLocalPlayer.prepare(); - mLocalPlayer.start(); + startLocalPlayer(); afd.close(); return true; } else { @@ -352,4 +372,20 @@ public class Ringtone { void setTitle(String title) { mTitle = title; } + + @Override + protected void finalize() { + if (mLocalPlayer != null) { + mLocalPlayer.release(); + } + } + + class MyOnCompletionListener implements MediaPlayer.OnCompletionListener { + public void onCompletion(MediaPlayer mp) + { + synchronized (sActiveRingtones) { + sActiveRingtones.remove(Ringtone.this); + } + } + } } diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java index 29c3c75..cd0e587 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java @@ -731,27 +731,31 @@ public class CameraMetadataTest extends junit.framework.TestCase { @SmallTest public void testReadWriteHighSpeedVideoConfiguration() { - // int32 x 4 x 1 + // int32 x 5 x 1 checkKeyMarshal("android.control.availableHighSpeedVideoConfigurations", new HighSpeedVideoConfiguration( - /*width*/1000, /*height*/255, /*fpsMin*/30, /*fpsMax*/200), + /*width*/1000, /*height*/255, /*fpsMin*/30, /*fpsMax*/200, + /*batchSizeMax*/8), /* width, height, fpsMin, fpsMax */ - toByteArray(1000, 255, 30, 200)); + toByteArray(1000, 255, 30, 200, 8)); - // int32 x 4 x 3 + // int32 x 5 x 3 checkKeyMarshal("android.control.availableHighSpeedVideoConfigurations", new HighSpeedVideoConfiguration[] { new HighSpeedVideoConfiguration( - /*width*/1280, /*height*/720, /*fpsMin*/60, /*fpsMax*/120), + /*width*/1280, /*height*/720, /*fpsMin*/60, /*fpsMax*/120, + /*batchSizeMax*/8), new HighSpeedVideoConfiguration( - /*width*/123, /*height*/456, /*fpsMin*/1, /*fpsMax*/200), + /*width*/123, /*height*/456, /*fpsMin*/1, /*fpsMax*/200, + /*batchSizeMax*/4), new HighSpeedVideoConfiguration( - /*width*/4096, /*height*/2592, /*fpsMin*/30, /*fpsMax*/60) + /*width*/4096, /*height*/2592, /*fpsMin*/30, /*fpsMax*/60, + /*batchSizeMax*/2) }, toByteArray( - 1280, 720, 60, 120, - 123, 456, 1, 200, - 4096, 2592, 30, 60 + 1280, 720, 60, 120, 8, + 123, 456, 1, 200, 4, + 4096, 2592, 30, 60, 2 )); } diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index fd0ba73..640fb29 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -25,6 +25,7 @@ <!-- Standard permissions granted to the shell. --> <uses-permission android:name="android.permission.SEND_SMS" /> <uses-permission android:name="android.permission.CALL_PHONE" /> + <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.WRITE_CONTACTS" /> <uses-permission android:name="android.permission.READ_CALENDAR" /> @@ -97,6 +98,10 @@ <uses-permission android:name="android.permission.CHANGE_APP_IDLE_STATE" /> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /> <uses-permission android:name="android.permission.MOUNT_FORMAT_FILESYSTEMS" /> + <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" /> + <uses-permission android:name="android.permission.REGISTER_CALL_PROVIDER" /> + <uses-permission android:name="android.permission.REGISTER_CONNECTION_MANAGER" /> + <uses-permission android:name="android.permission.REGISTER_SIM_SUBSCRIPTION" /> <application android:label="@string/app_label"> <provider diff --git a/packages/SystemUI/res/layout/recents_task_view_header.xml b/packages/SystemUI/res/layout/recents_task_view_header.xml index 53047a3..477d9d7 100644 --- a/packages/SystemUI/res/layout/recents_task_view_header.xml +++ b/packages/SystemUI/res/layout/recents_task_view_header.xml @@ -21,6 +21,7 @@ android:layout_gravity="top|center_horizontal"> <com.android.systemui.recents.views.FixedSizeImageView android:id="@+id/application_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" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 18a19cb..9f38185 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -336,7 +336,7 @@ <dimen name="keyguard_affordance_min_background_radius">30dp</dimen> <!-- The size of the touch targets on the keyguard for the affordances. --> - <dimen name="keyguard_affordance_touch_target_size">96dp</dimen> + <dimen name="keyguard_affordance_touch_target_size">120dp</dimen> <!-- The grow amount for the camera and phone circles when hinting --> <dimen name="hint_grow_amount_sideways">60dp</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java index 0369ab5..5d74604 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java @@ -74,7 +74,11 @@ public class FlashlightTile extends QSTile<QSTile.BooleanState> implements state.visible = mFlashlightController.isAvailable(); state.label = mHost.getContext().getString(R.string.quick_settings_flashlight_label); if (arg instanceof UserBoolean) { - state.value = ((UserBoolean) arg).value; + boolean value = ((UserBoolean) arg).value; + if (value == state.value) { + return; + } + state.value = value; } final AnimationIcon icon = state.value ? mEnable : mDisable; icon.setAllowAnimation(arg instanceof UserBoolean && ((UserBoolean) arg).userInitiated); 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 01ed08a..4a5fef3 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -22,6 +22,7 @@ import android.animation.ValueAnimator; import android.content.Context; import android.graphics.*; import android.util.AttributeSet; +import android.view.accessibility.AccessibilityManager; import android.view.View; import android.view.ViewOutlineProvider; import android.view.animation.AccelerateInterpolator; @@ -672,7 +673,11 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, mThumbnailView.rebindToTask(mTask); mHeaderView.rebindToTask(mTask); // Rebind any listeners - mHeaderView.mApplicationIcon.setOnClickListener(this); + AccessibilityManager am = (AccessibilityManager) getContext(). + getSystemService(Context.ACCESSIBILITY_SERVICE); + if (Constants.DebugFlags.App.EnableTaskFiltering || (am != null && am.isEnabled())) { + mHeaderView.mApplicationIcon.setOnClickListener(this); + } mHeaderView.mDismissButton.setOnClickListener(this); if (mConfig.multiStackEnabled) { mHeaderView.mMoveTaskButton.setOnClickListener(this); @@ -718,9 +723,19 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, postDelayed(new Runnable() { @Override public void run() { - if (Constants.DebugFlags.App.EnableTaskFiltering && v == mHeaderView.mApplicationIcon) { - if (mCb != null) { - mCb.onTaskViewAppIconClicked(tv); + if (v == mHeaderView.mApplicationIcon) { + if (Constants.DebugFlags.App.EnableTaskFiltering) { + if (mCb != null) { + mCb.onTaskViewAppIconClicked(tv); + } + } else { + AccessibilityManager am = (AccessibilityManager) getContext(). + getSystemService(Context.ACCESSIBILITY_SERVICE); + if (am != null && am.isEnabled()) { + if (mCb != null) { + mCb.onTaskViewAppInfoClicked(tv); + } + } } } else if (v == mHeaderView.mDismissButton) { dismissTask(); 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 f397bc3..82f0f31 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java @@ -190,7 +190,6 @@ public class TaskViewHeader extends FrameLayout { } else if (t.applicationIcon != null) { mApplicationIcon.setImageDrawable(t.applicationIcon); } - mApplicationIcon.setContentDescription(t.contentDescription); if (!mActivityDescription.getText().toString().equals(t.activityLabel)) { mActivityDescription.setText(t.activityLabel); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java index 9ccff72..374d970 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java @@ -23,11 +23,14 @@ import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; +import android.graphics.CanvasProperty; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.util.AttributeSet; +import android.view.DisplayListCanvas; +import android.view.RenderNodeAnimator; import android.view.View; import android.view.ViewAnimationUtils; import android.view.animation.AnimationUtils; @@ -35,6 +38,7 @@ import android.view.animation.Interpolator; import android.widget.ImageView; import com.android.systemui.R; import com.android.systemui.statusbar.phone.KeyguardAffordanceHelper; +import com.android.systemui.statusbar.phone.PhoneStatusBar; /** * An ImageView which does not have overlapping renderings commands and therefore does not need a @@ -77,6 +81,14 @@ public class KeyguardAffordanceView extends ImageView { private float mMaxCircleSize; private Animator mPreviewClipper; private float mRestingAlpha = KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT; + private boolean mSupportHardware; + private boolean mFinishing; + + private CanvasProperty<Float> mHwCircleRadius; + private CanvasProperty<Float> mHwCenterX; + private CanvasProperty<Float> mHwCenterY; + private CanvasProperty<Paint> mHwCirclePaint; + private AnimatorListenerAdapter mClipEndListener = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -155,6 +167,7 @@ public class KeyguardAffordanceView extends ImageView { @Override protected void onDraw(Canvas canvas) { + mSupportHardware = canvas.isHardwareAccelerated(); drawBackgroundCircle(canvas); drawArrow(canvas); canvas.save(); @@ -196,8 +209,14 @@ public class KeyguardAffordanceView extends ImageView { private void drawBackgroundCircle(Canvas canvas) { if (mCircleRadius > 0) { - updateCircleColor(); - canvas.drawCircle(mCenterX, mCenterY, mCircleRadius, mCirclePaint); + if (mFinishing && mSupportHardware) { + DisplayListCanvas displayListCanvas = (DisplayListCanvas) canvas; + displayListCanvas.drawCircle(mHwCenterX, mHwCenterY, mHwCircleRadius, + mHwCirclePaint); + } else { + updateCircleColor(); + canvas.drawCircle(mCenterX, mCenterY, mCircleRadius, mCirclePaint); + } } } @@ -218,15 +237,23 @@ public class KeyguardAffordanceView extends ImageView { public void finishAnimation(float velocity, final Runnable mAnimationEndRunnable) { cancelAnimator(mCircleAnimator); cancelAnimator(mPreviewClipper); + mFinishing = true; mCircleStartRadius = mCircleRadius; float maxCircleSize = getMaxCircleSize(); - ValueAnimator animatorToRadius = getAnimatorToRadius(maxCircleSize); + Animator animatorToRadius; + if (mSupportHardware) { + initHwProperties(); + animatorToRadius = getRtAnimatorToRadius(maxCircleSize); + } else { + animatorToRadius = getAnimatorToRadius(maxCircleSize); + } mFlingAnimationUtils.applyDismissing(animatorToRadius, mCircleRadius, maxCircleSize, velocity, maxCircleSize); animatorToRadius.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mAnimationEndRunnable.run(); + mFinishing = false; } }); animatorToRadius.start(); @@ -240,9 +267,34 @@ public class KeyguardAffordanceView extends ImageView { velocity, maxCircleSize); mPreviewClipper.addListener(mClipEndListener); mPreviewClipper.start(); + if (mSupportHardware) { + startRtCircleFadeOut(animatorToRadius.getDuration()); + } } } + private void startRtCircleFadeOut(long duration) { + RenderNodeAnimator animator = new RenderNodeAnimator(mHwCirclePaint, + RenderNodeAnimator.PAINT_ALPHA, 0); + animator.setDuration(duration); + animator.setInterpolator(PhoneStatusBar.ALPHA_OUT); + animator.setTarget(this); + animator.start(); + } + + private Animator getRtAnimatorToRadius(float circleRadius) { + RenderNodeAnimator animator = new RenderNodeAnimator(mHwCircleRadius, circleRadius); + animator.setTarget(this); + return animator; + } + + private void initHwProperties() { + mHwCenterX = CanvasProperty.createFloat(mCenterX); + mHwCenterY = CanvasProperty.createFloat(mCenterY); + mHwCirclePaint = CanvasProperty.createPaint(mCirclePaint); + mHwCircleRadius = CanvasProperty.createFloat(mCircleRadius); + } + private float getMaxCircleSize() { getLocationInWindow(mTempPoint); float rootWidth = getRootView().getWidth(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 2fe98bb..4bc317a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -196,6 +196,7 @@ public class NotificationPanelView extends PanelView implements private boolean mCollapsedOnDown; private int mPositionMinSideMargin; private int mLastOrientation = -1; + private boolean mClosingWithAlphaFadeOut; private Runnable mHeadsUpExistenceChangedRunnable = new Runnable() { @Override @@ -527,6 +528,7 @@ public class NotificationPanelView extends PanelView implements protected void flingToHeight(float vel, boolean expand, float target, float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) { mHeadsUpTouchHelper.notifyFling(!expand); + setClosingWithAlphaFadeout(!expand && getFadeoutAlpha() == 1.0f); super.flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing); } @@ -638,10 +640,9 @@ public class NotificationPanelView extends PanelView implements @Override protected boolean isInContentBounds(float x, float y) { - float yTransformed = y - mNotificationStackScroller.getY(); float stackScrollerX = mNotificationStackScroller.getX(); - return mNotificationStackScroller.isInContentBounds(yTransformed) && stackScrollerX < x - && x < stackScrollerX + mNotificationStackScroller.getWidth(); + return !mNotificationStackScroller.isBelowLastNotification(x - stackScrollerX, y) + && stackScrollerX < x && x < stackScrollerX + mNotificationStackScroller.getWidth(); } private void initDownStates(MotionEvent event) { @@ -1074,8 +1075,12 @@ public class NotificationPanelView extends PanelView implements }; private void animateHeaderSlidingIn() { - mHeaderAnimating = true; - getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn); + // If the QS is already expanded we don't need to slide in the header as it's already + // visible. + if (!mQsExpanded) { + mHeaderAnimating = true; + getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn); + } } private void animateHeaderSlidingOut() { @@ -1585,26 +1590,22 @@ public class NotificationPanelView extends PanelView implements } } private void updateNotificationTranslucency() { - float alpha; - if (mExpandingFromHeadsUp || mHeadsUpManager.hasPinnedHeadsUp()) { - alpha = 1f; - } else { - alpha = (getNotificationsTopY() + mNotificationStackScroller.getItemHeight()) - / (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize() - - mNotificationStackScroller.getCollapseSecondCardPadding()); - alpha = Math.max(0, Math.min(alpha, 1)); - alpha = (float) Math.pow(alpha, 0.75); - } - - if (alpha != 1f && mNotificationStackScroller.getLayerType() != LAYER_TYPE_HARDWARE) { - mNotificationStackScroller.setLayerType(LAYER_TYPE_HARDWARE, null); - } else if (alpha == 1f - && mNotificationStackScroller.getLayerType() == LAYER_TYPE_HARDWARE) { - mNotificationStackScroller.setLayerType(LAYER_TYPE_NONE, null); + float alpha = 1f; + if (mClosingWithAlphaFadeOut && !mExpandingFromHeadsUp && !mHeadsUpManager.hasPinnedHeadsUp()) { + alpha = getFadeoutAlpha(); } mNotificationStackScroller.setAlpha(alpha); } + private float getFadeoutAlpha() { + float alpha = (getNotificationsTopY() + mNotificationStackScroller.getItemHeight()) + / (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize() + - mNotificationStackScroller.getCollapseSecondCardPadding()); + alpha = Math.max(0, Math.min(alpha, 1)); + alpha = (float) Math.pow(alpha, 0.75); + return alpha; + } + @Override protected float getOverExpansionAmount() { return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */); @@ -2261,6 +2262,12 @@ public class NotificationPanelView extends PanelView implements protected void onClosingFinished() { super.onClosingFinished(); resetVerticalPanelPosition(); + setClosingWithAlphaFadeout(false); + } + + private void setClosingWithAlphaFadeout(boolean closing) { + mClosingWithAlphaFadeOut = closing; + mNotificationStackScroller.forceNoOverlappingRendering(closing); } /** 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 6d35ff0..9d4997c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -777,6 +777,15 @@ public abstract class PanelView extends FrameLayout { public void setExpandedFraction(float frac) { setExpandedHeight(getMaxPanelHeight() * frac); + if (PhoneStatusBar.DEBUG_EMPTY_KEYGUARD + && mStatusBar.getBarState() == StatusBarState.KEYGUARD) { + if (frac == 0.0f) { + Log.i(PhoneStatusBar.TAG, "Panel collapsed! Stacktrace: " + + Log.getStackTraceString(new Throwable())); + } else if (frac == 1.0f) { + mStatusBar.endWindowManagerLogging(); + } + } } public float getExpandedHeight() { @@ -808,6 +817,11 @@ public abstract class PanelView extends FrameLayout { } public void collapse(boolean delayed, float speedUpFactor) { + if (PhoneStatusBar.DEBUG_EMPTY_KEYGUARD + && mStatusBar.getBarState() == StatusBarState.KEYGUARD) { + Log.i(PhoneStatusBar.TAG, "Panel collapsed! Stacktrace: " + + Log.getStackTraceString(new Throwable())); + } if (DEBUG) logf("collapse: " + this); if (mPeekPending || mPeekAnimator != null) { mCollapseAfterPeek = true; 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 def95aa..7c7bec9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -185,6 +185,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, HeadsUpManager.OnHeadsUpChangedListener { static final String TAG = "PhoneStatusBar"; public static final boolean DEBUG = BaseStatusBar.DEBUG; + public static final boolean DEBUG_EMPTY_KEYGUARD = true; public static final boolean SPEW = false; public static final boolean DUMPTRUCK = true; // extra dumpsys info public static final boolean DEBUG_GESTURES = false; @@ -2003,6 +2004,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mStatusBarWindowManager.setPanelExpanded(isExpanded); } + public void endWindowManagerLogging() { + mStatusBarWindowManager.setLogState(false); + } + /** * All changes to the status bar and notifications funnel through here and are batched. */ @@ -3547,6 +3552,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, // Make our window larger and the panel expanded. makeExpandedVisible(true); mNotificationPanel.instantExpand(); + if (DEBUG_EMPTY_KEYGUARD) { + mStatusBarWindowManager.setLogState(true); + } } private void instantCollapseNotificationPanel() { 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 aa499ad..b7e675d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -20,12 +20,14 @@ import android.content.Context; import android.content.res.Resources; import android.util.AttributeSet; import android.util.EventLog; +import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.accessibility.AccessibilityEvent; import com.android.systemui.EventLogTags; import com.android.systemui.R; +import com.android.systemui.statusbar.StatusBarState; public class PhoneStatusBarView extends PanelBar { private static final String TAG = "PhoneStatusBarView"; @@ -108,7 +110,11 @@ public class PhoneStatusBarView extends PanelBar { @Override public void onAllPanelsCollapsed() { super.onAllPanelsCollapsed(); - + if (PhoneStatusBar.DEBUG_EMPTY_KEYGUARD + && mBar.getBarState() == StatusBarState.KEYGUARD) { + Log.i(PhoneStatusBar.TAG, "Panel collapsed! Stacktrace: " + + Log.getStackTraceString(new Throwable())); + } // Close the status bar in the next frame so we can show the end of the animation. postOnAnimation(new Runnable() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java index de42643..58017d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java @@ -21,6 +21,7 @@ import android.content.pm.ActivityInfo; import android.content.res.Resources; import android.graphics.PixelFormat; import android.os.SystemProperties; +import android.util.Log; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; @@ -31,6 +32,8 @@ import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.StatusBarState; +import java.lang.reflect.Field; + /** * Encapsulates all logic for the status bar window state management. */ @@ -45,6 +48,7 @@ public class StatusBarWindowManager { private final boolean mKeyguardScreenRotation; private final State mCurrentState = new State(); + private boolean mLogState; public StatusBarWindowManager(Context context) { mContext = context; @@ -129,9 +133,7 @@ public class StatusBarWindowManager { } private void applyHeight(State state) { - boolean expanded = !state.forceCollapsed && (state.isKeyguardShowingAndNotOccluded() - || state.panelVisible || state.keyguardFadingAway || state.bouncerShowing - || state.headsUpShowing); + boolean expanded = isExpanded(state); if (expanded) { mLpChanged.height = ViewGroup.LayoutParams.MATCH_PARENT; } else { @@ -139,6 +141,12 @@ public class StatusBarWindowManager { } } + private boolean isExpanded(State state) { + return !state.forceCollapsed && (state.isKeyguardShowingAndNotOccluded() + || state.panelVisible || state.keyguardFadingAway || state.bouncerShowing + || state.headsUpShowing); + } + private void applyFitsSystemWindows(State state) { mStatusBarView.setFitsSystemWindows(!state.isKeyguardShowingAndNotOccluded()); } @@ -176,6 +184,9 @@ public class StatusBarWindowManager { applyFitsSystemWindows(state); applyModalFlag(state); if (mLp.copyFrom(mLpChanged) != 0) { + if (PhoneStatusBar.DEBUG_EMPTY_KEYGUARD && mLogState) { + logCurrentState(); + } mWindowManager.updateViewLayout(mStatusBarView, mLp); } } @@ -272,6 +283,21 @@ public class StatusBarWindowManager { apply(mCurrentState); } + public void setLogState(boolean logState) { + mLogState = logState; + if (logState) { + Log.w(PhoneStatusBar.TAG, "===== Started logging WM state changes ====="); + logCurrentState(); + } else { + Log.w(PhoneStatusBar.TAG, "===== Finished logging WM state changes ====="); + } + } + + private void logCurrentState() { + Log.i(PhoneStatusBar.TAG, mCurrentState.toString() + + "\n Expanded: " + isExpanded(mCurrentState)); + } + private static class State { boolean keyguardShowing; boolean keyguardOccluded; @@ -294,5 +320,31 @@ public class StatusBarWindowManager { private boolean isKeyguardShowingAndNotOccluded() { return keyguardShowing && !keyguardOccluded; } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + String newLine = "\n"; + result.append("Window State {"); + result.append(newLine); + + Field[] fields = this.getClass().getDeclaredFields(); + + // Print field names paired with their values + for (Field field : fields) { + result.append(" "); + try { + result.append(field.getName()); + result.append(": "); + //requires access to private field: + result.append(field.get(this)); + } catch (IllegalAccessException ex) { + } + result.append(newLine); + } + result.append("}"); + + return result.toString(); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java index 3b91751..6a8f8ee 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java @@ -171,7 +171,6 @@ public class StatusBarWindowView extends FrameLayout { if (mNotificationPanel.isFullyExpanded() && mStackScrollLayout.getVisibility() == View.VISIBLE && mService.getBarState() == StatusBarState.KEYGUARD - && !mService.isQsExpanded() && !mService.isBouncerShowing()) { intercept = mDragDownHelper.onInterceptTouchEvent(ev); // wake up on a touch down event, if dozing @@ -195,7 +194,7 @@ public class StatusBarWindowView extends FrameLayout { @Override public boolean onTouchEvent(MotionEvent ev) { boolean handled = false; - if (mService.getBarState() == StatusBarState.KEYGUARD && !mService.isQsExpanded()) { + if (mService.getBarState() == StatusBarState.KEYGUARD) { handled = mDragDownHelper.onTouchEvent(ev); } if (!handled) { 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 4ae800f..d8f6bcd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -225,6 +225,7 @@ public class NotificationStackScrollLayout extends ViewGroup private HeadsUpManager mHeadsUpManager; private boolean mTrackingHeadsUp; private ScrimController mScrimController; + private boolean mForceNoOverlappingRendering; public NotificationStackScrollLayout(Context context) { this(context, null); @@ -811,8 +812,7 @@ public class NotificationStackScrollLayout extends ViewGroup } handleEmptySpaceClick(ev); boolean expandWantsIt = false; - if (mIsExpanded && !mSwipingInProgress && !mOnlyScrollingInThisMotion - && isScrollingEnabled()) { + if (mIsExpanded && !mSwipingInProgress && !mOnlyScrollingInThisMotion) { if (isCancelOrUp) { mExpandHelper.onlyObserveMovements(false); } @@ -1566,7 +1566,7 @@ public class NotificationStackScrollLayout extends ViewGroup initDownStates(ev); handleEmptySpaceClick(ev); boolean expandWantsIt = false; - if (!mSwipingInProgress && !mOnlyScrollingInThisMotion && isScrollingEnabled()) { + if (!mSwipingInProgress && !mOnlyScrollingInThisMotion) { expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev); } boolean scrollWantsIt = false; @@ -2267,11 +2267,11 @@ public class NotificationStackScrollLayout extends ViewGroup private void updateScrollPositionOnExpandInBottom(ExpandableView view) { if (view instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) view; - if (row.isUserLocked()) { + if (row.isUserLocked() && row != getFirstChildNotGone()) { // We are actually expanding this view float endPosition = row.getTranslationY() + row.getActualHeight(); int stackEnd = mMaxLayoutHeight - mBottomStackPeekSize - - mBottomStackSlowDownHeight; + mBottomStackSlowDownHeight + (int) mStackTranslation; if (endPosition > stackEnd) { mOwnScrollY += endPosition - stackEnd; mDisallowScrollingInThisMotion = true; @@ -2614,7 +2614,7 @@ public class NotificationStackScrollLayout extends ViewGroup } } - private boolean isBelowLastNotification(float touchX, float touchY) { + public boolean isBelowLastNotification(float touchX, float touchY) { int childCount = getChildCount(); for (int i = childCount - 1; i >= 0; i--) { ExpandableView child = (ExpandableView) getChildAt(i); @@ -2640,7 +2640,7 @@ public class NotificationStackScrollLayout extends ViewGroup } } } - return touchY > mIntrinsicPadding; + return touchY > mTopPadding + mStackTranslation; } private void updateExpandButtons() { @@ -2732,6 +2732,15 @@ public class NotificationStackScrollLayout extends ViewGroup mScrimController = scrimController; } + public void forceNoOverlappingRendering(boolean force) { + mForceNoOverlappingRendering = force; + } + + @Override + public boolean hasOverlappingRendering() { + return !mForceNoOverlappingRendering && super.hasOverlappingRendering(); + } + /** * A listener that is notified when some child locations might have changed. */ diff --git a/rs/java/android/renderscript/Allocation.java b/rs/java/android/renderscript/Allocation.java index bea138e..0a50593 100644 --- a/rs/java/android/renderscript/Allocation.java +++ b/rs/java/android/renderscript/Allocation.java @@ -51,6 +51,7 @@ import android.os.Trace; * <a href="{@docRoot}guide/topics/renderscript/index.html">RenderScript</a> developer guide.</p> * </div> **/ + public class Allocation extends BaseObj { Type mType; Bitmap mBitmap; @@ -455,28 +456,31 @@ public class Allocation extends BaseObj { * */ public void syncAll(int srcLocation) { - Trace.traceBegin(RenderScript.TRACE_TAG, "syncAll"); - switch (srcLocation) { - case USAGE_GRAPHICS_TEXTURE: - case USAGE_SCRIPT: - if ((mUsage & USAGE_SHARED) != 0) { - copyFrom(mBitmap); - } - break; - case USAGE_GRAPHICS_CONSTANTS: - case USAGE_GRAPHICS_VERTEX: - break; - case USAGE_SHARED: - if ((mUsage & USAGE_SHARED) != 0) { - copyTo(mBitmap); + try { + Trace.traceBegin(RenderScript.TRACE_TAG, "syncAll"); + switch (srcLocation) { + case USAGE_GRAPHICS_TEXTURE: + case USAGE_SCRIPT: + if ((mUsage & USAGE_SHARED) != 0) { + copyFrom(mBitmap); + } + break; + case USAGE_GRAPHICS_CONSTANTS: + case USAGE_GRAPHICS_VERTEX: + break; + case USAGE_SHARED: + if ((mUsage & USAGE_SHARED) != 0) { + copyTo(mBitmap); + } + break; + default: + throw new RSIllegalArgumentException("Source must be exactly one usage type."); } - break; - default: - throw new RSIllegalArgumentException("Source must be exactly one usage type."); + mRS.validate(); + mRS.nAllocationSyncAll(getIDSafe(), srcLocation); + } finally { + Trace.traceEnd(RenderScript.TRACE_TAG); } - mRS.validate(); - mRS.nAllocationSyncAll(getIDSafe(), srcLocation); - Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -487,14 +491,17 @@ public class Allocation extends BaseObj { * */ public void ioSend() { - Trace.traceBegin(RenderScript.TRACE_TAG, "ioSend"); - if ((mUsage & USAGE_IO_OUTPUT) == 0) { - throw new RSIllegalArgumentException( - "Can only send buffer if IO_OUTPUT usage specified."); + try { + Trace.traceBegin(RenderScript.TRACE_TAG, "ioSend"); + if ((mUsage & USAGE_IO_OUTPUT) == 0) { + throw new RSIllegalArgumentException( + "Can only send buffer if IO_OUTPUT usage specified."); + } + mRS.validate(); + mRS.nAllocationIoSend(getID(mRS)); + } finally { + Trace.traceEnd(RenderScript.TRACE_TAG); } - mRS.validate(); - mRS.nAllocationIoSend(getID(mRS)); - Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -503,14 +510,17 @@ public class Allocation extends BaseObj { * */ public void ioReceive() { - Trace.traceBegin(RenderScript.TRACE_TAG, "ioReceive"); - if ((mUsage & USAGE_IO_INPUT) == 0) { - throw new RSIllegalArgumentException( - "Can only receive if IO_INPUT usage specified."); + try { + Trace.traceBegin(RenderScript.TRACE_TAG, "ioReceive"); + if ((mUsage & USAGE_IO_INPUT) == 0) { + throw new RSIllegalArgumentException( + "Can only receive if IO_INPUT usage specified."); + } + mRS.validate(); + mRS.nAllocationIoReceive(getID(mRS)); + } finally { + Trace.traceEnd(RenderScript.TRACE_TAG); } - mRS.validate(); - mRS.nAllocationIoReceive(getID(mRS)); - Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -519,28 +529,31 @@ public class Allocation extends BaseObj { * @param d Source array. */ public void copyFrom(BaseObj[] d) { - Trace.traceBegin(RenderScript.TRACE_TAG, "copyFrom"); - mRS.validate(); - validateIsObject(); - if (d.length != mCurrentCount) { - throw new RSIllegalArgumentException("Array size mismatch, allocation sizeX = " + - mCurrentCount + ", array length = " + d.length); - } - - if (RenderScript.sPointerSize == 8) { - long i[] = new long[d.length * 4]; - for (int ct=0; ct < d.length; ct++) { - i[ct * 4] = d[ct].getID(mRS); + try { + Trace.traceBegin(RenderScript.TRACE_TAG, "copyFrom"); + mRS.validate(); + validateIsObject(); + if (d.length != mCurrentCount) { + throw new RSIllegalArgumentException("Array size mismatch, allocation sizeX = " + + mCurrentCount + ", array length = " + d.length); } - copy1DRangeFromUnchecked(0, mCurrentCount, i); - } else { - int i[] = new int[d.length]; - for (int ct=0; ct < d.length; ct++) { - i[ct] = (int)d[ct].getID(mRS); + + if (RenderScript.sPointerSize == 8) { + long i[] = new long[d.length * 4]; + for (int ct=0; ct < d.length; ct++) { + i[ct * 4] = d[ct].getID(mRS); + } + copy1DRangeFromUnchecked(0, mCurrentCount, i); + } else { + int i[] = new int[d.length]; + for (int ct=0; ct < d.length; ct++) { + i[ct] = (int) d[ct].getID(mRS); + } + copy1DRangeFromUnchecked(0, mCurrentCount, i); } - copy1DRangeFromUnchecked(0, mCurrentCount, i); + } finally { + Trace.traceEnd(RenderScript.TRACE_TAG); } - Trace.traceEnd(RenderScript.TRACE_TAG); } private void validateBitmapFormat(Bitmap b) { @@ -599,16 +612,19 @@ public class Allocation extends BaseObj { } private void copyFromUnchecked(Object array, Element.DataType dt, int arrayLen) { - Trace.traceBegin(RenderScript.TRACE_TAG, "copyFromUnchecked"); - mRS.validate(); - if (mCurrentDimZ > 0) { - copy3DRangeFromUnchecked(0, 0, 0, mCurrentDimX, mCurrentDimY, mCurrentDimZ, array, dt, arrayLen); - } else if (mCurrentDimY > 0) { - copy2DRangeFromUnchecked(0, 0, mCurrentDimX, mCurrentDimY, array, dt, arrayLen); - } else { - copy1DRangeFromUnchecked(0, mCurrentCount, array, dt, arrayLen); + try { + Trace.traceBegin(RenderScript.TRACE_TAG, "copyFromUnchecked"); + mRS.validate(); + if (mCurrentDimZ > 0) { + copy3DRangeFromUnchecked(0, 0, 0, mCurrentDimX, mCurrentDimY, mCurrentDimZ, array, dt, arrayLen); + } else if (mCurrentDimY > 0) { + copy2DRangeFromUnchecked(0, 0, mCurrentDimX, mCurrentDimY, array, dt, arrayLen); + } else { + copy1DRangeFromUnchecked(0, mCurrentCount, array, dt, arrayLen); + } + } finally { + Trace.traceEnd(RenderScript.TRACE_TAG); } - Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -619,10 +635,13 @@ public class Allocation extends BaseObj { * @param array The source data array */ public void copyFromUnchecked(Object array) { - Trace.traceBegin(RenderScript.TRACE_TAG, "copyFromUnchecked"); - copyFromUnchecked(array, validateObjectIsPrimitiveArray(array, false), - java.lang.reflect.Array.getLength(array)); - Trace.traceEnd(RenderScript.TRACE_TAG); + try { + Trace.traceBegin(RenderScript.TRACE_TAG, "copyFromUnchecked"); + copyFromUnchecked(array, validateObjectIsPrimitiveArray(array, false), + java.lang.reflect.Array.getLength(array)); + } finally { + Trace.traceEnd(RenderScript.TRACE_TAG); + } } /** @@ -679,10 +698,13 @@ public class Allocation extends BaseObj { * @param array The source data array */ public void copyFrom(Object array) { - Trace.traceBegin(RenderScript.TRACE_TAG, "copyFrom"); - copyFromUnchecked(array, validateObjectIsPrimitiveArray(array, true), - java.lang.reflect.Array.getLength(array)); - Trace.traceEnd(RenderScript.TRACE_TAG); + try { + Trace.traceBegin(RenderScript.TRACE_TAG, "copyFrom"); + copyFromUnchecked(array, validateObjectIsPrimitiveArray(array, true), + java.lang.reflect.Array.getLength(array)); + } finally { + Trace.traceEnd(RenderScript.TRACE_TAG); + } } /** @@ -747,19 +769,22 @@ public class Allocation extends BaseObj { * @param b the source bitmap */ public void copyFrom(Bitmap b) { - Trace.traceBegin(RenderScript.TRACE_TAG, "copyFrom"); - mRS.validate(); - if (b.getConfig() == null) { - Bitmap newBitmap = Bitmap.createBitmap(b.getWidth(), b.getHeight(), Bitmap.Config.ARGB_8888); - Canvas c = new Canvas(newBitmap); - c.drawBitmap(b, 0, 0, null); - copyFrom(newBitmap); - return; + try { + Trace.traceBegin(RenderScript.TRACE_TAG, "copyFrom"); + mRS.validate(); + if (b.getConfig() == null) { + Bitmap newBitmap = Bitmap.createBitmap(b.getWidth(), b.getHeight(), Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(newBitmap); + c.drawBitmap(b, 0, 0, null); + copyFrom(newBitmap); + return; + } + validateBitmapSize(b); + validateBitmapFormat(b); + mRS.nAllocationCopyFromBitmap(getID(mRS), b); + } finally { + Trace.traceEnd(RenderScript.TRACE_TAG); } - validateBitmapSize(b); - validateBitmapFormat(b); - mRS.nAllocationCopyFromBitmap(getID(mRS), b); - Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -769,13 +794,16 @@ public class Allocation extends BaseObj { * @param a the source allocation */ public void copyFrom(Allocation a) { - Trace.traceBegin(RenderScript.TRACE_TAG, "copyFrom"); - mRS.validate(); - if (!mType.equals(a.getType())) { - throw new RSIllegalArgumentException("Types of allocations must match."); + try { + Trace.traceBegin(RenderScript.TRACE_TAG, "copyFrom"); + mRS.validate(); + if (!mType.equals(a.getType())) { + throw new RSIllegalArgumentException("Types of allocations must match."); + } + copy2DRangeFrom(0, 0, mCurrentDimX, mCurrentDimY, a, 0, 0); + } finally { + Trace.traceEnd(RenderScript.TRACE_TAG); } - copy2DRangeFrom(0, 0, mCurrentDimX, mCurrentDimY, a, 0, 0); - Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -890,17 +918,20 @@ public class Allocation extends BaseObj { private void copy1DRangeFromUnchecked(int off, int count, Object array, Element.DataType dt, int arrayLen) { - Trace.traceBegin(RenderScript.TRACE_TAG, "copy1DRangeFromUnchecked"); - final int dataSize = mType.mElement.getBytesSize() * count; - // AutoPadding for Vec3 Element - boolean usePadding = false; - if (mAutoPadding && (mType.getElement().getVectorSize() == 3)) { - usePadding = true; - } - data1DChecks(off, count, arrayLen * dt.mSize, dataSize, usePadding); - mRS.nAllocationData1D(getIDSafe(), off, mSelectedLOD, count, array, dataSize, dt, - mType.mElement.mType.mSize, usePadding); - Trace.traceEnd(RenderScript.TRACE_TAG); + try { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy1DRangeFromUnchecked"); + final int dataSize = mType.mElement.getBytesSize() * count; + // AutoPadding for Vec3 Element + boolean usePadding = false; + if (mAutoPadding && (mType.getElement().getVectorSize() == 3)) { + usePadding = true; + } + data1DChecks(off, count, arrayLen * dt.mSize, dataSize, usePadding); + mRS.nAllocationData1D(getIDSafe(), off, mSelectedLOD, count, array, dataSize, dt, + mType.mElement.mType.mSize, usePadding); + } finally { + Trace.traceEnd(RenderScript.TRACE_TAG); + } } /** @@ -1074,28 +1105,31 @@ public class Allocation extends BaseObj { void copy2DRangeFromUnchecked(int xoff, int yoff, int w, int h, Object array, Element.DataType dt, int arrayLen) { - Trace.traceBegin(RenderScript.TRACE_TAG, "copy2DRangeFromUnchecked"); - mRS.validate(); - validate2DRange(xoff, yoff, w, h); - final int dataSize = mType.mElement.getBytesSize() * w * h; - // AutoPadding for Vec3 Element - boolean usePadding = false; - int sizeBytes = arrayLen * dt.mSize; - if (mAutoPadding && (mType.getElement().getVectorSize() == 3)) { - if (dataSize / 4 * 3 > sizeBytes) { - throw new RSIllegalArgumentException("Array too small for allocation type."); - } - usePadding = true; - sizeBytes = dataSize; - } else { - if (dataSize > sizeBytes) { - throw new RSIllegalArgumentException("Array too small for allocation type."); + try { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy2DRangeFromUnchecked"); + mRS.validate(); + validate2DRange(xoff, yoff, w, h); + final int dataSize = mType.mElement.getBytesSize() * w * h; + // AutoPadding for Vec3 Element + boolean usePadding = false; + int sizeBytes = arrayLen * dt.mSize; + if (mAutoPadding && (mType.getElement().getVectorSize() == 3)) { + if (dataSize / 4 * 3 > sizeBytes) { + throw new RSIllegalArgumentException("Array too small for allocation type."); + } + usePadding = true; + sizeBytes = dataSize; + } else { + if (dataSize > sizeBytes) { + throw new RSIllegalArgumentException("Array too small for allocation type."); + } } + mRS.nAllocationData2D(getIDSafe(), xoff, yoff, mSelectedLOD, mSelectedFace.mID, w, h, + array, sizeBytes, dt, + mType.mElement.mType.mSize, usePadding); + } finally { + Trace.traceEnd(RenderScript.TRACE_TAG); } - mRS.nAllocationData2D(getIDSafe(), xoff, yoff, mSelectedLOD, mSelectedFace.mID, w, h, - array, sizeBytes, dt, - mType.mElement.mType.mSize, usePadding); - Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -1109,11 +1143,14 @@ public class Allocation extends BaseObj { * @param array Data to be placed into the Allocation */ public void copy2DRangeFrom(int xoff, int yoff, int w, int h, Object array) { - Trace.traceBegin(RenderScript.TRACE_TAG, "copy2DRangeFrom"); - copy2DRangeFromUnchecked(xoff, yoff, w, h, array, - validateObjectIsPrimitiveArray(array, true), - java.lang.reflect.Array.getLength(array)); - Trace.traceEnd(RenderScript.TRACE_TAG); + try { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy2DRangeFrom"); + copy2DRangeFromUnchecked(xoff, yoff, w, h, array, + validateObjectIsPrimitiveArray(array, true), + java.lang.reflect.Array.getLength(array)); + } finally { + Trace.traceEnd(RenderScript.TRACE_TAG); + } } /** @@ -1194,14 +1231,17 @@ public class Allocation extends BaseObj { */ public void copy2DRangeFrom(int xoff, int yoff, int w, int h, Allocation data, int dataXoff, int dataYoff) { - Trace.traceBegin(RenderScript.TRACE_TAG, "copy2DRangeFrom"); - mRS.validate(); - validate2DRange(xoff, yoff, w, h); - mRS.nAllocationData2D(getIDSafe(), xoff, yoff, - mSelectedLOD, mSelectedFace.mID, - w, h, data.getID(mRS), dataXoff, dataYoff, - data.mSelectedLOD, data.mSelectedFace.mID); - Trace.traceEnd(RenderScript.TRACE_TAG); + try { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy2DRangeFrom"); + mRS.validate(); + validate2DRange(xoff, yoff, w, h); + mRS.nAllocationData2D(getIDSafe(), xoff, yoff, + mSelectedLOD, mSelectedFace.mID, + w, h, data.getID(mRS), dataXoff, dataYoff, + data.mSelectedLOD, data.mSelectedFace.mID); + } finally { + Trace.traceEnd(RenderScript.TRACE_TAG); + } } /** @@ -1258,28 +1298,31 @@ public class Allocation extends BaseObj { */ private void copy3DRangeFromUnchecked(int xoff, int yoff, int zoff, int w, int h, int d, Object array, Element.DataType dt, int arrayLen) { - Trace.traceBegin(RenderScript.TRACE_TAG, "copy3DRangeFromUnchecked"); - mRS.validate(); - validate3DRange(xoff, yoff, zoff, w, h, d); - final int dataSize = mType.mElement.getBytesSize() * w * h * d; - // AutoPadding for Vec3 Element - boolean usePadding = false; - int sizeBytes = arrayLen * dt.mSize; - if (mAutoPadding && (mType.getElement().getVectorSize() == 3)) { - if (dataSize / 4 * 3 > sizeBytes) { - throw new RSIllegalArgumentException("Array too small for allocation type."); - } - usePadding = true; - sizeBytes = dataSize; - } else { - if (dataSize > sizeBytes) { - throw new RSIllegalArgumentException("Array too small for allocation type."); + try { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy3DRangeFromUnchecked"); + mRS.validate(); + validate3DRange(xoff, yoff, zoff, w, h, d); + final int dataSize = mType.mElement.getBytesSize() * w * h * d; + // AutoPadding for Vec3 Element + boolean usePadding = false; + int sizeBytes = arrayLen * dt.mSize; + if (mAutoPadding && (mType.getElement().getVectorSize() == 3)) { + if (dataSize / 4 * 3 > sizeBytes) { + throw new RSIllegalArgumentException("Array too small for allocation type."); + } + usePadding = true; + sizeBytes = dataSize; + } else { + if (dataSize > sizeBytes) { + throw new RSIllegalArgumentException("Array too small for allocation type."); + } } + mRS.nAllocationData3D(getIDSafe(), xoff, yoff, zoff, mSelectedLOD, w, h, d, + array, sizeBytes, dt, + mType.mElement.mType.mSize, usePadding); + } finally { + Trace.traceEnd(RenderScript.TRACE_TAG); } - mRS.nAllocationData3D(getIDSafe(), xoff, yoff, zoff, mSelectedLOD, w, h, d, - array, sizeBytes, dt, - mType.mElement.mType.mSize, usePadding); - Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -1295,11 +1338,14 @@ public class Allocation extends BaseObj { * @param array to be placed into the allocation */ public void copy3DRangeFrom(int xoff, int yoff, int zoff, int w, int h, int d, Object array) { - Trace.traceBegin(RenderScript.TRACE_TAG, "copy3DRangeFrom"); - copy3DRangeFromUnchecked(xoff, yoff, zoff, w, h, d, array, - validateObjectIsPrimitiveArray(array, true), - java.lang.reflect.Array.getLength(array)); - Trace.traceEnd(RenderScript.TRACE_TAG); + try { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy3DRangeFrom"); + copy3DRangeFromUnchecked(xoff, yoff, zoff, w, h, d, array, + validateObjectIsPrimitiveArray(array, true), + java.lang.reflect.Array.getLength(array)); + } finally { + Trace.traceEnd(RenderScript.TRACE_TAG); + } } /** @@ -1334,34 +1380,40 @@ public class Allocation extends BaseObj { * @param b The bitmap to be set from the Allocation. */ public void copyTo(Bitmap b) { - Trace.traceBegin(RenderScript.TRACE_TAG, "copyTo"); - mRS.validate(); - validateBitmapFormat(b); - validateBitmapSize(b); - mRS.nAllocationCopyToBitmap(getID(mRS), b); - Trace.traceEnd(RenderScript.TRACE_TAG); + try { + Trace.traceBegin(RenderScript.TRACE_TAG, "copyTo"); + mRS.validate(); + validateBitmapFormat(b); + validateBitmapSize(b); + mRS.nAllocationCopyToBitmap(getID(mRS), b); + } finally { + Trace.traceEnd(RenderScript.TRACE_TAG); + } } private void copyTo(Object array, Element.DataType dt, int arrayLen) { - Trace.traceBegin(RenderScript.TRACE_TAG, "copyTo"); - mRS.validate(); - boolean usePadding = false; - if (mAutoPadding && (mType.getElement().getVectorSize() == 3)) { - usePadding = true; - } - if (usePadding) { - if (dt.mSize * arrayLen < mSize / 4 * 3) { - throw new RSIllegalArgumentException( - "Size of output array cannot be smaller than size of allocation."); + try { + Trace.traceBegin(RenderScript.TRACE_TAG, "copyTo"); + mRS.validate(); + boolean usePadding = false; + if (mAutoPadding && (mType.getElement().getVectorSize() == 3)) { + usePadding = true; } - } else { - if (dt.mSize * arrayLen < mSize) { - throw new RSIllegalArgumentException( - "Size of output array cannot be smaller than size of allocation."); + if (usePadding) { + if (dt.mSize * arrayLen < mSize / 4 * 3) { + throw new RSIllegalArgumentException( + "Size of output array cannot be smaller than size of allocation."); + } + } else { + if (dt.mSize * arrayLen < mSize) { + throw new RSIllegalArgumentException( + "Size of output array cannot be smaller than size of allocation."); + } } + mRS.nAllocationRead(getID(mRS), array, dt, mType.mElement.mType.mSize, usePadding); + } finally { + Trace.traceEnd(RenderScript.TRACE_TAG); } - mRS.nAllocationRead(getID(mRS), array, dt, mType.mElement.mType.mSize, usePadding); - Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -1499,17 +1551,20 @@ public class Allocation extends BaseObj { private void copy1DRangeToUnchecked(int off, int count, Object array, Element.DataType dt, int arrayLen) { - Trace.traceBegin(RenderScript.TRACE_TAG, "copy1DRangeToUnchecked"); - final int dataSize = mType.mElement.getBytesSize() * count; - // AutoPadding for Vec3 Element - boolean usePadding = false; - if (mAutoPadding && (mType.getElement().getVectorSize() == 3)) { - usePadding = true; - } - data1DChecks(off, count, arrayLen * dt.mSize, dataSize, usePadding); - mRS.nAllocationRead1D(getIDSafe(), off, mSelectedLOD, count, array, dataSize, dt, - mType.mElement.mType.mSize, usePadding); - Trace.traceEnd(RenderScript.TRACE_TAG); + try { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy1DRangeToUnchecked"); + final int dataSize = mType.mElement.getBytesSize() * count; + // AutoPadding for Vec3 Element + boolean usePadding = false; + if (mAutoPadding && (mType.getElement().getVectorSize() == 3)) { + usePadding = true; + } + data1DChecks(off, count, arrayLen * dt.mSize, dataSize, usePadding); + mRS.nAllocationRead1D(getIDSafe(), off, mSelectedLOD, count, array, dataSize, dt, + mType.mElement.mType.mSize, usePadding); + } finally { + Trace.traceEnd(RenderScript.TRACE_TAG); + } } /** @@ -1649,27 +1704,30 @@ public class Allocation extends BaseObj { void copy2DRangeToUnchecked(int xoff, int yoff, int w, int h, Object array, Element.DataType dt, int arrayLen) { - Trace.traceBegin(RenderScript.TRACE_TAG, "copy2DRangeToUnchecked"); - mRS.validate(); - validate2DRange(xoff, yoff, w, h); - final int dataSize = mType.mElement.getBytesSize() * w * h; - // AutoPadding for Vec3 Element - boolean usePadding = false; - int sizeBytes = arrayLen * dt.mSize; - if (mAutoPadding && (mType.getElement().getVectorSize() == 3)) { - if (dataSize / 4 * 3 > sizeBytes) { - throw new RSIllegalArgumentException("Array too small for allocation type."); - } - usePadding = true; - sizeBytes = dataSize; - } else { - if (dataSize > sizeBytes) { - throw new RSIllegalArgumentException("Array too small for allocation type."); + try { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy2DRangeToUnchecked"); + mRS.validate(); + validate2DRange(xoff, yoff, w, h); + final int dataSize = mType.mElement.getBytesSize() * w * h; + // AutoPadding for Vec3 Element + boolean usePadding = false; + int sizeBytes = arrayLen * dt.mSize; + if (mAutoPadding && (mType.getElement().getVectorSize() == 3)) { + if (dataSize / 4 * 3 > sizeBytes) { + throw new RSIllegalArgumentException("Array too small for allocation type."); + } + usePadding = true; + sizeBytes = dataSize; + } else { + if (dataSize > sizeBytes) { + throw new RSIllegalArgumentException("Array too small for allocation type."); + } } + mRS.nAllocationRead2D(getIDSafe(), xoff, yoff, mSelectedLOD, mSelectedFace.mID, w, h, + array, sizeBytes, dt, mType.mElement.mType.mSize, usePadding); + } finally { + Trace.traceEnd(RenderScript.TRACE_TAG); } - mRS.nAllocationRead2D(getIDSafe(), xoff, yoff, mSelectedLOD, mSelectedFace.mID, w, h, - array, sizeBytes, dt, mType.mElement.mType.mSize, usePadding); - Trace.traceEnd(RenderScript.TRACE_TAG); } /** @@ -1757,27 +1815,30 @@ public class Allocation extends BaseObj { */ private void copy3DRangeToUnchecked(int xoff, int yoff, int zoff, int w, int h, int d, Object array, Element.DataType dt, int arrayLen) { - Trace.traceBegin(RenderScript.TRACE_TAG, "copy3DRangeToUnchecked"); - mRS.validate(); - validate3DRange(xoff, yoff, zoff, w, h, d); - final int dataSize = mType.mElement.getBytesSize() * w * h * d; - // AutoPadding for Vec3 Element - boolean usePadding = false; - int sizeBytes = arrayLen * dt.mSize; - if (mAutoPadding && (mType.getElement().getVectorSize() == 3)) { - if (dataSize / 4 * 3 > sizeBytes) { - throw new RSIllegalArgumentException("Array too small for allocation type."); - } - usePadding = true; - sizeBytes = dataSize; - } else { - if (dataSize > sizeBytes) { - throw new RSIllegalArgumentException("Array too small for allocation type."); + try { + Trace.traceBegin(RenderScript.TRACE_TAG, "copy3DRangeToUnchecked"); + mRS.validate(); + validate3DRange(xoff, yoff, zoff, w, h, d); + final int dataSize = mType.mElement.getBytesSize() * w * h * d; + // AutoPadding for Vec3 Element + boolean usePadding = false; + int sizeBytes = arrayLen * dt.mSize; + if (mAutoPadding && (mType.getElement().getVectorSize() == 3)) { + if (dataSize / 4 * 3 > sizeBytes) { + throw new RSIllegalArgumentException("Array too small for allocation type."); + } + usePadding = true; + sizeBytes = dataSize; + } else { + if (dataSize > sizeBytes) { + throw new RSIllegalArgumentException("Array too small for allocation type."); + } } + mRS.nAllocationRead3D(getIDSafe(), xoff, yoff, zoff, mSelectedLOD, w, h, d, + array, sizeBytes, dt, mType.mElement.mType.mSize, usePadding); + } finally { + Trace.traceEnd(RenderScript.TRACE_TAG); } - mRS.nAllocationRead3D(getIDSafe(), xoff, yoff, zoff, mSelectedLOD, w, h, d, - array, sizeBytes, dt, mType.mElement.mType.mSize, usePadding); - Trace.traceEnd(RenderScript.TRACE_TAG); } /* @@ -1815,17 +1876,20 @@ public class Allocation extends BaseObj { * utilized */ static public Allocation createTyped(RenderScript rs, Type type, MipmapControl mips, int usage) { - Trace.traceBegin(RenderScript.TRACE_TAG, "createTyped"); - rs.validate(); - if (type.getID(rs) == 0) { - throw new RSInvalidStateException("Bad Type"); - } - long id = rs.nAllocationCreateTyped(type.getID(rs), mips.mID, usage, 0); - if (id == 0) { - throw new RSRuntimeException("Allocation creation failed."); + try { + Trace.traceBegin(RenderScript.TRACE_TAG, "createTyped"); + rs.validate(); + if (type.getID(rs) == 0) { + throw new RSInvalidStateException("Bad Type"); + } + long id = rs.nAllocationCreateTyped(type.getID(rs), mips.mID, usage, 0); + if (id == 0) { + throw new RSRuntimeException("Allocation creation failed."); + } + return new Allocation(id, rs, type, usage); + } finally { + Trace.traceEnd(RenderScript.TRACE_TAG); } - Trace.traceEnd(RenderScript.TRACE_TAG); - return new Allocation(id, rs, type, usage); } /** @@ -1869,18 +1933,21 @@ public class Allocation extends BaseObj { */ static public Allocation createSized(RenderScript rs, Element e, int count, int usage) { - Trace.traceBegin(RenderScript.TRACE_TAG, "createSized"); - rs.validate(); - Type.Builder b = new Type.Builder(rs, e); - b.setX(count); - Type t = b.create(); + try { + Trace.traceBegin(RenderScript.TRACE_TAG, "createSized"); + rs.validate(); + Type.Builder b = new Type.Builder(rs, e); + b.setX(count); + Type t = b.create(); - long id = rs.nAllocationCreateTyped(t.getID(rs), MipmapControl.MIPMAP_NONE.mID, usage, 0); - if (id == 0) { - throw new RSRuntimeException("Allocation creation failed."); + long id = rs.nAllocationCreateTyped(t.getID(rs), MipmapControl.MIPMAP_NONE.mID, usage, 0); + if (id == 0) { + throw new RSRuntimeException("Allocation creation failed."); + } + return new Allocation(id, rs, t, usage); + } finally { + Trace.traceEnd(RenderScript.TRACE_TAG); } - Trace.traceEnd(RenderScript.TRACE_TAG); - return new Allocation(id, rs, t, usage); } /** @@ -1939,44 +2006,47 @@ public class Allocation extends BaseObj { static public Allocation createFromBitmap(RenderScript rs, Bitmap b, MipmapControl mips, int usage) { - Trace.traceBegin(RenderScript.TRACE_TAG, "createFromBitmap"); - rs.validate(); + try { + Trace.traceBegin(RenderScript.TRACE_TAG, "createFromBitmap"); + rs.validate(); + + // WAR undocumented color formats + if (b.getConfig() == null) { + if ((usage & USAGE_SHARED) != 0) { + throw new RSIllegalArgumentException("USAGE_SHARED cannot be used with a Bitmap that has a null config."); + } + Bitmap newBitmap = Bitmap.createBitmap(b.getWidth(), b.getHeight(), Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(newBitmap); + c.drawBitmap(b, 0, 0, null); + return createFromBitmap(rs, newBitmap, mips, usage); + } - // WAR undocumented color formats - if (b.getConfig() == null) { - if ((usage & USAGE_SHARED) != 0) { - throw new RSIllegalArgumentException("USAGE_SHARED cannot be used with a Bitmap that has a null config."); + Type t = typeFromBitmap(rs, b, mips); + + // enable optimized bitmap path only with no mipmap and script-only usage + if (mips == MipmapControl.MIPMAP_NONE && + t.getElement().isCompatible(Element.RGBA_8888(rs)) && + usage == (USAGE_SHARED | USAGE_SCRIPT | USAGE_GRAPHICS_TEXTURE)) { + long id = rs.nAllocationCreateBitmapBackedAllocation(t.getID(rs), mips.mID, b, usage); + if (id == 0) { + throw new RSRuntimeException("Load failed."); + } + + // keep a reference to the Bitmap around to prevent GC + Allocation alloc = new Allocation(id, rs, t, usage); + alloc.setBitmap(b); + return alloc; } - Bitmap newBitmap = Bitmap.createBitmap(b.getWidth(), b.getHeight(), Bitmap.Config.ARGB_8888); - Canvas c = new Canvas(newBitmap); - c.drawBitmap(b, 0, 0, null); - return createFromBitmap(rs, newBitmap, mips, usage); - } - Type t = typeFromBitmap(rs, b, mips); - // enable optimized bitmap path only with no mipmap and script-only usage - if (mips == MipmapControl.MIPMAP_NONE && - t.getElement().isCompatible(Element.RGBA_8888(rs)) && - usage == (USAGE_SHARED | USAGE_SCRIPT | USAGE_GRAPHICS_TEXTURE)) { - long id = rs.nAllocationCreateBitmapBackedAllocation(t.getID(rs), mips.mID, b, usage); + long id = rs.nAllocationCreateFromBitmap(t.getID(rs), mips.mID, b, usage); if (id == 0) { throw new RSRuntimeException("Load failed."); } - - // keep a reference to the Bitmap around to prevent GC - Allocation alloc = new Allocation(id, rs, t, usage); - alloc.setBitmap(b); - return alloc; - } - - - long id = rs.nAllocationCreateFromBitmap(t.getID(rs), mips.mID, b, usage); - if (id == 0) { - throw new RSRuntimeException("Load failed."); + return new Allocation(id, rs, t, usage); + } finally { + Trace.traceEnd(RenderScript.TRACE_TAG); } - Trace.traceEnd(RenderScript.TRACE_TAG); - return new Allocation(id, rs, t, usage); } /** diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index d4c3194..7bcbcfb 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -166,8 +166,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private final List<AccessibilityServiceInfo> mEnabledServicesForFeedbackTempList = new ArrayList<>(); - private final Region mTempRegion = new Region(); - private final Rect mTempRect = new Rect(); private final Rect mTempRect1 = new Rect(); @@ -2241,7 +2239,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { throws RemoteException { final int resolvedWindowId; IAccessibilityInteractionConnection connection = null; - Region partialInteractiveRegion = mTempRegion; + Region partialInteractiveRegion = Region.obtain(); synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles // share the accessibility state of the parent. The call below @@ -2265,6 +2263,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked( resolvedWindowId, partialInteractiveRegion)) { + partialInteractiveRegion.recycle(); partialInteractiveRegion = null; } } @@ -2282,6 +2281,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } finally { Binder.restoreCallingIdentity(identityToken); + // Recycle if passed to another process. + if (partialInteractiveRegion != null && Binder.isProxy(connection)) { + partialInteractiveRegion.recycle(); + } } return false; } @@ -2293,7 +2296,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { throws RemoteException { final int resolvedWindowId; IAccessibilityInteractionConnection connection = null; - Region partialInteractiveRegion = mTempRegion; + Region partialInteractiveRegion = Region.obtain(); synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles // share the accessibility state of the parent. The call below @@ -2317,6 +2320,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked( resolvedWindowId, partialInteractiveRegion)) { + partialInteractiveRegion.recycle(); partialInteractiveRegion = null; } } @@ -2334,6 +2338,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } finally { Binder.restoreCallingIdentity(identityToken); + // Recycle if passed to another process. + if (partialInteractiveRegion != null && Binder.isProxy(connection)) { + partialInteractiveRegion.recycle(); + } } return false; } @@ -2345,7 +2353,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { long interrogatingTid) throws RemoteException { final int resolvedWindowId; IAccessibilityInteractionConnection connection = null; - Region partialInteractiveRegion = mTempRegion; + Region partialInteractiveRegion = Region.obtain(); synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles // share the accessibility state of the parent. The call below @@ -2369,6 +2377,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked( resolvedWindowId, partialInteractiveRegion)) { + partialInteractiveRegion.recycle(); partialInteractiveRegion = null; } } @@ -2386,6 +2395,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } finally { Binder.restoreCallingIdentity(identityToken); + // Recycle if passed to another process. + if (partialInteractiveRegion != null && Binder.isProxy(connection)) { + partialInteractiveRegion.recycle(); + } } return false; } @@ -2397,7 +2410,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { throws RemoteException { final int resolvedWindowId; IAccessibilityInteractionConnection connection = null; - Region partialInteractiveRegion = mTempRegion; + Region partialInteractiveRegion = Region.obtain(); synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles // share the accessibility state of the parent. The call below @@ -2422,6 +2435,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked( resolvedWindowId, partialInteractiveRegion)) { + partialInteractiveRegion.recycle(); partialInteractiveRegion = null; } } @@ -2439,6 +2453,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } finally { Binder.restoreCallingIdentity(identityToken); + // Recycle if passed to another process. + if (partialInteractiveRegion != null && Binder.isProxy(connection)) { + partialInteractiveRegion.recycle(); + } } return false; } @@ -2450,7 +2468,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { throws RemoteException { final int resolvedWindowId; IAccessibilityInteractionConnection connection = null; - Region partialInteractiveRegion = mTempRegion; + Region partialInteractiveRegion = Region.obtain(); synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles // share the accessibility state of the parent. The call below @@ -2474,6 +2492,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked( resolvedWindowId, partialInteractiveRegion)) { + partialInteractiveRegion.recycle(); partialInteractiveRegion = null; } } @@ -2491,6 +2510,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } finally { Binder.restoreCallingIdentity(identityToken); + // Recycle if passed to another process. + if (partialInteractiveRegion != null && Binder.isProxy(connection)) { + partialInteractiveRegion.recycle(); + } } return false; } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index e07e4e2..44d00d7 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -1661,6 +1661,12 @@ public class ConnectivityService extends IConnectivityManager.Stub } private static final String DEFAULT_TCP_BUFFER_SIZES = "4096,87380,110208,4096,16384,110208"; + private static final String DEFAULT_TCP_RWND_KEY = "net.tcp.default_init_rwnd"; + + // Overridden for testing purposes to avoid writing to SystemProperties. + protected int getDefaultTcpRwnd() { + return SystemProperties.getInt(DEFAULT_TCP_RWND_KEY, 0); + } private void updateTcpBufferSizes(NetworkAgentInfo nai) { if (isDefaultNetwork(nai) == false) { @@ -1696,10 +1702,8 @@ public class ConnectivityService extends IConnectivityManager.Stub loge("Can't set TCP buffer sizes:" + e); } - final String defaultRwndKey = "net.tcp.default_init_rwnd"; - int defaultRwndValue = SystemProperties.getInt(defaultRwndKey, 0); Integer rwndValue = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.TCP_DEFAULT_INIT_RWND, defaultRwndValue); + Settings.Global.TCP_DEFAULT_INIT_RWND, getDefaultTcpRwnd()); final String sysctlKey = "sys.sysctl.tcp_def_init_rwnd"; if (rwndValue != 0) { SystemProperties.set(sysctlKey, rwndValue.toString()); @@ -3819,10 +3823,12 @@ public class ConnectivityService extends IConnectivityManager.Stub // } updateTcpBufferSizes(networkAgent); - // TODO: deprecate and remove mDefaultDns when we can do so safely. - // For now, use it only when the network has Internet access. http://b/18327075 - final boolean useDefaultDns = networkAgent.networkCapabilities.hasCapability( - NET_CAPABILITY_INTERNET); + // TODO: deprecate and remove mDefaultDns when we can do so safely. See http://b/18327075 + // In L, we used it only when the network had Internet access but provided no DNS servers. + // For now, just disable it, and if disabling it doesn't break things, remove it. + // final boolean useDefaultDns = networkAgent.networkCapabilities.hasCapability( + // NET_CAPABILITY_INTERNET); + final boolean useDefaultDns = false; final boolean flushDns = updateRoutes(newLp, oldLp, netId); updateDnses(newLp, oldLp, netId, flushDns, useDefaultDns); @@ -4503,7 +4509,7 @@ public class ConnectivityService extends IConnectivityManager.Stub info.setDetailedState(DetailedState.CONNECTED, null, info.getExtraInfo()); sendConnectedBroadcast(info); } else { - info.setDetailedState(DetailedState.DISCONNECTED, null, info.getExtraInfo()); + info.setDetailedState(DetailedState.DISCONNECTED, info.getReason(), info.getExtraInfo()); Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType()); diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index 201a4ca..0513d8b 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -960,26 +960,33 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } private void switchUserLocked(int newUserId) { + if (DEBUG) Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId + + " currentUserId=" + mSettings.getCurrentUserId()); + mSettings.setCurrentUserId(newUserId); updateCurrentProfileIds(); // InputMethodFileManager should be reset when the user is changed mFileManager = new InputMethodFileManager(mMethodMap, newUserId); final String defaultImiId = mSettings.getSelectedInputMethod(); + + if (DEBUG) Slog.d(TAG, "Switching user stage 2/3. newUserId=" + newUserId + + " defaultImiId=" + defaultImiId); + // For secondary users, the list of enabled IMEs may not have been updated since the // callbacks to PackageMonitor are ignored for the secondary user. Here, defaultImiId may // not be empty even if the IME has been uninstalled by the primary user. // Even in such cases, IMMS works fine because it will find the most applicable // IME for that user. final boolean initialUserSwitch = TextUtils.isEmpty(defaultImiId); - if (DEBUG) { - Slog.d(TAG, "Switch user: " + newUserId + " current ime = " + defaultImiId); - } resetAllInternalStateLocked(false /* updateOnlyWhenLocaleChanged */, initialUserSwitch /* needsToResetDefaultIme */); if (initialUserSwitch) { InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mContext.getPackageManager(), mSettings.getEnabledInputMethodListLocked()); } + + if (DEBUG) Slog.d(TAG, "Switching user stage 3/3. newUserId=" + newUserId + + " selectedIme=" + mSettings.getSelectedInputMethod()); } void updateCurrentProfileIds() { @@ -3706,6 +3713,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub p.println(" mCurUserActionNotificationSequenceNumber=" + mCurUserActionNotificationSequenceNumber); p.println(" mSystemReady=" + mSystemReady + " mInteractive=" + mScreenOn); + p.println(" mSwitchingController:"); + mSwitchingController.dump(p); } p.println(" "); diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 3315c89..21f96c9 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -1785,7 +1785,13 @@ public class AccountManagerService // Get the calling package. We will use it for the purpose of caching. final String callerPkg = loginOptions.getString(AccountManager.KEY_ANDROID_PACKAGE_NAME); - List<String> callerOwnedPackageNames = Arrays.asList(mPackageManager.getPackagesForUid(callerUid)); + List<String> callerOwnedPackageNames; + long ident = Binder.clearCallingIdentity(); + try { + callerOwnedPackageNames = Arrays.asList(mPackageManager.getPackagesForUid(callerUid)); + } finally { + Binder.restoreCallingIdentity(ident); + } if (callerPkg == null || !callerOwnedPackageNames.contains(callerPkg)) { String msg = String.format( "Uid %s is attempting to illegally masquerade as package %s!", @@ -1798,15 +1804,15 @@ public class AccountManagerService loginOptions.putInt(AccountManager.KEY_CALLER_UID, callerUid); loginOptions.putInt(AccountManager.KEY_CALLER_PID, Binder.getCallingPid()); - // Distill the caller's package signatures into a single digest. - final byte[] callerPkgSigDigest = calculatePackageSignatureDigest(callerPkg); - if (notifyOnAuthFailure) { loginOptions.putBoolean(AccountManager.KEY_NOTIFY_ON_FAILURE, true); } long identityToken = clearCallingIdentity(); try { + // Distill the caller's package signatures into a single digest. + final byte[] callerPkgSigDigest = calculatePackageSignatureDigest(callerPkg); + // if the caller has permission, do the peek. otherwise go the more expensive // route of starting a Session if (!customTokens && permissionGranted) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 81b8457..64e30e5 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -39,13 +39,13 @@ import static org.xmlpull.v1.XmlPullParser.START_TAG; import android.Manifest; import android.app.AppOpsManager; import android.app.ApplicationThreadNative; -import android.app.AssistContent; -import android.app.AssistStructure; import android.app.IActivityContainer; import android.app.IActivityContainerCallback; import android.app.IAppTask; import android.app.ITaskStackListener; import android.app.ProfilerInfo; +import android.app.assist.AssistContent; +import android.app.assist.AssistStructure; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.appwidget.AppWidgetManager; diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 745cb7e..80b8a93 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -1121,25 +1121,23 @@ public final class BroadcastQueue { } final void logBroadcastReceiverDiscardLocked(BroadcastRecord r) { - if (r.nextReceiver > 0) { - Object curReceiver = r.receivers.get(r.nextReceiver-1); + final int logIndex = r.nextReceiver - 1; + if (logIndex >= 0 && logIndex < r.receivers.size()) { + Object curReceiver = r.receivers.get(logIndex); if (curReceiver instanceof BroadcastFilter) { BroadcastFilter bf = (BroadcastFilter) curReceiver; EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_FILTER, bf.owningUserId, System.identityHashCode(r), - r.intent.getAction(), - r.nextReceiver - 1, - System.identityHashCode(bf)); + r.intent.getAction(), logIndex, System.identityHashCode(bf)); } else { - ResolveInfo ri = (ResolveInfo)curReceiver; + ResolveInfo ri = (ResolveInfo) curReceiver; EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP, UserHandle.getUserId(ri.activityInfo.applicationInfo.uid), - System.identityHashCode(r), r.intent.getAction(), - r.nextReceiver - 1, ri.toString()); + System.identityHashCode(r), r.intent.getAction(), logIndex, ri.toString()); } } else { - Slog.w(TAG, "Discarding broadcast before first receiver is invoked: " - + r); + if (logIndex < 0) Slog.w(TAG, + "Discarding broadcast before first receiver is invoked: " + r); EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP, -1, System.identityHashCode(r), r.intent.getAction(), diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java index bf896a5..fc50e2c 100644 --- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java @@ -32,6 +32,7 @@ import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.Handler; import android.os.Message; +import android.os.Process; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; @@ -48,6 +49,7 @@ import android.telephony.CellInfoWcdma; import android.telephony.TelephonyManager; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Protocol; import com.android.internal.util.State; import com.android.internal.util.StateMachine; @@ -177,8 +179,8 @@ public class NetworkMonitor extends StateMachine { private static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = BASE + 11; private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger"; - // Default to 30s linger time-out. - private static final int DEFAULT_LINGER_DELAY_MS = 30000; + // Default to 30s linger time-out. Modifyable only for testing. + private static int DEFAULT_LINGER_DELAY_MS = 30000; private final int mLingerDelayMs; private int mLingerToken = 0; @@ -771,4 +773,13 @@ public class NetworkMonitor extends StateMachine { mContext.sendBroadcastAsUser(latencyBroadcast, UserHandle.CURRENT, PERMISSION_ACCESS_NETWORK_CONDITIONS); } + + // Allow tests to override linger time. + @VisibleForTesting + public static void SetDefaultLingerTime(int time_ms) { + if (Process.myUid() == Process.SYSTEM_UID) { + throw new SecurityException("SetDefaultLingerTime only for internal testing."); + } + DEFAULT_LINGER_DELAY_MS = time_ms; + } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index dcf668d..1cec750 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -52,6 +52,7 @@ import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; +import android.os.SELinux; import android.os.UserHandle; import android.system.ErrnoException; import android.system.Os; @@ -77,6 +78,7 @@ import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -163,6 +165,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private final List<File> mResolvedStagedFiles = new ArrayList<>(); @GuardedBy("mLock") private final List<File> mResolvedInheritedFiles = new ArrayList<>(); + @GuardedBy("mLock") + private File mInheritedFilesBase; private final Handler.Callback mHandlerCallback = new Handler.Callback() { @Override @@ -511,8 +515,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final List<File> fromFiles = mResolvedInheritedFiles; final File toDir = resolveStageDir(); + if (LOGD) Slog.d(TAG, "Inherited files: " + mResolvedInheritedFiles); + if (!mResolvedInheritedFiles.isEmpty() && mInheritedFilesBase == null) { + throw new IllegalStateException("mInheritedFilesBase == null"); + } + if (isLinkPossible(fromFiles, toDir)) { - linkFiles(fromFiles, toDir); + createDirsAndLinkFiles(fromFiles, toDir, mInheritedFilesBase); } else { // TODO: this should delegate to DCS so the system process // avoids holding open FDs into containers. @@ -690,6 +699,31 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } } + + // Inherit compiled oat directory. + final File packageInstallDir = (new File(app.getBaseCodePath())).getParentFile(); + mInheritedFilesBase = packageInstallDir; + final File oatDir = new File(packageInstallDir, "oat"); + if (oatDir.exists()) { + final File[] archSubdirs = oatDir.listFiles(); + // Only add "oatDir" if it contains arch specific subdirs. + if (archSubdirs != null && archSubdirs.length > 0) { + mResolvedInheritedFiles.add(oatDir); + } + final String[] instructionSets = InstructionSets.getAllDexCodeInstructionSets(); + for (File archSubDir : archSubdirs) { + // Skip any directory that isn't an ISA subdir. + if (!ArrayUtils.contains(instructionSets, archSubDir.getName())) { + continue; + } + + List<File> oatFiles = Arrays.asList(archSubDir.listFiles()); + if (!oatFiles.isEmpty()) { + mResolvedInheritedFiles.add(archSubDir); + mResolvedInheritedFiles.addAll(oatFiles); + } + } + } } } @@ -768,14 +802,69 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return true; } - private static void linkFiles(List<File> fromFiles, File toDir) throws IOException { + /** + * Reparents the path of {@code file} from {@code oldBase} to {@code newBase}. {@code file} + * must necessarily be a subpath of {@code oldBase}. It is an error for {@code file} to have + * relative path components such as {@code "."} or {@code ".."}. For example, for we will + * reparent {@code /foo/bar/baz} to {@code /foo2/bar/baz} if {@code oldBase} was {@code /foo} + * and {@code newBase} was {@code /foo2}. + */ + private static File reparentPath(File file, File oldBase, File newBase) throws IOException { + final String oldBaseStr = oldBase.getAbsolutePath(); + final String pathStr = file.getAbsolutePath(); + + // Don't allow relative paths. + if (pathStr.contains("/.") ) { + throw new IOException("Invalid path (was relative) : " + pathStr); + } + + if (pathStr.startsWith(oldBaseStr)) { + final String relative = pathStr.substring(oldBaseStr.length()); + return new File(newBase, relative); + } + + throw new IOException("File: " + pathStr + " outside base: " + oldBaseStr); + } + + /** + * Recreates a directory and file structure, specified by a list of files {@code fromFiles} + * which are subpaths of {@code fromDir} to {@code toDir}. Directories are created with the + * same permissions, and regular files are linked. + * + * TODO: Move this function to installd so that the system process doesn't have to + * manipulate / relabel directories. + */ + private static void createDirsAndLinkFiles(List<File> fromFiles, File toDir, File fromDir) + throws IOException { for (File fromFile : fromFiles) { - final File toFile = new File(toDir, fromFile.getName()); + final File toFile = reparentPath(fromFile, fromDir, toDir); + final StructStat stat; try { - if (LOGD) Slog.d(TAG, "Linking " + fromFile + " to " + toFile); - Os.link(fromFile.getAbsolutePath(), toFile.getAbsolutePath()); + stat = Os.stat(fromFile.getAbsolutePath()); } catch (ErrnoException e) { - throw new IOException("Failed to link " + fromFile + " to " + toFile, e); + throw new IOException("Failed to stat: " + fromFile.getAbsolutePath(), e); + } + + if (OsConstants.S_ISDIR(stat.st_mode)) { + if (LOGD) Slog.d(TAG, "Creating directory " + toFile.getAbsolutePath()); + try { + Os.mkdir(toFile.getAbsolutePath(), stat.st_mode); + } catch (ErrnoException e) { + throw new IOException("Failed to create dir: " + toFile.getAbsolutePath(), e); + } + + // We do this to ensure that the oat/ directory is created with the right + // label (data_dalvikcache_file) instead of apk_tmpfile. + if (!SELinux.restorecon(toFile)) { + throw new IOException("Failed to restorecon: " + toFile.getAbsolutePath()); + } + } else { + if (LOGD) Slog.d(TAG, "Linking " + fromFile + " to " + toFile); + try { + Os.link(fromFile.getAbsolutePath(), toFile.getAbsolutePath()); + } catch (ErrnoException e) { + throw new IOException("Failed to link " + fromFile + " to " + toFile, e); + } } } Slog.d(TAG, "Linked " + fromFiles.size() + " files into " + toDir); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 47e3963..e370afc 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1876,7 +1876,7 @@ public class PackageManagerService extends IPackageManager.Stub { alreadyDexOpted.add(frameworkDir.getPath() + "/core-libart.jar"); /** - * And there are a number of commands implemented in Java, which + * There are a number of commands implemented in Java, which * we currently need to do the dexopt on so that they can be * run from a non-root shell. */ @@ -6361,63 +6361,39 @@ public class PackageManagerService extends IPackageManager.Stub { final String path = scanFile.getPath(); final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, pkgSetting); - if (isSystemApp(pkg) && !pkg.isUpdatedSystemApp()) { - setBundledAppAbisAndRoots(pkg, pkgSetting); - - // If we haven't found any native libraries for the app, check if it has - // renderscript code. We'll need to force the app to 32 bit if it has - // renderscript bitcode. - if (pkg.applicationInfo.primaryCpuAbi == null - && pkg.applicationInfo.secondaryCpuAbi == null - && Build.SUPPORTED_64_BIT_ABIS.length > 0) { - NativeLibraryHelper.Handle handle = null; - try { - handle = NativeLibraryHelper.Handle.create(scanFile); - if (NativeLibraryHelper.hasRenderscriptBitcode(handle)) { - pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_32_BIT_ABIS[0]; - } - } catch (IOException ioe) { - Slog.w(TAG, "Error scanning system app : " + ioe); - } finally { - IoUtils.closeQuietly(handle); - } - } - setNativeLibraryPaths(pkg); + if ((scanFlags & SCAN_NEW_INSTALL) == 0) { + derivePackageAbi(pkg, scanFile, cpuAbiOverride, true /* extract libs */); } else { - if ((scanFlags & SCAN_NEW_INSTALL) == 0) { - deriveNonSystemPackageAbi(pkg, scanFile, cpuAbiOverride, true /* extract libs */); - } else { - if ((scanFlags & SCAN_MOVE) != 0) { - // We haven't run dex-opt for this move (since we've moved the compiled output too) - // but we already have this packages package info in the PackageSetting. We just - // use that and derive the native library path based on the new codepath. - pkg.applicationInfo.primaryCpuAbi = pkgSetting.primaryCpuAbiString; - pkg.applicationInfo.secondaryCpuAbi = pkgSetting.secondaryCpuAbiString; - } - - // Set native library paths again. For moves, the path will be updated based on the - // ABIs we've determined above. For non-moves, the path will be updated based on the - // ABIs we determined during compilation, but the path will depend on the final - // package path (after the rename away from the stage path). - setNativeLibraryPaths(pkg); - } + if ((scanFlags & SCAN_MOVE) != 0) { + // We haven't run dex-opt for this move (since we've moved the compiled output too) + // but we already have this packages package info in the PackageSetting. We just + // use that and derive the native library path based on the new codepath. + pkg.applicationInfo.primaryCpuAbi = pkgSetting.primaryCpuAbiString; + pkg.applicationInfo.secondaryCpuAbi = pkgSetting.secondaryCpuAbiString; + } + + // Set native library paths again. For moves, the path will be updated based on the + // ABIs we've determined above. For non-moves, the path will be updated based on the + // ABIs we determined during compilation, but the path will depend on the final + // package path (after the rename away from the stage path). + setNativeLibraryPaths(pkg); + } - if (DEBUG_INSTALL) Slog.i(TAG, "Linking native library dir for " + path); - final int[] userIds = sUserManager.getUserIds(); - synchronized (mInstallLock) { - // Create a native library symlink only if we have native libraries - // and if the native libraries are 32 bit libraries. We do not provide - // this symlink for 64 bit libraries. - if (pkg.applicationInfo.primaryCpuAbi != null && - !VMRuntime.is64BitAbi(pkg.applicationInfo.primaryCpuAbi)) { - final String nativeLibPath = pkg.applicationInfo.nativeLibraryDir; - for (int userId : userIds) { - if (mInstaller.linkNativeLibraryDirectory(pkg.volumeUuid, pkg.packageName, - nativeLibPath, userId) < 0) { - throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, - "Failed linking native library dir (user=" + userId + ")"); - } + if (DEBUG_INSTALL) Slog.i(TAG, "Linking native library dir for " + path); + final int[] userIds = sUserManager.getUserIds(); + synchronized (mInstallLock) { + // Create a native library symlink only if we have native libraries + // and if the native libraries are 32 bit libraries. We do not provide + // this symlink for 64 bit libraries. + if (pkg.applicationInfo.primaryCpuAbi != null && + !VMRuntime.is64BitAbi(pkg.applicationInfo.primaryCpuAbi)) { + final String nativeLibPath = pkg.applicationInfo.nativeLibraryDir; + for (int userId : userIds) { + if (mInstaller.linkNativeLibraryDirectory(pkg.volumeUuid, pkg.packageName, + nativeLibPath, userId) < 0) { + throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, + "Failed linking native library dir (user=" + userId + ")"); } } } @@ -6946,8 +6922,8 @@ public class PackageManagerService extends IPackageManager.Stub { * * If {@code extractLibs} is true, native libraries are extracted from the app if required. */ - public void deriveNonSystemPackageAbi(PackageParser.Package pkg, File scanFile, - String cpuAbiOverride, boolean extractLibs) + public void derivePackageAbi(PackageParser.Package pkg, File scanFile, + String cpuAbiOverride, boolean extractLibs) throws PackageManagerException { // TODO: We can probably be smarter about this stuff. For installed apps, // we can calculate this information at install time once and for all. For @@ -6959,8 +6935,10 @@ public class PackageManagerService extends IPackageManager.Stub { setNativeLibraryPaths(pkg); // We would never need to extract libs for forward-locked and external packages, - // since the container service will do it for us. - if (pkg.isForwardLocked() || isExternal(pkg)) { + // since the container service will do it for us. We shouldn't attempt to + // extract libs from system app when it was not updated. + if (pkg.isForwardLocked() || isExternal(pkg) || + (isSystemApp(pkg) && !pkg.isUpdatedSystemApp()) ) { extractLibs = false; } @@ -7291,33 +7269,6 @@ public class PackageManagerService extends IPackageManager.Stub { } /** - * Calculate the abis and roots for a bundled app. These can uniquely - * be determined from the contents of the system partition, i.e whether - * it contains 64 or 32 bit shared libraries etc. We do not validate any - * of this information, and instead assume that the system was built - * sensibly. - */ - private void setBundledAppAbisAndRoots(PackageParser.Package pkg, - PackageSetting pkgSetting) { - final String apkName = deriveCodePathName(pkg.applicationInfo.getCodePath()); - - // If "/system/lib64/apkname" exists, assume that is the per-package - // native library directory to use; otherwise use "/system/lib/apkname". - final String apkRoot = calculateBundledApkRoot(pkg.applicationInfo.sourceDir); - setBundledAppAbi(pkg, apkRoot, apkName); - // pkgSetting might be null during rescan following uninstall of updates - // to a bundled app, so accommodate that possibility. The settings in - // that case will be established later from the parsed package. - // - // If the settings aren't null, sync them up with what we've just derived. - // note that apkRoot isn't stored in the package settings. - if (pkgSetting != null) { - pkgSetting.primaryCpuAbiString = pkg.applicationInfo.primaryCpuAbi; - pkgSetting.secondaryCpuAbiString = pkg.applicationInfo.secondaryCpuAbi; - } - } - - /** * Deduces the ABI of a bundled app and sets the relevant fields on the * parsed pkg object. * @@ -11504,7 +11455,7 @@ public class PackageManagerService extends IPackageManager.Stub { final boolean onExternal = (((installFlags & PackageManager.INSTALL_EXTERNAL) != 0) || (args.volumeUuid != null)); boolean replace = false; - int scanFlags = SCAN_NEW_INSTALL | SCAN_FORCE_DEX | SCAN_UPDATE_SIGNATURE; + int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE; // Result object to be returned res.returnCode = PackageManager.INSTALL_SUCCEEDED; @@ -11695,7 +11646,7 @@ public class PackageManagerService extends IPackageManager.Stub { scanFlags |= SCAN_NO_DEX; try { - deriveNonSystemPackageAbi(pkg, new File(pkg.codePath), args.abiOverride, + derivePackageAbi(pkg, new File(pkg.codePath), args.abiOverride, true /* extract libs */); } catch (PackageManagerException pme) { Slog.e(TAG, "Error deriving application ABI", pme); @@ -11705,7 +11656,7 @@ public class PackageManagerService extends IPackageManager.Stub { // Run dexopt before old package gets removed, to minimize time when app is unavailable int result = mPackageDexOptimizer - .performDexOpt(pkg, null /* instruction sets */, true /* forceDex */, + .performDexOpt(pkg, null /* instruction sets */, false /* forceDex */, false /* defer */, false /* inclDependencies */); if (result == PackageDexOptimizer.DEX_OPT_FAILED) { res.setError(INSTALL_FAILED_DEXOPT, "Dexopt failed for " + pkg.codePath); diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index b8d0692..46793b9 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1087,16 +1087,19 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private void sleepPress(KeyEvent event) { + private void sleepPress(long eventTime) { + if (mShortPressOnSleepBehavior == SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME) { + launchHomeFromHotKey(false /* awakenDreams */, true /*respectKeyguard*/); + } + } + + private void sleepRelease(long eventTime) { switch (mShortPressOnSleepBehavior) { case SHORT_PRESS_SLEEP_GO_TO_SLEEP: - mPowerManager.goToSleep(event.getEventTime(), - PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON, 0); - break; case SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME: - launchHomeFromHotKey(false /* awakenDreams */, true /*respectKeyguard*/); - mPowerManager.goToSleep(event.getEventTime(), - PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON, 0); + Slog.i(TAG, "sleepRelease() calling goToSleep(GO_TO_SLEEP_REASON_SLEEP_BUTTON)"); + mPowerManager.goToSleep(eventTime, + PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON, 0); break; } } @@ -2116,7 +2119,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { case TYPE_KEYGUARD_SCRIM: return false; default: - return true; + // Hide only windows below the keyguard host window. + return windowTypeToLayerLw(win.getBaseType()) + < windowTypeToLayerLw(TYPE_STATUS_BAR); } } @@ -4903,7 +4908,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (!mPowerManager.isInteractive()) { useHapticFeedback = false; // suppress feedback if already non-interactive } - sleepPress(event); + if (down) { + sleepPress(event.getEventTime()); + } else { + sleepRelease(event.getEventTime()); + } break; } @@ -5517,6 +5526,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { /** {@inheritDoc} */ @Override + public boolean isKeyguardShowingOrOccluded() { + return mKeyguardDelegate == null ? false : mKeyguardDelegate.isShowing(); + } + + /** {@inheritDoc} */ + @Override public boolean inKeyguardRestrictedKeyInputMode() { if (mKeyguardDelegate == null) return false; return mKeyguardDelegate.isInputRestricted(); diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java index 01c110f..f1f9c50 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java @@ -36,10 +36,11 @@ public class KeyguardStateMonitor extends IKeyguardStateCallback.Stub { // Keyguard changes its state, it always triggers a layout in window manager. Because // IKeyguardStateCallback is synchronous and because these states are declared volatile, it's // guaranteed that window manager picks up the new state all the time in the layout caused by - // the state change of Keyguard. - private volatile boolean mIsShowing; - private volatile boolean mSimSecure; - private volatile boolean mInputRestricted; + // the state change of Keyguard. To be extra safe, assume most restrictive values until Keyguard + // tells us the actual value. + private volatile boolean mIsShowing = true; + private volatile boolean mSimSecure = true; + private volatile boolean mInputRestricted = true; private int mCurrentUserId; diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index 9169351..3305e1e 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -191,7 +191,8 @@ public class WindowAnimator { private boolean shouldForceHide(WindowState win) { final WindowState imeTarget = mService.mInputMethodTarget; final boolean showImeOverKeyguard = imeTarget != null && imeTarget.isVisibleNow() && - (imeTarget.getAttrs().flags & FLAG_SHOW_WHEN_LOCKED) != 0; + ((imeTarget.getAttrs().flags & FLAG_SHOW_WHEN_LOCKED) != 0 + || !mPolicy.canBeForceHidden(imeTarget, imeTarget.mAttrs)); final WindowState winShowWhenLocked = (WindowState) mPolicy.getWinShowWhenLockedLw(); final AppWindowToken appShowWhenLocked = winShowWhenLocked == null ? @@ -203,7 +204,11 @@ public class WindowAnimator { || (win.mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0 && win.isAnimatingLw() // Show error dialogs over apps that dismiss keyguard. || (win.mAttrs.privateFlags & PRIVATE_FLAG_SYSTEM_ERROR) != 0))); - return (mForceHiding == KEYGUARD_SHOWN) && hideWhenLocked; + + // Only hide windows if the keyguard is active and not animating away. + boolean keyguardOn = mPolicy.isKeyguardShowingOrOccluded() + && mForceHiding != KEYGUARD_ANIMATING_OUT; + return keyguardOn && hideWhenLocked; } private void updateWindowsLocked(final int displayId) { @@ -228,6 +233,7 @@ public class WindowAnimator { winAnimator.mAnimation.setDuration(KEYGUARD_ANIM_TIMEOUT_MS); winAnimator.mAnimationIsEntrance = false; winAnimator.mAnimationStartTime = -1; + winAnimator.mKeyguardGoingAwayAnimation = true; } } else { if (DEBUG_KEYGUARD) Slog.d(TAG, @@ -310,7 +316,7 @@ public class WindowAnimator { mKeyguardGoingAway = false; } if (win.isReadyForDisplay()) { - if (nowAnimating) { + if (nowAnimating && win.mWinAnimator.mKeyguardGoingAwayAnimation) { mForceHiding = KEYGUARD_ANIMATING_OUT; } else { mForceHiding = win.isDrawnLw() ? KEYGUARD_SHOWN : KEYGUARD_NOT_SHOWN; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 7019453..6b5c224 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2734,14 +2734,16 @@ public class WindowManagerService extends IWindowManager.Stub } } final AppWindowToken appToken = win.mAppToken; + // Prevent an immediate window exit only for a real animation, ignoring e.g. + // dummy animations. + final boolean inAnimation = win.mWinAnimator.isWindowAnimatingNow(); // The starting window is the last window in this app token and it isn't animating. // Allow it to be removed now as there is no additional window or animation that will // trigger its removal. final boolean lastWinStartingNotAnimating = startingWindow && appToken!= null - && appToken.allAppWindows.size() == 1 && !win.mWinAnimator.isWindowAnimating(); - if (!lastWinStartingNotAnimating && (win.mExiting || win.mWinAnimator.isAnimating())) { + && appToken.allAppWindows.size() == 1 && !inAnimation; + if (!lastWinStartingNotAnimating && (win.mExiting || inAnimation)) { // The exit animation is running... wait for it! - //Slog.i(TAG, "*** Running exit animation..."); win.mExiting = true; win.mRemoveOnExit = true; final DisplayContent displayContent = win.getDisplayContent(); @@ -2757,7 +2759,6 @@ public class WindowManagerService extends IWindowManager.Stub if (focusChanged) { mInputMonitor.updateInputWindowsLw(false /*force*/); } - //dump(); Binder.restoreCallingIdentity(origId); return; } diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index e9023fd..b42c8eb 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -251,11 +251,21 @@ class WindowStateAnimator { && mAppAnimator.animation == AppWindowAnimator.sDummyAnimation; } - /** Is this window currently animating? */ + /** Is this window currently set to animate or currently animating? + * NOTE: The method will return true for cases where the window isn't currently animating, but + * is set to animate. i.e. if the window animation is currently set to a dummy placeholder + * animation. Use {@link #isWindowAnimatingNow} to know if the window is currently running a + * real animation. */ boolean isWindowAnimating() { return mAnimation != null; } + /** Is the window performing a real animation and not a dummy which is only waiting for an + * an animation to start? */ + boolean isWindowAnimatingNow() { + return isWindowAnimating() && !isDummyAnimation(); + } + void cancelExitAnimationForNextAnimationLocked() { if (mAnimation != null) { mAnimation.cancel(); diff --git a/services/net/java/android/net/dhcp/DhcpAckPacket.java b/services/net/java/android/net/dhcp/DhcpAckPacket.java index c0e1d19..334f708 100644 --- a/services/net/java/android/net/dhcp/DhcpAckPacket.java +++ b/services/net/java/android/net/dhcp/DhcpAckPacket.java @@ -30,8 +30,8 @@ class DhcpAckPacket extends DhcpPacket { private final Inet4Address mSrcIp; DhcpAckPacket(int transId, short secs, boolean broadcast, Inet4Address serverAddress, - Inet4Address clientIp, byte[] clientMac) { - super(transId, secs, INADDR_ANY, clientIp, serverAddress, INADDR_ANY, clientMac, broadcast); + Inet4Address clientIp, Inet4Address yourIp, byte[] clientMac) { + super(transId, secs, clientIp, yourIp, serverAddress, INADDR_ANY, clientMac, broadcast); mBroadcast = broadcast; mSrcIp = serverAddress; } diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java index d8d74e2..575a300 100644 --- a/services/net/java/android/net/dhcp/DhcpClient.java +++ b/services/net/java/android/net/dhcp/DhcpClient.java @@ -58,7 +58,7 @@ import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Random; -import libcore.io.IoUtils; +import libcore.io.IoBridge; import static android.system.OsConstants.*; import static android.net.dhcp.DhcpPacket.*; @@ -297,9 +297,15 @@ public class DhcpClient extends BaseDhcpStateMachine { return true; } + private static void closeQuietly(FileDescriptor fd) { + try { + IoBridge.closeAndSignalBlockedThreads(fd); + } catch (IOException ignored) {} + } + private void closeSockets() { - IoUtils.closeQuietly(mUdpSock); - IoUtils.closeQuietly(mPacketSock); + closeQuietly(mUdpSock); + closeQuietly(mPacketSock); } private boolean setIpAddress(LinkAddress address) { @@ -326,7 +332,7 @@ public class DhcpClient extends BaseDhcpStateMachine { @Override public void run() { - maybeLog("Starting receive thread"); + maybeLog("Receive thread started"); while (!stopped) { try { int length = Os.read(mPacketSock, mPacket, 0, mPacket.length); @@ -345,7 +351,7 @@ public class DhcpClient extends BaseDhcpStateMachine { } } } - maybeLog("Stopping receive thread"); + maybeLog("Receive thread stopped"); } } @@ -463,6 +469,8 @@ public class DhcpClient extends BaseDhcpStateMachine { return "CMD_KICK"; case CMD_RECEIVED_PACKET: return "CMD_RECEIVED_PACKET"; + case CMD_TIMEOUT: + return "CMD_TIMEOUT"; default: return Integer.toString(what); } @@ -601,12 +609,15 @@ public class DhcpClient extends BaseDhcpStateMachine { /** * Retransmits packets using jittered exponential backoff with an optional timeout. Packet - * transmission is triggered by CMD_KICK, which is sent by an AlarmManager alarm. + * transmission is triggered by CMD_KICK, which is sent by an AlarmManager alarm. If a subclass + * sets mTimeout to a positive value, then timeout() is called by an AlarmManager alarm mTimeout + * milliseconds after entering the state. Kicks and timeouts are cancelled when leaving the + * state. * * Concrete subclasses must implement sendPacket, which is called when the alarm fires and a * packet needs to be transmitted, and receivePacket, which is triggered by CMD_RECEIVED_PACKET - * sent by the receive thread. They may implement timeout, which is called when the timeout - * fires. + * sent by the receive thread. They may also set mTimeout and if desired override the default + * timeout implementation. */ abstract class PacketRetransmittingState extends LoggingState { @@ -647,7 +658,19 @@ public class DhcpClient extends BaseDhcpStateMachine { abstract protected boolean sendPacket(); abstract protected void receivePacket(DhcpPacket packet); - protected void timeout() {} + + // Default implementation of timeout. This is only invoked if mTimeout > 0, so it will never + // be called if the subclass does not set a timeout. + protected void timeout() { + maybeLog("Timeout in " + getName()); + notifyFailure(); + if (this != mDhcpInitState) { + // Only transition to INIT if we're not already there. Otherwise, we'll exit the + // state and re-enter it, which will reset the packet transmission interval, re-set + // the timeout, etc. + transitionTo(mDhcpInitState); + } + } protected void initTimer() { mTimer = FIRST_TIMEOUT_MS; @@ -696,11 +719,6 @@ public class DhcpClient extends BaseDhcpStateMachine { return sendDiscoverPacket(); } - protected void timeout() { - maybeLog("Timeout"); - notifyFailure(); - } - protected void receivePacket(DhcpPacket packet) { if (!isValidPacket(packet)) return; if (!(packet instanceof DhcpOfferPacket)) return; @@ -747,11 +765,6 @@ public class DhcpClient extends BaseDhcpStateMachine { transitionTo(mDhcpInitState); } } - - protected void timeout() { - notifyFailure(); - transitionTo(mDhcpInitState); - } } class DhcpHaveAddressState extends LoggingState { @@ -760,6 +773,8 @@ public class DhcpClient extends BaseDhcpStateMachine { super.enter(); if (!setIpAddress(mDhcpLease.ipAddress)) { notifyFailure(); + // There's likely no point in going into DhcpInitState here, we'll probably just + // repeat the transaction, get the same IP address as before, and fail. transitionTo(mStoppedState); } } @@ -797,7 +812,6 @@ public class DhcpClient extends BaseDhcpStateMachine { } } - // TODO: timeout. class DhcpRenewingState extends PacketRetransmittingState { public DhcpRenewingState() { super(); diff --git a/services/net/java/android/net/dhcp/DhcpOfferPacket.java b/services/net/java/android/net/dhcp/DhcpOfferPacket.java index af41708..7ca7100 100644 --- a/services/net/java/android/net/dhcp/DhcpOfferPacket.java +++ b/services/net/java/android/net/dhcp/DhcpOfferPacket.java @@ -32,8 +32,8 @@ class DhcpOfferPacket extends DhcpPacket { * Generates a OFFER packet with the specified parameters. */ DhcpOfferPacket(int transId, short secs, boolean broadcast, Inet4Address serverAddress, - Inet4Address clientIp, byte[] clientMac) { - super(transId, secs, INADDR_ANY, clientIp, INADDR_ANY, INADDR_ANY, clientMac, broadcast); + Inet4Address clientIp, Inet4Address yourIp, byte[] clientMac) { + super(transId, secs, clientIp, yourIp, INADDR_ANY, INADDR_ANY, clientMac, broadcast); mSrcIp = serverAddress; } diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/services/net/java/android/net/dhcp/DhcpPacket.java index 2a25d30..d42404b 100644 --- a/services/net/java/android/net/dhcp/DhcpPacket.java +++ b/services/net/java/android/net/dhcp/DhcpPacket.java @@ -919,7 +919,7 @@ abstract class DhcpPacket { break; case DHCP_MESSAGE_TYPE_OFFER: newPacket = new DhcpOfferPacket( - transactionId, secs, broadcast, ipSrc, yourIp, clientMac); + transactionId, secs, broadcast, ipSrc, clientIp, yourIp, clientMac); break; case DHCP_MESSAGE_TYPE_REQUEST: newPacket = new DhcpRequestPacket( @@ -932,7 +932,7 @@ abstract class DhcpPacket { break; case DHCP_MESSAGE_TYPE_ACK: newPacket = new DhcpAckPacket( - transactionId, secs, broadcast, ipSrc, yourIp, clientMac); + transactionId, secs, broadcast, ipSrc, clientIp, yourIp, clientMac); break; case DHCP_MESSAGE_TYPE_NAK: newPacket = new DhcpNakPacket( @@ -982,9 +982,9 @@ abstract class DhcpPacket { */ public DhcpResults toDhcpResults() { Inet4Address ipAddress = mYourIp; - if (ipAddress == Inet4Address.ANY) { + if (ipAddress.equals(Inet4Address.ANY)) { ipAddress = mClientIp; - if (ipAddress == Inet4Address.ANY) { + if (ipAddress.equals(Inet4Address.ANY)) { return null; } } @@ -1052,7 +1052,7 @@ abstract class DhcpPacket { Inet4Address gateway, List<Inet4Address> dnsServers, Inet4Address dhcpServerIdentifier, String domainName) { DhcpPacket pkt = new DhcpOfferPacket( - transactionId, (short) 0, broadcast, serverIpAddr, clientIpAddr, mac); + transactionId, (short) 0, broadcast, serverIpAddr, INADDR_ANY, clientIpAddr, mac); pkt.mGateway = gateway; pkt.mDnsServers = dnsServers; pkt.mLeaseTime = timeout; @@ -1072,7 +1072,7 @@ abstract class DhcpPacket { Inet4Address gateway, List<Inet4Address> dnsServers, Inet4Address dhcpServerIdentifier, String domainName) { DhcpPacket pkt = new DhcpAckPacket( - transactionId, (short) 0, broadcast, serverIpAddr, clientIpAddr, mac); + transactionId, (short) 0, broadcast, serverIpAddr, INADDR_ANY, clientIpAddr, mac); pkt.mGateway = gateway; pkt.mDnsServers = dnsServers; pkt.mLeaseTime = timeout; diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 636dd4d..919293a 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -36,6 +36,9 @@ <uses-permission android:name="android.permission.MANAGE_USERS" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" /> + <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" /> + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> <application> <uses-library android:name="android.test.runner" /> diff --git a/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java b/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java index f116042..e0e3fcf 100644 --- a/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java +++ b/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java @@ -18,6 +18,7 @@ package android.net.dhcp; import android.net.NetworkUtils; import android.net.DhcpResults; +import android.net.LinkAddress; import android.system.OsConstants; import android.test.suitebuilder.annotation.SmallTest; import junit.framework.TestCase; @@ -34,19 +35,27 @@ public class DhcpPacketTest extends TestCase { (Inet4Address) NetworkUtils.numericToInetAddress("192.0.2.1"); private static Inet4Address CLIENT_ADDR = (Inet4Address) NetworkUtils.numericToInetAddress("192.0.2.234"); + // Use our own empty address instead of Inet4Address.ANY or INADDR_ANY to ensure that the code + // doesn't use == instead of equals when comparing addresses. + private static Inet4Address ANY = (Inet4Address) NetworkUtils.numericToInetAddress("0.0.0.0"); + private static byte[] CLIENT_MAC = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 }; class TestDhcpPacket extends DhcpPacket { private byte mType; // TODO: Make this a map of option numbers to bytes instead. - private byte[] mDomainBytes, mVendorInfoBytes, mLeaseTimeBytes; + private byte[] mDomainBytes, mVendorInfoBytes, mLeaseTimeBytes, mNetmaskBytes; - public TestDhcpPacket(byte type) { - super(0xdeadbeef, (short) 0, INADDR_ANY, CLIENT_ADDR, INADDR_ANY, INADDR_ANY, + public TestDhcpPacket(byte type, Inet4Address clientIp, Inet4Address yourIp) { + super(0xdeadbeef, (short) 0, clientIp, yourIp, INADDR_ANY, INADDR_ANY, CLIENT_MAC, true); mType = type; } + public TestDhcpPacket(byte type) { + this(type, INADDR_ANY, CLIENT_ADDR); + } + public TestDhcpPacket setDomainBytes(byte[] domainBytes) { mDomainBytes = domainBytes; return this; @@ -62,6 +71,11 @@ public class DhcpPacketTest extends TestCase { return this; } + public TestDhcpPacket setNetmaskBytes(byte[] netmaskBytes) { + mNetmaskBytes = netmaskBytes; + return this; + } + public ByteBuffer buildPacket(int encap, short unusedDestUdp, short unusedSrcUdp) { ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH); fillInPacket(encap, CLIENT_ADDR, SERVER_ADDR, @@ -80,6 +94,9 @@ public class DhcpPacketTest extends TestCase { if (mLeaseTimeBytes != null) { addTlv(buffer, DHCP_LEASE_TIME, mLeaseTimeBytes); } + if (mNetmaskBytes != null) { + addTlv(buffer, DHCP_SUBNET_MASK, mNetmaskBytes); + } addTlvEnd(buffer); } @@ -175,4 +192,50 @@ public class DhcpPacketTest extends TestCase { assertLeaseTimeParses(true, -2147483647, 2147483649L * 1000, maxIntPlusOneLease); assertLeaseTimeParses(true, DhcpPacket.INFINITE_LEASE, 0, infiniteLease); } + + private void checkIpAddress(String expected, Inet4Address clientIp, Inet4Address yourIp, + byte[] netmaskBytes) { + checkIpAddress(expected, DHCP_MESSAGE_TYPE_OFFER, clientIp, yourIp, netmaskBytes); + checkIpAddress(expected, DHCP_MESSAGE_TYPE_ACK, clientIp, yourIp, netmaskBytes); + } + + private void checkIpAddress(String expected, byte type, + Inet4Address clientIp, Inet4Address yourIp, + byte[] netmaskBytes) { + ByteBuffer packet = new TestDhcpPacket(type, clientIp, yourIp) + .setNetmaskBytes(netmaskBytes) + .build(); + DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP); + DhcpResults results = offerPacket.toDhcpResults(); + + if (expected != null) { + LinkAddress expectedAddress = new LinkAddress(expected); + assertEquals(expectedAddress, results.ipAddress); + } else { + assertNull(results); + } + } + + @SmallTest + public void testIpAddress() throws Exception { + byte[] slash11Netmask = new byte[] { (byte) 0xff, (byte) 0xe0, 0x00, 0x00 }; + byte[] slash24Netmask = new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x00 }; + byte[] invalidNetmask = new byte[] { (byte) 0xff, (byte) 0xfb, (byte) 0xff, 0x00 }; + Inet4Address example1 = (Inet4Address) NetworkUtils.numericToInetAddress("192.0.2.1"); + Inet4Address example2 = (Inet4Address) NetworkUtils.numericToInetAddress("192.0.2.43"); + + // A packet without any addresses is not valid. + checkIpAddress(null, ANY, ANY, slash24Netmask); + + // ClientIP is used iff YourIP is not present. + checkIpAddress("192.0.2.1/24", example2, example1, slash24Netmask); + checkIpAddress("192.0.2.43/11", example2, ANY, slash11Netmask); + checkIpAddress("192.0.2.43/11", ANY, example2, slash11Netmask); + + // Invalid netmasks are ignored. + checkIpAddress(null, example2, ANY, invalidNetmask); + + // If there is no netmask, implicit netmasks are used. + checkIpAddress("192.0.2.43/24", ANY, example2, null); + } } diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java index 48d8ffb..56f1d48c 100644 --- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java @@ -20,6 +20,10 @@ import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.ConnectivityManager.getNetworkTypeName; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.isA; @@ -30,21 +34,36 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.ContextWrapper; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkAgent; +import android.net.NetworkCapabilities; import android.net.NetworkConfig; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; +import android.net.NetworkMisc; +import android.net.NetworkRequest; import android.net.RouteInfo; +import android.os.ConditionVariable; import android.os.Handler; +import android.os.Looper; import android.os.INetworkManagementService; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.LargeTest; import android.util.Log; import android.util.LogPrinter; +import com.android.server.connectivity.NetworkMonitor; + import org.mockito.ArgumentCaptor; import java.net.InetAddress; @@ -52,8 +71,10 @@ import java.util.concurrent.Future; /** * Tests for {@link ConnectivityService}. + * + * Build, install and run with: + * runtest frameworks-services -c com.android.server.ConnectivityServiceTest */ -@LargeTest public class ConnectivityServiceTest extends AndroidTestCase { private static final String TAG = "ConnectivityServiceTest"; @@ -75,73 +96,336 @@ public class ConnectivityServiceTest extends AndroidTestCase { private INetworkManagementService mNetManager; private INetworkStatsService mStatsService; private INetworkPolicyManager mPolicyService; -// private ConnectivityService.NetworkFactory mNetFactory; private BroadcastInterceptingContext mServiceContext; private ConnectivityService mService; + private ConnectivityManager mCm; + private MockNetworkAgent mWiFiNetworkAgent; + private MockNetworkAgent mCellNetworkAgent; + + private class MockContext extends BroadcastInterceptingContext { + MockContext(Context base) { + super(base); + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { + // PendingIntents sent by the AlarmManager are not intercepted by + // BroadcastInterceptingContext so we must really register the receiver. + // This shouldn't effect the real NetworkMonitors as the action contains a random token. + if (filter.getAction(0).startsWith("android.net.netmon.lingerExpired")) { + return getBaseContext().registerReceiver(receiver, filter); + } else { + return super.registerReceiver(receiver, filter); + } + } + + @Override + public Object getSystemService (String name) { + if (name == Context.CONNECTIVITY_SERVICE) return mCm; + return super.getSystemService(name); + } + } + + private class MockNetworkAgent { + private final NetworkInfo mNetworkInfo; + private final NetworkCapabilities mNetworkCapabilities; + private final Thread mThread; + private NetworkAgent mNetworkAgent; + + MockNetworkAgent(int transport) { + final int type = transportToLegacyType(transport); + final String typeName = ConnectivityManager.getNetworkTypeName(type); + mNetworkInfo = new NetworkInfo(type, 0, typeName, "Mock"); + mNetworkCapabilities = new NetworkCapabilities(); + mNetworkCapabilities.addTransportType(transport); + final int score; + switch (transport) { + case TRANSPORT_WIFI: + score = 60; + break; + case TRANSPORT_CELLULAR: + score = 50; + break; + default: + throw new UnsupportedOperationException("unimplemented network type"); + } + final ConditionVariable initComplete = new ConditionVariable(); + mThread = new Thread() { + public void run() { + Looper.prepare(); + mNetworkAgent = new NetworkAgent(Looper.myLooper(), mServiceContext, + "Mock" + typeName, mNetworkInfo, mNetworkCapabilities, + new LinkProperties(), score, new NetworkMisc()) { + public void unwanted() {} + }; + initComplete.open(); + Looper.loop(); + } + }; + mThread.start(); + initComplete.block(); + } + + /** + * Transition this NetworkAgent to CONNECTED state. + * @param validated Indicate if network should pretend to be validated. + */ + public void connect(boolean validated) { + assertEquals(mNetworkInfo.getDetailedState(), DetailedState.IDLE); + assertFalse(mNetworkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)); + + // To pretend network is validated, we transition it to the CONNECTED state without + // NET_CAPABILITY_INTERNET so NetworkMonitor doesn't bother trying to validate and + // just rubber stamps it as validated. Afterwards we add NET_CAPABILITY_INTERNET so + // the network can satisfy the default request. + NetworkCallback callback = null; + final ConditionVariable validatedCv = new ConditionVariable(); + if (validated) { + // If we connect a network without INTERNET capability, it'll get reaped. + // Prevent the reaping by adding a NetworkRequest. + NetworkRequest request = new NetworkRequest.Builder() + .addTransportType(mNetworkCapabilities.getTransportTypes()[0]) + .build(); + callback = new NetworkCallback() { + public void onCapabilitiesChanged(Network network, + NetworkCapabilities networkCapabilities) { + if (networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) { + validatedCv.open(); + } + } + }; + mCm.requestNetwork(request, callback); + } else { + mNetworkCapabilities.addCapability(NET_CAPABILITY_INTERNET); + mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); + } + + mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null); + mNetworkAgent.sendNetworkInfo(mNetworkInfo); + + if (validated) { + // Wait for network to validate. + validatedCv.block(); + mNetworkCapabilities.addCapability(NET_CAPABILITY_INTERNET); + mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); + } + + if (callback != null) mCm.unregisterNetworkCallback(callback); + } + + public void disconnect() { + mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null); + mNetworkAgent.sendNetworkInfo(mNetworkInfo); + } + + public Network getNetwork() { + return new Network(mNetworkAgent.netId); + } + } + + private class WrappedConnectivityService extends ConnectivityService { + public WrappedConnectivityService(Context context, INetworkManagementService netManager, + INetworkStatsService statsService, INetworkPolicyManager policyManager) { + super(context, netManager, statsService, policyManager); + } + + @Override + protected int getDefaultTcpRwnd() { + // Prevent wrapped ConnectivityService from trying to write to SystemProperties. + return 0; + } + } + + @Override + public void setUp() throws Exception { + super.setUp(); + + mServiceContext = new MockContext(getContext()); + + mNetManager = mock(INetworkManagementService.class); + mStatsService = mock(INetworkStatsService.class); + mPolicyService = mock(INetworkPolicyManager.class); + + mService = new WrappedConnectivityService( + mServiceContext, mNetManager, mStatsService, mPolicyService); + mService.systemReady(); + mCm = new ConnectivityManager(mService); + } + + private int transportToLegacyType(int transport) { + switch (transport) { + case TRANSPORT_WIFI: + return TYPE_WIFI; + case TRANSPORT_CELLULAR: + return TYPE_MOBILE; + default: + throw new IllegalStateException("Unknown transport" + transport); + } + } + + private void verifyActiveNetwork(int transport) { + // Test getActiveNetworkInfo() + assertNotNull(mCm.getActiveNetworkInfo()); + assertEquals(transportToLegacyType(transport), mCm.getActiveNetworkInfo().getType()); + // Test getActiveNetwork() + assertNotNull(mCm.getActiveNetwork()); + switch (transport) { + case TRANSPORT_WIFI: + assertEquals(mCm.getActiveNetwork(), mWiFiNetworkAgent.getNetwork()); + break; + case TRANSPORT_CELLULAR: + assertEquals(mCm.getActiveNetwork(), mCellNetworkAgent.getNetwork()); + break; + default: + throw new IllegalStateException("Unknown transport" + transport); + } + // Test getNetworkInfo(Network) + assertNotNull(mCm.getNetworkInfo(mCm.getActiveNetwork())); + assertEquals(transportToLegacyType(transport), mCm.getNetworkInfo(mCm.getActiveNetwork()).getType()); + // Test getNetworkCapabilities(Network) + assertNotNull(mCm.getNetworkCapabilities(mCm.getActiveNetwork())); + assertTrue(mCm.getNetworkCapabilities(mCm.getActiveNetwork()).hasTransport(transport)); + } + + private void verifyNoNetwork() { + // Test getActiveNetworkInfo() + assertNull(mCm.getActiveNetworkInfo()); + // Test getActiveNetwork() + assertNull(mCm.getActiveNetwork()); + // Test getAllNetworks() + assertEquals(0, mCm.getAllNetworks().length); + } + + /** + * Return a ConditionVariable that opens when {@code count} numbers of CONNECTIVITY_ACTION + * broadcasts are received. + */ + private ConditionVariable waitForConnectivityBroadcasts(final int count) { + final ConditionVariable cv = new ConditionVariable(); + mServiceContext.registerReceiver(new BroadcastReceiver() { + private int remaining = count; + public void onReceive(Context context, Intent intent) { + if (--remaining == 0) { + cv.open(); + mServiceContext.unregisterReceiver(this); + } + } + }, new IntentFilter(CONNECTIVITY_ACTION)); + return cv; + } + + @LargeTest + public void testLingering() throws Exception { + // Decrease linger timeout to the minimum allowed by AlarmManagerService. + NetworkMonitor.SetDefaultLingerTime(5000); + verifyNoNetwork(); + mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + assertNull(mCm.getActiveNetworkInfo()); + assertNull(mCm.getActiveNetwork()); + // Test bringing up validated cellular. + ConditionVariable cv = waitForConnectivityBroadcasts(1); + mCellNetworkAgent.connect(true); + cv.block(); + verifyActiveNetwork(TRANSPORT_CELLULAR); + assertEquals(2, mCm.getAllNetworks().length); + assertTrue(mCm.getAllNetworks()[0].equals(mCm.getActiveNetwork()) || + mCm.getAllNetworks()[1].equals(mCm.getActiveNetwork())); + assertTrue(mCm.getAllNetworks()[0].equals(mWiFiNetworkAgent.getNetwork()) || + mCm.getAllNetworks()[1].equals(mWiFiNetworkAgent.getNetwork())); + // Test bringing up validated WiFi. + cv = waitForConnectivityBroadcasts(2); + mWiFiNetworkAgent.connect(true); + cv.block(); + verifyActiveNetwork(TRANSPORT_WIFI); + assertEquals(2, mCm.getAllNetworks().length); + assertTrue(mCm.getAllNetworks()[0].equals(mCm.getActiveNetwork()) || + mCm.getAllNetworks()[1].equals(mCm.getActiveNetwork())); + assertTrue(mCm.getAllNetworks()[0].equals(mCellNetworkAgent.getNetwork()) || + mCm.getAllNetworks()[1].equals(mCellNetworkAgent.getNetwork())); + // Test cellular linger timeout. + try { + Thread.sleep(6000); + } catch (Exception e) { + } + verifyActiveNetwork(TRANSPORT_WIFI); + assertEquals(1, mCm.getAllNetworks().length); + assertEquals(mCm.getAllNetworks()[0], mCm.getActiveNetwork()); + // Test WiFi disconnect. + cv = waitForConnectivityBroadcasts(1); + mWiFiNetworkAgent.disconnect(); + cv.block(); + verifyNoNetwork(); + } + + @LargeTest + public void testValidatedCellularOutscoresUnvalidatedWiFi() throws Exception { + // Test bringing up unvalidated WiFi + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + ConditionVariable cv = waitForConnectivityBroadcasts(1); + mWiFiNetworkAgent.connect(false); + cv.block(); + verifyActiveNetwork(TRANSPORT_WIFI); + // Test bringing up unvalidated cellular + mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(false); + try { + Thread.sleep(1000); + } catch (Exception e) { + } + verifyActiveNetwork(TRANSPORT_WIFI); + // Test cellular disconnect. + mCellNetworkAgent.disconnect(); + try { + Thread.sleep(1000); + } catch (Exception e) { + } + verifyActiveNetwork(TRANSPORT_WIFI); + // Test bringing up validated cellular + mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + cv = waitForConnectivityBroadcasts(2); + mCellNetworkAgent.connect(true); + cv.block(); + verifyActiveNetwork(TRANSPORT_CELLULAR); + // Test cellular disconnect. + cv = waitForConnectivityBroadcasts(2); + mCellNetworkAgent.disconnect(); + cv.block(); + verifyActiveNetwork(TRANSPORT_WIFI); + // Test WiFi disconnect. + cv = waitForConnectivityBroadcasts(1); + mWiFiNetworkAgent.disconnect(); + cv.block(); + verifyNoNetwork(); + } + + @LargeTest + public void testUnvalidatedWifiOutscoresUnvalidatedCellular() throws Exception { + // Test bringing up unvalidated cellular. + mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + ConditionVariable cv = waitForConnectivityBroadcasts(1); + mCellNetworkAgent.connect(false); + cv.block(); + verifyActiveNetwork(TRANSPORT_CELLULAR); + // Test bringing up unvalidated WiFi. + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + cv = waitForConnectivityBroadcasts(2); + mWiFiNetworkAgent.connect(false); + cv.block(); + verifyActiveNetwork(TRANSPORT_WIFI); + // Test WiFi disconnect. + cv = waitForConnectivityBroadcasts(2); + mWiFiNetworkAgent.disconnect(); + cv.block(); + verifyActiveNetwork(TRANSPORT_CELLULAR); + // Test cellular disconnect. + cv = waitForConnectivityBroadcasts(1); + mCellNetworkAgent.disconnect(); + cv.block(); + verifyNoNetwork(); + } -// TODO: rework with network factory -// private MockNetwork mMobile; -// private MockNetwork mWifi; -// -// private Handler mTrackerHandler; -// -// private static class MockNetwork { -// public NetworkStateTracker tracker; -// public NetworkInfo info; -// public LinkProperties link; -// -// public MockNetwork(int type) { -// tracker = mock(NetworkStateTracker.class); -// info = new NetworkInfo(type, -1, getNetworkTypeName(type), null); -// link = new LinkProperties(); -// } -// -// public void doReturnDefaults() { -// // TODO: eventually CS should make defensive copies -// doReturn(new NetworkInfo(info)).when(tracker).getNetworkInfo(); -// doReturn(new LinkProperties(link)).when(tracker).getLinkProperties(); -// -// // fallback to default TCP buffers -// doReturn("").when(tracker).getTcpBufferSizesPropName(); -// } -// } -// -// @Override -// public void setUp() throws Exception { -// super.setUp(); -// -// mServiceContext = new BroadcastInterceptingContext(getContext()); -// -// mNetManager = mock(INetworkManagementService.class); -// mStatsService = mock(INetworkStatsService.class); -// mPolicyService = mock(INetworkPolicyManager.class); -// mNetFactory = mock(ConnectivityService.NetworkFactory.class); -// -// mMobile = new MockNetwork(TYPE_MOBILE); -// mWifi = new MockNetwork(TYPE_WIFI); -// -// // omit most network trackers -// doThrow(new IllegalArgumentException("Not supported in test environment")) -// .when(mNetFactory).createTracker(anyInt(), isA(NetworkConfig.class)); -// -// doReturn(mMobile.tracker) -// .when(mNetFactory).createTracker(eq(TYPE_MOBILE), isA(NetworkConfig.class)); -// doReturn(mWifi.tracker) -// .when(mNetFactory).createTracker(eq(TYPE_WIFI), isA(NetworkConfig.class)); -// -// final ArgumentCaptor<Handler> trackerHandler = ArgumentCaptor.forClass(Handler.class); -// doNothing().when(mMobile.tracker) -// .startMonitoring(isA(Context.class), trackerHandler.capture()); -// -// mService = new ConnectivityService( -// mServiceContext, mNetManager, mStatsService, mPolicyService); -// mService.systemReady(); -// -// mTrackerHandler = trackerHandler.getValue(); -// mTrackerHandler.getLooper().setMessageLogging(new LogPrinter(Log.INFO, TAG)); -// } -// // @Override // public void tearDown() throws Exception { // super.tearDown(); @@ -157,9 +441,9 @@ public class ConnectivityServiceTest extends AndroidTestCase { // mMobile.link.addRoute(MOBILE_ROUTE_V6); // mMobile.doReturnDefaults(); // -// nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION); +// cv = waitForConnectivityBroadcasts(1); // mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget(); -// nextConnBroadcast.get(); +// cv.block(); // // // verify that both routes were added // int mobileNetId = mMobile.tracker.getNetwork().netId; @@ -177,9 +461,9 @@ public class ConnectivityServiceTest extends AndroidTestCase { // mMobile.link.addRoute(MOBILE_ROUTE_V6); // mMobile.doReturnDefaults(); // -// nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION); +// cv = waitForConnectivityBroadcasts(1); // mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget(); -// nextConnBroadcast.get(); +// cv.block(); // // reset(mNetManager); // @@ -193,9 +477,9 @@ public class ConnectivityServiceTest extends AndroidTestCase { // // expect that mobile will be torn down // doReturn(true).when(mMobile.tracker).teardown(); // -// nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION); +// cv = waitForConnectivityBroadcasts(1); // mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mWifi.info).sendToTarget(); -// nextConnBroadcast.get(); +// cv.block(); // // // verify that wifi routes added, and teardown requested // int wifiNetId = mWifi.tracker.getNetwork().netId; @@ -212,9 +496,9 @@ public class ConnectivityServiceTest extends AndroidTestCase { // mMobile.link.clear(); // mMobile.doReturnDefaults(); // -// nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION); +// cv = waitForConnectivityBroadcasts(1); // mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget(); -// nextConnBroadcast.get(); +// cv.block(); // // verify(mNetManager).removeRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V4)); // verify(mNetManager).removeRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V6)); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index 6de887b..d8569bc 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -201,7 +201,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne Slog.w(TAG, "finish does not match active session"); return; } - mActiveSession.cancel(); + mActiveSession.cancelLocked(); mActiveSession = null; } @@ -251,7 +251,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne // If there is an active session, cancel it to allow it to clean up its window and other // state. if (mActiveSession != null) { - mActiveSession.cancel(); + mActiveSession.cancelLocked(); mActiveSession = null; } try { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java index b4629f2..0b430ca 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java @@ -19,9 +19,9 @@ package com.android.server.voiceinteraction; import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.AppOpsManager; -import android.app.AssistContent; -import android.app.AssistStructure; import android.app.IActivityManager; +import android.app.assist.AssistContent; +import android.app.assist.AssistStructure; import android.content.ClipData; import android.content.ComponentName; import android.content.ContentProvider; @@ -376,6 +376,40 @@ final class VoiceInteractionSessionConnection implements ServiceConnection { return false; } + public void cancelLocked() { + hideLocked(); + mCanceled = true; + if (mBound) { + if (mSession != null) { + try { + mSession.destroy(); + } catch (RemoteException e) { + Slog.w(TAG, "Voice interation session already dead"); + } + } + if (mSession != null) { + try { + mAm.finishVoiceTask(mSession); + } catch (RemoteException e) { + } + } + mContext.unbindService(this); + try { + mIWindowManager.removeWindowToken(mToken); + } catch (RemoteException e) { + Slog.w(TAG, "Failed removing window token", e); + } + mBound = false; + mService = null; + mSession = null; + mInteractor = null; + } + if (mFullyBound) { + mContext.unbindService(mFullConnection); + mFullyBound = false; + } + } + public boolean deliverNewSessionLocked(IVoiceInteractionSession session, IVoiceInteractor interactor) { mSession = session; @@ -432,39 +466,6 @@ final class VoiceInteractionSessionConnection implements ServiceConnection { mService = null; } - public void cancel() { - mCanceled = true; - if (mBound) { - if (mSession != null) { - try { - mSession.destroy(); - } catch (RemoteException e) { - Slog.w(TAG, "Voice interation session already dead"); - } - } - if (mSession != null) { - try { - mAm.finishVoiceTask(mSession); - } catch (RemoteException e) { - } - } - mContext.unbindService(this); - try { - mIWindowManager.removeWindowToken(mToken); - } catch (RemoteException e) { - Slog.w(TAG, "Failed removing window token", e); - } - mBound = false; - mService = null; - mSession = null; - mInteractor = null; - } - if (mFullyBound) { - mContext.unbindService(mFullConnection); - mFullyBound = false; - } - } - private boolean isStructureEnabled() { return Settings.Secure.getIntForUser(mContext.getContentResolver(), Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, mUser) != 0; diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index f51cb65..2445bf6 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -2298,7 +2298,8 @@ public class TelephonyManager { } /** - * Returns the voice mail count. Return 0 if unavailable. + * Returns the voice mail count. Return 0 if unavailable, -1 if there are unread voice messages + * but the count is unknown. * <p> * Requires Permission: * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java index f66a9ce..dae1ac3 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java @@ -17,9 +17,9 @@ package com.android.test.voiceinteraction; import android.app.ActivityManager; +import android.app.VoiceInteractor; import android.app.AssistContent; import android.app.AssistStructure; -import android.app.VoiceInteractor; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; @@ -71,7 +71,7 @@ public class MainInteractionSession extends VoiceInteractionSession public void onCreate(Bundle args, int startFlags) { super.onCreate(args, startFlags); ActivityManager am = getContext().getSystemService(ActivityManager.class); - am.setWatchHeapLimit(40*1024*1024); + am.setWatchHeapLimit(40 * 1024 * 1024); } @Override @@ -118,6 +118,30 @@ public class MainInteractionSession extends VoiceInteractionSession return mContentView; } + public void onHandleAssist(Bundle assistBundle) { + if (assistBundle != null) { + Bundle assistContext = assistBundle.getBundle(Intent.EXTRA_ASSIST_CONTEXT); + if (assistContext != null) { + mAssistStructure = AssistStructure.getAssistStructure(assistContext); + if (mAssistStructure != null) { + if (mAssistVisualizer != null) { + mAssistVisualizer.setAssistStructure(mAssistStructure); + } + } + AssistContent content = AssistContent.getAssistContent(assistContext); + if (content != null) { + Log.i(TAG, "Assist intent: " + content.getIntent()); + Log.i(TAG, "Assist clipdata: " + content.getClipData()); + } + return; + } + } + if (mAssistVisualizer != null) { + mAssistVisualizer.clearAssistData(); + } + } + + /* @Override public void onHandleAssist(Bundle data, AssistStructure structure, AssistContent content) { mAssistStructure = structure; @@ -131,6 +155,7 @@ public class MainInteractionSession extends VoiceInteractionSession Log.i(TAG, "Assist clipdata: " + content.getClipData()); } } + */ @Override public void onHandleScreenshot(Bitmap screenshot) { diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk index 23d679d..8c128d3 100644 --- a/tools/aapt2/Android.mk +++ b/tools/aapt2/Android.mk @@ -27,7 +27,6 @@ main := Main.cpp sources := \ BigBuffer.cpp \ BinaryResourceParser.cpp \ - BinaryXmlPullParser.cpp \ BindingXmlPullParser.cpp \ ConfigDescription.cpp \ Debug.cpp \ @@ -53,6 +52,7 @@ sources := \ ScopedXmlPullParser.cpp \ SourceXmlPullParser.cpp \ XliffXmlPullParser.cpp \ + XmlDom.cpp \ XmlFlattener.cpp \ ZipEntry.cpp \ ZipFile.cpp @@ -76,6 +76,7 @@ testSources := \ StringPool_test.cpp \ Util_test.cpp \ XliffXmlPullParser_test.cpp \ + XmlDom_test.cpp \ XmlFlattener_test.cpp cIncludes := \ diff --git a/tools/aapt2/BinaryXmlPullParser.cpp b/tools/aapt2/BinaryXmlPullParser.cpp deleted file mode 100644 index 476a215..0000000 --- a/tools/aapt2/BinaryXmlPullParser.cpp +++ /dev/null @@ -1,259 +0,0 @@ -/* - * 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 "BinaryXmlPullParser.h" -#include "Maybe.h" -#include "Util.h" - -#include <androidfw/ResourceTypes.h> -#include <memory> -#include <string> -#include <vector> - -namespace aapt { - -static XmlPullParser::Event codeToEvent(android::ResXMLParser::event_code_t code) { - switch (code) { - case android::ResXMLParser::START_DOCUMENT: - return XmlPullParser::Event::kStartDocument; - case android::ResXMLParser::END_DOCUMENT: - return XmlPullParser::Event::kEndDocument; - case android::ResXMLParser::START_NAMESPACE: - return XmlPullParser::Event::kStartNamespace; - case android::ResXMLParser::END_NAMESPACE: - return XmlPullParser::Event::kEndNamespace; - case android::ResXMLParser::START_TAG: - return XmlPullParser::Event::kStartElement; - case android::ResXMLParser::END_TAG: - return XmlPullParser::Event::kEndElement; - case android::ResXMLParser::TEXT: - return XmlPullParser::Event::kText; - default: - break; - } - return XmlPullParser::Event::kBadDocument; -} - -BinaryXmlPullParser::BinaryXmlPullParser(const std::shared_ptr<android::ResXMLTree>& parser) - : mParser(parser), mEvent(Event::kStartDocument), mHasComment(false), sEmpty(), sEmpty8(), - mDepth(0) { -} - -XmlPullParser::Event BinaryXmlPullParser::next() { - mStr1.clear(); - mStr2.clear(); - mAttributes.clear(); - - android::ResXMLParser::event_code_t code; - if (mHasComment) { - mHasComment = false; - code = mParser->getEventType(); - } else { - code = mParser->next(); - if (code != android::ResXMLParser::BAD_DOCUMENT) { - size_t len; - const char16_t* comment = mParser->getComment(&len); - if (comment) { - mHasComment = true; - mStr1.assign(comment, len); - return XmlPullParser::Event::kComment; - } - } - } - - size_t len; - const char16_t* data; - mEvent = codeToEvent(code); - switch (mEvent) { - case Event::kStartNamespace: - case Event::kEndNamespace: { - data = mParser->getNamespacePrefix(&len); - if (data) { - mStr1.assign(data, len); - } else { - mStr1.clear(); - } - data = mParser->getNamespaceUri(&len); - if (data) { - mStr2.assign(data, len); - } else { - mStr2.clear(); - } - - Maybe<std::u16string> result = util::extractPackageFromNamespace(mStr2); - if (result) { - if (mEvent == Event::kStartNamespace) { - mPackageAliases.emplace_back(mStr1, result.value()); - } else { - assert(mPackageAliases.back().second == result.value()); - mPackageAliases.pop_back(); - } - } - break; - } - - case Event::kStartElement: - copyAttributes(); - // fallthrough - - case Event::kEndElement: - data = mParser->getElementNamespace(&len); - if (data) { - mStr1.assign(data, len); - } else { - mStr1.clear(); - } - data = mParser->getElementName(&len); - if (data) { - mStr2.assign(data, len); - } else { - mStr2.clear(); - } - break; - - case Event::kText: - data = mParser->getText(&len); - if (data) { - mStr1.assign(data, len); - } else { - mStr1.clear(); - } - break; - - default: - break; - } - return mEvent; -} - -XmlPullParser::Event BinaryXmlPullParser::getEvent() const { - if (mHasComment) { - return XmlPullParser::Event::kComment; - } - return mEvent; -} - -const std::string& BinaryXmlPullParser::getLastError() const { - return sEmpty8; -} - -const std::u16string& BinaryXmlPullParser::getComment() const { - if (mHasComment) { - return mStr1; - } - return sEmpty; -} - -size_t BinaryXmlPullParser::getLineNumber() const { - return mParser->getLineNumber(); -} - -size_t BinaryXmlPullParser::getDepth() const { - return mDepth; -} - -const std::u16string& BinaryXmlPullParser::getText() const { - if (!mHasComment && mEvent == XmlPullParser::Event::kText) { - return mStr1; - } - return sEmpty; -} - -const std::u16string& BinaryXmlPullParser::getNamespacePrefix() const { - if (!mHasComment && (mEvent == XmlPullParser::Event::kStartNamespace || - mEvent == XmlPullParser::Event::kEndNamespace)) { - return mStr1; - } - return sEmpty; -} - -const std::u16string& BinaryXmlPullParser::getNamespaceUri() const { - if (!mHasComment && (mEvent == XmlPullParser::Event::kStartNamespace || - mEvent == XmlPullParser::Event::kEndNamespace)) { - return mStr2; - } - return sEmpty; -} - -bool BinaryXmlPullParser::applyPackageAlias(std::u16string* package, - const std::u16string& defaultPackage) const { - const auto endIter = mPackageAliases.rend(); - for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) { - if (iter->first == *package) { - if (iter->second.empty()) { - *package = defaultPackage; - } else { - *package = iter->second; - } - return true; - } - } - return false; -} - -const std::u16string& BinaryXmlPullParser::getElementNamespace() const { - if (!mHasComment && (mEvent == XmlPullParser::Event::kStartElement || - mEvent == XmlPullParser::Event::kEndElement)) { - return mStr1; - } - return sEmpty; -} - -const std::u16string& BinaryXmlPullParser::getElementName() const { - if (!mHasComment && (mEvent == XmlPullParser::Event::kStartElement || - mEvent == XmlPullParser::Event::kEndElement)) { - return mStr2; - } - return sEmpty; -} - -size_t BinaryXmlPullParser::getAttributeCount() const { - return mAttributes.size(); -} - -XmlPullParser::const_iterator BinaryXmlPullParser::beginAttributes() const { - return mAttributes.begin(); -} - -XmlPullParser::const_iterator BinaryXmlPullParser::endAttributes() const { - return mAttributes.end(); -} - -void BinaryXmlPullParser::copyAttributes() { - const size_t attrCount = mParser->getAttributeCount(); - if (attrCount > 0) { - mAttributes.reserve(attrCount); - for (size_t i = 0; i < attrCount; i++) { - XmlPullParser::Attribute attr; - size_t len; - const char16_t* str = mParser->getAttributeNamespace(i, &len); - if (str) { - attr.namespaceUri.assign(str, len); - } - str = mParser->getAttributeName(i, &len); - if (str) { - attr.name.assign(str, len); - } - str = mParser->getAttributeStringValue(i, &len); - if (str) { - attr.value.assign(str, len); - } - mAttributes.push_back(std::move(attr)); - } - } -} - -} // namespace aapt diff --git a/tools/aapt2/BinaryXmlPullParser.h b/tools/aapt2/BinaryXmlPullParser.h deleted file mode 100644 index 16fc8b7..0000000 --- a/tools/aapt2/BinaryXmlPullParser.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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. - */ - -#ifndef AAPT_BINARY_XML_PULL_PARSER_H -#define AAPT_BINARY_XML_PULL_PARSER_H - -#include "XmlPullParser.h" - -#include <androidfw/ResourceTypes.h> -#include <memory> -#include <string> -#include <vector> - -namespace aapt { - -/** - * Wraps a ResTable into the canonical XmlPullParser interface. - */ -class BinaryXmlPullParser : public XmlPullParser { -public: - BinaryXmlPullParser(const std::shared_ptr<android::ResXMLTree>& parser); - BinaryXmlPullParser(const BinaryXmlPullParser& rhs) = delete; - - Event getEvent() const override; - const std::string& getLastError() const override; - Event next() override; - - const std::u16string& getComment() const override; - size_t getLineNumber() const override; - size_t getDepth() const override; - - const std::u16string& getText() const override; - - const std::u16string& getNamespacePrefix() const override; - const std::u16string& getNamespaceUri() const override; - bool applyPackageAlias(std::u16string* package, const std::u16string& defaultpackage) - const override; - - const std::u16string& getElementNamespace() const override; - const std::u16string& getElementName() const override; - - const_iterator beginAttributes() const override; - const_iterator endAttributes() const override; - size_t getAttributeCount() const override; - -private: - void copyAttributes(); - - std::shared_ptr<android::ResXMLTree> mParser; - std::u16string mStr1; - std::u16string mStr2; - std::vector<Attribute> mAttributes; - Event mEvent; - bool mHasComment; - const std::u16string sEmpty; - const std::string sEmpty8; - size_t mDepth; - std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases; -}; - -} // namespace aapt - -#endif // AAPT_BINARY_XML_PULL_PARSER_H diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp index 91639c5..84957b4 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -17,7 +17,6 @@ #include "AppInfo.h" #include "BigBuffer.h" #include "BinaryResourceParser.h" -#include "BinaryXmlPullParser.h" #include "BindingXmlPullParser.h" #include "Debug.h" #include "Files.h" @@ -128,7 +127,7 @@ void versionStylesForCompat(const std::shared_ptr<ResourceTable>& table) { auto iter = style.entries.begin(); while (iter != style.entries.end()) { if (iter->key.name.package == u"android") { - size_t sdkLevel = findAttributeSdkLevel(iter->key.name.entry); + size_t sdkLevel = findAttributeSdkLevel(iter->key.name); if (sdkLevel > 1 && sdkLevel > configValue.config.sdkVersion) { // Record that we are about to strip this. stripped.emplace_back(std::move(*iter)); @@ -300,6 +299,42 @@ struct AaptOptions { ResourceName dumpStyleTarget; }; +struct IdCollector : public xml::Visitor { + IdCollector(const Source& source, const std::shared_ptr<ResourceTable>& table) : + mSource(source), mTable(table) { + } + + virtual void visit(xml::Text* node) override {} + + virtual void visit(xml::Namespace* node) override { + for (const auto& child : node->children) { + child->accept(this); + } + } + + virtual void visit(xml::Element* node) override { + for (const xml::Attribute& attr : node->attributes) { + bool create = false; + bool priv = false; + ResourceNameRef nameRef; + if (ResourceParser::tryParseReference(attr.value, &nameRef, &create, &priv)) { + if (create) { + mTable->addResource(nameRef, {}, mSource.line(node->lineNumber), + util::make_unique<Id>()); + } + } + } + + for (const auto& child : node->children) { + child->accept(this); + } + } + +private: + Source mSource; + std::shared_ptr<ResourceTable> mTable; +}; + bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table, const CompileItem& item, ZipFile* outApk) { std::ifstream in(item.source.path, std::ifstream::binary); @@ -308,20 +343,19 @@ bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable> return false; } - BigBuffer outBuffer(1024); - - // No resolver, since we are not compiling attributes here. - XmlFlattener flattener(table, {}); - - XmlFlattener::Options xmlOptions; - xmlOptions.defaultPackage = table->getPackage(); - xmlOptions.keepRawValues = true; + SourceLogger logger(item.source); + std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger); + if (!root) { + return false; + } - std::shared_ptr<XmlPullParser> parser = std::make_shared<SourceXmlPullParser>(in); + // Collect any resource ID's declared here. + IdCollector idCollector(item.source, table); + root->accept(&idCollector); - Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer, - xmlOptions); - if (!minStrippedSdk) { + BigBuffer outBuffer(1024); + if (!xml::flatten(root.get(), options.appInfo.package, &outBuffer)) { + logger.error() << "failed to encode XML." << std::endl; return false; } @@ -369,19 +403,13 @@ bool shouldGenerateVersionedResource(const std::shared_ptr<const ResourceTable>& bool linkXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table, const std::shared_ptr<IResolver>& resolver, const LinkItem& item, const void* data, size_t dataLen, ZipFile* outApk, std::queue<LinkItem>* outQueue) { - std::shared_ptr<android::ResXMLTree> tree = std::make_shared<android::ResXMLTree>(); - if (tree->setTo(data, dataLen, false) != android::NO_ERROR) { + SourceLogger logger(item.source); + std::unique_ptr<xml::Node> root = xml::inflate(data, dataLen, &logger); + if (!root) { return false; } - std::shared_ptr<XmlPullParser> parser = std::make_shared<BinaryXmlPullParser>(tree); - - BigBuffer outBuffer(1024); - XmlFlattener flattener({}, resolver); - - XmlFlattener::Options xmlOptions; - xmlOptions.defaultPackage = item.originalPackage; - + xml::FlattenOptions xmlOptions; if (options.packageType == AaptOptions::PackageType::StaticLibrary) { xmlOptions.keepRawValues = true; } @@ -392,16 +420,12 @@ bool linkXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& t xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1; } - std::shared_ptr<BindingXmlPullParser> binding; - if (item.name.type == ResourceType::kLayout) { - // Layouts may have defined bindings, so we need to make sure they get processed. - binding = std::make_shared<BindingXmlPullParser>(parser); - parser = binding; - } - - Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer, - xmlOptions); + BigBuffer outBuffer(1024); + Maybe<size_t> minStrippedSdk = xml::flattenAndLink(item.source, root.get(), + item.originalPackage, resolver, + xmlOptions, &outBuffer); if (!minStrippedSdk) { + logger.error() << "failed to encode XML." << std::endl; return false; } @@ -431,30 +455,6 @@ bool linkXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& t << buildFileReference(item) << "' to apk." << std::endl; return false; } - - if (binding && !options.bindingOutput.path.empty()) { - // We generated a binding xml file, write it out. - Source bindingOutput = options.bindingOutput; - appendPath(&bindingOutput.path, buildFileReference(item)); - - if (!mkdirs(bindingOutput.path)) { - Logger::error(bindingOutput) << strerror(errno) << std::endl; - return false; - } - - appendPath(&bindingOutput.path, "bind.xml"); - - std::ofstream bout(bindingOutput.path); - if (!bout) { - Logger::error(bindingOutput) << strerror(errno) << std::endl; - return false; - } - - if (!binding->writeToFile(bout)) { - Logger::error(bindingOutput) << strerror(errno) << std::endl; - return false; - } - } return true; } @@ -504,13 +504,15 @@ bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver return false; } - BigBuffer outBuffer(1024); - std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in); - XmlFlattener flattener({}, resolver); + SourceLogger logger(options.manifest); + std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger); + if (!root) { + return false; + } - XmlFlattener::Options xmlOptions; - xmlOptions.defaultPackage = options.appInfo.package; - if (!flattener.flatten(options.manifest, xmlParser, &outBuffer, xmlOptions)) { + BigBuffer outBuffer(1024); + if (!xml::flattenAndLink(options.manifest, root.get(), options.appInfo.package, resolver, {}, + &outBuffer)) { return false; } diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp index 3f156a6..9bdae49 100644 --- a/tools/aapt2/SdkConstants.cpp +++ b/tools/aapt2/SdkConstants.cpp @@ -14,11 +14,51 @@ * limitations under the License. */ +#include "SdkConstants.h" + +#include <algorithm> #include <string> #include <unordered_map> +#include <vector> namespace aapt { +static const std::vector<std::pair<uint16_t, size_t>> sAttrIdMap = { + { 0x021c, 1 }, + { 0x021d, 2 }, + { 0x0269, SDK_CUPCAKE }, + { 0x028d, SDK_DONUT }, + { 0x02ad, SDK_ECLAIR }, + { 0x02b3, SDK_ECLAIR_0_1 }, + { 0x02b5, SDK_ECLAIR_MR1 }, + { 0x02bd, SDK_FROYO }, + { 0x02cb, SDK_GINGERBREAD }, + { 0x0361, SDK_HONEYCOMB }, + { 0x0366, SDK_HONEYCOMB_MR1 }, + { 0x03a6, SDK_HONEYCOMB_MR2 }, + { 0x03ae, SDK_JELLY_BEAN }, + { 0x03cc, SDK_JELLY_BEAN_MR1 }, + { 0x03da, SDK_JELLY_BEAN_MR2 }, + { 0x03f1, SDK_KITKAT }, + { 0x03f6, SDK_KITKAT_WATCH }, + { 0x04ce, SDK_LOLLIPOP }, +}; + +static bool lessEntryId(const std::pair<uint16_t, size_t>& p, uint16_t entryId) { + return p.first < entryId; +} + +size_t findAttributeSdkLevel(ResourceId id) { + if (id.packageId() != 0x01 && id.typeId() != 0x01) { + return 0; + } + auto iter = std::lower_bound(sAttrIdMap.begin(), sAttrIdMap.end(), id.entryId(), lessEntryId); + if (iter == sAttrIdMap.end()) { + return SDK_LOLLIPOP_MR1; + } + return iter->second; +} + static const std::unordered_map<std::u16string, size_t> sAttrMap = { { u"marqueeRepeatLimit", 2 }, { u"windowNoDisplay", 3 }, @@ -682,12 +722,16 @@ static const std::unordered_map<std::u16string, size_t> sAttrMap = { { u"colorEdgeEffect", 21 } }; -size_t findAttributeSdkLevel(const std::u16string& name) { - auto iter = sAttrMap.find(name); +size_t findAttributeSdkLevel(const ResourceName& name) { + if (name.package != u"android" && name.type != ResourceType::kAttr) { + return 0; + } + + auto iter = sAttrMap.find(name.entry); if (iter != sAttrMap.end()) { return iter->second; } - return 0; + return SDK_LOLLIPOP_MR1; } } // namespace aapt diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h index 469c355..803da03 100644 --- a/tools/aapt2/SdkConstants.h +++ b/tools/aapt2/SdkConstants.h @@ -19,8 +19,6 @@ #include "Resource.h" -#include <string> - namespace aapt { enum { @@ -46,7 +44,8 @@ enum { SDK_LOLLIPOP_MR1 = 22, }; -size_t findAttributeSdkLevel(const std::u16string& name); +size_t findAttributeSdkLevel(ResourceId id); +size_t findAttributeSdkLevel(const ResourceName& name); } // namespace aapt diff --git a/tools/aapt2/XmlDom.cpp b/tools/aapt2/XmlDom.cpp new file mode 100644 index 0000000..763029f --- /dev/null +++ b/tools/aapt2/XmlDom.cpp @@ -0,0 +1,431 @@ +/* + * 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 "Logger.h" +#include "Util.h" +#include "XmlDom.h" +#include "XmlPullParser.h" + +#include <cassert> +#include <memory> +#include <stack> +#include <string> +#include <tuple> + +namespace aapt { +namespace xml { + +constexpr char kXmlNamespaceSep = 1; + +struct Stack { + std::unique_ptr<xml::Node> root; + std::stack<xml::Node*> nodeStack; + std::u16string pendingComment; +}; + +/** + * Extracts the namespace and name of an expanded element or attribute name. + */ +static void splitName(const char* name, std::u16string* outNs, std::u16string* outName) { + const char* p = name; + while (*p != 0 && *p != kXmlNamespaceSep) { + p++; + } + + if (*p == 0) { + outNs->clear(); + *outName = util::utf8ToUtf16(name); + } else { + *outNs = util::utf8ToUtf16(StringPiece(name, (p - name))); + *outName = util::utf8ToUtf16(p + 1); + } +} + +static void addToStack(Stack* stack, XML_Parser parser, std::unique_ptr<Node> node) { + node->lineNumber = XML_GetCurrentLineNumber(parser); + node->columnNumber = XML_GetCurrentColumnNumber(parser); + + Node* thisNode = node.get(); + if (!stack->nodeStack.empty()) { + stack->nodeStack.top()->addChild(std::move(node)); + } else { + stack->root = std::move(node); + } + + if (thisNode->type != NodeType::kText) { + stack->nodeStack.push(thisNode); + } +} + +static void XMLCALL startNamespaceHandler(void* userData, const char* prefix, const char* uri) { + XML_Parser parser = reinterpret_cast<XML_Parser>(userData); + Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); + + std::unique_ptr<Namespace> ns = util::make_unique<Namespace>(); + if (prefix) { + ns->namespacePrefix = util::utf8ToUtf16(prefix); + } + + if (uri) { + ns->namespaceUri = util::utf8ToUtf16(uri); + } + + addToStack(stack, parser, std::move(ns)); +} + +static void XMLCALL endNamespaceHandler(void* userData, const char* prefix) { + XML_Parser parser = reinterpret_cast<XML_Parser>(userData); + Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); + + assert(!stack->nodeStack.empty()); + stack->nodeStack.pop(); +} + +static bool lessAttribute(const Attribute& lhs, const Attribute& rhs) { + return std::tie(lhs.namespaceUri, lhs.name, lhs.value) < + std::tie(rhs.namespaceUri, rhs.name, rhs.value); +} + +static void XMLCALL startElementHandler(void* userData, const char* name, const char** attrs) { + XML_Parser parser = reinterpret_cast<XML_Parser>(userData); + Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); + + std::unique_ptr<Element> el = util::make_unique<Element>(); + splitName(name, &el->namespaceUri, &el->name); + + while (*attrs) { + Attribute attribute; + splitName(*attrs++, &attribute.namespaceUri, &attribute.name); + attribute.value = util::utf8ToUtf16(*attrs++); + + // Insert in sorted order. + auto iter = std::lower_bound(el->attributes.begin(), el->attributes.end(), attribute, + lessAttribute); + el->attributes.insert(iter, std::move(attribute)); + } + + el->comment = std::move(stack->pendingComment); + addToStack(stack, parser, std::move(el)); +} + +static void XMLCALL endElementHandler(void* userData, const char* name) { + XML_Parser parser = reinterpret_cast<XML_Parser>(userData); + Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); + + assert(!stack->nodeStack.empty()); + stack->nodeStack.top()->comment = std::move(stack->pendingComment); + stack->nodeStack.pop(); +} + +static void XMLCALL characterDataHandler(void* userData, const char* s, int len) { + XML_Parser parser = reinterpret_cast<XML_Parser>(userData); + Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); + + if (!s || len <= 0) { + return; + } + + // See if we can just append the text to a previous text node. + if (!stack->nodeStack.empty()) { + Node* currentParent = stack->nodeStack.top(); + if (!currentParent->children.empty()) { + Node* lastChild = currentParent->children.back().get(); + if (lastChild->type == NodeType::kText) { + Text* text = static_cast<Text*>(lastChild); + text->text += util::utf8ToUtf16(StringPiece(s, len)); + return; + } + } + } + + std::unique_ptr<Text> text = util::make_unique<Text>(); + text->text = util::utf8ToUtf16(StringPiece(s, len)); + addToStack(stack, parser, std::move(text)); +} + +static void XMLCALL commentDataHandler(void* userData, const char* comment) { + XML_Parser parser = reinterpret_cast<XML_Parser>(userData); + Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); + + if (!stack->pendingComment.empty()) { + stack->pendingComment += '\n'; + } + stack->pendingComment += util::utf8ToUtf16(comment); +} + +std::unique_ptr<Node> inflate(std::istream* in, SourceLogger* logger) { + Stack stack; + + XML_Parser parser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep); + XML_SetUserData(parser, &stack); + XML_UseParserAsHandlerArg(parser); + XML_SetElementHandler(parser, startElementHandler, endElementHandler); + XML_SetNamespaceDeclHandler(parser, startNamespaceHandler, endNamespaceHandler); + XML_SetCharacterDataHandler(parser, characterDataHandler); + XML_SetCommentHandler(parser, commentDataHandler); + + char buffer[1024]; + while (!in->eof()) { + in->read(buffer, sizeof(buffer) / sizeof(buffer[0])); + if (in->bad() && !in->eof()) { + stack.root = {}; + logger->error() << strerror(errno) << std::endl; + break; + } + + if (XML_Parse(parser, buffer, in->gcount(), in->eof()) == XML_STATUS_ERROR) { + stack.root = {}; + logger->error(XML_GetCurrentLineNumber(parser)) + << XML_ErrorString(XML_GetErrorCode(parser)) << std::endl; + break; + } + } + + XML_ParserFree(parser); + return std::move(stack.root); +} + +static void copyAttributes(Element* el, android::ResXMLParser* parser) { + const size_t attrCount = parser->getAttributeCount(); + if (attrCount > 0) { + el->attributes.reserve(attrCount); + for (size_t i = 0; i < attrCount; i++) { + Attribute attr; + size_t len; + const char16_t* str16 = parser->getAttributeNamespace(i, &len); + if (str16) { + attr.namespaceUri.assign(str16, len); + } + + str16 = parser->getAttributeName(i, &len); + if (str16) { + attr.name.assign(str16, len); + } + + str16 = parser->getAttributeStringValue(i, &len); + if (str16) { + attr.value.assign(str16, len); + } + el->attributes.push_back(std::move(attr)); + } + } +} + +std::unique_ptr<Node> inflate(const void* data, size_t dataLen, SourceLogger* logger) { + std::unique_ptr<Node> root; + std::stack<Node*> nodeStack; + + android::ResXMLTree tree; + if (tree.setTo(data, dataLen) != android::NO_ERROR) { + return {}; + } + + android::ResXMLParser::event_code_t code; + while ((code = tree.next()) != android::ResXMLParser::BAD_DOCUMENT && + code != android::ResXMLParser::END_DOCUMENT) { + std::unique_ptr<Node> newNode; + switch (code) { + case android::ResXMLParser::START_NAMESPACE: { + std::unique_ptr<Namespace> node = util::make_unique<Namespace>(); + size_t len; + const char16_t* str16 = tree.getNamespacePrefix(&len); + if (str16) { + node->namespacePrefix.assign(str16, len); + } + + str16 = tree.getNamespaceUri(&len); + if (str16) { + node->namespaceUri.assign(str16, len); + } + newNode = std::move(node); + break; + } + + case android::ResXMLParser::START_TAG: { + std::unique_ptr<Element> node = util::make_unique<Element>(); + size_t len; + const char16_t* str16 = tree.getElementNamespace(&len); + if (str16) { + node->namespaceUri.assign(str16, len); + } + + str16 = tree.getElementName(&len); + if (str16) { + node->name.assign(str16, len); + } + + copyAttributes(node.get(), &tree); + + newNode = std::move(node); + break; + } + + case android::ResXMLParser::TEXT: { + std::unique_ptr<Text> node = util::make_unique<Text>(); + size_t len; + const char16_t* str16 = tree.getText(&len); + if (str16) { + node->text.assign(str16, len); + } + newNode = std::move(node); + break; + } + + case android::ResXMLParser::END_NAMESPACE: + case android::ResXMLParser::END_TAG: + assert(!nodeStack.empty()); + nodeStack.pop(); + break; + + default: + assert(false); + break; + } + + if (newNode) { + newNode->lineNumber = tree.getLineNumber(); + + Node* thisNode = newNode.get(); + if (!root) { + assert(nodeStack.empty()); + root = std::move(newNode); + } else { + assert(!nodeStack.empty()); + nodeStack.top()->addChild(std::move(newNode)); + } + + if (thisNode->type != NodeType::kText) { + nodeStack.push(thisNode); + } + } + } + return std::move(root); +} + +Node::Node(NodeType type) : type(type), parent(nullptr), lineNumber(0), columnNumber(0) { +} + +void Node::addChild(std::unique_ptr<Node> child) { + child->parent = this; + children.push_back(std::move(child)); +} + +Namespace::Namespace() : BaseNode(NodeType::kNamespace) { +} + +std::unique_ptr<Node> Namespace::clone() const { + Namespace* ns = new Namespace(); + ns->lineNumber = lineNumber; + ns->columnNumber = columnNumber; + ns->comment = comment; + ns->namespacePrefix = namespacePrefix; + ns->namespaceUri = namespaceUri; + for (auto& child : children) { + ns->addChild(child->clone()); + } + return std::unique_ptr<Node>(ns); +} + +Element::Element() : BaseNode(NodeType::kElement) { +} + +std::unique_ptr<Node> Element::clone() const { + Element* el = new Element(); + el->lineNumber = lineNumber; + el->columnNumber = columnNumber; + el->comment = comment; + el->namespaceUri = namespaceUri; + el->name = name; + el->attributes = attributes; + for (auto& child : children) { + el->addChild(child->clone()); + } + return std::unique_ptr<Node>(el); +} + +Attribute* Element::findAttribute(const StringPiece16& ns, const StringPiece16& name) { + for (auto& attr : attributes) { + if (ns == attr.namespaceUri && name == attr.name) { + return &attr; + } + } + return nullptr; +} + +Element* Element::findChild(const StringPiece16& ns, const StringPiece16& name) { + return findChildWithAttribute(ns, name, nullptr); +} + +Element* Element::findChildWithAttribute(const StringPiece16& ns, const StringPiece16& name, + const Attribute* reqAttr) { + for (auto& childNode : children) { + Node* child = childNode.get(); + while (child->type == NodeType::kNamespace) { + if (child->children.empty()) { + break; + } + child = child->children[0].get(); + } + + if (child->type == NodeType::kElement) { + Element* el = static_cast<Element*>(child); + if (ns == el->namespaceUri && name == el->name) { + if (!reqAttr) { + return el; + } + + Attribute* attrName = el->findAttribute(reqAttr->namespaceUri, reqAttr->name); + if (attrName && attrName->value == reqAttr->value) { + return el; + } + } + } + } + return nullptr; +} + +std::vector<Element*> Element::getChildElements() { + std::vector<Element*> elements; + for (auto& childNode : children) { + Node* child = childNode.get(); + while (child->type == NodeType::kNamespace) { + if (child->children.empty()) { + break; + } + child = child->children[0].get(); + } + + if (child->type == NodeType::kElement) { + elements.push_back(static_cast<Element*>(child)); + } + } + return elements; +} + +Text::Text() : BaseNode(NodeType::kText) { +} + +std::unique_ptr<Node> Text::clone() const { + Text* el = new Text(); + el->lineNumber = lineNumber; + el->columnNumber = columnNumber; + el->comment = comment; + el->text = text; + return std::unique_ptr<Node>(el); +} + +} // namespace xml +} // namespace aapt diff --git a/tools/aapt2/XmlDom.h b/tools/aapt2/XmlDom.h new file mode 100644 index 0000000..6931884 --- /dev/null +++ b/tools/aapt2/XmlDom.h @@ -0,0 +1,154 @@ +/* + * 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. + */ + +#ifndef AAPT_XML_DOM_H +#define AAPT_XML_DOM_H + +#include "Logger.h" +#include "StringPiece.h" + +#include <istream> +#include <libexpat/expat.h> +#include <memory> +#include <string> +#include <vector> + +namespace aapt { +namespace xml { + +struct Visitor; + +/** + * The type of node. Can be used to downcast to the concrete XML node + * class. + */ +enum class NodeType { + kNamespace, + kElement, + kText, +}; + +/** + * Base class for all XML nodes. + */ +struct Node { + NodeType type; + Node* parent; + size_t lineNumber; + size_t columnNumber; + std::u16string comment; + std::vector<std::unique_ptr<Node>> children; + + Node(NodeType type); + void addChild(std::unique_ptr<Node> child); + virtual std::unique_ptr<Node> clone() const = 0; + virtual void accept(Visitor* visitor) = 0; + virtual ~Node() {} +}; + +/** + * Base class that implements the visitor methods for a + * subclass of Node. + */ +template <typename Derived> +struct BaseNode : public Node { + BaseNode(NodeType t); + virtual void accept(Visitor* visitor) override; +}; + +/** + * A Namespace XML node. Can only have one child. + */ +struct Namespace : public BaseNode<Namespace> { + std::u16string namespacePrefix; + std::u16string namespaceUri; + + Namespace(); + virtual std::unique_ptr<Node> clone() const override; +}; + +/** + * An XML attribute. + */ +struct Attribute { + std::u16string namespaceUri; + std::u16string name; + std::u16string value; +}; + +/** + * An Element XML node. + */ +struct Element : public BaseNode<Element> { + std::u16string namespaceUri; + std::u16string name; + std::vector<Attribute> attributes; + + Element(); + virtual std::unique_ptr<Node> clone() const override; + Attribute* findAttribute(const StringPiece16& ns, const StringPiece16& name); + xml::Element* findChild(const StringPiece16& ns, const StringPiece16& name); + xml::Element* findChildWithAttribute(const StringPiece16& ns, const StringPiece16& name, + const xml::Attribute* reqAttr); + std::vector<xml::Element*> getChildElements(); +}; + +/** + * A Text (CDATA) XML node. Can not have any children. + */ +struct Text : public BaseNode<Text> { + std::u16string text; + + Text(); + virtual std::unique_ptr<Node> clone() const override; +}; + +/** + * Inflates an XML DOM from a text stream, logging errors to the logger. + * Returns the root node on success, or nullptr on failure. + */ +std::unique_ptr<Node> inflate(std::istream* in, SourceLogger* logger); + +/** + * Inflates an XML DOM from a binary ResXMLTree, logging errors to the logger. + * Returns the root node on success, or nullptr on failure. + */ +std::unique_ptr<Node> inflate(const void* data, size_t dataLen, SourceLogger* logger); + +/** + * A visitor interface for the different XML Node subtypes. + */ +struct Visitor { + virtual void visit(Namespace* node) = 0; + virtual void visit(Element* node) = 0; + virtual void visit(Text* text) = 0; +}; + +// Implementations + +template <typename Derived> +BaseNode<Derived>::BaseNode(NodeType type) : Node(type) { +} + +template <typename Derived> +void BaseNode<Derived>::accept(Visitor* visitor) { + visitor->visit(static_cast<Derived*>(this)); +} + +} // namespace xml +} // namespace aapt + +#endif // AAPT_XML_DOM_H diff --git a/tools/aapt2/XmlDom_test.cpp b/tools/aapt2/XmlDom_test.cpp new file mode 100644 index 0000000..0217144 --- /dev/null +++ b/tools/aapt2/XmlDom_test.cpp @@ -0,0 +1,49 @@ +/* + * 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 "XmlDom.h" + +#include <gtest/gtest.h> +#include <sstream> +#include <string> + +namespace aapt { + +constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; + +TEST(XmlDomTest, Inflate) { + std::stringstream in(kXmlPreamble); + in << R"EOF( + <Layout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <TextView android:id="@+id/id" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + </Layout> + )EOF"; + + SourceLogger logger(Source{ "/test/path" }); + std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger); + ASSERT_NE(root, nullptr); + + EXPECT_EQ(root->type, xml::NodeType::kNamespace); + xml::Namespace* ns = static_cast<xml::Namespace*>(root.get()); + EXPECT_EQ(ns->namespaceUri, u"http://schemas.android.com/apk/res/android"); + EXPECT_EQ(ns->namespacePrefix, u"android"); +} + +} // namespace aapt diff --git a/tools/aapt2/XmlFlattener.cpp b/tools/aapt2/XmlFlattener.cpp index f78e38d..56b5613 100644 --- a/tools/aapt2/XmlFlattener.cpp +++ b/tools/aapt2/XmlFlattener.cpp @@ -34,425 +34,444 @@ #include <vector> namespace aapt { +namespace xml { -constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android"; - -struct AttributeValueFlattener : ValueVisitor { - AttributeValueFlattener( - std::shared_ptr<IResolver> resolver, SourceLogger* logger, - android::Res_value* outValue, std::shared_ptr<XmlPullParser> parser, bool* outError, - StringPool::Ref rawValue, std::u16string* defaultPackage, - std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>* outStringRefs) : - mResolver(resolver), mLogger(logger), mOutValue(outValue), mParser(parser), - mError(outError), mRawValue(rawValue), mDefaultPackage(defaultPackage), - mStringRefs(outStringRefs) { +constexpr uint32_t kLowPriority = 0xffffffffu; + +// A vector that maps String refs to their final destination in the out buffer. +using FlatStringRefList = std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>; + +struct XmlFlattener : public Visitor { + XmlFlattener(BigBuffer* outBuffer, StringPool* pool, FlatStringRefList* stringRefs, + const std::u16string& defaultPackage) : + mOut(outBuffer), mPool(pool), mStringRefs(stringRefs), + mDefaultPackage(defaultPackage) { } - void visit(Reference& reference, ValueVisitorArgs&) override { - // First see if we can convert the package name from a prefix to a real - // package name. - ResourceName aliasedName = reference.name; + // No copying. + XmlFlattener(const XmlFlattener&) = delete; + XmlFlattener& operator=(const XmlFlattener&) = delete; + + void writeNamespace(Namespace* node, uint16_t type) { + const size_t startIndex = mOut->size(); + android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>(); + android::ResXMLTree_namespaceExt* flatNs = + mOut->nextBlock<android::ResXMLTree_namespaceExt>(); + mOut->align4(); + + flatNode->header = { type, sizeof(*flatNode), (uint32_t)(mOut->size() - startIndex) }; + flatNode->lineNumber = node->lineNumber; + flatNode->comment.index = -1; + addString(node->namespacePrefix, kLowPriority, &flatNs->prefix); + addString(node->namespaceUri, kLowPriority, &flatNs->uri); + } - if (!reference.name.package.empty()) { - // Only if we specified a package do we look for its alias. - mParser->applyPackageAlias(&reference.name.package, *mDefaultPackage); - } else { - reference.name.package = *mDefaultPackage; + virtual void visit(Namespace* node) override { + // Extract the package/prefix from this namespace node. + Maybe<std::u16string> package = util::extractPackageFromNamespace(node->namespaceUri); + if (package) { + mPackageAliases.emplace_back( + node->namespacePrefix, + package.value().empty() ? mDefaultPackage : package.value()); } - Maybe<ResourceId> result = mResolver->findId(reference.name); - if (!result || !result.value().isValid()) { - std::ostream& out = mLogger->error(mParser->getLineNumber()) - << "unresolved reference '" - << aliasedName - << "'"; - if (aliasedName != reference.name) { - out << " (aka '" << reference.name << "')"; - } - out << "'." << std::endl; - *mError = true; - } else { - reference.id = result.value(); - reference.flatten(*mOutValue); + writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE); + for (const auto& child : node->children) { + child->accept(this); } - } + writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE); - void visit(String& string, ValueVisitorArgs&) override { - mOutValue->dataType = android::Res_value::TYPE_STRING; - mStringRefs->emplace_back( - mRawValue, - reinterpret_cast<android::ResStringPool_ref*>(mOutValue->data)); + if (package) { + mPackageAliases.pop_back(); + } } - void visitItem(Item& item, ValueVisitorArgs&) override { - item.flatten(*mOutValue); + virtual void visit(Text* node) override { + if (util::trimWhitespace(node->text).empty()) { + return; + } + + const size_t startIndex = mOut->size(); + android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>(); + android::ResXMLTree_cdataExt* flatText = mOut->nextBlock<android::ResXMLTree_cdataExt>(); + mOut->align4(); + + const uint16_t type = android::RES_XML_CDATA_TYPE; + flatNode->header = { type, sizeof(*flatNode), (uint32_t)(mOut->size() - startIndex) }; + flatNode->lineNumber = node->lineNumber; + flatNode->comment.index = -1; + addString(node->text, kLowPriority, &flatText->data); } -private: - std::shared_ptr<IResolver> mResolver; - SourceLogger* mLogger; - android::Res_value* mOutValue; - std::shared_ptr<XmlPullParser> mParser; - bool* mError; - StringPool::Ref mRawValue; - std::u16string* mDefaultPackage; - std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>* mStringRefs; -}; + virtual void visit(Element* node) override { + const size_t startIndex = mOut->size(); + android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>(); + android::ResXMLTree_attrExt* flatElem = mOut->nextBlock<android::ResXMLTree_attrExt>(); -struct XmlAttribute { - uint32_t resourceId; - const XmlPullParser::Attribute* xmlAttr; - const Attribute* attr; - StringPool::Ref nameRef; -}; + const uint16_t type = android::RES_XML_START_ELEMENT_TYPE; + flatNode->header = { type, sizeof(*flatNode), 0 }; + flatNode->lineNumber = node->lineNumber; + flatNode->comment.index = -1; -static bool lessAttributeId(const XmlAttribute& a, uint32_t id) { - return a.resourceId < id; -} + addString(node->namespaceUri, kLowPriority, &flatElem->ns); + addString(node->name, kLowPriority, &flatElem->name); + flatElem->attributeStart = sizeof(*flatElem); + flatElem->attributeSize = sizeof(android::ResXMLTree_attribute); + flatElem->attributeCount = node->attributes.size(); -XmlFlattener::XmlFlattener(const std::shared_ptr<ResourceTable>& table, - const std::shared_ptr<IResolver>& resolver) : - mTable(table), mResolver(resolver) { -} + if (!writeAttributes(mOut, node, flatElem)) { + mError = true; + } -/** - * Reads events from the parser and writes to a BigBuffer. The binary XML file - * expects the StringPool to appear first, but we haven't collected the strings yet. We - * write to a temporary BigBuffer while parsing the input, adding strings we encounter - * to the StringPool. At the end, we write the StringPool to the given BigBuffer and - * then move the data from the temporary BigBuffer into the given one. This incurs no - * copies as the given BigBuffer simply takes ownership of the data. - */ -Maybe<size_t> XmlFlattener::flatten(const Source& source, - const std::shared_ptr<XmlPullParser>& parser, - BigBuffer* outBuffer, Options options) { - SourceLogger logger(source); - StringPool pool; - bool error = false; + mOut->align4(); + flatNode->header.size = (uint32_t)(mOut->size() - startIndex); - size_t smallestStrippedAttributeSdk = std::numeric_limits<size_t>::max(); + for (const auto& child : node->children) { + child->accept(this); + } - // Attribute names are stored without packages, but we use - // their StringPool index to lookup their resource IDs. - // This will cause collisions, so we can't dedupe - // attribute names from different packages. We use separate - // pools that we later combine. - std::map<std::u16string, StringPool> packagePools; + const size_t startEndIndex = mOut->size(); + android::ResXMLTree_node* flatEndNode = mOut->nextBlock<android::ResXMLTree_node>(); + android::ResXMLTree_endElementExt* flatEndElem = + mOut->nextBlock<android::ResXMLTree_endElementExt>(); + mOut->align4(); - // Attribute resource IDs are stored in the same order - // as the attribute names appear in the StringPool. - // Since the StringPool contains more than just attribute - // names, to maintain a tight packing of resource IDs, - // we must ensure that attribute names appear first - // in our StringPool. For this, we assign a low priority - // (0xffffffff) to non-attribute strings. Attribute - // names will be stored along with a priority equal - // to their resource ID so that they are ordered. - StringPool::Context lowPriority { 0xffffffffu }; + const uint16_t endType = android::RES_XML_END_ELEMENT_TYPE; + flatEndNode->header = { endType, sizeof(*flatEndNode), + (uint32_t)(mOut->size() - startEndIndex) }; + flatEndNode->lineNumber = node->lineNumber; + flatEndNode->comment.index = -1; - // Once we sort the StringPool, we can assign the updated indices - // to the correct data locations. - std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>> stringRefs; + addString(node->namespaceUri, kLowPriority, &flatEndElem->ns); + addString(node->name, kLowPriority, &flatEndElem->name); + } - // Since we don't know the size of the final StringPool, we write to this - // temporary BigBuffer, which we will append to outBuffer later. - BigBuffer out(1024); - while (XmlPullParser::isGoodEvent(parser->next())) { - XmlPullParser::Event event = parser->getEvent(); - switch (event) { - case XmlPullParser::Event::kStartNamespace: - case XmlPullParser::Event::kEndNamespace: { - const size_t startIndex = out.size(); - android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>(); - if (event == XmlPullParser::Event::kStartNamespace) { - node->header.type = android::RES_XML_START_NAMESPACE_TYPE; - } else { - node->header.type = android::RES_XML_END_NAMESPACE_TYPE; - } + bool success() const { + return !mError; + } - node->header.headerSize = sizeof(*node); - node->lineNumber = parser->getLineNumber(); - node->comment.index = -1; +protected: + void addString(const StringPiece16& str, uint32_t priority, android::ResStringPool_ref* dest) { + if (!str.empty()) { + mStringRefs->emplace_back(mPool->makeRef(str, StringPool::Context{ priority }), dest); + } else { + // The device doesn't think a string of size 0 is the same as null. + dest->index = -1; + } + } - android::ResXMLTree_namespaceExt* ns = - out.nextBlock<android::ResXMLTree_namespaceExt>(); - stringRefs.emplace_back( - pool.makeRef(parser->getNamespacePrefix(), lowPriority), &ns->prefix); - stringRefs.emplace_back( - pool.makeRef(parser->getNamespaceUri(), lowPriority), &ns->uri); + void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) { + mStringRefs->emplace_back(ref, dest); + } - out.align4(); - node->header.size = out.size() - startIndex; - break; + Maybe<std::u16string> getPackageAlias(const std::u16string& prefix) { + const auto endIter = mPackageAliases.rend(); + for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) { + if (iter->first == prefix) { + return iter->second; } + } + return {}; + } - case XmlPullParser::Event::kStartElement: { - const size_t startIndex = out.size(); - android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>(); - node->header.type = android::RES_XML_START_ELEMENT_TYPE; - node->header.headerSize = sizeof(*node); - node->lineNumber = parser->getLineNumber(); - node->comment.index = -1; - - android::ResXMLTree_attrExt* elem = out.nextBlock<android::ResXMLTree_attrExt>(); - if (!parser->getElementNamespace().empty()) { - stringRefs.emplace_back( - pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns); - } else { - // The device doesn't think a string of size 0 is the same as null. - elem->ns.index = -1; - } - stringRefs.emplace_back( - pool.makeRef(parser->getElementName(), lowPriority), &elem->name); - elem->attributeStart = sizeof(*elem); - elem->attributeSize = sizeof(android::ResXMLTree_attribute); - - // The resource system expects attributes to be sorted by resource ID. - std::vector<XmlAttribute> sortedAttributes; - uint32_t nextAttributeId = 0; - const auto endAttrIter = parser->endAttributes(); - for (auto attrIter = parser->beginAttributes(); - attrIter != endAttrIter; - ++attrIter) { - uint32_t id; - StringPool::Ref nameRef; - const Attribute* attr = nullptr; - - if (options.maxSdkAttribute && attrIter->namespaceUri == kSchemaAndroid) { - size_t sdkVersion = findAttributeSdkLevel(attrIter->name); - if (sdkVersion > options.maxSdkAttribute.value()) { - // We will silently omit this attribute - smallestStrippedAttributeSdk = - std::min(smallestStrippedAttributeSdk, sdkVersion); - continue; - } - } - - ResourceNameRef genIdName; - bool create = false; - bool privateRef = false; - if (mTable && ResourceParser::tryParseReference(attrIter->value, &genIdName, - &create, &privateRef) && create) { - mTable->addResource(genIdName, {}, source.line(parser->getLineNumber()), - util::make_unique<Id>()); - } + const std::u16string& getDefaultPackage() const { + return mDefaultPackage; + } + /** + * Subclasses override this to deal with attributes. Attributes can be flattened as + * raw values or as resources. + */ + virtual bool writeAttributes(BigBuffer* out, Element* node, + android::ResXMLTree_attrExt* flatElem) = 0; - Maybe<std::u16string> package = util::extractPackageFromNamespace( - attrIter->namespaceUri); - if (!package || !mResolver) { - // Attributes that have no resource ID (because they don't belong to a - // package) should appear after those that do have resource IDs. Assign - // them some integer value that will appear after. - id = 0x80000000u | nextAttributeId++; - nameRef = pool.makeRef(attrIter->name, StringPool::Context{ id }); - - } else { - // Find the Attribute object via our Resolver. - ResourceName attrName = { - package.value(), ResourceType::kAttr, attrIter->name }; - - if (attrName.package.empty()) { - attrName.package = options.defaultPackage; - } - - Maybe<IResolver::Entry> result = mResolver->findAttribute(attrName); - if (!result || !result.value().id.isValid()) { - logger.error(parser->getLineNumber()) - << "unresolved attribute '" - << attrName - << "'." - << std::endl; - error = true; - continue; - } - - if (!result.value().attr) { - logger.error(parser->getLineNumber()) - << "not a valid attribute '" - << attrName - << "'." - << std::endl; - error = true; - continue; - } - - id = result.value().id.id; - attr = result.value().attr; - - // Put the attribute name into a package specific pool, since we don't - // want to collapse names from different packages. - nameRef = packagePools[package.value()].makeRef( - attrIter->name, StringPool::Context{ id }); - } +private: + BigBuffer* mOut; + StringPool* mPool; + FlatStringRefList* mStringRefs; + std::u16string mDefaultPackage; + bool mError = false; + std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases; +}; - // Insert the attribute into the sorted vector. - auto iter = std::lower_bound(sortedAttributes.begin(), sortedAttributes.end(), - id, lessAttributeId); - sortedAttributes.insert(iter, XmlAttribute{ id, &*attrIter, attr, nameRef }); - } +/** + * Flattens XML, encoding the attributes as raw strings. This is used in the compile phase. + */ +struct CompileXmlFlattener : public XmlFlattener { + CompileXmlFlattener(BigBuffer* outBuffer, StringPool* pool, FlatStringRefList* stringRefs, + const std::u16string& defaultPackage) : + XmlFlattener(outBuffer, pool, stringRefs, defaultPackage) { + } - if (error) { - break; - } + virtual bool writeAttributes(BigBuffer* out, Element* node, + android::ResXMLTree_attrExt* flatElem) override { + flatElem->attributeCount = node->attributes.size(); + if (node->attributes.empty()) { + return true; + } - // Now that we have filtered out some attributes, get the final count. - elem->attributeCount = sortedAttributes.size(); - - // Flatten the sorted attributes. - uint16_t attributeIndex = 1; - for (auto entry : sortedAttributes) { - android::ResXMLTree_attribute* attr = - out.nextBlock<android::ResXMLTree_attribute>(); - if (!entry.xmlAttr->namespaceUri.empty()) { - stringRefs.emplace_back( - pool.makeRef(entry.xmlAttr->namespaceUri, lowPriority), &attr->ns); - } else { - attr->ns.index = -1; - } + android::ResXMLTree_attribute* flatAttrs = out->nextBlock<android::ResXMLTree_attribute>( + node->attributes.size()); + for (const Attribute& attr : node->attributes) { + addString(attr.namespaceUri, kLowPriority, &flatAttrs->ns); + addString(attr.name, kLowPriority, &flatAttrs->name); + addString(attr.value, kLowPriority, &flatAttrs->rawValue); + flatAttrs++; + } + return true; + } +}; - StringPool::Ref rawValueRef = pool.makeRef(entry.xmlAttr->value, lowPriority); +struct AttributeToFlatten { + uint32_t resourceId = 0; + const Attribute* xmlAttr = nullptr; + const ::aapt::Attribute* resourceAttr = nullptr; +}; - stringRefs.emplace_back(entry.nameRef, &attr->name); +static bool lessAttributeId(const AttributeToFlatten& a, uint32_t id) { + return a.resourceId < id; +} - if (options.keepRawValues) { - stringRefs.emplace_back(rawValueRef, &attr->rawValue); - } else { - attr->rawValue.index = -1; - } +/** + * Flattens XML, encoding the attributes as resources. + */ +struct LinkedXmlFlattener : public XmlFlattener { + LinkedXmlFlattener(BigBuffer* outBuffer, StringPool* pool, + std::map<std::u16string, StringPool>* packagePools, + FlatStringRefList* stringRefs, + const std::u16string& defaultPackage, + const std::shared_ptr<IResolver>& resolver, + SourceLogger* logger, + const FlattenOptions& options) : + XmlFlattener(outBuffer, pool, stringRefs, defaultPackage), mResolver(resolver), + mLogger(logger), mPackagePools(packagePools), mOptions(options) { + } - // Assign the indices for specific attributes. - if (entry.xmlAttr->namespaceUri == kSchemaAndroid && - entry.xmlAttr->name == u"id") { - elem->idIndex = attributeIndex; - } else if (entry.xmlAttr->namespaceUri.empty()) { - if (entry.xmlAttr->name == u"class") { - elem->classIndex = attributeIndex; - } else if (entry.xmlAttr->name == u"style") { - elem->styleIndex = attributeIndex; - } - } - attributeIndex++; - - std::unique_ptr<Item> value; - if (entry.attr) { - value = ResourceParser::parseItemForAttribute(entry.xmlAttr->value, - *entry.attr); - } else { - bool create = false; - value = ResourceParser::tryParseReference(entry.xmlAttr->value, &create); - } + virtual bool writeAttributes(BigBuffer* out, Element* node, + android::ResXMLTree_attrExt* flatElem) override { + bool error = false; + std::vector<AttributeToFlatten> sortedAttributes; + uint32_t nextAttributeId = 0x80000000u; + + // Sort and filter attributes by their resource ID. + for (const Attribute& attr : node->attributes) { + AttributeToFlatten attrToFlatten; + attrToFlatten.xmlAttr = &attr; + + Maybe<std::u16string> package = util::extractPackageFromNamespace(attr.namespaceUri); + if (package) { + // Find the Attribute object via our Resolver. + ResourceName attrName = { package.value(), ResourceType::kAttr, attr.name }; + if (attrName.package.empty()) { + attrName.package = getDefaultPackage(); + } - if (mResolver && value) { - AttributeValueFlattener flattener( - mResolver, - &logger, - &attr->typedValue, - parser, - &error, - rawValueRef, - &options.defaultPackage, - &stringRefs); - value->accept(flattener, {}); - } else if (!value && entry.attr && - !(entry.attr->typeMask & android::ResTable_map::TYPE_STRING)) { - logger.error(parser->getLineNumber()) - << "'" - << *rawValueRef - << "' is not compatible with attribute " - << *entry.attr - << "." - << std::endl; - error = true; - } else { - attr->typedValue.dataType = android::Res_value::TYPE_STRING; - if (!options.keepRawValues) { - // Don't set the string twice. - stringRefs.emplace_back(rawValueRef, &attr->rawValue); - } - stringRefs.emplace_back(rawValueRef, - reinterpret_cast<android::ResStringPool_ref*>( - &attr->typedValue.data)); + Maybe<IResolver::Entry> result = mResolver->findAttribute(attrName); + if (!result || !result.value().id.isValid() || !result.value().attr) { + error = true; + mLogger->error(node->lineNumber) + << "unresolved attribute '" << attrName << "'." + << std::endl; + } else { + attrToFlatten.resourceId = result.value().id.id; + attrToFlatten.resourceAttr = result.value().attr; + + size_t sdk = findAttributeSdkLevel(attrToFlatten.resourceId); + if (mOptions.maxSdkAttribute && sdk > mOptions.maxSdkAttribute.value()) { + // We need to filter this attribute out. + mSmallestFilteredSdk = std::min(mSmallestFilteredSdk, sdk); + continue; } - attr->typedValue.size = sizeof(attr->typedValue); } - - out.align4(); - node->header.size = out.size() - startIndex; - break; } - case XmlPullParser::Event::kEndElement: { - const size_t startIndex = out.size(); - android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>(); - node->header.type = android::RES_XML_END_ELEMENT_TYPE; - node->header.headerSize = sizeof(*node); - node->lineNumber = parser->getLineNumber(); - node->comment.index = -1; - - android::ResXMLTree_endElementExt* elem = - out.nextBlock<android::ResXMLTree_endElementExt>(); - stringRefs.emplace_back( - pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns); - stringRefs.emplace_back( - pool.makeRef(parser->getElementName(), lowPriority), &elem->name); - - out.align4(); - node->header.size = out.size() - startIndex; - break; + if (attrToFlatten.resourceId == 0) { + // Attributes that have no resource ID (because they don't belong to a + // package) should appear after those that do have resource IDs. Assign + // them some integer value that will appear after. + attrToFlatten.resourceId = nextAttributeId++; } - case XmlPullParser::Event::kText: { - StringPiece16 text = util::trimWhitespace(parser->getText()); - if (text.empty()) { - break; + // Insert the attribute into the sorted vector. + auto iter = std::lower_bound(sortedAttributes.begin(), sortedAttributes.end(), + attrToFlatten.resourceId, lessAttributeId); + sortedAttributes.insert(iter, std::move(attrToFlatten)); + } + + flatElem->attributeCount = sortedAttributes.size(); + if (sortedAttributes.empty()) { + return true; + } + + android::ResXMLTree_attribute* flatAttr = out->nextBlock<android::ResXMLTree_attribute>( + sortedAttributes.size()); + + // Now that we have sorted the attributes into their final encoded order, it's time + // to actually write them out. + uint16_t attributeIndex = 1; + for (const AttributeToFlatten& attrToFlatten : sortedAttributes) { + Maybe<std::u16string> package = util::extractPackageFromNamespace( + attrToFlatten.xmlAttr->namespaceUri); + + // Assign the indices for specific attributes. + if (package && package.value() == u"android" && attrToFlatten.xmlAttr->name == u"id") { + flatElem->idIndex = attributeIndex; + } else if (attrToFlatten.xmlAttr->namespaceUri.empty()) { + if (attrToFlatten.xmlAttr->name == u"class") { + flatElem->classIndex = attributeIndex; + } else if (attrToFlatten.xmlAttr->name == u"style") { + flatElem->styleIndex = attributeIndex; + } + } + attributeIndex++; + + // Add the namespaceUri and name to the list of StringRefs to encode. + addString(attrToFlatten.xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns); + flatAttr->rawValue.index = -1; + + if (!attrToFlatten.resourceAttr) { + addString(attrToFlatten.xmlAttr->name, kLowPriority, &flatAttr->name); + } else { + // We've already extracted the package successfully before. + assert(package); + + // Attribute names are stored without packages, but we use + // their StringPool index to lookup their resource IDs. + // This will cause collisions, so we can't dedupe + // attribute names from different packages. We use separate + // pools that we later combine. + // + // Lookup the StringPool for this package and make the reference there. + StringPool::Ref nameRef = (*mPackagePools)[package.value()].makeRef( + attrToFlatten.xmlAttr->name, + StringPool::Context{ attrToFlatten.resourceId }); + + // Add it to the list of strings to flatten. + addString(nameRef, &flatAttr->name); + + if (mOptions.keepRawValues) { + // Keep raw values (this is for static libraries). + // TODO(with a smarter inflater for binary XML, we can do without this). + addString(attrToFlatten.xmlAttr->value, kLowPriority, &flatAttr->rawValue); } + } - const size_t startIndex = out.size(); - android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>(); - node->header.type = android::RES_XML_CDATA_TYPE; - node->header.headerSize = sizeof(*node); - node->lineNumber = parser->getLineNumber(); - node->comment.index = -1; + error |= !flattenItem(node, attrToFlatten.xmlAttr->value, attrToFlatten.resourceAttr, + flatAttr); + flatAttr->typedValue.size = sizeof(flatAttr->typedValue); + flatAttr++; + } + return !error; + } - android::ResXMLTree_cdataExt* elem = out.nextBlock<android::ResXMLTree_cdataExt>(); - stringRefs.emplace_back(pool.makeRef(text, lowPriority), &elem->data); + Maybe<size_t> getSmallestFilteredSdk() const { + if (mSmallestFilteredSdk == std::numeric_limits<size_t>::max()) { + return {}; + } + return mSmallestFilteredSdk; + } - out.align4(); - node->header.size = out.size() - startIndex; - break; +private: + bool flattenItem(const Node* el, const std::u16string& value, const ::aapt::Attribute* attr, + android::ResXMLTree_attribute* flatAttr) { + std::unique_ptr<Item> item; + if (!attr) { + bool create = false; + item = ResourceParser::tryParseReference(value, &create); + if (!item) { + flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING; + addString(value, kLowPriority, &flatAttr->rawValue); + addString(value, kLowPriority, reinterpret_cast<android::ResStringPool_ref*>( + &flatAttr->typedValue.data)); + return true; } + } else { + item = ResourceParser::parseItemForAttribute(value, *attr); + if (!item) { + if (!(attr->typeMask & android::ResTable_map::TYPE_STRING)) { + mLogger->error(el->lineNumber) + << "'" + << value + << "' is not compatible with attribute '" + << *attr + << "'." + << std::endl; + return false; + } - default: - break; + flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING; + addString(value, kLowPriority, &flatAttr->rawValue); + addString(value, kLowPriority, reinterpret_cast<android::ResStringPool_ref*>( + &flatAttr->typedValue.data)); + return true; + } } - } - out.align4(); + assert(item); - if (error) { - return {}; - } + bool error = false; - if (parser->getEvent() == XmlPullParser::Event::kBadDocument) { - logger.error(parser->getLineNumber()) - << parser->getLastError() - << std::endl; - return {}; - } + // If this is a reference, resolve the name into an ID. + visitFunc<Reference>(*item, [&](Reference& reference) { + // First see if we can convert the package name from a prefix to a real + // package name. + ResourceName realName = reference.name; + if (!realName.package.empty()) { + Maybe<std::u16string> package = getPackageAlias(realName.package); + if (package) { + realName.package = package.value(); + } + } else { + realName.package = getDefaultPackage(); + } - // Merge the package pools into the main pool. - for (auto& packagePoolEntry : packagePools) { - pool.merge(std::move(packagePoolEntry.second)); + Maybe<ResourceId> result = mResolver->findId(realName); + if (!result || !result.value().isValid()) { + std::ostream& out = mLogger->error(el->lineNumber) + << "unresolved reference '" + << reference.name + << "'"; + if (realName != reference.name) { + out << " (aka '" << realName << "')"; + } + out << "'." << std::endl; + error = true; + } else { + reference.id = result.value(); + } + }); + + if (error) { + return false; + } + + item->flatten(flatAttr->typedValue); + return true; } - // Sort so that attribute resource IDs show up first. - pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { + std::shared_ptr<IResolver> mResolver; + SourceLogger* mLogger; + std::map<std::u16string, StringPool>* mPackagePools; + FlattenOptions mOptions; + size_t mSmallestFilteredSdk = std::numeric_limits<size_t>::max(); +}; + +/** + * The binary XML file expects the StringPool to appear first, but we haven't collected the + * strings yet. We write to a temporary BigBuffer while parsing the input, adding strings + * we encounter to the StringPool. At the end, we write the StringPool to the given BigBuffer and + * then move the data from the temporary BigBuffer into the given one. This incurs no + * copies as the given BigBuffer simply takes ownership of the data. + */ +static void flattenXml(StringPool* pool, FlatStringRefList* stringRefs, BigBuffer* outBuffer, + BigBuffer&& xmlTreeBuffer) { + // Sort the string pool so that attribute resource IDs show up first. + pool->sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { return a.context.priority < b.context.priority; }); // Now we flatten the string pool references into the correct places. - for (const auto& refEntry : stringRefs) { + for (const auto& refEntry : *stringRefs) { refEntry.second->index = refEntry.first.getIndex(); } @@ -463,36 +482,93 @@ Maybe<size_t> XmlFlattener::flatten(const Source& source, header->header.headerSize = sizeof(*header); // Flatten the StringPool. - StringPool::flattenUtf16(outBuffer, pool); + StringPool::flattenUtf16(outBuffer, *pool); // Write the array of resource IDs, indexed by StringPool order. const size_t beforeResIdMapIndex = outBuffer->size(); android::ResChunk_header* resIdMapChunk = outBuffer->nextBlock<android::ResChunk_header>(); resIdMapChunk->type = android::RES_XML_RESOURCE_MAP_TYPE; resIdMapChunk->headerSize = sizeof(*resIdMapChunk); - for (const auto& str : pool) { + for (const auto& str : *pool) { ResourceId id { str->context.priority }; - if (!id.isValid()) { + if (id.id == kLowPriority || !id.isValid()) { // When we see the first non-resource ID, // we're done. break; } - uint32_t* flatId = outBuffer->nextBlock<uint32_t>(); - *flatId = id.id; + *outBuffer->nextBlock<uint32_t>() = id.id; } resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex; // Move the temporary BigBuffer into outBuffer. - outBuffer->appendBuffer(std::move(out)); - + outBuffer->appendBuffer(std::move(xmlTreeBuffer)); header->header.size = outBuffer->size() - beforeXmlTreeIndex; +} + +bool flatten(Node* root, const std::u16string& defaultPackage, BigBuffer* outBuffer) { + StringPool pool; + + // This will hold the StringRefs and the location in which to write the index. + // Once we sort the StringPool, we can assign the updated indices + // to the correct data locations. + FlatStringRefList stringRefs; + + // Since we don't know the size of the final StringPool, we write to this + // temporary BigBuffer, which we will append to outBuffer later. + BigBuffer out(1024); + + CompileXmlFlattener flattener(&out, &pool, &stringRefs, defaultPackage); + root->accept(&flattener); + + if (!flattener.success()) { + return false; + } + + flattenXml(&pool, &stringRefs, outBuffer, std::move(out)); + return true; +}; + +Maybe<size_t> flattenAndLink(const Source& source, Node* root, + const std::u16string& defaultPackage, + const std::shared_ptr<IResolver>& resolver, + const FlattenOptions& options, BigBuffer* outBuffer) { + SourceLogger logger(source); + StringPool pool; + + // Attribute names are stored without packages, but we use + // their StringPool index to lookup their resource IDs. + // This will cause collisions, so we can't dedupe + // attribute names from different packages. We use separate + // pools that we later combine. + std::map<std::u16string, StringPool> packagePools; + + FlatStringRefList stringRefs; + + // Since we don't know the size of the final StringPool, we write to this + // temporary BigBuffer, which we will append to outBuffer later. + BigBuffer out(1024); + + LinkedXmlFlattener flattener(&out, &pool, &packagePools, &stringRefs, defaultPackage, resolver, + &logger, options); + root->accept(&flattener); + + if (!flattener.success()) { + return {}; + } + + // Merge the package pools into the main pool. + for (auto& packagePoolEntry : packagePools) { + pool.merge(std::move(packagePoolEntry.second)); + } + + flattenXml(&pool, &stringRefs, outBuffer, std::move(out)); - if (smallestStrippedAttributeSdk == std::numeric_limits<size_t>::max()) { - // Nothing was stripped - return 0u; + if (flattener.getSmallestFilteredSdk()) { + return flattener.getSmallestFilteredSdk(); } - return smallestStrippedAttributeSdk; + return 0; } +} // namespace xml } // namespace aapt diff --git a/tools/aapt2/XmlFlattener.h b/tools/aapt2/XmlFlattener.h index 2cfcc16..4ece0a3 100644 --- a/tools/aapt2/XmlFlattener.h +++ b/tools/aapt2/XmlFlattener.h @@ -21,64 +21,49 @@ #include "Maybe.h" #include "Resolver.h" #include "Source.h" -#include "XmlPullParser.h" +#include "XmlDom.h" #include <string> namespace aapt { +namespace xml { /** * Flattens an XML file into a binary representation parseable by - * the Android resource system. References to resources are checked - * and string values are transformed to typed data where possible. + * the Android resource system. */ -class XmlFlattener { -public: - struct Options { - /** - * The package to use when a reference has no package specified - * (or a namespace URI equals "http://schemas.android.com/apk/res-auto"). - */ - std::u16string defaultPackage; - - /** - * If set, tells the XmlFlattener to strip out - * attributes that have been introduced after - * max SDK. - */ - Maybe<size_t> maxSdkAttribute; - - /** - * Setting this to true will keep the raw string value of - * an attribute's value when it has been resolved. - */ - bool keepRawValues = false; - }; +bool flatten(Node* root, const std::u16string& defaultPackage, BigBuffer* outBuffer); +/** + * Options for flattenAndLink. + */ +struct FlattenOptions { /** - * Creates a flattener with a Resolver to resolve references - * and attributes. + * Keep attribute raw string values along with typed values. */ - XmlFlattener(const std::shared_ptr<ResourceTable>& table, - const std::shared_ptr<IResolver>& resolver); - - XmlFlattener(const XmlFlattener&) = delete; // Not copyable. + bool keepRawValues = false; /** - * Flatten an XML file, reading from the XML parser and writing to the - * BigBuffer. The source object is mainly for logging errors. If the - * function succeeds, returns the smallest SDK version of an attribute that - * was stripped out. If no attributes were stripped out, the return value - * is 0. + * If set, any attribute introduced in a later SDK will not be encoded. */ - Maybe<size_t> flatten(const Source& source, const std::shared_ptr<XmlPullParser>& parser, - BigBuffer* outBuffer, Options options); - -private: - std::shared_ptr<ResourceTable> mTable; - std::shared_ptr<IResolver> mResolver; + Maybe<size_t> maxSdkAttribute; }; +/** + * Like flatten(Node*,BigBuffer*), but references to resources are checked + * and string values are transformed to typed data where possible. + * + * `defaultPackage` is used when a reference has no package or the namespace URI + * "http://schemas.android.com/apk/res-auto" is used. + * + * `resolver` is used to resolve references to resources. + */ +Maybe<size_t> flattenAndLink(const Source& source, Node* root, + const std::u16string& defaultPackage, + const std::shared_ptr<IResolver>& resolver, + const FlattenOptions& options, BigBuffer* outBuffer); + +} // namespace xml } // namespace aapt #endif // AAPT_XML_FLATTENER_H diff --git a/tools/aapt2/XmlFlattener_test.cpp b/tools/aapt2/XmlFlattener_test.cpp index b45cd9b..8915d24 100644 --- a/tools/aapt2/XmlFlattener_test.cpp +++ b/tools/aapt2/XmlFlattener_test.cpp @@ -17,7 +17,6 @@ #include "MockResolver.h" #include "ResourceTable.h" #include "ResourceValues.h" -#include "SourceXmlPullParser.h" #include "Util.h" #include "XmlFlattener.h" @@ -30,13 +29,14 @@ using namespace android; namespace aapt { +namespace xml { constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; class XmlFlattenerTest : public ::testing::Test { public: virtual void SetUp() override { - std::shared_ptr<IResolver> resolver = std::make_shared<MockResolver>( + mResolver = std::make_shared<MockResolver>( std::make_shared<ResourceTable>(), std::map<ResourceName, ResourceId>({ { ResourceName{ u"android", ResourceType::kAttr, u"attr" }, @@ -47,18 +47,21 @@ public: ResourceId{ 0x01010001u } }, { ResourceName{ u"com.lib", ResourceType::kId, u"id" }, ResourceId{ 0x01020001u } }})); - - mFlattener = std::make_shared<XmlFlattener>(nullptr, resolver); } ::testing::AssertionResult testFlatten(const std::string& in, ResXMLTree* outTree) { std::stringstream input(kXmlPreamble); input << in << std::endl; - std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(input); + + SourceLogger logger(Source{ "test.xml" }); + std::unique_ptr<Node> root = inflate(&input, &logger); + if (!root) { + return ::testing::AssertionFailure(); + } + BigBuffer outBuffer(1024); - XmlFlattener::Options xmlOptions; - xmlOptions.defaultPackage = u"android"; - if (!mFlattener->flatten(Source{ "test" }, xmlParser, &outBuffer, xmlOptions)) { + if (!flattenAndLink(Source{ "test.xml" }, root.get(), std::u16string(u"android"), + mResolver, {}, &outBuffer)) { return ::testing::AssertionFailure(); } @@ -69,16 +72,48 @@ public: return ::testing::AssertionSuccess(); } - std::shared_ptr<XmlFlattener> mFlattener; + std::shared_ptr<IResolver> mResolver; }; TEST_F(XmlFlattenerTest, ParseSimpleView) { - std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" - " android:attr=\"@id/id\">\n" - "</View>"; + std::string input = R"EOF( + <View xmlns:android="http://schemas.android.com/apk/res/android" + android:attr="@id/id" + class="str" + style="@id/id"> + </View> + )EOF"; ResXMLTree tree; ASSERT_TRUE(testFlatten(input, &tree)); + while (tree.next() != ResXMLTree::START_TAG) { + ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT); + ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); + } + + const StringPiece16 androidNs = u"http://schemas.android.com/apk/res/android"; + const StringPiece16 attrName = u"attr"; + ssize_t idx = tree.indexOfAttribute(androidNs.data(), androidNs.size(), attrName.data(), + attrName.size()); + ASSERT_GE(idx, 0); + EXPECT_EQ(tree.getAttributeNameResID(idx), 0x01010000u); + EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_REFERENCE); + + const StringPiece16 class16 = u"class"; + idx = tree.indexOfAttribute(nullptr, 0, class16.data(), class16.size()); + ASSERT_GE(idx, 0); + EXPECT_EQ(tree.getAttributeNameResID(idx), 0u); + EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_STRING); + EXPECT_EQ(tree.getAttributeData(idx), tree.getAttributeValueStringID(idx)); + + const StringPiece16 style16 = u"style"; + idx = tree.indexOfAttribute(nullptr, 0, style16.data(), style16.size()); + ASSERT_GE(idx, 0); + EXPECT_EQ(tree.getAttributeNameResID(idx), 0u); + EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_REFERENCE); + EXPECT_EQ((uint32_t) tree.getAttributeData(idx), 0x01020000u); + EXPECT_EQ(tree.getAttributeValueStringID(idx), -1); + while (tree.next() != ResXMLTree::END_DOCUMENT) { ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); } @@ -193,4 +228,5 @@ TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) { EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0); } +} // namespace xml } // namespace aapt diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java index 5f430f0..a3dc077 100644 --- a/wifi/java/android/net/wifi/WifiScanner.java +++ b/wifi/java/android/net/wifi/WifiScanner.java @@ -1007,7 +1007,7 @@ public class WifiScanner { ((ParcelableScanResults) msg.obj).getResults()); return; case CMD_SINGLE_SCAN_COMPLETED: - Log.d(TAG, "removing listener for single scan"); + if (DBG) Log.d(TAG, "removing listener for single scan"); removeListener(msg.arg2); break; default: |
