diff options
109 files changed, 2596 insertions, 3634 deletions
diff --git a/api/current.txt b/api/current.txt index ec0faf495053..6da09e35fe5e 100644 --- a/api/current.txt +++ b/api/current.txt @@ -1234,6 +1234,7 @@ package android { field public static final int summaryOff = 16843248; // 0x10101f0 field public static final int summaryOn = 16843247; // 0x10101ef field public static final int supportsAssist = 16844016; // 0x10104f0 + field public static final int supportsDismissingWindow = 16844104; // 0x1010548 field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1 field public static final int supportsLocalInteraction = 16844047; // 0x101050f field public static final int supportsPictureInPicture = 16844023; // 0x10104f7 @@ -6024,10 +6025,14 @@ package android.app.admin { method public void onLockTaskModeEntering(android.content.Context, android.content.Intent, java.lang.String); method public void onLockTaskModeExiting(android.content.Context, android.content.Intent); method public void onNetworkLogsAvailable(android.content.Context, android.content.Intent, long, int); - method public void onPasswordChanged(android.content.Context, android.content.Intent); - method public void onPasswordExpiring(android.content.Context, android.content.Intent); - method public void onPasswordFailed(android.content.Context, android.content.Intent); - method public void onPasswordSucceeded(android.content.Context, android.content.Intent); + method public deprecated void onPasswordChanged(android.content.Context, android.content.Intent); + method public void onPasswordChanged(android.content.Context, android.content.Intent, android.os.UserHandle); + method public deprecated void onPasswordExpiring(android.content.Context, android.content.Intent); + method public void onPasswordExpiring(android.content.Context, android.content.Intent, android.os.UserHandle); + method public deprecated void onPasswordFailed(android.content.Context, android.content.Intent); + method public void onPasswordFailed(android.content.Context, android.content.Intent, android.os.UserHandle); + method public deprecated void onPasswordSucceeded(android.content.Context, android.content.Intent); + method public void onPasswordSucceeded(android.content.Context, android.content.Intent, android.os.UserHandle); method public void onProfileProvisioningComplete(android.content.Context, android.content.Intent); method public deprecated void onReadyForUserInitialization(android.content.Context, android.content.Intent); method public void onReceive(android.content.Context, android.content.Intent); @@ -8955,6 +8960,7 @@ package android.content { field public static final java.lang.String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS"; field public static final java.lang.String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT"; field public static final java.lang.String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER"; + field public static final java.lang.String EXTRA_CONTENT_ANNOTATIONS = "android.intent.extra.CONTENT_ANNOTATIONS"; field public static final java.lang.String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED"; field public static final java.lang.String EXTRA_DOCK_STATE = "android.intent.extra.DOCK_STATE"; field public static final int EXTRA_DOCK_STATE_CAR = 2; // 0x2 @@ -9944,6 +9950,7 @@ package android.content.pm { method public void setAppLabel(java.lang.CharSequence); method public void setAppPackageName(java.lang.String); method public void setInstallLocation(int); + method public void setInstallReason(int); method public void setOriginatingUid(int); method public void setOriginatingUri(android.net.Uri); method public void setReferrerUri(android.net.Uri); @@ -10177,6 +10184,8 @@ package android.content.pm { field public static final int GET_SIGNATURES = 64; // 0x40 field public static final deprecated int GET_UNINSTALLED_PACKAGES = 8192; // 0x2000 field public static final int GET_URI_PERMISSION_PATTERNS = 2048; // 0x800 + field public static final int INSTALL_REASON_POLICY = 1; // 0x1 + field public static final int INSTALL_REASON_UNKNOWN = 0; // 0x0 field public static final int MATCH_ALL = 131072; // 0x20000 field public static final int MATCH_DEFAULT_ONLY = 65536; // 0x10000 field public static final int MATCH_DIRECT_BOOT_AWARE = 524288; // 0x80000 diff --git a/api/system-current.txt b/api/system-current.txt index 882727969a1d..4109a1fbdddd 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1347,6 +1347,7 @@ package android { field public static final int summaryOff = 16843248; // 0x10101f0 field public static final int summaryOn = 16843247; // 0x10101ef field public static final int supportsAssist = 16844016; // 0x10104f0 + field public static final int supportsDismissingWindow = 16844104; // 0x1010548 field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1 field public static final int supportsLocalInteraction = 16844047; // 0x101050f field public static final int supportsPictureInPicture = 16844023; // 0x10104f7 @@ -6203,10 +6204,14 @@ package android.app.admin { method public void onLockTaskModeEntering(android.content.Context, android.content.Intent, java.lang.String); method public void onLockTaskModeExiting(android.content.Context, android.content.Intent); method public void onNetworkLogsAvailable(android.content.Context, android.content.Intent, long, int); - method public void onPasswordChanged(android.content.Context, android.content.Intent); - method public void onPasswordExpiring(android.content.Context, android.content.Intent); - method public void onPasswordFailed(android.content.Context, android.content.Intent); - method public void onPasswordSucceeded(android.content.Context, android.content.Intent); + method public deprecated void onPasswordChanged(android.content.Context, android.content.Intent); + method public void onPasswordChanged(android.content.Context, android.content.Intent, android.os.UserHandle); + method public deprecated void onPasswordExpiring(android.content.Context, android.content.Intent); + method public void onPasswordExpiring(android.content.Context, android.content.Intent, android.os.UserHandle); + method public deprecated void onPasswordFailed(android.content.Context, android.content.Intent); + method public void onPasswordFailed(android.content.Context, android.content.Intent, android.os.UserHandle); + method public deprecated void onPasswordSucceeded(android.content.Context, android.content.Intent); + method public void onPasswordSucceeded(android.content.Context, android.content.Intent, android.os.UserHandle); method public void onProfileProvisioningComplete(android.content.Context, android.content.Intent); method public deprecated void onReadyForUserInitialization(android.content.Context, android.content.Intent); method public void onReceive(android.content.Context, android.content.Intent); @@ -9331,6 +9336,7 @@ package android.content { field public static final java.lang.String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS"; field public static final java.lang.String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT"; field public static final java.lang.String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER"; + field public static final java.lang.String EXTRA_CONTENT_ANNOTATIONS = "android.intent.extra.CONTENT_ANNOTATIONS"; field public static final java.lang.String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED"; field public static final java.lang.String EXTRA_DOCK_STATE = "android.intent.extra.DOCK_STATE"; field public static final int EXTRA_DOCK_STATE_CAR = 2; // 0x2 @@ -10365,6 +10371,7 @@ package android.content.pm { method public void setGrantedRuntimePermissions(java.lang.String[]); method public void setInstallAsInstantApp(boolean); method public void setInstallLocation(int); + method public void setInstallReason(int); method public void setOriginatingUid(int); method public void setOriginatingUri(android.net.Uri); method public void setReferrerUri(android.net.Uri); @@ -10653,6 +10660,8 @@ package android.content.pm { field public static final int INSTALL_PARSE_FAILED_NOT_APK = -100; // 0xffffff9c field public static final int INSTALL_PARSE_FAILED_NO_CERTIFICATES = -103; // 0xffffff99 field public static final int INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION = -102; // 0xffffff9a + field public static final int INSTALL_REASON_POLICY = 1; // 0x1 + field public static final int INSTALL_REASON_UNKNOWN = 0; // 0x0 field public static final int INSTALL_SUCCEEDED = 1; // 0x1 field public static final int INTENT_FILTER_VERIFICATION_FAILURE = -1; // 0xffffffff field public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1 @@ -21841,6 +21850,7 @@ package android.media { method public android.media.AudioAttributes getAudioAttributes(); method public int getClientPid(); method public int getClientUid(); + method public int getPlayerInterfaceId(); method public int getPlayerState(); method public int getPlayerType(); method public void writeToParcel(android.os.Parcel, int); diff --git a/api/test-current.txt b/api/test-current.txt index dcc693ad989d..714cac9fc559 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1234,6 +1234,7 @@ package android { field public static final int summaryOff = 16843248; // 0x10101f0 field public static final int summaryOn = 16843247; // 0x10101ef field public static final int supportsAssist = 16844016; // 0x10104f0 + field public static final int supportsDismissingWindow = 16844104; // 0x1010548 field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1 field public static final int supportsLocalInteraction = 16844047; // 0x101050f field public static final int supportsPictureInPicture = 16844023; // 0x10104f7 @@ -6041,10 +6042,14 @@ package android.app.admin { method public void onLockTaskModeEntering(android.content.Context, android.content.Intent, java.lang.String); method public void onLockTaskModeExiting(android.content.Context, android.content.Intent); method public void onNetworkLogsAvailable(android.content.Context, android.content.Intent, long, int); - method public void onPasswordChanged(android.content.Context, android.content.Intent); - method public void onPasswordExpiring(android.content.Context, android.content.Intent); - method public void onPasswordFailed(android.content.Context, android.content.Intent); - method public void onPasswordSucceeded(android.content.Context, android.content.Intent); + method public deprecated void onPasswordChanged(android.content.Context, android.content.Intent); + method public void onPasswordChanged(android.content.Context, android.content.Intent, android.os.UserHandle); + method public deprecated void onPasswordExpiring(android.content.Context, android.content.Intent); + method public void onPasswordExpiring(android.content.Context, android.content.Intent, android.os.UserHandle); + method public deprecated void onPasswordFailed(android.content.Context, android.content.Intent); + method public void onPasswordFailed(android.content.Context, android.content.Intent, android.os.UserHandle); + method public deprecated void onPasswordSucceeded(android.content.Context, android.content.Intent); + method public void onPasswordSucceeded(android.content.Context, android.content.Intent, android.os.UserHandle); method public void onProfileProvisioningComplete(android.content.Context, android.content.Intent); method public deprecated void onReadyForUserInitialization(android.content.Context, android.content.Intent); method public void onReceive(android.content.Context, android.content.Intent); @@ -8980,6 +8985,7 @@ package android.content { field public static final java.lang.String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS"; field public static final java.lang.String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT"; field public static final java.lang.String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER"; + field public static final java.lang.String EXTRA_CONTENT_ANNOTATIONS = "android.intent.extra.CONTENT_ANNOTATIONS"; field public static final java.lang.String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED"; field public static final java.lang.String EXTRA_DOCK_STATE = "android.intent.extra.DOCK_STATE"; field public static final int EXTRA_DOCK_STATE_CAR = 2; // 0x2 @@ -9972,6 +9978,7 @@ package android.content.pm { method public void setAppLabel(java.lang.CharSequence); method public void setAppPackageName(java.lang.String); method public void setInstallLocation(int); + method public void setInstallReason(int); method public void setOriginatingUid(int); method public void setOriginatingUri(android.net.Uri); method public void setReferrerUri(android.net.Uri); @@ -10044,6 +10051,7 @@ package android.content.pm { method public abstract android.graphics.drawable.Drawable getDefaultActivityIcon(); method public abstract java.lang.String getDefaultBrowserPackageNameAsUser(int); method public abstract android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo); + method public abstract int getInstallReason(java.lang.String, android.os.UserHandle); method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int); method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int); method public abstract java.lang.String getInstallerPackageName(java.lang.String); @@ -10206,6 +10214,8 @@ package android.content.pm { field public static final int GET_SIGNATURES = 64; // 0x40 field public static final deprecated int GET_UNINSTALLED_PACKAGES = 8192; // 0x2000 field public static final int GET_URI_PERMISSION_PATTERNS = 2048; // 0x800 + field public static final int INSTALL_REASON_POLICY = 1; // 0x1 + field public static final int INSTALL_REASON_UNKNOWN = 0; // 0x0 field public static final int MATCH_ALL = 131072; // 0x20000 field public static final int MATCH_DEFAULT_ONLY = 65536; // 0x10000 field public static final int MATCH_DIRECT_BOOT_AWARE = 524288; // 0x80000 @@ -39123,6 +39133,7 @@ package android.test.mock { method public android.graphics.drawable.Drawable getDefaultActivityIcon(); method public java.lang.String getDefaultBrowserPackageNameAsUser(int); method public android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo); + method public int getInstallReason(java.lang.String, android.os.UserHandle); method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int); method public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int); method public java.lang.String getInstallerPackageName(java.lang.String); diff --git a/compiled-classes-phone b/compiled-classes-phone index 7239a7f664c8..ebc54f2128e9 100644 --- a/compiled-classes-phone +++ b/compiled-classes-phone @@ -5998,10 +5998,6 @@ com.android.internal.widget.LockPatternUtils$StrongAuthTracker$H com.android.internal.widget.MediaNotificationView com.android.internal.widget.NotificationActionListLayout com.android.internal.widget.NotificationActionListLayout$-void__clinit___LambdaImpl0 -com.android.internal.widget.PasswordEntryKeyboard -com.android.internal.widget.PasswordEntryKeyboard$LatinKey -com.android.internal.widget.PasswordEntryKeyboardHelper -com.android.internal.widget.PasswordEntryKeyboardView com.android.internal.widget.PreferenceImageView com.android.internal.widget.ResolverDrawerLayout com.android.internal.widget.ResolverDrawerLayout$1 diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 45d325a859fa..0b3ae3a989d5 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -62,6 +62,7 @@ import android.os.Bundle; import android.os.Debug; import android.os.DropBoxManager; import android.os.Environment; +import android.os.GraphicsEnvironment; import android.os.Handler; import android.os.IBinder; import android.os.LocaleList; @@ -5106,7 +5107,7 @@ public final class ActivityThread { WindowManagerGlobal.getInstance().trimMemory(level); } - private void setupGraphicsSupport(LoadedApk info, File cacheDir) { + private void setupGraphicsSupport(Context context, File cacheDir) { if (Process.isIsolated()) { // Isolated processes aren't going to do UI. return; @@ -5119,6 +5120,7 @@ public final class ActivityThread { if (packages != null) { ThreadedRenderer.setupDiskCache(cacheDir); RenderScriptCacheDir.setupDiskCache(cacheDir); + GraphicsEnvironment.setupGraphicsEnvironment(context); } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -5422,7 +5424,7 @@ public final class ActivityThread { final Context deviceContext = appContext.createDeviceProtectedStorageContext(); final File codeCacheDir = deviceContext.getCodeCacheDir(); if (codeCacheDir != null) { - setupGraphicsSupport(data.info, codeCacheDir); + setupGraphicsSupport(appContext, codeCacheDir); } else { Log.e(TAG, "Unable to setupGraphicsSupport due to missing code-cache directory"); } diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index aedcdce980a2..f3185a8d3a6b 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -1635,7 +1635,8 @@ public class ApplicationPackageManager extends PackageManager { public int installExistingPackageAsUser(String packageName, int userId) throws NameNotFoundException { try { - int res = mPM.installExistingPackageAsUser(packageName, userId); + int res = mPM.installExistingPackageAsUser(packageName, userId, + PackageManager.INSTALL_REASON_UNKNOWN); if (res == INSTALL_FAILED_INVALID_URI) { throw new NameNotFoundException("Package " + packageName + " doesn't exist"); } @@ -2411,6 +2412,18 @@ public class ApplicationPackageManager extends PackageManager { return getUserManager().isManagedProfile(userId); } + /** + * @hide + */ + @Override + public int getInstallReason(String packageName, UserHandle user) { + try { + return mPM.getInstallReason(packageName, user.getIdentifier()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** {@hide} */ private static class MoveCallbackDelegate extends IPackageMoveObserver.Stub implements Handler.Callback { diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java index aae80ed65035..a248bce2a6ca 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -28,6 +28,7 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; +import android.os.Process; import android.os.UserHandle; import android.security.KeyChain; @@ -123,7 +124,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * of the new password with {@link DevicePolicyManager#isActivePasswordSufficient() * DevicePolicyManager.isActivePasswordSufficient()}. * You will generally - * handle this in {@link DeviceAdminReceiver#onPasswordChanged}. + * handle this in {@link DeviceAdminReceiver#onPasswordChanged(Context, Intent, UserHandle)}. * * <p>The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to receive @@ -139,7 +140,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * number of failed password attempts there have been with * {@link DevicePolicyManager#getCurrentFailedPasswordAttempts * DevicePolicyManager.getCurrentFailedPasswordAttempts()}. You will generally - * handle this in {@link DeviceAdminReceiver#onPasswordFailed}. + * handle this in {@link DeviceAdminReceiver#onPasswordFailed(Context, Intent, UserHandle)}. * * <p>The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} to receive @@ -152,7 +153,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { /** * Action sent to a device administrator when the user has successfully entered their device * or profile challenge password, after failing one or more times. You will generally - * handle this in {@link DeviceAdminReceiver#onPasswordSucceeded}. + * handle this in {@link DeviceAdminReceiver#onPasswordSucceeded(Context, Intent, UserHandle)}. * * <p>The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} to receive @@ -165,7 +166,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { /** * Action periodically sent to a device administrator when the device or profile challenge * password is expiring. You will generally - * handle this in {@link DeviceAdminReceiver#onPasswordExpiring}. + * handle this in {@link DeviceAdminReceiver#onPasswordExpiring(Context, Intent, UserHandle)}. * * <p>The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_EXPIRE_PASSWORD} to receive @@ -497,33 +498,90 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * to retrieve the active password characteristics. * @param context The running context as per {@link #onReceive}. * @param intent The received intent as per {@link #onReceive}. + * + * @deprecated From {@link android.os.Build.VERSION_CODES#O}, use + * {@link #onPasswordChanged(Context, Intent, UserHandle)} instead. */ + @Deprecated public void onPasswordChanged(Context context, Intent intent) { } /** + * Called after the user has changed their device or profile challenge password, as a result of + * receiving {@link #ACTION_PASSWORD_CHANGED}. At this point you + * can use {@link DevicePolicyManager#getPasswordQuality(android.content.ComponentName)} + * to retrieve the active password characteristics. + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + * @param user The user or profile for whom the password changed. To see whether this + * user is the current profile or a parent user, check for equality with + * {@link Process#myUserHandle}. + */ + public void onPasswordChanged(Context context, Intent intent, UserHandle user) { + onPasswordChanged(context, intent); + } + + /** * Called after the user has failed at entering their device or profile challenge password, * as a result of receiving {@link #ACTION_PASSWORD_FAILED}. At this point you can use * {@link DevicePolicyManager#getCurrentFailedPasswordAttempts()} to retrieve the number of * failed password attempts. * @param context The running context as per {@link #onReceive}. * @param intent The received intent as per {@link #onReceive}. + * + * @deprecated From {@link android.os.Build.VERSION_CODES#O}, use + * {@link #onPasswordFailed(Context, Intent, UserHandle)} instead. */ + @Deprecated public void onPasswordFailed(Context context, Intent intent) { } /** + * Called after the user has failed at entering their device or profile challenge password, + * as a result of receiving {@link #ACTION_PASSWORD_FAILED}. At this point you can use + * {@link DevicePolicyManager#getCurrentFailedPasswordAttempts()} to retrieve the number of + * failed password attempts. + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + * @param user The user or profile for whom the password check failed. To see whether this + * user is the current profile or a parent user, check for equality with + * {@link Process#myUserHandle}. + */ + public void onPasswordFailed(Context context, Intent intent, UserHandle user) { + onPasswordFailed(context, intent); + } + + /** * Called after the user has succeeded at entering their device or profile challenge password, * as a result of receiving {@link #ACTION_PASSWORD_SUCCEEDED}. This will * only be received the first time they succeed after having previously * failed. * @param context The running context as per {@link #onReceive}. * @param intent The received intent as per {@link #onReceive}. + * + * @deprecated From {@link android.os.Build.VERSION_CODES#O}, use + * {@link #onPasswordSucceeded(Context, Intent, UserHandle)} instead. */ + @Deprecated public void onPasswordSucceeded(Context context, Intent intent) { } /** + * Called after the user has succeeded at entering their device or profile challenge password, + * as a result of receiving {@link #ACTION_PASSWORD_SUCCEEDED}. This will + * only be received the first time they succeed after having previously + * failed. + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + * @param user The user of profile for whom the password check succeeded. To see whether this + * user is the current profile or a parent user, check for equality with + * {@link Process#myUserHandle}. + */ + public void onPasswordSucceeded(Context context, Intent intent, UserHandle user) { + onPasswordSucceeded(context, intent); + } + + /** * Called periodically when the device or profile challenge password is about to expire * or has expired. It will typically be called at these times: on device boot, once per day * before the password expires, and at the time when the password expires. @@ -540,11 +598,40 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * * @param context The running context as per {@link #onReceive}. * @param intent The received intent as per {@link #onReceive}. + * + * @deprecated From {@link android.os.Build.VERSION_CODES#O}, use + * {@link #onPasswordExpiring(Context, Intent, UserHandle)} instead. */ + @Deprecated public void onPasswordExpiring(Context context, Intent intent) { } /** + * Called periodically when the device or profile challenge password is about to expire + * or has expired. It will typically be called at these times: on device boot, once per day + * before the password expires, and at the time when the password expires. + * + * <p>If the password is not updated by the user, this method will continue to be called + * once per day until the password is changed or the device admin disables password expiration. + * + * <p>The admin will typically post a notification requesting the user to change their password + * in response to this call. The actual password expiration time can be obtained by calling + * {@link DevicePolicyManager#getPasswordExpiration(ComponentName) } + * + * <p>The admin should be sure to take down any notifications it posted in response to this call + * when it receives {@link DeviceAdminReceiver#onPasswordChanged(Context, Intent, UserHandle) }. + * + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + * @param user The user or profile for whom the password is expiring. To see whether this + * user is the current profile or a parent user, check for equality with + * {@link Process#myUserHandle}. + */ + public void onPasswordExpiring(Context context, Intent intent, UserHandle user) { + onPasswordExpiring(context, intent); + } + + /** * Called when provisioning of a managed profile or managed device has completed successfully. * * <p> As a prerequisite for the execution of this callback the {@link DeviceAdminReceiver} has @@ -681,6 +768,10 @@ public class DeviceAdminReceiver extends BroadcastReceiver { /** * Called when a new batch of security logs can be retrieved. * + * <p>If a secondary user or profile is created, this callback won't be received until all users + * become affiliated again (even if security logging is enabled). + * See {@link DevicePolicyManager#setAffiliationIds} + * * <p>This callback is only applicable to device owners. * * @param context The running context as per {@link #onReceive}. @@ -695,13 +786,18 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * ever be called when network logging is enabled. The logs can only be retrieved while network * logging is enabled. * + * <p>If a secondary user or profile is created, this callback won't be received until all users + * become affiliated again (even if network logging is enabled). It will also no longer be + * possible to retrieve the network logs batch with the most recent {@code batchToken} provided + * by this callback. See {@link DevicePolicyManager#setAffiliationIds}. + * * <p>This callback is only applicable to device owners. * * @param context The running context as per {@link #onReceive}. * @param intent The received intent as per {@link #onReceive}. * @param batchToken The token representing the current batch of network logs. * @param networkLogsCount The total count of events in the current batch of network logs. - * @see DevicePolicyManager#retrieveNetworkLogs(ComponentName) + * @see DevicePolicyManager#retrieveNetworkLogs */ public void onNetworkLogsAvailable(Context context, Intent intent, long batchToken, int networkLogsCount) { @@ -741,11 +837,11 @@ public class DeviceAdminReceiver extends BroadcastReceiver { String action = intent.getAction(); if (ACTION_PASSWORD_CHANGED.equals(action)) { - onPasswordChanged(context, intent); + onPasswordChanged(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER)); } else if (ACTION_PASSWORD_FAILED.equals(action)) { - onPasswordFailed(context, intent); + onPasswordFailed(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER)); } else if (ACTION_PASSWORD_SUCCEEDED.equals(action)) { - onPasswordSucceeded(context, intent); + onPasswordSucceeded(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER)); } else if (ACTION_DEVICE_ADMIN_ENABLED.equals(action)) { onEnabled(context, intent); } else if (ACTION_DEVICE_ADMIN_DISABLE_REQUESTED.equals(action)) { @@ -757,7 +853,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { } else if (ACTION_DEVICE_ADMIN_DISABLED.equals(action)) { onDisabled(context, intent); } else if (ACTION_PASSWORD_EXPIRING.equals(action)) { - onPasswordExpiring(context, intent); + onPasswordExpiring(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER)); } else if (ACTION_PROFILE_PROVISIONING_COMPLETE.equals(action)) { onProfileProvisioningComplete(context, intent); } else if (ACTION_CHOOSE_PRIVATE_KEY_ALIAS.equals(action)) { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 074326fd3531..c95e0113e801 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -3651,15 +3651,16 @@ public class DevicePolicyManager { /** * Called by a device owner to request a bugreport. * <p> - * There must be only one user on the device, managed by the device owner. Otherwise a - * {@link SecurityException} will be thrown. + * If the device contains secondary users or profiles, they must be affiliated with the device + * owner user. Otherwise a {@link SecurityException} will be thrown. See + * {@link #setAffiliationIds}. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @return {@code true} if the bugreport collection started successfully, or {@code false} if it * wasn't triggered because a previous bugreport operation is still active (either the * bugreport is still running or waiting for the user to share or decline) - * @throws SecurityException if {@code admin} is not a device owner, or if there are users other - * than the one managed by the device owner. + * @throws SecurityException if {@code admin} is not a device owner, or there is at least one + * profile or secondary user that is not affiliated with the device owner user. */ public boolean requestBugreport(@NonNull ComponentName admin) { throwIfParentInstance("requestBugreport"); @@ -6631,14 +6632,16 @@ public class DevicePolicyManager { } /** - * Called by device owner to control the security logging feature. Logging can only be - * enabled on single user devices where the sole user is managed by the device owner. + * Called by device owner to control the security logging feature. * * <p> Security logs contain various information intended for security auditing purposes. * See {@link SecurityEvent} for details. * - * <p>There must be only one user on the device, managed by the device owner. - * Otherwise a {@link SecurityException} will be thrown. + * <p><strong>Note:</strong> The device owner won't be able to retrieve security logs if there + * are unaffiliated secondary users or profiles on the device, regardless of whether the + * feature is enabled. Logs will be discarded if the internal buffer fills up while waiting for + * all users to become affiliated. Therefore it's recommended that affiliation ids are set for + * new users as soon as possible after provisioning via {@link #setAffiliationIds}. * * @param admin Which device owner this request is associated with. * @param enabled whether security logging should be enabled or not. @@ -6680,13 +6683,16 @@ public class DevicePolicyManager { * <p> Access to the logs is rate limited and it will only return new logs after the device * owner has been notified via {@link DeviceAdminReceiver#onSecurityLogsAvailable}. * - * <p>There must be only one user on the device, managed by the device owner. - * Otherwise a {@link SecurityException} will be thrown. + * <p>If there is any other user or profile on the device, it must be affiliated with the + * device owner. Otherwise a {@link SecurityException} will be thrown. See + * {@link #setAffiliationIds} * * @param admin Which device owner this request is associated with. * @return the new batch of security logs which is a list of {@link SecurityEvent}, * or {@code null} if rate limitation is exceeded or if logging is currently disabled. - * @throws SecurityException if {@code admin} is not a device owner. + * @throws SecurityException if {@code admin} is not a device owner, or there is at least one + * profile or secondary user that is not affiliated with the device owner user. + * @see DeviceAdminReceiver#onSecurityLogsAvailable */ public @Nullable List<SecurityEvent> retrieveSecurityLogs(@NonNull ComponentName admin) { throwIfParentInstance("retrieveSecurityLogs"); @@ -6726,14 +6732,17 @@ public class DevicePolicyManager { * will result in {@code null} being returned. The device logs are retrieved from a RAM region * which is not guaranteed to be corruption-free during power cycles, as a result be cautious * about data corruption when parsing. </strong> - * <p> - * There must be only one user on the device, managed by the device owner. Otherwise a - * {@link SecurityException} will be thrown. + * + * <p>If there is any other user or profile on the device, it must be affiliated with the + * device owner. Otherwise a {@link SecurityException} will be thrown. See + * {@link #setAffiliationIds} * * @param admin Which device owner this request is associated with. * @return Device logs from before the latest reboot of the system, or {@code null} if this API * is not supported on the device. - * @throws SecurityException if {@code admin} is not a device owner. + * @throws SecurityException if {@code admin} is not a device owner, or there is at least one + * profile or secondary user that is not affiliated with the device owner user. + * @see #retrieveSecurityLogs */ public @Nullable List<SecurityEvent> retrievePreRebootSecurityLogs( @NonNull ComponentName admin) { @@ -6939,6 +6948,12 @@ public class DevicePolicyManager { * Indicates the entity that controls the device or profile owner. Two users/profiles are * affiliated if the set of ids set by their device or profile owners intersect. * + * <p><strong>Note:</strong> Features that depend on user affiliation (such as security logging + * or {@link #bindDeviceAdminServiceAsUser}) won't be available when a secondary user or profile + * is created, until it becomes affiliated. Therefore it is recommended that the appropriate + * affiliation ids are set by its profile owner as soon as possible after the user/profile is + * created. + * * @param admin Which profile or device owner this request is associated with. * @param ids A list of opaque non-empty affiliation ids. Duplicate elements will be ignored. * @@ -7138,15 +7153,19 @@ public class DevicePolicyManager { } /** - * Called by a device owner to control the network logging feature. Logging can only be - * enabled on single user devices where the sole user is managed by the device owner. If a new - * user is added on the device, logging is disabled. + * Called by a device owner to control the network logging feature. * * <p> Network logs contain DNS lookup and connect() library call events. * + * <p><strong>Note:</strong> The device owner won't be able to retrieve network logs if there + * are unaffiliated secondary users or profiles on the device, regardless of whether the + * feature is enabled. Logs will be discarded if the internal buffer fills up while waiting for + * all users to become affiliated. Therefore it's recommended that affiliation ids are set for + * new users as soon as possible after provisioning via {@link #setAffiliationIds}. + * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param enabled whether network logging should be enabled or not. - * @throws {@link SecurityException} if {@code admin} is not a device owner. + * @throws SecurityException if {@code admin} is not a device owner. * @see #retrieveNetworkLogs */ public void setNetworkLoggingEnabled(@NonNull ComponentName admin, boolean enabled) { @@ -7164,7 +7183,7 @@ public class DevicePolicyManager { * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Can only * be {@code null} if the caller has MANAGE_USERS permission. * @return {@code true} if network logging is enabled by device owner, {@code false} otherwise. - * @throws {@link SecurityException} if {@code admin} is not a device owner and caller has + * @throws SecurityException if {@code admin} is not a device owner and caller has * no MANAGE_USERS permission */ public boolean isNetworkLoggingEnabled(@Nullable ComponentName admin) { @@ -7190,12 +7209,19 @@ public class DevicePolicyManager { * after the device device owner has been notified via * {@link DeviceAdminReceiver#onNetworkLogsAvailable}. * + * <p>If a secondary user or profile is created, calling this method will throw a + * {@link SecurityException} until all users become affiliated again. It will also no longer be + * possible to retrieve the network logs batch with the most recent batchToken provided + * by {@link DeviceAdminReceiver#onNetworkLogsAvailable}. See + * {@link DevicePolicyManager#setAffiliationIds}. + * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param batchToken A token of the batch to retrieve * @return A new batch of network logs which is a list of {@link NetworkEvent}. Returns * {@code null} if the batch represented by batchToken is no longer available or if * logging is disabled. - * @throws {@link SecurityException} if {@code admin} is not a device owner. + * @throws SecurityException if {@code admin} is not a device owner, or there is at least one + * profile or secondary user that is not affiliated with the device owner user. * @see DeviceAdminReceiver#onNetworkLogsAvailable */ public @Nullable List<NetworkEvent> retrieveNetworkLogs(@NonNull ComponentName admin, diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index b05ceaa8b723..d8358f9883e8 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -3843,6 +3843,52 @@ public class Intent implements Parcelable, Cloneable { = "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER"; /** + * An {@code ArrayList} of {@code String} annotations describing content for + * {@link #ACTION_CHOOSER}. + * + * <p>If {@link #EXTRA_CONTENT_ANNOTATIONS} is present in an intent used to start a + * {@link #ACTION_CHOOSER} activity, the first three annotations will be used to rank apps.</p> + * + * <p>Annotations should describe the major components or topics of the content. It is up to + * apps initiating {@link #ACTION_CHOOSER} to learn and add annotations. Annotations should be + * learned in advance, e.g., when creating or saving content, to avoid increasing latency to + * start {@link #ACTION_CHOOSER}. Performance on customized annotations can suffer, if they are + * rarely used for {@link #ACTION_CHOOSER} in the past 14 days. Therefore, it is recommended to + * use the following annotations when applicable:</p> + * <ul> + * <li>"product": represents that the topic of the content is mainly about products, e.g., + * health & beauty, and office supplies.</li> + * <li>"emotion": represents that the topic of the content is mainly about emotions, e.g., + * happy, and sad.</li> + * <li>"person": represents that the topic of the content is mainly about persons, e.g., + * face, finger, standing, and walking.</li> + * <li>"child": represents that the topic of the content is mainly about children, e.g., + * child, and baby.</li> + * <li>"selfie": represents that the topic of the content is mainly about selfies.</li> + * <li>"crowd": represents that the topic of the content is mainly about crowds.</li> + * <li>"party": represents that the topic of the content is mainly about parties.</li> + * <li>"animal": represent that the topic of the content is mainly about animals.</li> + * <li>"plant": represents that the topic of the content is mainly about plants, e.g., + * flowers.</li> + * <li>"vacation": represents that the topic of the content is mainly about vacations.</li> + * <li>"fashion": represents that the topic of the content is mainly about fashion, e.g. + * sunglasses, jewelry, handbags and clothing.</li> + * <li>"material": represents that the topic of the content is mainly about materials, e.g., + * paper, and silk.</li> + * <li>"vehicle": represents that the topic of the content is mainly about vehicles, like + * cars, and boats.</li> + * <li>"document": represents that the topic of the content is mainly about documents, e.g. + * posters.</li> + * <li>"design": represents that the topic of the content is mainly about design, e.g. arts + * and designs of houses.</li> + * <li>"holiday": represents that the topic of the content is mainly about holidays, e.g., + * Christmas and Thanksgiving.</li> + * </ul> + */ + public static final String EXTRA_CONTENT_ANNOTATIONS + = "android.intent.extra.CONTENT_ANNOTATIONS"; + + /** * A {@link ResultReceiver} used to return data back to the sender. * * <p>Used to complete an app-specific diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index eb0ca2ed10be..19cca8ed1f66 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -523,7 +523,7 @@ interface IPackageManager { boolean setInstallLocation(int loc); int getInstallLocation(); - int installExistingPackageAsUser(String packageName, int userId); + int installExistingPackageAsUser(String packageName, int userId, int installReason); void verifyPendingInstall(int id, int verificationCode); void extendVerificationTimeout(int id, int verificationCodeAtTimeout, long millisecondsToDelay); @@ -584,4 +584,6 @@ interface IPackageManager { boolean isPackageDeviceAdminOnAnyUser(String packageName); List<String> getPreviousCodePaths(in String packageName); + + int getInstallReason(String packageName, int userId); } diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 646bd3c8e78b..db3f63708be4 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -881,6 +881,8 @@ public class PackageInstaller { /** {@hide} */ public int installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY; /** {@hide} */ + public int installReason = PackageManager.INSTALL_REASON_UNKNOWN; + /** {@hide} */ public long sizeBytes = -1; /** {@hide} */ public String appPackageName; @@ -919,6 +921,7 @@ public class PackageInstaller { mode = source.readInt(); installFlags = source.readInt(); installLocation = source.readInt(); + installReason = source.readInt(); sizeBytes = source.readLong(); appPackageName = source.readString(); appIcon = source.readParcelable(null); @@ -1076,6 +1079,10 @@ public class PackageInstaller { } } + public void setInstallReason(int installReason) { + this.installReason = installReason; + } + /** {@hide} */ public void dump(IndentingPrintWriter pw) { pw.printPair("mode", mode); @@ -1104,6 +1111,7 @@ public class PackageInstaller { dest.writeInt(mode); dest.writeInt(installFlags); dest.writeInt(installLocation); + dest.writeInt(installReason); dest.writeLong(sizeBytes); dest.writeString(appPackageName); dest.writeParcelable(appIcon, flags); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 0cd67b793dc1..04e649cba092 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -740,6 +740,21 @@ public abstract class PackageManager { */ public static final int DONT_KILL_APP = 0x00000001; + /** @hide */ + @IntDef({INSTALL_REASON_UNKNOWN, INSTALL_REASON_POLICY}) + @Retention(RetentionPolicy.SOURCE) + public @interface InstallReason {} + + /** + * Code indicating that the reason for installing this package is unknown. + */ + public static final int INSTALL_REASON_UNKNOWN = 0; + + /** + * Code indicating that this package was installed due to enterprise policy. + */ + public static final int INSTALL_REASON_POLICY = 1; + /** * Installation return code: this is passed to the * {@link IPackageInstallObserver} on success. @@ -5881,4 +5896,25 @@ public abstract class PackageManager { } } } + + /** + * Return the install reason that was recorded when a package was first installed for a specific + * user. Requesting the install reason for another user will require the permission + * INTERACT_ACROSS_USERS_FULL. + * + * @param packageName The package for which to retrieve the install reason + * @param user The user for whom to retrieve the install reason + * + * @return The install reason, currently one of {@code INSTALL_REASON_UNKNOWN} and + * {@code INSTALL_REASON_POLICY}. If the package is not installed for the given user, + * {@code INSTALL_REASON_UNKNOWN} is returned. + * + * @see #INSTALL_REASON_UNKNOWN + * @see #INSTALL_REASON_POLICY + * + * @hide + */ + @TestApi + public abstract @InstallReason int getInstallReason(String packageName, + @NonNull UserHandle user); } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index a48762bd36fa..083e4cc6676b 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -950,8 +950,8 @@ public class PackageParser { try { final byte[] bytes = IoUtils.readFileAsByteArray(cacheFile.getAbsolutePath()); return fromCacheEntry(bytes); - } catch (IOException ioe) { - Slog.w(TAG, "Error reading package cache: ", ioe); + } catch (Exception e) { + Slog.w(TAG, "Error reading package cache: ", e); // If something went wrong while reading the cache entry, delete the cache file // so that we regenerate it the next time. diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java index eb3f27570970..e19aa99599ef 100644 --- a/core/java/android/content/pm/PackageUserState.java +++ b/core/java/android/content/pm/PackageUserState.java @@ -48,6 +48,7 @@ public class PackageUserState { public int domainVerificationStatus; public int appLinkGeneration; public int categoryHint = ApplicationInfo.CATEGORY_UNDEFINED; + public int installReason; public ArraySet<String> disabledComponents; public ArraySet<String> enabledComponents; @@ -59,6 +60,7 @@ public class PackageUserState { enabled = COMPONENT_ENABLED_STATE_DEFAULT; domainVerificationStatus = PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; + installReason = PackageManager.INSTALL_REASON_UNKNOWN; } public PackageUserState(PackageUserState o) { @@ -74,6 +76,7 @@ public class PackageUserState { domainVerificationStatus = o.domainVerificationStatus; appLinkGeneration = o.appLinkGeneration; categoryHint = o.categoryHint; + installReason = o.installReason; disabledComponents = ArrayUtils.cloneOrNull(o.disabledComponents); enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents); } @@ -202,6 +205,9 @@ public class PackageUserState { if (categoryHint != oldState.categoryHint) { return false; } + if (installReason != oldState.installReason) { + return false; + } if ((disabledComponents == null && oldState.disabledComponents != null) || (disabledComponents != null && oldState.disabledComponents == null)) { return false; diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java new file mode 100644 index 000000000000..4b130ed1adc8 --- /dev/null +++ b/core/java/android/os/GraphicsEnvironment.java @@ -0,0 +1,95 @@ +/* + * Copyright 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.SystemProperties; +import android.util.Log; + +import dalvik.system.VMRuntime; + +import java.io.File; + +/** @hide */ +public final class GraphicsEnvironment { + + private static final boolean DEBUG = false; + private static final String TAG = "GraphicsEnvironment"; + private static final String PROPERTY_GFX_DRIVER = "ro.gfx.driver.0"; + + public static void setupGraphicsEnvironment(Context context) { + String driverPackageName = SystemProperties.get(PROPERTY_GFX_DRIVER); + if (driverPackageName == null || driverPackageName.isEmpty()) { + return; + } + // To minimize risk of driver updates crippling the device beyond user repair, never use an + // updated driver for privileged or non-updated system apps. Presumably pre-installed apps + // were tested thoroughly with the pre-installed driver. + ApplicationInfo ai = context.getApplicationInfo(); + if (ai.isPrivilegedApp() || (ai.isSystemApp() && !ai.isUpdatedSystemApp())) { + if (DEBUG) Log.v(TAG, "ignoring driver package for privileged/non-updated system app"); + return; + } + ApplicationInfo driverInfo; + try { + driverInfo = context.getPackageManager().getApplicationInfo(driverPackageName, + PackageManager.MATCH_SYSTEM_ONLY); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "driver package '" + driverPackageName + "' not installed"); + return; + } + String abi = chooseAbi(driverInfo); + if (abi == null) { + if (DEBUG) { + // This is the normal case for the pre-installed empty driver package, don't spam + if (driverInfo.isUpdatedSystemApp()) { + Log.w(TAG, "updated driver package has no compatible native libraries"); + } + } + return; + } + + StringBuilder sb = new StringBuilder(); + sb.append(driverInfo.nativeLibraryDir) + .append(File.pathSeparator); + sb.append(driverInfo.sourceDir) + .append("!/lib/") + .append(abi); + String paths = sb.toString(); + + if (DEBUG) Log.v(TAG, "gfx driver package libs: " + paths); + setDriverPath(paths); + } + + private static String chooseAbi(ApplicationInfo ai) { + String isa = VMRuntime.getCurrentInstructionSet(); + if (ai.primaryCpuAbi != null && + isa.equals(VMRuntime.getInstructionSet(ai.primaryCpuAbi))) { + return ai.primaryCpuAbi; + } + if (ai.secondaryCpuAbi != null && + isa.equals(VMRuntime.getInstructionSet(ai.secondaryCpuAbi))) { + return ai.secondaryCpuAbi; + } + return null; + } + + private static native void setDriverPath(String path); + +} diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java index 5c8e6dc39960..b6da1d8c9de3 100644 --- a/core/java/android/view/inputmethod/InputMethodInfo.java +++ b/core/java/android/view/inputmethod/InputMethodInfo.java @@ -57,6 +57,7 @@ import java.util.Map; * @attr ref android.R.styleable#InputMethod_settingsActivity * @attr ref android.R.styleable#InputMethod_isDefault * @attr ref android.R.styleable#InputMethod_supportsSwitchingToNextInputMethod + * @attr ref android.R.styleable#InputMethod_supportsDismissingWindow */ public final class InputMethodInfo implements Parcelable { static final String TAG = "InputMethodInfo"; @@ -104,6 +105,11 @@ public final class InputMethodInfo implements Parcelable { private final boolean mSupportsSwitchingToNextInputMethod; /** + * The flag whether this IME supports ways to dismiss its window (e.g. dismiss button.) + */ + private final boolean mSupportsDismissingWindow; + + /** * Constructor. * * @param context The Context in which we are parsing the input method. @@ -132,6 +138,7 @@ public final class InputMethodInfo implements Parcelable { mId = new ComponentName(si.packageName, si.name).flattenToShortString(); boolean isAuxIme = true; boolean supportsSwitchingToNextInputMethod = false; // false as default + boolean supportsDismissingWindow = false; // false as default mForceDefault = false; PackageManager pm = context.getPackageManager(); @@ -171,6 +178,8 @@ public final class InputMethodInfo implements Parcelable { supportsSwitchingToNextInputMethod = sa.getBoolean( com.android.internal.R.styleable.InputMethod_supportsSwitchingToNextInputMethod, false); + supportsDismissingWindow = sa.getBoolean( + com.android.internal.R.styleable.InputMethod_supportsDismissingWindow, false); sa.recycle(); final int depth = parser.getDepth(); @@ -242,6 +251,7 @@ public final class InputMethodInfo implements Parcelable { mIsDefaultResId = isDefaultResId; mIsAuxIme = isAuxIme; mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod; + mSupportsDismissingWindow = supportsDismissingWindow; } InputMethodInfo(Parcel source) { @@ -250,6 +260,7 @@ public final class InputMethodInfo implements Parcelable { mIsDefaultResId = source.readInt(); mIsAuxIme = source.readInt() == 1; mSupportsSwitchingToNextInputMethod = source.readInt() == 1; + mSupportsDismissingWindow = source.readInt() == 1; mService = ResolveInfo.CREATOR.createFromParcel(source); mSubtypes = new InputMethodSubtypeArray(source); mForceDefault = false; @@ -260,8 +271,10 @@ public final class InputMethodInfo implements Parcelable { */ public InputMethodInfo(String packageName, String className, CharSequence label, String settingsActivity) { - this(buildDummyResolveInfo(packageName, className, label), false, settingsActivity, null, - 0, false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */); + this(buildDummyResolveInfo(packageName, className, label), false /* isAuxIme */, + settingsActivity, null /* subtypes */, 0 /* isDefaultResId */, + false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */, + true /* supportsDismissingWindow */); } /** @@ -271,17 +284,18 @@ public final class InputMethodInfo implements Parcelable { public InputMethodInfo(ResolveInfo ri, boolean isAuxIme, String settingsActivity, List<InputMethodSubtype> subtypes, int isDefaultResId, boolean forceDefault) { - this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId, - forceDefault, true /* supportsSwitchingToNextInputMethod */); + this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId, forceDefault, + true /* supportsSwitchingToNextInputMethod */, + true /* supportsDismissingWindow */); } /** * Temporary API for creating a built-in input method for test. * @hide */ - public InputMethodInfo(ResolveInfo ri, boolean isAuxIme, - String settingsActivity, List<InputMethodSubtype> subtypes, int isDefaultResId, - boolean forceDefault, boolean supportsSwitchingToNextInputMethod) { + public InputMethodInfo(ResolveInfo ri, boolean isAuxIme, String settingsActivity, + List<InputMethodSubtype> subtypes, int isDefaultResId, boolean forceDefault, + boolean supportsSwitchingToNextInputMethod, boolean supportsDismissingWindow) { final ServiceInfo si = ri.serviceInfo; mService = ri; mId = new ComponentName(si.packageName, si.name).flattenToShortString(); @@ -291,6 +305,7 @@ public final class InputMethodInfo implements Parcelable { mSubtypes = new InputMethodSubtypeArray(subtypes); mForceDefault = forceDefault; mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod; + mSupportsDismissingWindow = supportsDismissingWindow; } private static ResolveInfo buildDummyResolveInfo(String packageName, String className, @@ -431,7 +446,8 @@ public final class InputMethodInfo implements Parcelable { public void dump(Printer pw, String prefix) { pw.println(prefix + "mId=" + mId + " mSettingsActivityName=" + mSettingsActivityName - + " mSupportsSwitchingToNextInputMethod=" + mSupportsSwitchingToNextInputMethod); + + " mSupportsSwitchingToNextInputMethod=" + mSupportsSwitchingToNextInputMethod + + " mSupportsDismissingWindow=" + mSupportsDismissingWindow); pw.println(prefix + "mIsDefaultResId=0x" + Integer.toHexString(mIsDefaultResId)); pw.println(prefix + "Service:"); @@ -484,6 +500,14 @@ public final class InputMethodInfo implements Parcelable { } /** + * @return true if this input method supports ways to dismiss its window. + * @hide + */ + public boolean supportsDismissingWindow() { + return mSupportsDismissingWindow; + } + + /** * Used to package this object into a {@link Parcel}. * * @param dest The {@link Parcel} to be written. @@ -496,6 +520,7 @@ public final class InputMethodInfo implements Parcelable { dest.writeInt(mIsDefaultResId); dest.writeInt(mIsAuxIme ? 1 : 0); dest.writeInt(mSupportsSwitchingToNextInputMethod ? 1 : 0); + dest.writeInt(mSupportsDismissingWindow ? 1 : 0); mService.writeToParcel(dest, flags); mSubtypes.writeToParcel(dest); } diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 787861126692..d4baa18f7100 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -418,7 +418,7 @@ public class ChooserActivity extends ResolverActivity { } } } - updateChooserCounts(target); + updateModelAndChooserCounts(target); return super.onTargetSelected(target, alwaysCheck); } @@ -575,27 +575,18 @@ public class ChooserActivity extends ResolverActivity { // Do nothing. We'll send the voice stuff ourselves. } - void updateChooserCounts(TargetInfo info) { + void updateModelAndChooserCounts(TargetInfo info) { if (info != null) { - UsageStatsManager usageStatsManager = - (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE); - if (usageStatsManager == null) { - if (DEBUG) { - Log.d(TAG, "Can not start UsageStatsManager"); - } - return; - } final ResolveInfo ri = info.getResolveInfo(); Intent targetIntent = getTargetIntent(); if (ri != null && ri.activityInfo != null && targetIntent != null) { - usageStatsManager.reportChooserSelection(ri.activityInfo.packageName, getUserId(), - targetIntent.getType(), null, targetIntent.getAction()); if (mAdapter != null) { mAdapter.updateModel(info.getResolvedComponentName()); + mAdapter.updateChooserCounts(ri.activityInfo.packageName, getUserId(), + targetIntent.getAction()); } if (DEBUG) { Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName); - Log.d(TAG, "Annotation to be updated is " + targetIntent.getType()); Log.d(TAG, "Action to be updated is " + targetIntent.getAction()); } } else if(DEBUG) { @@ -618,7 +609,7 @@ public class ChooserActivity extends ResolverActivity { } else { TargetInfo clonedTarget = selectedTarget.cloneFilledIn(matchingIntent, 0); if (super.onTargetSelected(clonedTarget, false)) { - updateChooserCounts(clonedTarget); + updateModelAndChooserCounts(clonedTarget); finish(); return; } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 095063099395..d734d17b2dc9 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -1299,6 +1299,10 @@ public class ResolverActivity extends Activity { mResolverListController.updateModel(componentName); } + public void updateChooserCounts(String packageName, int userId, String action) { + mResolverListController.updateChooserCounts(packageName, userId, action); + } + /** * Rebuild the list of resolvers. In some cases some parts will need some asynchronous work * to complete. diff --git a/core/java/com/android/internal/app/ResolverComparator.java b/core/java/com/android/internal/app/ResolverComparator.java index 45fad97f6422..d9ab47e780d9 100644 --- a/core/java/com/android/internal/app/ResolverComparator.java +++ b/core/java/com/android/internal/app/ResolverComparator.java @@ -52,6 +52,8 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> { private static final boolean DEBUG = false; + private static final int NUM_OF_TOP_ANNOTATIONS_TO_USE = 3; + // One week private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 7; @@ -74,7 +76,8 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> { private final long mSinceTime; private final LinkedHashMap<ComponentName, ScoredTarget> mScoredTargets = new LinkedHashMap<>(); private final String mReferrerPackage; - public String mContentType; + private String mContentType; + private String[] mAnnotations; private String mAction; private LogisticRegressionAppRanker mRanker; @@ -91,10 +94,26 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> { mSinceTime = mCurrentTime - USAGE_STATS_PERIOD; mStats = mUsm.queryAndAggregateUsageStats(mSinceTime, mCurrentTime); mContentType = intent.getType(); + getContentAnnotations(intent); mAction = intent.getAction(); mRanker = new LogisticRegressionAppRanker(context); } + public void getContentAnnotations(Intent intent) { + ArrayList<String> annotations = intent.getStringArrayListExtra( + Intent.EXTRA_CONTENT_ANNOTATIONS); + if (annotations != null) { + int size = annotations.size(); + if (size > NUM_OF_TOP_ANNOTATIONS_TO_USE) { + size = NUM_OF_TOP_ANNOTATIONS_TO_USE; + } + mAnnotations = new String[size]; + for (int i = 0; i < size; i++) { + mAnnotations[i] = annotations.get(i); + } + } + } + public void compute(List<ResolvedComponentInfo> targets) { mScoredTargets.clear(); @@ -132,12 +151,18 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> { if (launched > mostLaunched) { mostLaunched = launched; } - // TODO(kanlig): get and combine counts of categories. int selected = 0; if (pkStats.mChooserCounts != null && mAction != null && pkStats.mChooserCounts.get(mAction) != null) { selected = pkStats.mChooserCounts.get(mAction).getOrDefault(mContentType, 0); + if (mAnnotations != null) { + final int size = mAnnotations.length; + for (int i = 0; i < size; i++) { + selected += pkStats.mChooserCounts.get(mAction) + .getOrDefault(mAnnotations[i], 0); + } + } } if (DEBUG) { if (mAction == null) { @@ -288,6 +313,12 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> { } } + public void updateChooserCounts(String packageName, int userId, String action) { + if (mUsm != null) { + mUsm.reportChooserSelection(packageName, userId, mContentType, mAnnotations, action); + } + } + public void updateModel(ComponentName componentName) { if (mScoredTargets == null || componentName == null || !mScoredTargets.containsKey(componentName)) { diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java index d864a310e91b..f88f6f9a07e0 100644 --- a/core/java/com/android/internal/app/ResolverListController.java +++ b/core/java/com/android/internal/app/ResolverListController.java @@ -224,4 +224,10 @@ public class ResolverListController { mResolverComparator.updateModel(componentName); } } + + public void updateChooserCounts(String packageName, int userId, String action) { + if (mResolverComparator != null) { + mResolverComparator.updateChooserCounts(packageName, userId, action); + } + } } diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 8eb75c06b871..b36843759449 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -80,7 +80,6 @@ import java.security.Provider; public class ZygoteInit { private static final String TAG = "Zygote"; - private static final String PROPERTY_DISABLE_OPENGL_PRELOADING = "ro.zygote.disable_gl_preload"; private static final String PROPERTY_RUNNING_IN_CONTAINER = "ro.boot.container"; private static final int LOG_BOOT_PROGRESS_PRELOAD_START = 3020; @@ -125,9 +124,6 @@ public class ZygoteInit { bootTimingsTraceLog.traceBegin("PreloadResources"); preloadResources(); bootTimingsTraceLog.traceEnd(); // PreloadResources - bootTimingsTraceLog.traceBegin("PreloadOpenGL"); - preloadOpenGL(); - bootTimingsTraceLog.traceEnd(); // PreloadOpenGL preloadSharedLibraries(); preloadTextResources(); // Ask the WebViewFactory to do any initialization that must run in the zygote process, @@ -177,12 +173,6 @@ public class ZygoteInit { System.loadLibrary("jnigraphics"); } - private static void preloadOpenGL() { - if (!SystemProperties.getBoolean(PROPERTY_DISABLE_OPENGL_PRELOADING, false)) { - EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); - } - } - private static void preloadTextResources() { Hyphenator.init(); TextView.preloadFontCache(); diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboard.java b/core/java/com/android/internal/widget/PasswordEntryKeyboard.java deleted file mode 100644 index 7483e7584b87..000000000000 --- a/core/java/com/android/internal/widget/PasswordEntryKeyboard.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (C) 2010 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.internal.widget; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.XmlResourceParser; -import android.graphics.drawable.Drawable; -import android.inputmethodservice.Keyboard; -import android.inputmethodservice.KeyboardView; -import com.android.internal.R; - -/** - * A basic, embed-able keyboard designed for password entry. Allows entry of all Latin-1 characters. - * - * It has two modes: alpha and numeric. In alpha mode, it allows all Latin-1 characters and enables - * an additional keyboard with symbols. In numeric mode, it shows a 12-key DTMF dialer-like - * keypad with alpha characters hints. - */ -public class PasswordEntryKeyboard extends Keyboard { - private static final int SHIFT_OFF = 0; - private static final int SHIFT_ON = 1; - private static final int SHIFT_LOCKED = 2; - public static final int KEYCODE_SPACE = ' '; - - private Drawable mShiftIcon; - private Drawable mShiftLockIcon; - - // These two arrays must be the same length - private Drawable[] mOldShiftIcons = { null, null }; - private Key[] mShiftKeys = { null, null }; - - private Key mEnterKey; - private Key mF1Key; - private Key mSpaceKey; - private int mShiftState = SHIFT_OFF; - - static int sSpacebarVerticalCorrection; - - public PasswordEntryKeyboard(Context context, int xmlLayoutResId) { - this(context, xmlLayoutResId, 0); - } - - public PasswordEntryKeyboard(Context context, int xmlLayoutResId, int width, int height) { - this(context, xmlLayoutResId, 0, width, height); - } - - public PasswordEntryKeyboard(Context context, int xmlLayoutResId, int mode) { - super(context, xmlLayoutResId, mode); - init(context); - } - - public PasswordEntryKeyboard(Context context, int xmlLayoutResId, int mode, - int width, int height) { - super(context, xmlLayoutResId, mode, width, height); - init(context); - } - - private void init(Context context) { - final Resources res = context.getResources(); - mShiftIcon = context.getDrawable(R.drawable.sym_keyboard_shift); - mShiftLockIcon = context.getDrawable(R.drawable.sym_keyboard_shift_locked); - sSpacebarVerticalCorrection = res.getDimensionPixelOffset( - R.dimen.password_keyboard_spacebar_vertical_correction); - } - - public PasswordEntryKeyboard(Context context, int layoutTemplateResId, - CharSequence characters, int columns, int horizontalPadding) { - super(context, layoutTemplateResId, characters, columns, horizontalPadding); - } - - @Override - protected Key createKeyFromXml(Resources res, Row parent, int x, int y, - XmlResourceParser parser) { - LatinKey key = new LatinKey(res, parent, x, y, parser); - final int code = key.codes[0]; - if (code >=0 && code != '\n' && (code < 32 || code > 127)) { - // Log.w(TAG, "Key code for " + key.label + " is not latin-1"); - key.label = " "; - key.setEnabled(false); - } - switch (key.codes[0]) { - case 10: - mEnterKey = key; - break; - case PasswordEntryKeyboardView.KEYCODE_F1: - mF1Key = key; - break; - case 32: - mSpaceKey = key; - break; - } - return key; - } - - /** - * Allows enter key resources to be overridden - * @param res resources to grab given items from - * @param previewId preview drawable shown on enter key - * @param iconId normal drawable shown on enter key - * @param labelId string shown on enter key - */ - void setEnterKeyResources(Resources res, int previewId, int iconId, int labelId) { - if (mEnterKey != null) { - // Reset some of the rarely used attributes. - mEnterKey.popupCharacters = null; - mEnterKey.popupResId = 0; - mEnterKey.text = null; - - mEnterKey.iconPreview = res.getDrawable(previewId); - mEnterKey.icon = res.getDrawable(iconId); - mEnterKey.label = res.getText(labelId); - - // Set the initial size of the preview icon - if (mEnterKey.iconPreview != null) { - mEnterKey.iconPreview.setBounds(0, 0, - mEnterKey.iconPreview.getIntrinsicWidth(), - mEnterKey.iconPreview.getIntrinsicHeight()); - } - } - } - - /** - * Allows shiftlock to be turned on. See {@link #setShiftLocked(boolean)} - * - */ - void enableShiftLock() { - int i = 0; - for (int index : getShiftKeyIndices()) { - if (index >= 0 && i < mShiftKeys.length) { - mShiftKeys[i] = getKeys().get(index); - if (mShiftKeys[i] instanceof LatinKey) { - ((LatinKey)mShiftKeys[i]).enableShiftLock(); - } - mOldShiftIcons[i] = mShiftKeys[i].icon; - i++; - } - } - } - - /** - * Turn on shift lock. This turns on the LED for this key, if it has one. - * It should be followed by a call to {@link KeyboardView#invalidateKey(int)} - * or {@link KeyboardView#invalidateAllKeys()} - * - * @param shiftLocked - */ - void setShiftLocked(boolean shiftLocked) { - for (Key shiftKey : mShiftKeys) { - if (shiftKey != null) { - shiftKey.on = shiftLocked; - shiftKey.icon = mShiftLockIcon; - } - } - mShiftState = shiftLocked ? SHIFT_LOCKED : SHIFT_ON; - } - - /** - * Turn on shift mode. Sets shift mode and turns on icon for shift key. - * It should be followed by a call to {@link KeyboardView#invalidateKey(int)} - * or {@link KeyboardView#invalidateAllKeys()} - * - * @param shiftLocked - */ - @Override - public boolean setShifted(boolean shiftState) { - boolean shiftChanged = false; - if (shiftState == false) { - shiftChanged = mShiftState != SHIFT_OFF; - mShiftState = SHIFT_OFF; - } else if (mShiftState == SHIFT_OFF) { - shiftChanged = mShiftState == SHIFT_OFF; - mShiftState = SHIFT_ON; - } - for (int i = 0; i < mShiftKeys.length; i++) { - if (mShiftKeys[i] != null) { - if (shiftState == false) { - mShiftKeys[i].on = false; - mShiftKeys[i].icon = mOldShiftIcons[i]; - } else if (mShiftState == SHIFT_OFF) { - mShiftKeys[i].on = false; - mShiftKeys[i].icon = mShiftIcon; - } - } else { - // return super.setShifted(shiftState); - } - } - return shiftChanged; - } - - /** - * Whether or not keyboard is shifted. - * @return true if keyboard state is shifted. - */ - @Override - public boolean isShifted() { - if (mShiftKeys[0] != null) { - return mShiftState != SHIFT_OFF; - } else { - return super.isShifted(); - } - } - - static class LatinKey extends Keyboard.Key { - private boolean mShiftLockEnabled; - private boolean mEnabled = true; - - public LatinKey(Resources res, Keyboard.Row parent, int x, int y, - XmlResourceParser parser) { - super(res, parent, x, y, parser); - if (popupCharacters != null && popupCharacters.length() == 0) { - // If there is a keyboard with no keys specified in popupCharacters - popupResId = 0; - } - } - - void setEnabled(boolean enabled) { - mEnabled = enabled; - } - - void enableShiftLock() { - mShiftLockEnabled = true; - } - - @Override - public void onReleased(boolean inside) { - if (!mShiftLockEnabled) { - super.onReleased(inside); - } else { - pressed = !pressed; - } - } - - /** - * Overriding this method so that we can reduce the target area for certain keys. - */ - @Override - public boolean isInside(int x, int y) { - if (!mEnabled) { - return false; - } - final int code = codes[0]; - if (code == KEYCODE_SHIFT || code == KEYCODE_DELETE) { - y -= height / 10; - if (code == KEYCODE_SHIFT) x += width / 6; - if (code == KEYCODE_DELETE) x -= width / 6; - } else if (code == KEYCODE_SPACE) { - y += PasswordEntryKeyboard.sSpacebarVerticalCorrection; - } - return super.isInside(x, y); - } - } -} diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java deleted file mode 100644 index b2c9dc53da39..000000000000 --- a/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java +++ /dev/null @@ -1,340 +0,0 @@ -/* - * Copyright (C) 2010 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.internal.widget; - -import android.content.Context; -import android.content.res.Resources; -import android.inputmethodservice.Keyboard; -import android.inputmethodservice.KeyboardView; -import android.inputmethodservice.KeyboardView.OnKeyboardActionListener; -import android.os.SystemClock; -import android.provider.Settings; -import android.util.Log; -import android.view.HapticFeedbackConstants; -import android.view.KeyCharacterMap; -import android.view.KeyEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewGroup.LayoutParams; -import android.view.ViewRootImpl; -import com.android.internal.R; - -public class PasswordEntryKeyboardHelper implements OnKeyboardActionListener { - - public static final int KEYBOARD_MODE_ALPHA = 0; - public static final int KEYBOARD_MODE_NUMERIC = 1; - private static final int KEYBOARD_STATE_NORMAL = 0; - private static final int KEYBOARD_STATE_SHIFTED = 1; - private static final int KEYBOARD_STATE_CAPSLOCK = 2; - private static final String TAG = "PasswordEntryKeyboardHelper"; - private int mKeyboardMode = KEYBOARD_MODE_ALPHA; - private int mKeyboardState = KEYBOARD_STATE_NORMAL; - private PasswordEntryKeyboard mQwertyKeyboard; - private PasswordEntryKeyboard mQwertyKeyboardShifted; - private PasswordEntryKeyboard mSymbolsKeyboard; - private PasswordEntryKeyboard mSymbolsKeyboardShifted; - private PasswordEntryKeyboard mNumericKeyboard; - private final Context mContext; - private final View mTargetView; - private final KeyboardView mKeyboardView; - private long[] mVibratePattern; - private boolean mEnableHaptics = false; - - private static final int NUMERIC = 0; - private static final int QWERTY = 1; - private static final int QWERTY_SHIFTED = 2; - private static final int SYMBOLS = 3; - private static final int SYMBOLS_SHIFTED = 4; - - int mLayouts[] = new int[] { - R.xml.password_kbd_numeric, - R.xml.password_kbd_qwerty, - R.xml.password_kbd_qwerty_shifted, - R.xml.password_kbd_symbols, - R.xml.password_kbd_symbols_shift - }; - - private boolean mUsingScreenWidth; - - public PasswordEntryKeyboardHelper(Context context, KeyboardView keyboardView, View targetView) { - this(context, keyboardView, targetView, true, null); - } - - public PasswordEntryKeyboardHelper(Context context, KeyboardView keyboardView, View targetView, - boolean useFullScreenWidth) { - this(context, keyboardView, targetView, useFullScreenWidth, null); - } - - public PasswordEntryKeyboardHelper(Context context, KeyboardView keyboardView, View targetView, - boolean useFullScreenWidth, int layouts[]) { - mContext = context; - mTargetView = targetView; - mKeyboardView = keyboardView; - mKeyboardView.setOnKeyboardActionListener(this); - mUsingScreenWidth = useFullScreenWidth; - if (layouts != null) { - if (layouts.length != mLayouts.length) { - throw new RuntimeException("Wrong number of layouts"); - } - for (int i = 0; i < mLayouts.length; i++) { - mLayouts[i] = layouts[i]; - } - } - createKeyboards(); - } - - public void createKeyboards() { - LayoutParams lp = mKeyboardView.getLayoutParams(); - if (mUsingScreenWidth || lp.width == ViewGroup.LayoutParams.MATCH_PARENT) { - createKeyboardsWithDefaultWidth(); - } else { - createKeyboardsWithSpecificSize(lp.width, lp.height); - } - } - - public void setEnableHaptics(boolean enabled) { - mEnableHaptics = enabled; - } - - public boolean isAlpha() { - return mKeyboardMode == KEYBOARD_MODE_ALPHA; - } - - private void createKeyboardsWithSpecificSize(int width, int height) { - mNumericKeyboard = new PasswordEntryKeyboard(mContext, mLayouts[NUMERIC], width, height); - mQwertyKeyboard = new PasswordEntryKeyboard(mContext, mLayouts[QWERTY], R.id.mode_normal, - width, height); - mQwertyKeyboard.enableShiftLock(); - - mQwertyKeyboardShifted = new PasswordEntryKeyboard(mContext, mLayouts[QWERTY_SHIFTED], - R.id.mode_normal, width, height); - mQwertyKeyboardShifted.enableShiftLock(); - mQwertyKeyboardShifted.setShifted(true); // always shifted. - - mSymbolsKeyboard = new PasswordEntryKeyboard(mContext, mLayouts[SYMBOLS], width, height); - mSymbolsKeyboard.enableShiftLock(); - - mSymbolsKeyboardShifted = new PasswordEntryKeyboard(mContext, mLayouts[SYMBOLS_SHIFTED], - width, height); - mSymbolsKeyboardShifted.enableShiftLock(); - mSymbolsKeyboardShifted.setShifted(true); // always shifted - } - - private void createKeyboardsWithDefaultWidth() { - mNumericKeyboard = new PasswordEntryKeyboard(mContext, mLayouts[NUMERIC]); - mQwertyKeyboard = new PasswordEntryKeyboard(mContext, mLayouts[QWERTY], R.id.mode_normal); - mQwertyKeyboard.enableShiftLock(); - - mQwertyKeyboardShifted = new PasswordEntryKeyboard(mContext, mLayouts[QWERTY_SHIFTED], - R.id.mode_normal); - mQwertyKeyboardShifted.enableShiftLock(); - mQwertyKeyboardShifted.setShifted(true); // always shifted. - - mSymbolsKeyboard = new PasswordEntryKeyboard(mContext, mLayouts[SYMBOLS]); - mSymbolsKeyboard.enableShiftLock(); - - mSymbolsKeyboardShifted = new PasswordEntryKeyboard(mContext, mLayouts[SYMBOLS_SHIFTED]); - mSymbolsKeyboardShifted.enableShiftLock(); - mSymbolsKeyboardShifted.setShifted(true); // always shifted - } - - public void setKeyboardMode(int mode) { - switch (mode) { - case KEYBOARD_MODE_ALPHA: - mKeyboardView.setKeyboard(mQwertyKeyboard); - mKeyboardState = KEYBOARD_STATE_NORMAL; - final boolean visiblePassword = Settings.System.getInt( - mContext.getContentResolver(), - Settings.System.TEXT_SHOW_PASSWORD, 1) != 0; - final boolean enablePreview = false; // TODO: grab from configuration - mKeyboardView.setPreviewEnabled(visiblePassword && enablePreview); - break; - case KEYBOARD_MODE_NUMERIC: - mKeyboardView.setKeyboard(mNumericKeyboard); - mKeyboardState = KEYBOARD_STATE_NORMAL; - mKeyboardView.setPreviewEnabled(false); // never show popup for numeric keypad - break; - } - mKeyboardMode = mode; - } - - private void sendKeyEventsToTarget(int character) { - ViewRootImpl viewRootImpl = mTargetView.getViewRootImpl(); - KeyEvent[] events = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD).getEvents( - new char[] { (char) character }); - if (events != null) { - final int N = events.length; - for (int i=0; i<N; i++) { - KeyEvent event = events[i]; - event = KeyEvent.changeFlags(event, event.getFlags() - | KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE); - viewRootImpl.dispatchInputEvent(event); - } - } - } - - public void sendDownUpKeyEvents(int keyEventCode) { - long eventTime = SystemClock.uptimeMillis(); - ViewRootImpl viewRootImpl = mTargetView.getViewRootImpl(); - viewRootImpl.dispatchKeyFromIme( - new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, keyEventCode, 0, 0, - KeyCharacterMap.VIRTUAL_KEYBOARD, 0, - KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)); - viewRootImpl.dispatchKeyFromIme( - new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP, keyEventCode, 0, 0, - KeyCharacterMap.VIRTUAL_KEYBOARD, 0, - KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)); - } - - public void onKey(int primaryCode, int[] keyCodes) { - if (primaryCode == Keyboard.KEYCODE_DELETE) { - handleBackspace(); - } else if (primaryCode == Keyboard.KEYCODE_SHIFT) { - handleShift(); - } else if (primaryCode == Keyboard.KEYCODE_CANCEL) { - handleClose(); - return; - } else if (primaryCode == Keyboard.KEYCODE_MODE_CHANGE && mKeyboardView != null) { - handleModeChange(); - } else { - handleCharacter(primaryCode, keyCodes); - // Switch back to old keyboard if we're not in capslock mode - if (mKeyboardState == KEYBOARD_STATE_SHIFTED) { - // skip to the unlocked state - mKeyboardState = KEYBOARD_STATE_CAPSLOCK; - handleShift(); - } - } - } - - /** - * Sets and enables vibrate pattern. If id is 0 (or can't be loaded), vibrate is disabled. - * @param id resource id for array containing vibrate pattern. - */ - public void setVibratePattern(int id) { - int[] tmpArray = null; - try { - tmpArray = mContext.getResources().getIntArray(id); - } catch (Resources.NotFoundException e) { - if (id != 0) { - Log.e(TAG, "Vibrate pattern missing", e); - } - } - if (tmpArray == null) { - mVibratePattern = null; - return; - } - mVibratePattern = new long[tmpArray.length]; - for (int i = 0; i < tmpArray.length; i++) { - mVibratePattern[i] = tmpArray[i]; - } - } - - private void handleModeChange() { - final Keyboard current = mKeyboardView.getKeyboard(); - Keyboard next = null; - if (current == mQwertyKeyboard || current == mQwertyKeyboardShifted) { - next = mSymbolsKeyboard; - } else if (current == mSymbolsKeyboard || current == mSymbolsKeyboardShifted) { - next = mQwertyKeyboard; - } - if (next != null) { - mKeyboardView.setKeyboard(next); - mKeyboardState = KEYBOARD_STATE_NORMAL; - } - } - - public void handleBackspace() { - sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); - performHapticFeedback(); - } - - private void handleShift() { - if (mKeyboardView == null) { - return; - } - Keyboard current = mKeyboardView.getKeyboard(); - PasswordEntryKeyboard next = null; - final boolean isAlphaMode = current == mQwertyKeyboard - || current == mQwertyKeyboardShifted; - if (mKeyboardState == KEYBOARD_STATE_NORMAL) { - mKeyboardState = isAlphaMode ? KEYBOARD_STATE_SHIFTED : KEYBOARD_STATE_CAPSLOCK; - next = isAlphaMode ? mQwertyKeyboardShifted : mSymbolsKeyboardShifted; - } else if (mKeyboardState == KEYBOARD_STATE_SHIFTED) { - mKeyboardState = KEYBOARD_STATE_CAPSLOCK; - next = isAlphaMode ? mQwertyKeyboardShifted : mSymbolsKeyboardShifted; - } else if (mKeyboardState == KEYBOARD_STATE_CAPSLOCK) { - mKeyboardState = KEYBOARD_STATE_NORMAL; - next = isAlphaMode ? mQwertyKeyboard : mSymbolsKeyboard; - } - if (next != null) { - if (next != current) { - mKeyboardView.setKeyboard(next); - } - next.setShiftLocked(mKeyboardState == KEYBOARD_STATE_CAPSLOCK); - mKeyboardView.setShifted(mKeyboardState != KEYBOARD_STATE_NORMAL); - } - } - - private void handleCharacter(int primaryCode, int[] keyCodes) { - // Maybe turn off shift if not in capslock mode. - if (mKeyboardView.isShifted() && primaryCode != ' ' && primaryCode != '\n') { - primaryCode = Character.toUpperCase(primaryCode); - } - sendKeyEventsToTarget(primaryCode); - } - - private void handleClose() { - - } - - public void onPress(int primaryCode) { - performHapticFeedback(); - } - - private void performHapticFeedback() { - if (mEnableHaptics) { - mKeyboardView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, - HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING - | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); - } - } - - public void onRelease(int primaryCode) { - - } - - public void onText(CharSequence text) { - - } - - public void swipeDown() { - - } - - public void swipeLeft() { - - } - - public void swipeRight() { - - } - - public void swipeUp() { - - } -}; diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboardView.java b/core/java/com/android/internal/widget/PasswordEntryKeyboardView.java deleted file mode 100644 index d27346ba645e..000000000000 --- a/core/java/com/android/internal/widget/PasswordEntryKeyboardView.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2010 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.internal.widget; - -import android.content.Context; -import android.inputmethodservice.KeyboardView; -import android.util.AttributeSet; - -public class PasswordEntryKeyboardView extends KeyboardView { - - static final int KEYCODE_OPTIONS = -100; - static final int KEYCODE_SHIFT_LONGPRESS = -101; - static final int KEYCODE_VOICE = -102; - static final int KEYCODE_F1 = -103; - static final int KEYCODE_NEXT_LANGUAGE = -104; - - public PasswordEntryKeyboardView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public PasswordEntryKeyboardView(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public PasswordEntryKeyboardView( - Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - @Override - public boolean setShifted(boolean shifted) { - boolean result = super.setShifted(shifted); - // invalidate both shift keys - int[] indices = getKeyboard().getShiftKeyIndices(); - for (int index : indices) { - invalidateKey(index); - } - return result; - } - -} diff --git a/core/java/com/android/internal/widget/RotarySelector.java b/core/java/com/android/internal/widget/RotarySelector.java deleted file mode 100644 index 866f89b3a272..000000000000 --- a/core/java/com/android/internal/widget/RotarySelector.java +++ /dev/null @@ -1,780 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.widget; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Matrix; -import android.media.AudioAttributes; -import android.os.UserHandle; -import android.os.Vibrator; -import android.provider.Settings; -import android.util.AttributeSet; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; -import android.view.VelocityTracker; -import android.view.ViewConfiguration; -import android.view.animation.DecelerateInterpolator; - -import static android.view.animation.AnimationUtils.currentAnimationTimeMillis; - -import com.android.internal.R; - - -/** - * Custom view that presents up to two items that are selectable by rotating a semi-circle from - * left to right, or right to left. Used by incoming call screen, and the lock screen when no - * security pattern is set. - */ -public class RotarySelector extends View { - public static final int HORIZONTAL = 0; - public static final int VERTICAL = 1; - - private static final String LOG_TAG = "RotarySelector"; - private static final boolean DBG = false; - private static final boolean VISUAL_DEBUG = false; - - private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() - .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) - .build(); - - // Listener for onDialTrigger() callbacks. - private OnDialTriggerListener mOnDialTriggerListener; - - private float mDensity; - - // UI elements - private Bitmap mBackground; - private Bitmap mDimple; - private Bitmap mDimpleDim; - - private Bitmap mLeftHandleIcon; - private Bitmap mRightHandleIcon; - - private Bitmap mArrowShortLeftAndRight; - private Bitmap mArrowLongLeft; // Long arrow starting on the left, pointing clockwise - private Bitmap mArrowLongRight; // Long arrow starting on the right, pointing CCW - - // positions of the left and right handle - private int mLeftHandleX; - private int mRightHandleX; - - // current offset of rotary widget along the x axis - private int mRotaryOffsetX = 0; - - // state of the animation used to bring the handle back to its start position when - // the user lets go before triggering an action - private boolean mAnimating = false; - private long mAnimationStartTime; - private long mAnimationDuration; - private int mAnimatingDeltaXStart; // the animation will interpolate from this delta to zero - private int mAnimatingDeltaXEnd; - - private DecelerateInterpolator mInterpolator; - - private Paint mPaint = new Paint(); - - // used to rotate the background and arrow assets depending on orientation - final Matrix mBgMatrix = new Matrix(); - final Matrix mArrowMatrix = new Matrix(); - - /** - * If the user is currently dragging something. - */ - private int mGrabbedState = NOTHING_GRABBED; - public static final int NOTHING_GRABBED = 0; - public static final int LEFT_HANDLE_GRABBED = 1; - public static final int RIGHT_HANDLE_GRABBED = 2; - - /** - * Whether the user has triggered something (e.g dragging the left handle all the way over to - * the right). - */ - private boolean mTriggered = false; - - // Vibration (haptic feedback) - private Vibrator mVibrator; - private static final long VIBRATE_SHORT = 20; // msec - private static final long VIBRATE_LONG = 20; // msec - - /** - * The drawable for the arrows need to be scrunched this many dips towards the rotary bg below - * it. - */ - private static final int ARROW_SCRUNCH_DIP = 6; - - /** - * How far inset the left and right circles should be - */ - private static final int EDGE_PADDING_DIP = 9; - - /** - * How far from the edge of the screen the user must drag to trigger the event. - */ - private static final int EDGE_TRIGGER_DIP = 100; - - /** - * Dimensions of arc in background drawable. - */ - static final int OUTER_ROTARY_RADIUS_DIP = 390; - static final int ROTARY_STROKE_WIDTH_DIP = 83; - static final int SNAP_BACK_ANIMATION_DURATION_MILLIS = 300; - static final int SPIN_ANIMATION_DURATION_MILLIS = 800; - - private int mEdgeTriggerThresh; - private int mDimpleWidth; - private int mBackgroundWidth; - private int mBackgroundHeight; - private final int mOuterRadius; - private final int mInnerRadius; - private int mDimpleSpacing; - - private VelocityTracker mVelocityTracker; - private int mMinimumVelocity; - private int mMaximumVelocity; - - /** - * The number of dimples we are flinging when we do the "spin" animation. Used to know when to - * wrap the icons back around so they "rotate back" onto the screen. - * @see #updateAnimation() - */ - private int mDimplesOfFling = 0; - - /** - * Either {@link #HORIZONTAL} or {@link #VERTICAL}. - */ - private int mOrientation; - - - public RotarySelector(Context context) { - this(context, null); - } - - /** - * Constructor used when this widget is created from a layout file. - */ - public RotarySelector(Context context, AttributeSet attrs) { - super(context, attrs); - - TypedArray a = - context.obtainStyledAttributes(attrs, R.styleable.RotarySelector); - mOrientation = a.getInt(R.styleable.RotarySelector_orientation, HORIZONTAL); - a.recycle(); - - Resources r = getResources(); - mDensity = r.getDisplayMetrics().density; - if (DBG) log("- Density: " + mDensity); - - // Assets (all are BitmapDrawables). - mBackground = getBitmapFor(R.drawable.jog_dial_bg); - mDimple = getBitmapFor(R.drawable.jog_dial_dimple); - mDimpleDim = getBitmapFor(R.drawable.jog_dial_dimple_dim); - - mArrowLongLeft = getBitmapFor(R.drawable.jog_dial_arrow_long_left_green); - mArrowLongRight = getBitmapFor(R.drawable.jog_dial_arrow_long_right_red); - mArrowShortLeftAndRight = getBitmapFor(R.drawable.jog_dial_arrow_short_left_and_right); - - mInterpolator = new DecelerateInterpolator(1f); - - mEdgeTriggerThresh = (int) (mDensity * EDGE_TRIGGER_DIP); - - mDimpleWidth = mDimple.getWidth(); - - mBackgroundWidth = mBackground.getWidth(); - mBackgroundHeight = mBackground.getHeight(); - mOuterRadius = (int) (mDensity * OUTER_ROTARY_RADIUS_DIP); - mInnerRadius = (int) ((OUTER_ROTARY_RADIUS_DIP - ROTARY_STROKE_WIDTH_DIP) * mDensity); - - final ViewConfiguration configuration = ViewConfiguration.get(mContext); - mMinimumVelocity = configuration.getScaledMinimumFlingVelocity() * 2; - mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); - } - - private Bitmap getBitmapFor(int resId) { - return BitmapFactory.decodeResource(getContext().getResources(), resId); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - - final int edgePadding = (int) (EDGE_PADDING_DIP * mDensity); - mLeftHandleX = edgePadding + mDimpleWidth / 2; - final int length = isHoriz() ? w : h; - mRightHandleX = length - edgePadding - mDimpleWidth / 2; - mDimpleSpacing = (length / 2) - mLeftHandleX; - - // bg matrix only needs to be calculated once - mBgMatrix.setTranslate(0, 0); - if (!isHoriz()) { - // set up matrix for translating drawing of background and arrow assets - final int left = w - mBackgroundHeight; - mBgMatrix.preRotate(-90, 0, 0); - mBgMatrix.postTranslate(left, h); - - } else { - mBgMatrix.postTranslate(0, h - mBackgroundHeight); - } - } - - private boolean isHoriz() { - return mOrientation == HORIZONTAL; - } - - /** - * Sets the left handle icon to a given resource. - * - * The resource should refer to a Drawable object, or use 0 to remove - * the icon. - * - * @param resId the resource ID. - */ - public void setLeftHandleResource(int resId) { - if (resId != 0) { - mLeftHandleIcon = getBitmapFor(resId); - } - invalidate(); - } - - /** - * Sets the right handle icon to a given resource. - * - * The resource should refer to a Drawable object, or use 0 to remove - * the icon. - * - * @param resId the resource ID. - */ - public void setRightHandleResource(int resId) { - if (resId != 0) { - mRightHandleIcon = getBitmapFor(resId); - } - invalidate(); - } - - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final int length = isHoriz() ? - MeasureSpec.getSize(widthMeasureSpec) : - MeasureSpec.getSize(heightMeasureSpec); - final int arrowScrunch = (int) (ARROW_SCRUNCH_DIP * mDensity); - final int arrowH = mArrowShortLeftAndRight.getHeight(); - - // by making the height less than arrow + bg, arrow and bg will be scrunched together, - // overlaying somewhat (though on transparent portions of the drawable). - // this works because the arrows are drawn from the top, and the rotary bg is drawn - // from the bottom. - final int height = mBackgroundHeight + arrowH - arrowScrunch; - - if (isHoriz()) { - setMeasuredDimension(length, height); - } else { - setMeasuredDimension(height, length); - } - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - final int width = getWidth(); - - if (VISUAL_DEBUG) { - // draw bounding box around widget - mPaint.setColor(0xffff0000); - mPaint.setStyle(Paint.Style.STROKE); - canvas.drawRect(0, 0, width, getHeight(), mPaint); - } - - final int height = getHeight(); - - // update animating state before we draw anything - if (mAnimating) { - updateAnimation(); - } - - // Background: - canvas.drawBitmap(mBackground, mBgMatrix, mPaint); - - // Draw the correct arrow(s) depending on the current state: - mArrowMatrix.reset(); - switch (mGrabbedState) { - case NOTHING_GRABBED: - //mArrowShortLeftAndRight; - break; - case LEFT_HANDLE_GRABBED: - mArrowMatrix.setTranslate(0, 0); - if (!isHoriz()) { - mArrowMatrix.preRotate(-90, 0, 0); - mArrowMatrix.postTranslate(0, height); - } - canvas.drawBitmap(mArrowLongLeft, mArrowMatrix, mPaint); - break; - case RIGHT_HANDLE_GRABBED: - mArrowMatrix.setTranslate(0, 0); - if (!isHoriz()) { - mArrowMatrix.preRotate(-90, 0, 0); - // since bg width is > height of screen in landscape mode... - mArrowMatrix.postTranslate(0, height + (mBackgroundWidth - height)); - } - canvas.drawBitmap(mArrowLongRight, mArrowMatrix, mPaint); - break; - default: - throw new IllegalStateException("invalid mGrabbedState: " + mGrabbedState); - } - - final int bgHeight = mBackgroundHeight; - final int bgTop = isHoriz() ? - height - bgHeight: - width - bgHeight; - - if (VISUAL_DEBUG) { - // draw circle bounding arc drawable: good sanity check we're doing the math correctly - float or = OUTER_ROTARY_RADIUS_DIP * mDensity; - final int vOffset = mBackgroundWidth - height; - final int midX = isHoriz() ? width / 2 : mBackgroundWidth / 2 - vOffset; - if (isHoriz()) { - canvas.drawCircle(midX, or + bgTop, or, mPaint); - } else { - canvas.drawCircle(or + bgTop, midX, or, mPaint); - } - } - - // left dimple / icon - { - final int xOffset = mLeftHandleX + mRotaryOffsetX; - final int drawableY = getYOnArc( - mBackgroundWidth, - mInnerRadius, - mOuterRadius, - xOffset); - final int x = isHoriz() ? xOffset : drawableY + bgTop; - final int y = isHoriz() ? drawableY + bgTop : height - xOffset; - if (mGrabbedState != RIGHT_HANDLE_GRABBED) { - drawCentered(mDimple, canvas, x, y); - drawCentered(mLeftHandleIcon, canvas, x, y); - } else { - drawCentered(mDimpleDim, canvas, x, y); - } - } - - // center dimple - { - final int xOffset = isHoriz() ? - width / 2 + mRotaryOffsetX: - height / 2 + mRotaryOffsetX; - final int drawableY = getYOnArc( - mBackgroundWidth, - mInnerRadius, - mOuterRadius, - xOffset); - - if (isHoriz()) { - drawCentered(mDimpleDim, canvas, xOffset, drawableY + bgTop); - } else { - // vertical - drawCentered(mDimpleDim, canvas, drawableY + bgTop, height - xOffset); - } - } - - // right dimple / icon - { - final int xOffset = mRightHandleX + mRotaryOffsetX; - final int drawableY = getYOnArc( - mBackgroundWidth, - mInnerRadius, - mOuterRadius, - xOffset); - - final int x = isHoriz() ? xOffset : drawableY + bgTop; - final int y = isHoriz() ? drawableY + bgTop : height - xOffset; - if (mGrabbedState != LEFT_HANDLE_GRABBED) { - drawCentered(mDimple, canvas, x, y); - drawCentered(mRightHandleIcon, canvas, x, y); - } else { - drawCentered(mDimpleDim, canvas, x, y); - } - } - - // draw extra left hand dimples - int dimpleLeft = mRotaryOffsetX + mLeftHandleX - mDimpleSpacing; - final int halfdimple = mDimpleWidth / 2; - while (dimpleLeft > -halfdimple) { - final int drawableY = getYOnArc( - mBackgroundWidth, - mInnerRadius, - mOuterRadius, - dimpleLeft); - - if (isHoriz()) { - drawCentered(mDimpleDim, canvas, dimpleLeft, drawableY + bgTop); - } else { - drawCentered(mDimpleDim, canvas, drawableY + bgTop, height - dimpleLeft); - } - dimpleLeft -= mDimpleSpacing; - } - - // draw extra right hand dimples - int dimpleRight = mRotaryOffsetX + mRightHandleX + mDimpleSpacing; - final int rightThresh = mRight + halfdimple; - while (dimpleRight < rightThresh) { - final int drawableY = getYOnArc( - mBackgroundWidth, - mInnerRadius, - mOuterRadius, - dimpleRight); - - if (isHoriz()) { - drawCentered(mDimpleDim, canvas, dimpleRight, drawableY + bgTop); - } else { - drawCentered(mDimpleDim, canvas, drawableY + bgTop, height - dimpleRight); - } - dimpleRight += mDimpleSpacing; - } - } - - /** - * Assuming bitmap is a bounding box around a piece of an arc drawn by two concentric circles - * (as the background drawable for the rotary widget is), and given an x coordinate along the - * drawable, return the y coordinate of a point on the arc that is between the two concentric - * circles. The resulting y combined with the incoming x is a point along the circle in - * between the two concentric circles. - * - * @param backgroundWidth The width of the asset (the bottom of the box surrounding the arc). - * @param innerRadius The radius of the circle that intersects the drawable at the bottom two - * corders of the drawable (top two corners in terms of drawing coordinates). - * @param outerRadius The radius of the circle who's top most point is the top center of the - * drawable (bottom center in terms of drawing coordinates). - * @param x The distance along the x axis of the desired point. @return The y coordinate, in drawing coordinates, that will place (x, y) along the circle - * in between the two concentric circles. - */ - private int getYOnArc(int backgroundWidth, int innerRadius, int outerRadius, int x) { - - // the hypotenuse - final int halfWidth = (outerRadius - innerRadius) / 2; - final int middleRadius = innerRadius + halfWidth; - - // the bottom leg of the triangle - final int triangleBottom = (backgroundWidth / 2) - x; - - // "Our offense is like the pythagorean theorem: There is no answer!" - Shaquille O'Neal - final int triangleY = - (int) Math.sqrt(middleRadius * middleRadius - triangleBottom * triangleBottom); - - // convert to drawing coordinates: - // middleRadius - triangleY = - // the vertical distance from the outer edge of the circle to the desired point - // from there we add the distance from the top of the drawable to the middle circle - return middleRadius - triangleY + halfWidth; - } - - /** - * Handle touch screen events. - * - * @param event The motion event. - * @return True if the event was handled, false otherwise. - */ - @Override - public boolean onTouchEvent(MotionEvent event) { - if (mAnimating) { - return true; - } - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(event); - - final int height = getHeight(); - - final int eventX = isHoriz() ? - (int) event.getX(): - height - ((int) event.getY()); - final int hitWindow = mDimpleWidth; - - final int action = event.getAction(); - switch (action) { - case MotionEvent.ACTION_DOWN: - if (DBG) log("touch-down"); - mTriggered = false; - if (mGrabbedState != NOTHING_GRABBED) { - reset(); - invalidate(); - } - if (eventX < mLeftHandleX + hitWindow) { - mRotaryOffsetX = eventX - mLeftHandleX; - setGrabbedState(LEFT_HANDLE_GRABBED); - invalidate(); - vibrate(VIBRATE_SHORT); - } else if (eventX > mRightHandleX - hitWindow) { - mRotaryOffsetX = eventX - mRightHandleX; - setGrabbedState(RIGHT_HANDLE_GRABBED); - invalidate(); - vibrate(VIBRATE_SHORT); - } - break; - - case MotionEvent.ACTION_MOVE: - if (DBG) log("touch-move"); - if (mGrabbedState == LEFT_HANDLE_GRABBED) { - mRotaryOffsetX = eventX - mLeftHandleX; - invalidate(); - final int rightThresh = isHoriz() ? getRight() : height; - if (eventX >= rightThresh - mEdgeTriggerThresh && !mTriggered) { - mTriggered = true; - dispatchTriggerEvent(OnDialTriggerListener.LEFT_HANDLE); - final VelocityTracker velocityTracker = mVelocityTracker; - velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); - final int rawVelocity = isHoriz() ? - (int) velocityTracker.getXVelocity(): - -(int) velocityTracker.getYVelocity(); - final int velocity = Math.max(mMinimumVelocity, rawVelocity); - mDimplesOfFling = Math.max( - 8, - Math.abs(velocity / mDimpleSpacing)); - startAnimationWithVelocity( - eventX - mLeftHandleX, - mDimplesOfFling * mDimpleSpacing, - velocity); - } - } else if (mGrabbedState == RIGHT_HANDLE_GRABBED) { - mRotaryOffsetX = eventX - mRightHandleX; - invalidate(); - if (eventX <= mEdgeTriggerThresh && !mTriggered) { - mTriggered = true; - dispatchTriggerEvent(OnDialTriggerListener.RIGHT_HANDLE); - final VelocityTracker velocityTracker = mVelocityTracker; - velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); - final int rawVelocity = isHoriz() ? - (int) velocityTracker.getXVelocity(): - - (int) velocityTracker.getYVelocity(); - final int velocity = Math.min(-mMinimumVelocity, rawVelocity); - mDimplesOfFling = Math.max( - 8, - Math.abs(velocity / mDimpleSpacing)); - startAnimationWithVelocity( - eventX - mRightHandleX, - -(mDimplesOfFling * mDimpleSpacing), - velocity); - } - } - break; - case MotionEvent.ACTION_UP: - if (DBG) log("touch-up"); - // handle animating back to start if they didn't trigger - if (mGrabbedState == LEFT_HANDLE_GRABBED - && Math.abs(eventX - mLeftHandleX) > 5) { - // set up "snap back" animation - startAnimation(eventX - mLeftHandleX, 0, SNAP_BACK_ANIMATION_DURATION_MILLIS); - } else if (mGrabbedState == RIGHT_HANDLE_GRABBED - && Math.abs(eventX - mRightHandleX) > 5) { - // set up "snap back" animation - startAnimation(eventX - mRightHandleX, 0, SNAP_BACK_ANIMATION_DURATION_MILLIS); - } - mRotaryOffsetX = 0; - setGrabbedState(NOTHING_GRABBED); - invalidate(); - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); // wishin' we had generational GC - mVelocityTracker = null; - } - break; - case MotionEvent.ACTION_CANCEL: - if (DBG) log("touch-cancel"); - reset(); - invalidate(); - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - break; - } - return true; - } - - private void startAnimation(int startX, int endX, int duration) { - mAnimating = true; - mAnimationStartTime = currentAnimationTimeMillis(); - mAnimationDuration = duration; - mAnimatingDeltaXStart = startX; - mAnimatingDeltaXEnd = endX; - setGrabbedState(NOTHING_GRABBED); - mDimplesOfFling = 0; - invalidate(); - } - - private void startAnimationWithVelocity(int startX, int endX, int pixelsPerSecond) { - mAnimating = true; - mAnimationStartTime = currentAnimationTimeMillis(); - mAnimationDuration = 1000 * (endX - startX) / pixelsPerSecond; - mAnimatingDeltaXStart = startX; - mAnimatingDeltaXEnd = endX; - setGrabbedState(NOTHING_GRABBED); - invalidate(); - } - - private void updateAnimation() { - final long millisSoFar = currentAnimationTimeMillis() - mAnimationStartTime; - final long millisLeft = mAnimationDuration - millisSoFar; - final int totalDeltaX = mAnimatingDeltaXStart - mAnimatingDeltaXEnd; - final boolean goingRight = totalDeltaX < 0; - if (DBG) log("millisleft for animating: " + millisLeft); - if (millisLeft <= 0) { - reset(); - return; - } - // from 0 to 1 as animation progresses - float interpolation = - mInterpolator.getInterpolation((float) millisSoFar / mAnimationDuration); - final int dx = (int) (totalDeltaX * (1 - interpolation)); - mRotaryOffsetX = mAnimatingDeltaXEnd + dx; - - // once we have gone far enough to animate the current buttons off screen, we start - // wrapping the offset back to the other side so that when the animation is finished, - // the buttons will come back into their original places. - if (mDimplesOfFling > 0) { - if (!goingRight && mRotaryOffsetX < -3 * mDimpleSpacing) { - // wrap around on fling left - mRotaryOffsetX += mDimplesOfFling * mDimpleSpacing; - } else if (goingRight && mRotaryOffsetX > 3 * mDimpleSpacing) { - // wrap around on fling right - mRotaryOffsetX -= mDimplesOfFling * mDimpleSpacing; - } - } - invalidate(); - } - - private void reset() { - mAnimating = false; - mRotaryOffsetX = 0; - mDimplesOfFling = 0; - setGrabbedState(NOTHING_GRABBED); - mTriggered = false; - } - - /** - * Triggers haptic feedback. - */ - private synchronized void vibrate(long duration) { - final boolean hapticEnabled = Settings.System.getIntForUser( - mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 1, - UserHandle.USER_CURRENT) != 0; - if (hapticEnabled) { - if (mVibrator == null) { - mVibrator = (android.os.Vibrator) getContext() - .getSystemService(Context.VIBRATOR_SERVICE); - } - mVibrator.vibrate(duration, VIBRATION_ATTRIBUTES); - } - } - - /** - * Draw the bitmap so that it's centered - * on the point (x,y), then draws it using specified canvas. - * TODO: is there already a utility method somewhere for this? - */ - private void drawCentered(Bitmap d, Canvas c, int x, int y) { - int w = d.getWidth(); - int h = d.getHeight(); - - c.drawBitmap(d, x - (w / 2), y - (h / 2), mPaint); - } - - - /** - * Registers a callback to be invoked when the dial - * is "triggered" by rotating it one way or the other. - * - * @param l the OnDialTriggerListener to attach to this view - */ - public void setOnDialTriggerListener(OnDialTriggerListener l) { - mOnDialTriggerListener = l; - } - - /** - * Dispatches a trigger event to our listener. - */ - private void dispatchTriggerEvent(int whichHandle) { - vibrate(VIBRATE_LONG); - if (mOnDialTriggerListener != null) { - mOnDialTriggerListener.onDialTrigger(this, whichHandle); - } - } - - /** - * Sets the current grabbed state, and dispatches a grabbed state change - * event to our listener. - */ - private void setGrabbedState(int newState) { - if (newState != mGrabbedState) { - mGrabbedState = newState; - if (mOnDialTriggerListener != null) { - mOnDialTriggerListener.onGrabbedStateChange(this, mGrabbedState); - } - } - } - - /** - * Interface definition for a callback to be invoked when the dial - * is "triggered" by rotating it one way or the other. - */ - public interface OnDialTriggerListener { - /** - * The dial was triggered because the user grabbed the left handle, - * and rotated the dial clockwise. - */ - public static final int LEFT_HANDLE = 1; - - /** - * The dial was triggered because the user grabbed the right handle, - * and rotated the dial counterclockwise. - */ - public static final int RIGHT_HANDLE = 2; - - /** - * Called when the dial is triggered. - * - * @param v The view that was triggered - * @param whichHandle Which "dial handle" the user grabbed, - * either {@link #LEFT_HANDLE}, {@link #RIGHT_HANDLE}. - */ - void onDialTrigger(View v, int whichHandle); - - /** - * Called when the "grabbed state" changes (i.e. when - * the user either grabs or releases one of the handles.) - * - * @param v the view that was triggered - * @param grabbedState the new state: either {@link #NOTHING_GRABBED}, - * {@link #LEFT_HANDLE_GRABBED}, or {@link #RIGHT_HANDLE_GRABBED}. - */ - void onGrabbedStateChange(View v, int grabbedState); - } - - - // Debugging / testing code - - private void log(String msg) { - Log.d(LOG_TAG, msg); - } -} diff --git a/core/jni/Android.mk b/core/jni/Android.mk index a1f8e92961d6..68034f65c39a 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -82,6 +82,7 @@ LOCAL_SRC_FILES:= \ android_text_AndroidBidi.cpp \ android_text_StaticLayout.cpp \ android_os_Debug.cpp \ + android_os_GraphicsEnvironment.cpp \ android_os_HwBinder.cpp \ android_os_HwBlob.cpp \ android_os_HwParcel.cpp \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index c195cfe23bfb..fb5d0373725a 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -150,6 +150,7 @@ extern int register_android_database_SQLiteGlobal(JNIEnv* env); extern int register_android_database_SQLiteDebug(JNIEnv* env); extern int register_android_nio_utils(JNIEnv* env); extern int register_android_os_Debug(JNIEnv* env); +extern int register_android_os_GraphicsEnvironment(JNIEnv* env); extern int register_android_os_HwBinder(JNIEnv *env); extern int register_android_os_HwBlob(JNIEnv *env); extern int register_android_os_HwParcel(JNIEnv *env); @@ -1355,6 +1356,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_database_SQLiteDebug), REG_JNI(register_android_os_Debug), REG_JNI(register_android_os_FileObserver), + REG_JNI(register_android_os_GraphicsEnvironment), REG_JNI(register_android_os_MessageQueue), REG_JNI(register_android_os_SELinux), REG_JNI(register_android_os_Trace), diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp new file mode 100644 index 000000000000..905a85adc551 --- /dev/null +++ b/core/jni/android_os_GraphicsEnvironment.cpp @@ -0,0 +1,44 @@ +/* + * Copyright 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "GraphicsEnvironment" + +#include <gui/GraphicsEnv.h> +#include <nativehelper/ScopedUtfChars.h> +#include "core_jni_helpers.h" + +namespace { + +void setDriverPath(JNIEnv* env, jobject clazz, jstring path) { + ScopedUtfChars pathChars(env, path); + android::GraphicsEnv::getInstance().setDriverPath(pathChars.c_str()); +} + +const JNINativeMethod g_methods[] = { + { "setDriverPath", "(Ljava/lang/String;)V", reinterpret_cast<void*>(setDriverPath) }, +}; + +const char* const kGraphicsEnvironmentName = "android/os/GraphicsEnvironment"; + +} // anonymous namespace + +namespace android { + +int register_android_os_GraphicsEnvironment(JNIEnv* env) { + return RegisterMethodsOrDie(env, kGraphicsEnvironmentName, g_methods, NELEM(g_methods)); +} + +} // namespace android diff --git a/core/jni/android_os_HwBinder.h b/core/jni/android_os_HwBinder.h index 2ebc38164da8..fa8fe01d6e93 100644 --- a/core/jni/android_os_HwBinder.h +++ b/core/jni/android_os_HwBinder.h @@ -24,7 +24,7 @@ namespace android { -struct JHwBinder : public hardware::BBinder { +struct JHwBinder : public hardware::BHwBinder { static void InitClass(JNIEnv *env); static sp<JHwBinder> SetNativeContext( diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index e171cce93642..dd33718dfb2a 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3178,6 +3178,18 @@ and subtype in order to provide the consistent user experience in switching between IMEs and subtypes. --> <attr name="supportsSwitchingToNextInputMethod" format="boolean" /> + <!-- Set to true if this input method supports ways to dismiss the windows assigned to + the input method (e.g. a dismiss button rendered by the input method itself). The + System UI may optimize the UI by not showing system-level dismiss button if this + value is true. + <p> Must be a boolean value, either "true" or "false". The default value is "false". + <p> This may also be a reference to a resource (in the form "@[package:]type:name") + or theme attribute (in the form "?[package:]type:name") containing a value of this + type. + <p> A UI element that dismisses the input method window should report + {@link android.view.accessibility.AccessibilityNodeInfo#ACTION_DISMISS} action, so + that accessibility services can handle it accordingly. --> + <attr name="supportsDismissingWindow" format="boolean" /> </declare-styleable> <!-- This is the subtype of InputMethod. Subtype can describe locales (e.g. en_US, fr_FR...) @@ -5153,13 +5165,6 @@ <attr name="layout_scale" format="float" /> </declare-styleable> - <!-- attributes for internal rotary widget used in lock screen and phone app - @hide --> - <declare-styleable name="RotarySelector"> - <!-- Use "horizontal" or "vertical". The default is horizontal. --> - <attr name="orientation" /> - </declare-styleable> - <!-- @hide --> <declare-styleable name="WeightedLinearLayout"> <attr name="majorWeightMin" format="float" /> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 73cba89a32d2..064d31e84cca 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2784,6 +2784,7 @@ <public name="focusedByDefault" /> <public name="appCategory" /> <public name="autoSizeMaxTextSize" /> + <public name="supportsDismissingWindow" /> </public-group> <public-group type="style" first-id="0x010302e0"> diff --git a/core/tests/coretests/res/xml/ime_meta.xml b/core/tests/coretests/res/xml/ime_meta.xml new file mode 100644 index 000000000000..a975718c50e9 --- /dev/null +++ b/core/tests/coretests/res/xml/ime_meta.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<input-method + xmlns:android="http://schemas.android.com/apk/res/android" + android:settingsActivity="com.android.inputmethod.latin.settings.SettingsActivity" +> + <subtype + android:label="subtype1" + android:imeSubtypeLocale="en_US" + android:imeSubtypeMode="keyboard" /> +</input-method> diff --git a/core/tests/coretests/res/xml/ime_meta_dismiss.xml b/core/tests/coretests/res/xml/ime_meta_dismiss.xml new file mode 100644 index 000000000000..59f8ecc278ad --- /dev/null +++ b/core/tests/coretests/res/xml/ime_meta_dismiss.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<input-method + xmlns:android="http://schemas.android.com/apk/res/android" + android:settingsActivity="com.android.inputmethod.latin.settings.SettingsActivity" + android:supportsDismissingWindow="true" +> + <subtype + android:label="subtype1" + android:imeSubtypeLocale="en_US" + android:imeSubtypeMode="keyboard" /> +</input-method> diff --git a/core/tests/coretests/res/xml/ime_meta_sw_next.xml b/core/tests/coretests/res/xml/ime_meta_sw_next.xml new file mode 100644 index 000000000000..2e2ee33e9933 --- /dev/null +++ b/core/tests/coretests/res/xml/ime_meta_sw_next.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<input-method + xmlns:android="http://schemas.android.com/apk/res/android" + android:settingsActivity="com.android.inputmethod.latin.settings.SettingsActivity" + android:supportsSwitchingToNextInputMethod="true" +> + <subtype + android:label="subtype1" + android:imeSubtypeLocale="en_US" + android:imeSubtypeMode="keyboard" /> +</input-method> diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java new file mode 100644 index 000000000000..23dc80f97b61 --- /dev/null +++ b/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.inputmethod; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import android.annotation.XmlRes; +import android.content.Context; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.os.Bundle; +import android.os.Parcel; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import com.android.frameworks.coretests.R; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class InputMethodInfoTest { + + @Test + public void testEqualsAndHashCode() throws Exception { + final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta); + final InputMethodInfo clone = cloneViaParcel(imi); + + assertThat(clone.equals(imi), is(true)); + assertThat(clone.hashCode(), equalTo(imi.hashCode())); + } + + @Test + public void testBooleanAttributes_DefaultValues() throws Exception { + final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta); + + assertThat(imi.supportsSwitchingToNextInputMethod(), is(false)); + assertThat(imi.supportsDismissingWindow(), is(false)); + + final InputMethodInfo clone = cloneViaParcel(imi); + + assertThat(clone.supportsSwitchingToNextInputMethod(), is(false)); + assertThat(clone.supportsDismissingWindow(), is(false)); + } + + @Test + public void testSupportsSwitchingToNextInputMethod() throws Exception { + final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta_sw_next); + + assertThat(imi.supportsSwitchingToNextInputMethod(), is(true)); + + final InputMethodInfo clone = cloneViaParcel(imi); + + assertThat(clone.supportsSwitchingToNextInputMethod(), is(true)); + } + + @Test + public void testSupportsDismissingWindow() throws Exception { + final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta_dismiss); + + assertThat(imi.supportsDismissingWindow(), is(true)); + + final InputMethodInfo clone = cloneViaParcel(imi); + + assertThat(clone.supportsDismissingWindow(), is(true)); + } + + private InputMethodInfo buildInputMethodForTest(final @XmlRes int metaDataRes) + throws Exception { + final Context context = InstrumentationRegistry.getContext(); + final ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.applicationInfo = context.getApplicationInfo(); + serviceInfo.packageName = context.getPackageName(); + serviceInfo.name = "DummyImeForTest"; + serviceInfo.metaData = new Bundle(); + serviceInfo.metaData.putInt(InputMethod.SERVICE_META_DATA, metaDataRes); + final ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.serviceInfo = serviceInfo; + return new InputMethodInfo(context, resolveInfo, null /* additionalSubtypesMap */); + } + + private InputMethodInfo cloneViaParcel(final InputMethodInfo original) { + Parcel parcel = null; + try { + parcel = Parcel.obtain(); + original.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + return InputMethodInfo.CREATOR.createFromParcel(parcel); + } finally { + if (parcel != null) { + parcel.recycle(); + } + } + } +} diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java index 8385b69f091d..aab4698fc49a 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -124,7 +124,7 @@ public class ChooserActivityTest { } @Test - public void updateChooserCountsAfterUserSelection() throws InterruptedException { + public void updateChooserCountsAndModelAfterUserSelection() throws InterruptedException { Intent sendIntent = createSendImageIntent(); List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); @@ -142,19 +142,15 @@ public class ChooserActivityTest { sOverrides.onSafelyStartCallback = targetInfo -> { return true; }; - String action = sendIntent.getAction(); - String annotation = sendIntent.getType(); ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0); - String packageName = toChoose.activityInfo.packageName; - long toChooseCount = getCount(usm, packageName, action, annotation); onView(withText(toChoose.activityInfo.name)) .perform(click()); waitForIdle(); verify(sOverrides.resolverListController, times(1)) + .updateChooserCounts(Mockito.anyString(), Mockito.anyInt(), Mockito.anyString()); + verify(sOverrides.resolverListController, times(1)) .updateModel(toChoose.activityInfo.getComponentName()); assertThat(activity.getIsSelected(), is(true)); - long updatedCount = getCount(usm, packageName, action, annotation); - assertThat(updatedCount, is(toChooseCount + 1l)); } @Test diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java index ba5206adeb77..34c34d7c07bb 100644 --- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java +++ b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java @@ -74,7 +74,8 @@ public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTe } final InputMethodInfo imi = new InputMethodInfo(ri, DUMMY_IS_AUX_IME, DUMMY_SETTING_ACTIVITY_NAME, subtypes, DUMMY_IS_DEFAULT_RES_ID, - DUMMY_FORCE_DEFAULT, supportsSwitchingToNextInputMethod); + DUMMY_FORCE_DEFAULT, supportsSwitchingToNextInputMethod, + false /* supportsDismissingWindow */); if (subtypes == null) { items.add(new ImeSubtypeListItem(imeName, null /* variableName */, imi, NOT_A_SUBTYPE_ID, null, SYSTEM_LOCALE)); diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java index 70339005581f..d2873e968e33 100644 --- a/location/java/android/location/GnssMeasurement.java +++ b/location/java/android/location/GnssMeasurement.java @@ -47,6 +47,7 @@ public final class GnssMeasurement implements Parcelable { private double mCarrierPhaseUncertainty; private int mMultipathIndicator; private double mSnrInDb; + private double mAgcLevelDb; // The following enumerations must be in sync with the values declared in gps.h @@ -56,6 +57,7 @@ public final class GnssMeasurement implements Parcelable { private static final int HAS_CARRIER_CYCLES = (1<<10); private static final int HAS_CARRIER_PHASE = (1<<11); private static final int HAS_CARRIER_PHASE_UNCERTAINTY = (1<<12); + private static final int HAS_AUTOMATIC_GAIN_CONTROL = (1<<13); /** * The status of the multipath indicator. @@ -111,6 +113,20 @@ public final class GnssMeasurement implements Parcelable { public static final int STATE_GAL_E1B_PAGE_SYNC = (1<<12); /** This SBAS measurement's tracking state has whole second level sync. */ public static final int STATE_SBAS_SYNC = (1<<13); + /** + * This GNSS measurement's tracking state has time-of-week known, possibly not decoded + * over the air but has been determined from other sources. If TOW decoded is set then TOW Known + * will also be set. + * @hide + */ + public static final int STATE_TOW_KNOWN = (1<<14); + /** + * This Glonass measurement's tracking state has time-of-day known, possibly not decoded + * over the air but has been determined from other sources. If TOD decoded is set then TOD Known + * will also be set. + * @hide + */ + public static final int STATE_GLO_TOD_KNOWN = (1<<15); /** * All the GNSS receiver state flags, for bit masking purposes (not a sensible state for any @@ -180,6 +196,7 @@ public final class GnssMeasurement implements Parcelable { mCarrierPhaseUncertainty = measurement.mCarrierPhaseUncertainty; mMultipathIndicator = measurement.mMultipathIndicator; mSnrInDb = measurement.mSnrInDb; + mAgcLevelDb = measurement.mAgcLevelDb; } /** @@ -299,6 +316,9 @@ public final class GnssMeasurement implements Parcelable { if ((mState & STATE_TOW_DECODED) != 0) { builder.append("TowDecoded|"); } + if ((mState & STATE_TOW_KNOWN) != 0) { + builder.append("TowKnown|"); + } if ((mState & STATE_MSEC_AMBIGUOUS) != 0) { builder.append("MsecAmbiguous|"); } @@ -311,6 +331,9 @@ public final class GnssMeasurement implements Parcelable { if ((mState & STATE_GLO_TOD_DECODED) != 0) { builder.append("GloTodDecoded|"); } + if ((mState & STATE_GLO_TOD_KNOWN) != 0) { + builder.append("GloTodKnown|"); + } if ((mState & STATE_BDS_D2_BIT_SYNC) != 0) { builder.append("BdsD2BitSync|"); } @@ -356,10 +379,14 @@ public final class GnssMeasurement implements Parcelable { * C/A code lock : [ 0 1ms ] : STATE_CODE_LOCK is set * Bit sync : [ 0 20ms ] : STATE_BIT_SYNC is set * Subframe sync : [ 0 6s ] : STATE_SUBFRAME_SYNC is set - * TOW decoded : [ 0 1week ] : STATE_TOW_DECODED is set</pre> + * TOW decoded : [ 0 1week ] : STATE_TOW_DECODED is set + * TOW Known : [ 0 1week ] : STATE_TOW_KNOWN set</pre> + * + * Note: TOW Known refers to the case where TOW is possibly not decoded over the air but has + * been determined from other sources. If TOW decoded is set then TOW Known must also be set. * * <p>Note well: if there is any ambiguity in integer millisecond, {@code STATE_MSEC_AMBIGUOUS} - * should be set accordingly, in the 'state' field. + * must be set accordingly, in the 'state' field. * * <p>This value must be populated if 'state' != {@code STATE_UNKNOWN}. * @@ -371,12 +398,17 @@ public final class GnssMeasurement implements Parcelable { * <p>Given the highest sync state that can be achieved, per each satellite, valid range for * this field can be: * <pre> - * Searching : [ 0 ] : STATE_UNKNOWN - * C/A code lock : [ 0 1ms ] : STATE_CODE_LOCK is set - * Symbol sync : [ 0 10ms ] : STATE_SYMBOL_SYNC is set - * Bit sync : [ 0 20ms ] : STATE_BIT_SYNC is set - * String sync : [ 0 2s ] : STATE_GLO_STRING_SYNC is set - * Time of day : [ 0 1day ] : STATE_GLO_TOD_DECODED is set</pre> + * Searching : [ 0 ] : STATE_UNKNOWN + * C/A code lock : [ 0 1ms ] : STATE_CODE_LOCK is set + * Symbol sync : [ 0 10ms ] : STATE_SYMBOL_SYNC is set + * Bit sync : [ 0 20ms ] : STATE_BIT_SYNC is set + * String sync : [ 0 2s ] : STATE_GLO_STRING_SYNC is set + * Time of day decoded : [ 0 1day ] : STATE_GLO_TOD_DECODED is set + * Time of day known : [ 0 1day ] : STATE_GLO_TOD_KNOWN set</pre> + * + * Note: Time of day known refers to the case where it is possibly not decoded over the air but + * has been determined from other sources. If Time of day decoded is set then Time of day known + * must also be set. * * <p>For Beidou, this is: * <ul> @@ -386,23 +418,31 @@ public final class GnssMeasurement implements Parcelable { * <p>Given the highest sync state that can be achieved, per each satellite, valid range for * this field can be: * <pre> - * Searching : [ 0 ] : STATE_UNKNOWN - * C/A code lock : [ 0 1ms ] : STATE_CODE_LOCK is set - * Bit sync (D2) : [ 0 2ms ] : STATE_BDS_D2_BIT_SYNC is set - * Bit sync (D1) : [ 0 20ms ] : STATE_BIT_SYNC is set - * Subframe (D2) : [ 0 0.6s ] : STATE_BDS_D2_SUBFRAME_SYNC is set - * Subframe (D1) : [ 0 6s ] : STATE_SUBFRAME_SYNC is set - * Time of week : [ 0 1week ] : STATE_TOW_DECODED is set</pre> + * Searching : [ 0 ] : STATE_UNKNOWN + * C/A code lock : [ 0 1ms ] : STATE_CODE_LOCK is set + * Bit sync (D2) : [ 0 2ms ] : STATE_BDS_D2_BIT_SYNC is set + * Bit sync (D1) : [ 0 20ms ] : STATE_BIT_SYNC is set + * Subframe (D2) : [ 0 0.6s ] : STATE_BDS_D2_SUBFRAME_SYNC is set + * Subframe (D1) : [ 0 6s ] : STATE_SUBFRAME_SYNC is set + * Time of week decoded : [ 0 1week ] : STATE_TOW_DECODED is set + * Time of week known : [ 0 1week ] : STATE_TOW_KNOWN set</pre> + * + * Note: TOW Known refers to the case where TOW is possibly not decoded over the air but has + * been determined from other sources. If TOW decoded is set then TOW Known must also be set. * * <p>For Galileo, this is: * <ul> * <li>Received Galileo time of week, at the measurement time in nanoseconds.</li> * </ul> * <pre> - * E1BC code lock : [ 0 4ms ] : STATE_GAL_E1BC_CODE_LOCK is set - * E1C 2nd code lock: [ 0 100ms ] : STATE_GAL_E1C_2ND_CODE_LOCK is set - * E1B page : [ 0 2s ] : STATE_GAL_E1B_PAGE_SYNC is set - * Time of week : [ 0 1week ] : STATE_GAL_TOW_DECODED is set</pre> + * E1BC code lock : [ 0 4ms ] : STATE_GAL_E1BC_CODE_LOCK is set + * E1C 2nd code lock : [ 0 100ms ] : STATE_GAL_E1C_2ND_CODE_LOCK is set + * E1B page : [ 0 2s ] : STATE_GAL_E1B_PAGE_SYNC is set + * Time of week decoded : [ 0 1week ] : STATE_GAL_TOW_DECODED is set + * Time of week known : [ 0 1week ] : STATE_GAL_TOW_KNOWN set</pre> + * + * Note: TOW Known refers to the case where TOW is possibly not decoded over the air but has + * been determined from other sources. If TOW decoded is set then TOW Known must also be set. * * <p>For SBAS, this is: * <ul> @@ -620,10 +660,10 @@ public final class GnssMeasurement implements Parcelable { } /** - * Gets the carrier frequency at which codes and messages are modulated. + * Gets the carrier frequency of the tracked signal. * - * <p>For GPS, e.g., it can be L1 or L2. If the field is not set, it is the primary common use - * frequency, e.g. L1 for GPS. + * <p>For example it can be the GPS L1 = 1.57542e9 Hz, or L2, L5, varying GLO channels, etc. If + * the field is not set, it is the primary common use frequency, e.g. L1 for GPS. * * <p>The value is only available if {@link #hasCarrierFrequencyHz()} is {@code true}. */ @@ -632,7 +672,7 @@ public final class GnssMeasurement implements Parcelable { } /** - * Sets the Carrier frequency (L1 or L2) in Hz. + * Sets the Carrier frequency in Hz. * @hide */ @TestApi @@ -642,7 +682,7 @@ public final class GnssMeasurement implements Parcelable { } /** - * Resets the Carrier frequency (L1 or L2) in Hz. + * Resets the Carrier frequency in Hz. * @hide */ @TestApi @@ -843,6 +883,51 @@ public final class GnssMeasurement implements Parcelable { mSnrInDb = Double.NaN; } + /** + * Returns {@code true} if {@link #getAgcLevelDb()} is available, {@code false} otherwise. + * @hide + */ + public boolean hasAgcLevelDb() { + return isFlagSet(HAS_AUTOMATIC_GAIN_CONTROL); + } + + /** + * Gets the Automatic Gain Control level in dB. + * + * <p> AGC acts as a variable gain amplifier adjusting the power of the incoming signal to + * minimize the quantization losses. The AGC level may be used to indicate potential + * interference. When AGC is at a nominal level, this value must be set as 0. Higher gain + * (and/or lower input power) shall be output as a positive number. Hence in cases of strong + * jamming, in the band of this signal, this value will go more negative. + * <p>Note: Different hardware designs (e.g. antenna, pre-amplification, or other RF HW + * components) may also affect the typical output of of this value on any given hardware design + * in an open sky test - the important aspect of this output is that changes in this value are + * indicative of changes on input signal power in the frequency band for this measurement. + * <p>The value is only available if {@link #hasAgcLevelDb()} is {@code true}. + * @hide + */ + public double getAgcLevelDb() { + return mAgcLevelDb; + } + + /** + * Sets the Automatic Gain Control level in dB. + * @hide + */ + public void setAgcLevelDb(double agcLevelDb) { + setFlag(HAS_AUTOMATIC_GAIN_CONTROL); + mAgcLevelDb = agcLevelDb; + } + + /** + * Resets the Automatic Gain Control level. + * @hide + */ + public void resetAgcLevel() { + resetFlag(HAS_AUTOMATIC_GAIN_CONTROL); + mAgcLevelDb = Double.NaN; + } + public static final Creator<GnssMeasurement> CREATOR = new Creator<GnssMeasurement>() { @Override public GnssMeasurement createFromParcel(Parcel parcel) { @@ -867,6 +952,7 @@ public final class GnssMeasurement implements Parcelable { gnssMeasurement.mCarrierPhaseUncertainty = parcel.readDouble(); gnssMeasurement.mMultipathIndicator = parcel.readInt(); gnssMeasurement.mSnrInDb = parcel.readDouble(); + gnssMeasurement.mAgcLevelDb = parcel.readDouble(); return gnssMeasurement; } @@ -898,6 +984,7 @@ public final class GnssMeasurement implements Parcelable { parcel.writeDouble(mCarrierPhaseUncertainty); parcel.writeInt(mMultipathIndicator); parcel.writeDouble(mSnrInDb); + parcel.writeDouble(mAgcLevelDb); } @Override @@ -968,6 +1055,10 @@ public final class GnssMeasurement implements Parcelable { format, "SnrInDb", hasSnrInDb() ? mSnrInDb : null)); + builder.append(String.format( + format, + "AgcLevelDb", + hasAgcLevelDb() ? mAgcLevelDb : null)); return builder.toString(); } @@ -991,6 +1082,7 @@ public final class GnssMeasurement implements Parcelable { resetCarrierPhaseUncertainty(); setMultipathIndicator(MULTIPATH_INDICATOR_UNKNOWN); resetSnrInDb(); + resetAgcLevel(); } private void setFlag(int flag) { diff --git a/location/java/android/location/GnssStatus.java b/location/java/android/location/GnssStatus.java index f10514427455..6565042cc95f 100644 --- a/location/java/android/location/GnssStatus.java +++ b/location/java/android/location/GnssStatus.java @@ -51,6 +51,8 @@ public final class GnssStatus { public static final int GNSS_SV_FLAGS_HAS_ALMANAC_DATA = (1 << 1); /** @hide */ public static final int GNSS_SV_FLAGS_USED_IN_FIX = (1 << 2); + /** @hide */ + public static final int GNSS_SV_FLAGS_HAS_CARRIER_FREQUENCY = (1 << 3); /** @hide */ public static final int SVID_SHIFT_WIDTH = 7; @@ -101,14 +103,16 @@ public final class GnssStatus { /* package */ float[] mElevations; /* package */ float[] mAzimuths; /* package */ int mSvCount; + /* package */ float[] mCarrierFrequencies; GnssStatus(int svCount, int[] svidWithFlags, float[] cn0s, float[] elevations, - float[] azimuths) { + float[] azimuths, float[] carrierFrequencies) { mSvCount = svCount; mSvidWithFlags = svidWithFlags; mCn0DbHz = cn0s; mElevations = elevations; mAzimuths = azimuths; + mCarrierFrequencies = carrierFrequencies; } /** @@ -213,4 +217,28 @@ public final class GnssStatus { public boolean usedInFix(int satIndex) { return (mSvidWithFlags[satIndex] & GNSS_SV_FLAGS_USED_IN_FIX) != 0; } + + /** + * Reports whether {@link #getCarrierFrequencyHz(int satIndex)} is available (i.e. carrier + * frequency is available for the satellite at the specified index). + * + * @param satIndex the index of the satellite in the list. + * @hide + */ + public boolean hasCarrierFrequency(int satIndex) { + return (mSvidWithFlags[satIndex] & GNSS_SV_FLAGS_HAS_CARRIER_FREQUENCY) != 0; + } + + /** + * Gets the carrier frequency of the signal tracked. + * + * For example it can be the GPS L1 = 1.57542e9 Hz, or L2, L5, varying GLO channels, etc. If + * the field is not set, it is the primary common use frequency, e.g. L1 for GPS. + * + * <p>The value is only available if {@link #hasCarrierFrequency(int satIndex)} is {@code true}. + * @hide + */ + public float getCarrierFrequencyHz(int satIndex) { + return mCarrierFrequencies[satIndex]; + } } diff --git a/location/java/android/location/IGnssStatusListener.aidl b/location/java/android/location/IGnssStatusListener.aidl index d84614f1b894..d824cb18765a 100644 --- a/location/java/android/location/IGnssStatusListener.aidl +++ b/location/java/android/location/IGnssStatusListener.aidl @@ -27,6 +27,7 @@ oneway interface IGnssStatusListener void onGnssStopped(); void onFirstFix(int ttff); void onSvStatusChanged(int svCount, in int[] svidWithFlags, in float[] cn0s, - in float[] elevations, in float[] azimuths); + in float[] elevations, in float[] azimuths, + in float[] carrierFreqs); void onNmeaReceived(long timestamp, String nmea); } diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java index 50f0badf6ac1..f5f437e0bbe5 100644 --- a/location/java/android/location/Location.java +++ b/location/java/android/location/Location.java @@ -81,23 +81,35 @@ public class Location implements Parcelable { /** * Bit mask for mFieldsMask indicating the presence of mAltitude. */ - private static final byte HAS_ALTITUDE_MASK = 1; + private static final int HAS_ALTITUDE_MASK = 1; /** * Bit mask for mFieldsMask indicating the presence of mSpeed. */ - private static final byte HAS_SPEED_MASK = 2; + private static final int HAS_SPEED_MASK = 2; /** * Bit mask for mFieldsMask indicating the presence of mBearing. */ - private static final byte HAS_BEARING_MASK = 4; + private static final int HAS_BEARING_MASK = 4; /** - * Bit mask for mFieldsMask indicating the presence of mAccuracy. + * Bit mask for mFieldsMask indicating the presence of mHorizontalAccuracy. */ - private static final byte HAS_ACCURACY_MASK = 8; + private static final int HAS_HORIZONTAL_ACCURACY_MASK = 8; /** * Bit mask for mFieldsMask indicating location is from a mock provider. */ - private static final byte HAS_MOCK_PROVIDER_MASK = 16; + private static final int HAS_MOCK_PROVIDER_MASK = 16; + /** + * Bit mask for mFieldsMask indicating the presence of mVerticalAccuracy. + */ + private static final int HAS_VERTICAL_ACCURACY_MASK = 32; + /** + * Bit mask for mFieldsMask indicating the presence of mSpeedAccuracy. + */ + private static final int HAS_SPEED_ACCURACY_MASK = 64; + /** + * Bit mask for mFieldsMask indicating the presence of mBearingAccuracy. + */ + private static final int HAS_BEARING_ACCURACY_MASK = 128; // Cached data to make bearing/distance computations more efficient for the case // where distanceTo and bearingTo are called in sequence. Assume this typically happens @@ -118,7 +130,11 @@ public class Location implements Parcelable { private double mAltitude = 0.0f; private float mSpeed = 0.0f; private float mBearing = 0.0f; - private float mAccuracy = 0.0f; + private float mHorizontalAccuracyMeters = 0.0f; + private float mVerticalAccuracyMeters = 0.0f; + private float mSpeedAccuracyMetersPerSecond = 0.0f; + private float mBearingAccuracyDegrees = 0.0f; + private Bundle mExtras = null; // A bitmask of fields present in this object (see HAS_* constants defined above). @@ -156,7 +172,10 @@ public class Location implements Parcelable { mAltitude = l.mAltitude; mSpeed = l.mSpeed; mBearing = l.mBearing; - mAccuracy = l.mAccuracy; + mHorizontalAccuracyMeters = l.mHorizontalAccuracyMeters; + mVerticalAccuracyMeters = l.mVerticalAccuracyMeters; + mSpeedAccuracyMetersPerSecond = l.mSpeedAccuracyMetersPerSecond; + mBearingAccuracyDegrees = l.mBearingAccuracyDegrees; mExtras = (l.mExtras == null) ? null : new Bundle(l.mExtras); } @@ -173,7 +192,10 @@ public class Location implements Parcelable { mAltitude = 0; mSpeed = 0; mBearing = 0; - mAccuracy = 0; + mHorizontalAccuracyMeters = 0; + mVerticalAccuracyMeters = 0; + mSpeedAccuracyMetersPerSecond = 0; + mBearingAccuracyDegrees = 0; mExtras = null; } @@ -718,19 +740,18 @@ public class Location implements Parcelable { } /** - * True if this location has an accuracy. + * True if this location has a horizontal accuracy. * - * <p>All locations generated by the {@link LocationManager} have an - * accuracy. + * <p>All locations generated by the {@link LocationManager} have an horizontal accuracy. */ public boolean hasAccuracy() { - return (mFieldsMask & HAS_ACCURACY_MASK) != 0; + return (mFieldsMask & HAS_HORIZONTAL_ACCURACY_MASK) != 0; } /** - * Get the estimated accuracy of this location, in meters. + * Get the estimated horizontal accuracy of this location, radial, in meters. * - * <p>We define accuracy as the radius of 68% confidence. In other + * <p>We define horizontal accuracy as the radius of 68% confidence. In other * words, if you draw a circle centered at this location's * latitude and longitude, and with a radius equal to the accuracy, * then there is a 68% probability that the true location is inside @@ -745,35 +766,183 @@ public class Location implements Parcelable { * accuracy, and does not indicate the accuracy of bearing, * velocity or altitude if those are included in this Location. * - * <p>If this location does not have an accuracy, then 0.0 is returned. - * All locations generated by the {@link LocationManager} include - * an accuracy. + * <p>If this location does not have a horizontal accuracy, then 0.0 is returned. + * All locations generated by the {@link LocationManager} include horizontal accuracy. */ public float getAccuracy() { - return mAccuracy; + return mHorizontalAccuracyMeters; } /** - * Set the estimated accuracy of this location, meters. + * Set the estimated horizontal accuracy of this location, meters. * - * <p>See {@link #getAccuracy} for the definition of accuracy. + * <p>See {@link #getAccuracy} for the definition of horizontal accuracy. * * <p>Following this call {@link #hasAccuracy} will return true. */ - public void setAccuracy(float accuracy) { - mAccuracy = accuracy; - mFieldsMask |= HAS_ACCURACY_MASK; + public void setAccuracy(float horizontalAccuracy) { + mHorizontalAccuracyMeters = horizontalAccuracy; + mFieldsMask |= HAS_HORIZONTAL_ACCURACY_MASK; } /** - * Remove the accuracy from this location. + * Remove the horizontal accuracy from this location. * * <p>Following this call {@link #hasAccuracy} will return false, and * {@link #getAccuracy} will return 0.0. */ public void removeAccuracy() { - mAccuracy = 0.0f; - mFieldsMask &= ~HAS_ACCURACY_MASK; + mHorizontalAccuracyMeters = 0.0f; + mFieldsMask &= ~HAS_HORIZONTAL_ACCURACY_MASK; + } + + /** + * True if this location has a vertical accuracy. + * @hide + */ + public boolean hasVerticalAccuracy() { + return (mFieldsMask & HAS_VERTICAL_ACCURACY_MASK) != 0; + } + + /** + * Get the estimated vertical accuracy of this location, in meters. + * + * <p>We define vertical accuracy as the radius of 68% confidence. In other + * words, if you draw a circle centered at this location's altitude, and with a radius + * equal to the vertical accuracy, then there is a 68% probability that the true altitude is + * inside the circle. + * + * <p>In statistical terms, it is assumed that location errors + * are random with a normal distribution, so the 68% confidence circle + * represents one standard deviation. Note that in practice, location + * errors do not always follow such a simple distribution. + * + * <p>If this location does not have a vertical accuracy, then 0.0 is returned. + * @hide + */ + public float getVerticalAccuracyMeters() { + return mVerticalAccuracyMeters; + } + + /** + * Set the estimated vertical accuracy of this location, meters. + * + * <p>See {@link #getVerticalAccuracyMeters} for the definition of vertical accuracy. + * + * <p>Following this call {@link #hasVerticalAccuracy} will return true. + * @hide + */ + public void setVerticalAccuracyMeters(float verticalAccuracyMeters) { + mVerticalAccuracyMeters = verticalAccuracyMeters; + mFieldsMask |= HAS_VERTICAL_ACCURACY_MASK; + } + + /** + * Remove the vertical accuracy from this location. + * + * <p>Following this call {@link #hasVerticalAccuracy} will return false, and + * {@link #getVerticalAccuracyMeters} will return 0.0. + * @hide + */ + public void removeVerticalAccuracy() { + mVerticalAccuracyMeters = 0.0f; + mFieldsMask &= ~HAS_VERTICAL_ACCURACY_MASK; + } + + /** + * True if this location has a speed accuracy. + * @hide + */ + public boolean hasSpeedAccuracy() { + return (mFieldsMask & HAS_SPEED_ACCURACY_MASK) != 0; + } + + /** + * Get the estimated speed accuracy of this location, in meters per second. + * + * <p>We define speed accuracy as the radius of 68% confidence. In other + * words, if you draw a circle centered at this location's speed, and with a radius + * equal to the speed accuracy, then there is a 68% probability that the true speed is + * inside the circle. + * + * <p>If this location does not have a speed accuracy, then 0.0 is returned. + * @hide + */ + public float getSpeedAccuracyMetersPerSecond() { + return mSpeedAccuracyMetersPerSecond; + } + + /** + * Set the estimated speed accuracy of this location, meters per second. + * + * <p>See {@link #getSpeedAccuracyMetersPerSecond} for the definition of speed accuracy. + * + * <p>Following this call {@link #hasSpeedAccuracy} will return true. + * @hide + */ + public void setSpeedAccuracyMetersPerSecond(float speedAccuracyMeterPerSecond) { + mSpeedAccuracyMetersPerSecond = speedAccuracyMeterPerSecond; + mFieldsMask |= HAS_SPEED_ACCURACY_MASK; + } + + /** + * Remove the speed accuracy from this location. + * + * <p>Following this call {@link #hasSpeedAccuracy} will return false, and + * {@link #getSpeedAccuracyMetersPerSecond} will return 0.0. + * @hide + */ + public void removeSpeedAccuracy() { + mSpeedAccuracyMetersPerSecond = 0.0f; + mFieldsMask &= ~HAS_SPEED_ACCURACY_MASK; + } + + /** + * True if this location has a bearing accuracy. + * @hide + */ + public boolean hasBearingAccuracy() { + return (mFieldsMask & HAS_BEARING_ACCURACY_MASK) != 0; + } + + /** + * Get the estimated bearing accuracy of this location, in degrees. + * + * <p>We define bearing accuracy as the radius of 68% confidence. In other + * words, if you draw a circle centered at this location's bearing, and with a radius + * equal to the bearing accuracy, then there is a 68% probability that the true bearing is + * inside the circle. + * + * <p>If this location does not have a bearing accuracy, then 0.0 is returned. + * @hide + */ + public float getBearingAccuracyDegrees() { + return mBearingAccuracyDegrees; + } + + /** + * Set the estimated bearing accuracy of this location, degrees. + * + * <p>See {@link #getBearingAccuracyDegrees} for the definition of bearing accuracy. + * + * <p>Following this call {@link #hasBearingAccuracy} will return true. + * @hide + */ + public void setBearingAccuracyDegrees(float bearingAccuracyDegrees) { + mBearingAccuracyDegrees = bearingAccuracyDegrees; + mFieldsMask |= HAS_BEARING_ACCURACY_MASK; + } + + /** + * Remove the bearing accuracy from this location. + * + * <p>Following this call {@link #hasBearingAccuracy} will return false, and + * {@link #getBearingAccuracyDegrees} will return 0.0. + * @hide + */ + public void removeBearingAccuracy() { + mBearingAccuracyDegrees = 0.0f; + mFieldsMask &= ~HAS_BEARING_ACCURACY_MASK; } /** @@ -810,8 +979,8 @@ public class Location implements Parcelable { public void makeComplete() { if (mProvider == null) mProvider = "?"; if (!hasAccuracy()) { - mFieldsMask |= HAS_ACCURACY_MASK; - mAccuracy = 100.0f; + mFieldsMask |= HAS_HORIZONTAL_ACCURACY_MASK; + mHorizontalAccuracyMeters = 100.0f; } if (mTime == 0) mTime = System.currentTimeMillis(); if (mElapsedRealtimeNanos == 0) mElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos(); @@ -849,8 +1018,8 @@ public class Location implements Parcelable { s.append("Location["); s.append(mProvider); s.append(String.format(" %.6f,%.6f", mLatitude, mLongitude)); - if (hasAccuracy()) s.append(String.format(" acc=%.0f", mAccuracy)); - else s.append(" acc=???"); + if (hasAccuracy()) s.append(String.format(" hAcc=%.0f", mHorizontalAccuracyMeters)); + else s.append(" hAcc=???"); if (mTime == 0) { s.append(" t=?!?"); } @@ -863,6 +1032,12 @@ public class Location implements Parcelable { if (hasAltitude()) s.append(" alt=").append(mAltitude); if (hasSpeed()) s.append(" vel=").append(mSpeed); if (hasBearing()) s.append(" bear=").append(mBearing); + if (hasVerticalAccuracy()) s.append(String.format(" vAcc=%.0f", mVerticalAccuracyMeters)); + else s.append(" vAcc=???"); + if (hasSpeedAccuracy()) s.append(String.format(" sAcc=%.0f", mSpeedAccuracyMetersPerSecond)); + else s.append(" sAcc=???"); + if (hasBearingAccuracy()) s.append(String.format(" bAcc=%.0f", mBearingAccuracyDegrees)); + else s.append(" bAcc=???"); if (isFromMockProvider()) s.append(" mock"); if (mExtras != null) { @@ -890,7 +1065,10 @@ public class Location implements Parcelable { l.mAltitude = in.readDouble(); l.mSpeed = in.readFloat(); l.mBearing = in.readFloat(); - l.mAccuracy = in.readFloat(); + l.mHorizontalAccuracyMeters = in.readFloat(); + l.mVerticalAccuracyMeters = in.readFloat(); + l.mSpeedAccuracyMetersPerSecond = in.readFloat(); + l.mBearingAccuracyDegrees = in.readFloat(); l.mExtras = Bundle.setDefusable(in.readBundle(), true); return l; } @@ -917,7 +1095,10 @@ public class Location implements Parcelable { parcel.writeDouble(mAltitude); parcel.writeFloat(mSpeed); parcel.writeFloat(mBearing); - parcel.writeFloat(mAccuracy); + parcel.writeFloat(mHorizontalAccuracyMeters); + parcel.writeFloat(mVerticalAccuracyMeters); + parcel.writeFloat(mSpeedAccuracyMetersPerSecond); + parcel.writeFloat(mBearingAccuracyDegrees); parcel.writeBundle(mExtras); } diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 087e74f54006..4e146268b256 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -1554,9 +1554,10 @@ public class LocationManager { @Override public void onSvStatusChanged(int svCount, int[] prnWithFlags, - float[] cn0s, float[] elevations, float[] azimuths) { + float[] cn0s, float[] elevations, float[] azimuths, float[] carrierFreqs) { if (mGnssCallback != null) { - mGnssStatus = new GnssStatus(svCount, prnWithFlags, cn0s, elevations, azimuths); + mGnssStatus = new GnssStatus(svCount, prnWithFlags, cn0s, elevations, azimuths, + carrierFreqs); Message msg = Message.obtain(); msg.what = GpsStatus.GPS_EVENT_SATELLITE_STATUS; diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 1d124c5e7f91..7c603853a3ba 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -633,6 +633,15 @@ public class AudioManager { /** * @hide + * For test purposes only, will throw NPE with some methods that require a Context. + */ + public AudioManager() { + mUseVolumeKeySounds = true; + mUseFixedVolume = false; + } + + /** + * @hide */ public AudioManager(Context context) { setContext(context); diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java index 147c5dffca48..b38a07f15511 100644 --- a/media/java/android/media/AudioPlaybackConfiguration.java +++ b/media/java/android/media/AudioPlaybackConfiguration.java @@ -20,8 +20,10 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Binder; +import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; +import android.os.RemoteException; import android.util.Log; import java.io.PrintWriter; @@ -36,6 +38,8 @@ import java.util.Objects; public final class AudioPlaybackConfiguration implements Parcelable { private final static String TAG = new String("AudioPlaybackConfiguration"); + private final static boolean DEBUG = false; + /** @hide */ public final static int PLAYER_PIID_INVALID = -1; /** @hide */ @@ -147,6 +151,8 @@ public final class AudioPlaybackConfiguration implements Parcelable { private int mPlayerType; private int mClientUid; private int mClientPid; + // the IPlayer reference and death monitor + private IPlayerShell mIPlayerShell; private int mPlayerState; private AudioAttributes mPlayerAttr; // never null @@ -156,18 +162,34 @@ public final class AudioPlaybackConfiguration implements Parcelable { */ private AudioPlaybackConfiguration(int piid) { mPlayerIId = piid; + mIPlayerShell = null; } /** * @hide */ public AudioPlaybackConfiguration(PlayerBase.PlayerIdCard pic, int piid, int uid, int pid) { + if (DEBUG) { Log.d(TAG, "new: piid=" + piid + " iplayer=" + pic.mIPlayer); } mPlayerIId = piid; mPlayerType = pic.mPlayerType; mClientUid = uid; mClientPid = pid; mPlayerState = PLAYER_STATE_IDLE; mPlayerAttr = pic.mAttributes; + if ((sPlayerDeathMonitor != null) && (pic.mIPlayer != null)) { + mIPlayerShell = new IPlayerShell(this, pic.mIPlayer); + } else { + mIPlayerShell = null; + } + } + + /** + * @hide + */ + public void init() { + if (mIPlayerShell != null) { + mIPlayerShell.monitorDeath(); + } } // Note that this method is called server side, so no "privileged" information is ever sent @@ -191,6 +213,7 @@ public final class AudioPlaybackConfiguration implements Parcelable { anonymCopy.mPlayerType = PLAYER_TYPE_UNKNOWN; anonymCopy.mClientUid = PLAYER_UPID_INVALID; anonymCopy.mClientPid = PLAYER_UPID_INVALID; + anonymCopy.mIPlayerShell = null; return anonymCopy; } @@ -250,6 +273,25 @@ public final class AudioPlaybackConfiguration implements Parcelable { /** * @hide + * Return an identifier unique for the lifetime of the player. + * @return a player interface identifier + */ + @SystemApi + public int getPlayerInterfaceId() { + return mPlayerIId; + } + + /** + * @hide + * FIXME return a player proxy instead, make systemApi + * @return + */ + public IPlayer getPlayerProxy() { + return mIPlayerShell == null ? null : mIPlayerShell.getIPlayer(); + } + + /** + * @hide * Handle a change of audio attributes * @param attr */ @@ -268,9 +310,26 @@ public final class AudioPlaybackConfiguration implements Parcelable { public boolean handleStateEvent(int event) { final boolean changed = (mPlayerState != event); mPlayerState = event; + if ((event == PLAYER_STATE_RELEASED) && (mIPlayerShell != null)) { + mIPlayerShell.release(); + } return changed; } + // To report IPlayer death from death recipient + /** @hide */ + public interface PlayerDeathMonitor { + public void playerDeath(int piid); + } + /** @hide */ + public static PlayerDeathMonitor sPlayerDeathMonitor; + + private void playerDied() { + if (sPlayerDeathMonitor != null) { + sPlayerDeathMonitor.playerDeath(mPlayerIId); + } + } + /** * @hide * Returns true if the player is considered "active", i.e. actively playing, and thus @@ -338,6 +397,7 @@ public final class AudioPlaybackConfiguration implements Parcelable { dest.writeInt(mClientPid); dest.writeInt(mPlayerState); mPlayerAttr.writeToParcel(dest, 0); + dest.writeStrongInterface(mIPlayerShell == null ? null : mIPlayerShell.getIPlayer()); } private AudioPlaybackConfiguration(Parcel in) { @@ -347,6 +407,8 @@ public final class AudioPlaybackConfiguration implements Parcelable { mClientPid = in.readInt(); mPlayerState = in.readInt(); mPlayerAttr = AudioAttributes.CREATOR.createFromParcel(in); + final IPlayer p = IPlayer.Stub.asInterface(in.readStrongBinder()); + mIPlayerShell = (p == null) ? null : new IPlayerShell(null, p); } @Override @@ -363,6 +425,46 @@ public final class AudioPlaybackConfiguration implements Parcelable { } //===================================================================== + // Inner class for corresponding IPlayer and its death monitoring + final static class IPlayerShell implements IBinder.DeathRecipient { + + final AudioPlaybackConfiguration mMonitor; // never null + private IPlayer mIPlayer; + + IPlayerShell(@NonNull AudioPlaybackConfiguration monitor, @NonNull IPlayer iplayer) { + mMonitor = monitor; + mIPlayer = iplayer; + } + + void monitorDeath() { + try { + mIPlayer.asBinder().linkToDeath(this, 0); + } catch (RemoteException e) { + if (mMonitor != null) { + Log.w(TAG, "Could not link to client death for piid=" + mMonitor.mPlayerIId, e); + } else { + Log.w(TAG, "Could not link to client death", e); + } + } + } + + IPlayer getIPlayer() { + return mIPlayer; + } + + public void binderDied() { + if (mMonitor != null) { + if (DEBUG) { Log.i(TAG, "IPlayerShell binderDied for piid=" + mMonitor.mPlayerIId);} + mMonitor.playerDied(); + } else if (DEBUG) { Log.i(TAG, "IPlayerShell binderDied"); } + } + + void release() { + mIPlayer.asBinder().unlinkToDeath(this, 0); + } + } + + //===================================================================== // Utilities /** @hide */ diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index 464cbdb985a4..031ac0667c64 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -537,6 +537,8 @@ public class AudioTrack extends PlayerBase } else { mState = STATE_INITIALIZED; } + + baseRegisterPlayer(); } /** @@ -566,6 +568,7 @@ public class AudioTrack extends PlayerBase // other initialization... if (nativeTrackInJavaObj != 0) { + baseRegisterPlayer(); deferred_connect(nativeTrackInJavaObj); } else { mState = STATE_UNINITIALIZED; @@ -2739,6 +2742,24 @@ public class AudioTrack extends PlayerBase } //--------------------------------------------------------- + // Methods for IPlayer interface + //-------------------- + @Override + void playerStart() { + play(); + } + + @Override + void playerPause() { + pause(); + } + + @Override + void playerStop() { + stop(); + } + + //--------------------------------------------------------- // Java methods called from the native side //-------------------- @SuppressWarnings("unused") diff --git a/media/java/android/media/IPlayer.aidl b/media/java/android/media/IPlayer.aidl index 32984f98264c..ccb60f7fd843 100644 --- a/media/java/android/media/IPlayer.aidl +++ b/media/java/android/media/IPlayer.aidl @@ -24,4 +24,5 @@ interface IPlayer { oneway void start(); oneway void pause(); oneway void stop(); + oneway void setVolume(float vol); } diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 500556aa7256..e3a0f25d5ccd 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -645,6 +645,8 @@ public class MediaPlayer extends PlayerBase * It's easier to create it here than in C++. */ native_setup(new WeakReference<MediaPlayer>(this)); + + baseRegisterPlayer(); } /* @@ -1261,6 +1263,21 @@ public class MediaPlayer extends PlayerBase private native void _pause() throws IllegalStateException; + @Override + void playerStart() { + start(); + } + + @Override + void playerPause() { + pause(); + } + + @Override + void playerStop() { + stop(); + } + /** * Set the low-level power management behavior for this MediaPlayer. This * can be used when the MediaPlayer is not playing through a SurfaceHolder diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java index 4eacb3880a94..981939583e3b 100644 --- a/media/java/android/media/PlayerBase.java +++ b/media/java/android/media/PlayerBase.java @@ -56,14 +56,14 @@ public abstract class PlayerBase { protected float mAuxEffectSendLevel = 0.0f; // for AppOps - private final IAppOpsService mAppOps; - private final IAppOpsCallback mAppOpsCallback; + private IAppOpsService mAppOps; + private IAppOpsCallback mAppOpsCallback; private boolean mHasAppOpsPlayAudio = true; private final Object mAppOpsLock = new Object(); private final int mImplType; // uniquely identifies the Player Interface throughout the system (P I Id) - private final int mPlayerIId; + private int mPlayerIId; private int mState; @@ -78,6 +78,12 @@ public abstract class PlayerBase { } mAttributes = attr; mImplType = implType; + }; + + /** + * Call from derived class when instantiation / initialization is successful + */ + protected void baseRegisterPlayer() { int newPiid = AudioPlaybackConfiguration.PLAYER_PIID_INVALID; IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE); mAppOps = IAppOpsService.Stub.asInterface(b); @@ -100,14 +106,16 @@ public abstract class PlayerBase { mHasAppOpsPlayAudio = false; } try { - newPiid = getService().trackPlayer(new PlayerIdCard(mImplType, mAttributes)); + if (mIPlayer == null) { + throw new IllegalStateException("Cannot register a player with a null mIPlayer"); + } + newPiid = getService().trackPlayer(new PlayerIdCard(mImplType, mAttributes, mIPlayer)); } catch (RemoteException e) { Log.e(TAG, "Error talking to audio service, player will not be tracked", e); } mPlayerIId = newPiid; } - /** * To be called whenever the audio attributes of the player change * @param attr non-null audio attributes @@ -295,16 +303,34 @@ public abstract class PlayerBase { */ abstract void playerSetVolume(boolean muting, float leftVolume, float rightVolume); abstract int playerSetAuxEffectSendLevel(boolean muting, float level); + abstract void playerStart(); + abstract void playerPause(); + abstract void playerStop(); //===================================================================== - // Implementation of IPlayer - private final IPlayer mIPlayer = new IPlayer.Stub() { + /** + * Implementation of IPlayer for all subclasses of PlayerBase + */ + private IPlayer mIPlayer = new IPlayer.Stub() { @Override - public void start() {} + public void start() { + playerStart(); + } + + @Override + public void pause() { + playerPause(); + } + @Override - public void pause() {} + public void stop() { + playerStop(); + } + @Override - public void stop() {} + public void setVolume(float vol) { + baseSetVolume(vol, vol); + } }; //===================================================================== @@ -317,10 +343,12 @@ public abstract class PlayerBase { public final static int AUDIO_ATTRIBUTES_NONE = 0; public final static int AUDIO_ATTRIBUTES_DEFINED = 1; public final AudioAttributes mAttributes; + public final IPlayer mIPlayer; - PlayerIdCard(int type, @NonNull AudioAttributes attr) { + PlayerIdCard(int type, @NonNull AudioAttributes attr, @NonNull IPlayer iplayer) { mPlayerType = type; mAttributes = attr; + mIPlayer = iplayer; } @Override @@ -337,6 +365,7 @@ public abstract class PlayerBase { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mPlayerType); mAttributes.writeToParcel(dest, 0); + dest.writeStrongBinder(mIPlayer == null ? null : mIPlayer.asBinder()); } public static final Parcelable.Creator<PlayerIdCard> CREATOR @@ -357,6 +386,9 @@ public abstract class PlayerBase { private PlayerIdCard(Parcel in) { mPlayerType = in.readInt(); mAttributes = AudioAttributes.CREATOR.createFromParcel(in); + // IPlayer can be null if unmarshalling a Parcel coming from who knows where + final IBinder b = in.readStrongBinder(); + mIPlayer = (b == null ? null : IPlayer.Stub.asInterface(b)); } @Override diff --git a/media/java/android/media/SoundPool.java b/media/java/android/media/SoundPool.java index c985cbd010d4..4cc1f8ebe4b4 100644 --- a/media/java/android/media/SoundPool.java +++ b/media/java/android/media/SoundPool.java @@ -159,6 +159,8 @@ public class SoundPool extends PlayerBase { } mLock = new Object(); mAttributes = attributes; + + baseRegisterPlayer(); } /** @@ -399,6 +401,21 @@ public class SoundPool extends PlayerBase { return AudioSystem.SUCCESS; } + @Override + void playerStart() { + // FIXME implement resuming any paused sound + } + + @Override + void playerPause() { + // FIXME implement pausing any playing sound + } + + @Override + void playerStop() { + // FIXME implement pausing any playing sound + } + /** * Similar, except set volume of all channels to same value. * @hide diff --git a/packages/SettingsLib/res/drawable/ic_bt_cellphone.xml b/packages/SettingsLib/res/drawable/ic_bt_cellphone.xml index cc9b73238d71..be9b094134c8 100644 --- a/packages/SettingsLib/res/drawable/ic_bt_cellphone.xml +++ b/packages/SettingsLib/res/drawable/ic_bt_cellphone.xml @@ -18,7 +18,7 @@ android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0" - android:tint="?android:attr/colorAccent" + android:tint="?android:attr/colorControlNormal" android:autoMirrored="true"> <path android:fillColor="#FF000000" diff --git a/packages/SettingsLib/res/drawable/ic_bt_headphones_a2dp.xml b/packages/SettingsLib/res/drawable/ic_bt_headphones_a2dp.xml index bbe39143c539..32f39a39754f 100644 --- a/packages/SettingsLib/res/drawable/ic_bt_headphones_a2dp.xml +++ b/packages/SettingsLib/res/drawable/ic_bt_headphones_a2dp.xml @@ -18,7 +18,7 @@ android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0" - android:tint="?android:attr/colorAccent"> + android:tint="?android:attr/colorControlNormal"> <path android:fillColor="#FF000000" android:pathData="M12,1c-4.97,0 -9,4.03 -9,9v7c0,1.66 1.34,3 3,3h3v-8H5v-2c0,-3.87 diff --git a/packages/SettingsLib/res/drawable/ic_bt_headset_hfp.xml b/packages/SettingsLib/res/drawable/ic_bt_headset_hfp.xml index ceeef191bc99..e43fe39409af 100644 --- a/packages/SettingsLib/res/drawable/ic_bt_headset_hfp.xml +++ b/packages/SettingsLib/res/drawable/ic_bt_headset_hfp.xml @@ -18,7 +18,7 @@ android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0" - android:tint="?android:attr/colorAccent"> + android:tint="?android:attr/colorControlNormal"> <path android:fillColor="#FF000000" android:pathData="M12,1c-4.97,0 -9,4.03 -9,9v7c0,1.66 1.34,3 3,3h3v-8H5v-2c0,-3.87 diff --git a/packages/SettingsLib/res/drawable/ic_bt_misc_hid.xml b/packages/SettingsLib/res/drawable/ic_bt_misc_hid.xml index 67b42ae020d8..ac460ab675e9 100644 --- a/packages/SettingsLib/res/drawable/ic_bt_misc_hid.xml +++ b/packages/SettingsLib/res/drawable/ic_bt_misc_hid.xml @@ -18,7 +18,7 @@ android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0" - android:tint="?android:attr/colorAccent"> + android:tint="?android:attr/colorControlNormal"> <path android:fillColor="#FF000000" android:pathData="M15,7.5V2H9v5.5l3,3 3,-3zM7.5,9H2v6h5.5l3,-3 -3,-3zM9,16.5V22h6v-5.5l-3,-3 diff --git a/packages/SettingsLib/res/drawable/ic_bt_network_pan.xml b/packages/SettingsLib/res/drawable/ic_bt_network_pan.xml index c5ab01c5ede8..6e4361b95eab 100644 --- a/packages/SettingsLib/res/drawable/ic_bt_network_pan.xml +++ b/packages/SettingsLib/res/drawable/ic_bt_network_pan.xml @@ -18,7 +18,7 @@ android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0" - android:tint="?android:attr/colorAccent"> + android:tint="?android:attr/colorControlNormal"> <path android:fillColor="#FF000000" android:pathData="M14.24,12.01l2.32,2.32c0.28,-0.72 0.44,-1.51 0.44,-2.33 0,-0.82 diff --git a/packages/SettingsLib/res/drawable/ic_bt_pointing_hid.xml b/packages/SettingsLib/res/drawable/ic_bt_pointing_hid.xml index 821618dbc4a6..de97e249789f 100644 --- a/packages/SettingsLib/res/drawable/ic_bt_pointing_hid.xml +++ b/packages/SettingsLib/res/drawable/ic_bt_pointing_hid.xml @@ -18,7 +18,7 @@ android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0" - android:tint="?android:attr/colorAccent"> + android:tint="?android:attr/colorControlNormal"> <path android:fillColor="#FF000000" android:pathData="M13,1.07L13,9h7c0,-4.08 -3.05,-7.44 -7,-7.93zM4,15c0,4.42 diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AppRestrictionsHelper.java b/packages/SettingsLib/src/com/android/settingslib/users/AppRestrictionsHelper.java index c4aa57d7b593..4c1119757eba 100644 --- a/packages/SettingsLib/src/com/android/settingslib/users/AppRestrictionsHelper.java +++ b/packages/SettingsLib/src/com/android/settingslib/users/AppRestrictionsHelper.java @@ -115,7 +115,8 @@ public class AppRestrictionsHelper { PackageManager.MATCH_ANY_USER, userId); if (info == null || !info.enabled || (info.flags&ApplicationInfo.FLAG_INSTALLED) == 0) { - mIPm.installExistingPackageAsUser(packageName, mUser.getIdentifier()); + mIPm.installExistingPackageAsUser(packageName, mUser.getIdentifier(), + PackageManager.INSTALL_REASON_UNKNOWN); if (DEBUG) { Log.d(TAG, "Installing " + packageName); } diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java index 3f989bd20065..4df199cbd4bd 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java @@ -145,7 +145,8 @@ public class AppRestrictionsHelperTest extends BaseTest { mock(AppRestrictionsHelper.OnDisableUiForPackageListener.class); mHelper.applyUserAppsStates(mockListener); - verify(mIpm, times(1)).installExistingPackageAsUser("app1", testUserId); + verify(mIpm, times(1)).installExistingPackageAsUser("app1", testUserId, + PackageManager.INSTALL_REASON_UNKNOWN); verify(mIpm, times(1)).setApplicationHiddenSettingAsUser("app2", false, testUserId); verify(mockListener).onDisableUiForPackage("app2"); verify(mPm, times(1)).deletePackageAsUser(eq("app3"), any(IPackageDeleteObserver.class), @@ -158,14 +159,14 @@ public class AppRestrictionsHelperTest extends BaseTest { for (String pkg : defaultImes) { final ResolveInfo ri = createResolveInfoForSystemApp(pkg); final InputMethodInfo inputMethodInfo = new InputMethodInfo( - ri, false, null, null, 0, true, true); + ri, false, null, null, 0, true, true, false); inputMethods.add(inputMethodInfo); addInstalledApp(ri); } for (String pkg : otherImes) { final ResolveInfo ri = createResolveInfoForSystemApp(pkg); final InputMethodInfo inputMethodInfo = new InputMethodInfo( - ri, false, null, null, 0, false, true); + ri, false, null, null, 0, false, true, false); inputMethods.add(inputMethodInfo); addInstalledApp(ri); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java index 3ae51b0bde35..e3bf1df76677 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java @@ -18,6 +18,7 @@ package com.android.systemui.recents.views; import android.content.Context; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.Canvas; @@ -62,6 +63,8 @@ public class TaskViewThumbnail extends View { @ViewDebug.ExportedProperty(category="recents") private float mThumbnailScale; private float mFullscreenThumbnailScale; + /** The height, in pixels, of the task view's title bar. */ + private int mTitleBarHeight; private boolean mSizeToFit = false; private boolean mOverlayHeaderOnThumbnailActionBar = true; private ThumbnailData mThumbnailData; @@ -104,12 +107,13 @@ public class TaskViewThumbnail extends View { mDrawPaint.setColorFilter(mLightingColorFilter); mDrawPaint.setFilterBitmap(true); mDrawPaint.setAntiAlias(true); - mCornerRadius = getResources().getDimensionPixelSize( - R.dimen.recents_task_view_rounded_corners_radius); + Resources res = getResources(); + mCornerRadius = res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius); mBgFillPaint.setColor(Color.WHITE); mLockedPaint.setColor(Color.WHITE); - mFullscreenThumbnailScale = context.getResources().getFraction( + mFullscreenThumbnailScale = res.getFraction( com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1); + mTitleBarHeight = res.getDimensionPixelSize(R.dimen.recents_grid_task_view_header_height); } /** @@ -246,7 +250,20 @@ public class TaskViewThumbnail extends View { // If we haven't measured , skip the thumbnail drawing and only draw the background // color mThumbnailScale = 0f; - } else if (isStackTask && !mSizeToFit) { + } else if (mSizeToFit) { + // Make sure we fill the entire space regardless of the orientation. + float viewAspectRatio = (float) mTaskViewRect.width() / + (float) (mTaskViewRect.height() - mTitleBarHeight); + float thumbnailAspectRatio = + (float) mThumbnailRect.width() / (float) mThumbnailRect.height(); + if (viewAspectRatio > thumbnailAspectRatio) { + mThumbnailScale = + (float) mTaskViewRect.width() / (float) mThumbnailRect.width(); + } else { + mThumbnailScale = (float) (mTaskViewRect.height() - mTitleBarHeight) + / (float) mThumbnailRect.height(); + } + } else if (isStackTask) { float invThumbnailScale = 1f / mFullscreenThumbnailScale; if (mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT) { if (mThumbnailData.orientation == Configuration.ORIENTATION_PORTRAIT) { diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index d6b4bee102aa..c6b2cf64c30c 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -41,7 +41,8 @@ import java.util.List; /** * Class to receive and dispatch updates from AudioSystem about recording configurations. */ -public final class PlaybackActivityMonitor { +public final class PlaybackActivityMonitor + implements AudioPlaybackConfiguration.PlayerDeathMonitor { public final static String TAG = "AudioService.PlaybackActivityMonitor"; private final static boolean DEBUG = false; @@ -57,7 +58,8 @@ public final class PlaybackActivityMonitor { new HashMap<Integer, AudioPlaybackConfiguration>(); PlaybackActivityMonitor() { - PlayMonitorClient.sMonitor = this; + PlayMonitorClient.sListenerDeathMonitor = this; + AudioPlaybackConfiguration.sPlayerDeathMonitor = this; } //================================================================= @@ -72,6 +74,7 @@ public final class PlaybackActivityMonitor { final AudioPlaybackConfiguration apc = new AudioPlaybackConfiguration(pic, newPiid, Binder.getCallingUid(), Binder.getCallingPid()); + apc.init(); synchronized(mPlayerLock) { mPlayers.put(newPiid, apc); } @@ -124,6 +127,12 @@ public final class PlaybackActivityMonitor { } } + // Implementation of AudioPlaybackConfiguration.PlayerDeathMonitor + @Override + public void playerDeath(int piid) { + releasePlayer(piid, 0); + } + protected void dump(PrintWriter pw) { pw.println("\nPlaybackActivityMonitor dump time: " + DateFormat.getTimeInstance().format(new Date())); @@ -275,7 +284,7 @@ public final class PlaybackActivityMonitor { private final static class PlayMonitorClient implements IBinder.DeathRecipient { // can afford to be static because only one PlaybackActivityMonitor ever instantiated - static PlaybackActivityMonitor sMonitor; + static PlaybackActivityMonitor sListenerDeathMonitor; final IPlaybackConfigDispatcher mDispatcherCb; final boolean mIsPrivileged; @@ -291,7 +300,7 @@ public final class PlaybackActivityMonitor { public void binderDied() { Log.w(TAG, "client died"); - sMonitor.unregisterPlaybackCallback(mDispatcherCb); + sListenerDeathMonitor.unregisterPlaybackCallback(mDispatcherCb); } boolean init() { diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java index 70faa5ac4a4a..68fe5053b585 100644 --- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java +++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java @@ -182,7 +182,7 @@ public class NetworkNotificationManager { details = r.getString(R.string.network_switch_metered_detail, toTransport, fromTransport); } else { - Slog.wtf(TAG, "Unknown notification type " + notifyType + "on network transport " + Slog.wtf(TAG, "Unknown notification type " + notifyType + " on network transport " + getTransportName(transportType)); return; } diff --git a/services/core/java/com/android/server/location/FlpHardwareProvider.java b/services/core/java/com/android/server/location/FlpHardwareProvider.java index 6d08c3642988..5c9b0eaa29cc 100644 --- a/services/core/java/com/android/server/location/FlpHardwareProvider.java +++ b/services/core/java/com/android/server/location/FlpHardwareProvider.java @@ -17,523 +17,58 @@ package com.android.server.location; import android.content.Context; -import android.hardware.location.GeofenceHardware; -import android.hardware.location.GeofenceHardwareImpl; -import android.hardware.location.GeofenceHardwareRequestParcelable; import android.hardware.location.IFusedLocationHardware; -import android.hardware.location.IFusedLocationHardwareSink; -import android.location.FusedBatchOptions; import android.location.IFusedGeofenceHardware; -import android.location.Location; -import android.location.LocationListener; -import android.location.LocationManager; -import android.location.LocationRequest; -import android.os.Bundle; -import android.os.Looper; -import android.os.RemoteException; -import android.os.SystemClock; import android.util.Log; /** - * This class is an interop layer for JVM types and the JNI code that interacts + * This class was an interop layer for JVM types and the JNI code that interacted * with the FLP HAL implementation. * + * Now, after Treble FLP & GNSS HAL simplification, it is a thin shell that acts like the + * pre-existing cases where there was no FLP Hardware support, to keep legacy users of this + * class operating. + * * {@hide} + * {@Deprecated} */ public class FlpHardwareProvider { - private static final int FIRST_VERSION_WITH_FLUSH_LOCATIONS = 2; - private GeofenceHardwareImpl mGeofenceHardwareSink = null; - private IFusedLocationHardwareSink mLocationSink = null; - // Capabilities provided by FlpCallbacks - private boolean mHaveBatchingCapabilities; - private int mBatchingCapabilities; - private int mVersion = 1; - private static FlpHardwareProvider sSingletonInstance = null; private final static String TAG = "FlpHardwareProvider"; - private final Context mContext; - private final Object mLocationSinkLock = new Object(); - // FlpHal result codes, they must be equal to the ones in fused_location.h - private static final int FLP_RESULT_SUCCESS = 0; - private static final int FLP_RESULT_ERROR = -1; - private static final int FLP_RESULT_INSUFFICIENT_MEMORY = -2; - private static final int FLP_RESULT_TOO_MANY_GEOFENCES = -3; - private static final int FLP_RESULT_ID_EXISTS = -4; - private static final int FLP_RESULT_ID_UNKNOWN = -5; - private static final int FLP_RESULT_INVALID_GEOFENCE_TRANSITION = -6; - - // FlpHal monitor status codes, they must be equal to the ones in fused_location.h - private static final int FLP_GEOFENCE_MONITOR_STATUS_UNAVAILABLE = 1<<0; - private static final int FLP_GEOFENCE_MONITOR_STATUS_AVAILABLE = 1<<1; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); public static FlpHardwareProvider getInstance(Context context) { if (sSingletonInstance == null) { - sSingletonInstance = new FlpHardwareProvider(context); - sSingletonInstance.nativeInit(); + sSingletonInstance = new FlpHardwareProvider(); + if (DEBUG) Log.d(TAG, "getInstance() created empty provider"); } - return sSingletonInstance; } - private FlpHardwareProvider(Context context) { - mContext = context; - - // register for listening for passive provider data - LocationManager manager = (LocationManager) mContext.getSystemService( - Context.LOCATION_SERVICE); - final long minTime = 0; - final float minDistance = 0; - final boolean oneShot = false; - LocationRequest request = LocationRequest.createFromDeprecatedProvider( - LocationManager.PASSIVE_PROVIDER, - minTime, - minDistance, - oneShot); - // Don't keep track of this request since it's done on behalf of other clients - // (which are kept track of separately). - request.setHideFromAppOps(true); - manager.requestLocationUpdates( - request, - new NetworkLocationListener(), - Looper.myLooper()); + private FlpHardwareProvider() { } public static boolean isSupported() { - return nativeIsSupported(); - } - - /** - * Private callback functions used by FLP HAL. - */ - // FlpCallbacks members - private void onLocationReport(Location[] locations) { - for (Location location : locations) { - location.setProvider(LocationManager.FUSED_PROVIDER); - // set the elapsed time-stamp just as GPS provider does - location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos()); - } - - IFusedLocationHardwareSink sink; - synchronized (mLocationSinkLock) { - sink = mLocationSink; - } - try { - if (sink != null) { - sink.onLocationAvailable(locations); - } - } catch (RemoteException e) { - Log.e(TAG, "RemoteException calling onLocationAvailable"); - } - } - - private void onBatchingCapabilities(int capabilities) { - synchronized (mLocationSinkLock) { - mHaveBatchingCapabilities = true; - mBatchingCapabilities = capabilities; - } - - maybeSendCapabilities(); - - if (mGeofenceHardwareSink != null) { - mGeofenceHardwareSink.setVersion(getVersion()); - } - } - - private void onBatchingStatus(int status) { - IFusedLocationHardwareSink sink; - synchronized (mLocationSinkLock) { - sink = mLocationSink; - } - try { - if (sink != null) { - sink.onStatusChanged(status); - } - } catch (RemoteException e) { - Log.e(TAG, "RemoteException calling onBatchingStatus"); - } - } - - // Returns the current version of the FLP HAL. This depends both on the version of the - // structure returned by the hardware layer, and whether or not we've received the - // capabilities callback on initialization. Assume original version until we get - // the new initialization callback. - private int getVersion() { - synchronized (mLocationSinkLock) { - if (mHaveBatchingCapabilities) { - return mVersion; - } - } - return 1; - } - - private void setVersion(int version) { - mVersion = version; - if (mGeofenceHardwareSink != null) { - mGeofenceHardwareSink.setVersion(getVersion()); - } - } - - private void maybeSendCapabilities() { - IFusedLocationHardwareSink sink; - boolean haveBatchingCapabilities; - int batchingCapabilities; - synchronized (mLocationSinkLock) { - sink = mLocationSink; - haveBatchingCapabilities = mHaveBatchingCapabilities; - batchingCapabilities = mBatchingCapabilities; - } - try { - if (sink != null && haveBatchingCapabilities) { - sink.onCapabilities(batchingCapabilities); - } - } catch (RemoteException e) { - Log.e(TAG, "RemoteException calling onLocationAvailable"); - } - } - - // FlpDiagnosticCallbacks members - private void onDataReport(String data) { - IFusedLocationHardwareSink sink; - synchronized (mLocationSinkLock) { - sink = mLocationSink; - } - try { - if (mLocationSink != null) { - sink.onDiagnosticDataAvailable(data); - } - } catch (RemoteException e) { - Log.e(TAG, "RemoteException calling onDiagnosticDataAvailable"); - } - } - - // FlpGeofenceCallbacks members - private void onGeofenceTransition( - int geofenceId, - Location location, - int transition, - long timestamp, - int sourcesUsed) { - // the transition Id does not require translation because the values in fused_location.h - // and GeofenceHardware are in sync - getGeofenceHardwareSink().reportGeofenceTransition( - geofenceId, - updateLocationInformation(location), - transition, - timestamp, - GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE, - sourcesUsed); - } - - private void onGeofenceMonitorStatus(int status, int source, Location location) { - // allow the location to be optional in this event - Location updatedLocation = null; - if(location != null) { - updatedLocation = updateLocationInformation(location); - } - - int monitorStatus; - switch (status) { - case FLP_GEOFENCE_MONITOR_STATUS_UNAVAILABLE: - monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_UNAVAILABLE; - break; - case FLP_GEOFENCE_MONITOR_STATUS_AVAILABLE: - monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_AVAILABLE; - break; - default: - Log.e(TAG, "Invalid FlpHal Geofence monitor status: " + status); - monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_UNAVAILABLE; - break; - } - - getGeofenceHardwareSink().reportGeofenceMonitorStatus( - GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE, - monitorStatus, - updatedLocation, - source); + if (DEBUG) Log.d(TAG, "isSupported() returning false"); + return false; } - private void onGeofenceAdd(int geofenceId, int result) { - getGeofenceHardwareSink().reportGeofenceAddStatus( - geofenceId, - translateToGeofenceHardwareStatus(result)); - } - - private void onGeofenceRemove(int geofenceId, int result) { - getGeofenceHardwareSink().reportGeofenceRemoveStatus( - geofenceId, - translateToGeofenceHardwareStatus(result)); - } - - private void onGeofencePause(int geofenceId, int result) { - getGeofenceHardwareSink().reportGeofencePauseStatus( - geofenceId, - translateToGeofenceHardwareStatus(result)); - } - - private void onGeofenceResume(int geofenceId, int result) { - getGeofenceHardwareSink().reportGeofenceResumeStatus( - geofenceId, - translateToGeofenceHardwareStatus(result)); - } - - private void onGeofencingCapabilities(int capabilities) { - getGeofenceHardwareSink().onCapabilities(capabilities); - } - - /** - * Private native methods accessing FLP HAL. - */ - static { nativeClassInit(); } - - // Core members - private static native void nativeClassInit(); - private static native boolean nativeIsSupported(); - - // FlpLocationInterface members - private native void nativeInit(); - private native int nativeGetBatchSize(); - private native void nativeStartBatching(int requestId, FusedBatchOptions options); - private native void nativeUpdateBatchingOptions(int requestId, FusedBatchOptions optionsObject); - private native void nativeStopBatching(int id); - private native void nativeRequestBatchedLocation(int lastNLocations); - private native void nativeFlushBatchedLocations(); - private native void nativeInjectLocation(Location location); - private native void nativeCleanup(); - - // FlpDiagnosticsInterface members - private native boolean nativeIsDiagnosticSupported(); - private native void nativeInjectDiagnosticData(String data); - - // FlpDeviceContextInterface members - private native boolean nativeIsDeviceContextSupported(); - private native void nativeInjectDeviceContext(int deviceEnabledContext); - - // FlpGeofencingInterface members - private native boolean nativeIsGeofencingSupported(); - private native void nativeAddGeofences( - GeofenceHardwareRequestParcelable[] geofenceRequestsArray); - private native void nativePauseGeofence(int geofenceId); - private native void nativeResumeGeofence(int geofenceId, int monitorTransitions); - private native void nativeModifyGeofenceOption( - int geofenceId, - int lastTransition, - int monitorTransitions, - int notificationResponsiveness, - int unknownTimer, - int sourcesToUse); - private native void nativeRemoveGeofences(int[] geofenceIdsArray); - /** * Interface implementations for services built on top of this functionality. */ public static final String LOCATION = "Location"; - public static final String GEOFENCING = "Geofencing"; public IFusedLocationHardware getLocationHardware() { - return mLocationHardware; + return null; } public IFusedGeofenceHardware getGeofenceHardware() { - return mGeofenceHardwareService; + return null; } public void cleanup() { - Log.i(TAG, "Calling nativeCleanup()"); - nativeCleanup(); - } - - private final IFusedLocationHardware mLocationHardware = new IFusedLocationHardware.Stub() { - @Override - public void registerSink(IFusedLocationHardwareSink eventSink) { - synchronized (mLocationSinkLock) { - // only one sink is allowed at the moment - if (mLocationSink != null) { - Log.e(TAG, "Replacing an existing IFusedLocationHardware sink"); - } - - mLocationSink = eventSink; - } - maybeSendCapabilities(); - } - - @Override - public void unregisterSink(IFusedLocationHardwareSink eventSink) { - synchronized (mLocationSinkLock) { - // don't throw if the sink is not registered, simply make it a no-op - if (mLocationSink == eventSink) { - mLocationSink = null; - } - } - } - - @Override - public int getSupportedBatchSize() { - return nativeGetBatchSize(); - } - - @Override - public void startBatching(int requestId, FusedBatchOptions options) { - nativeStartBatching(requestId, options); - } - - @Override - public void stopBatching(int requestId) { - nativeStopBatching(requestId); - } - - @Override - public void updateBatchingOptions(int requestId, FusedBatchOptions options) { - nativeUpdateBatchingOptions(requestId, options); - } - - @Override - public void requestBatchOfLocations(int batchSizeRequested) { - nativeRequestBatchedLocation(batchSizeRequested); - } - - @Override - public void flushBatchedLocations() { - if (getVersion() >= FIRST_VERSION_WITH_FLUSH_LOCATIONS) { - nativeFlushBatchedLocations(); - } else { - Log.wtf(TAG, - "Tried to call flushBatchedLocations on an unsupported implementation"); - } - } - - @Override - public boolean supportsDiagnosticDataInjection() { - return nativeIsDiagnosticSupported(); - } - - @Override - public void injectDiagnosticData(String data) { - nativeInjectDiagnosticData(data); - } - - @Override - public boolean supportsDeviceContextInjection() { - return nativeIsDeviceContextSupported(); - } - - @Override - public void injectDeviceContext(int deviceEnabledContext) { - nativeInjectDeviceContext(deviceEnabledContext); - } - - @Override - public int getVersion() { - return FlpHardwareProvider.this.getVersion(); - } - }; - - private final IFusedGeofenceHardware mGeofenceHardwareService = - new IFusedGeofenceHardware.Stub() { - @Override - public boolean isSupported() { - return nativeIsGeofencingSupported(); - } - - @Override - public void addGeofences(GeofenceHardwareRequestParcelable[] geofenceRequestsArray) { - nativeAddGeofences(geofenceRequestsArray); - } - - @Override - public void removeGeofences(int[] geofenceIds) { - nativeRemoveGeofences(geofenceIds); - } - - @Override - public void pauseMonitoringGeofence(int geofenceId) { - nativePauseGeofence(geofenceId); - } - - @Override - public void resumeMonitoringGeofence(int geofenceId, int monitorTransitions) { - nativeResumeGeofence(geofenceId, monitorTransitions); - } - - @Override - public void modifyGeofenceOptions(int geofenceId, - int lastTransition, - int monitorTransitions, - int notificationResponsiveness, - int unknownTimer, - int sourcesToUse) { - nativeModifyGeofenceOption( - geofenceId, - lastTransition, - monitorTransitions, - notificationResponsiveness, - unknownTimer, - sourcesToUse); - } - }; - - /** - * Internal classes and functions used by the provider. - */ - private final class NetworkLocationListener implements LocationListener { - @Override - public void onLocationChanged(Location location) { - if ( - !LocationManager.NETWORK_PROVIDER.equals(location.getProvider()) || - !location.hasAccuracy() - ) { - return; - } - - nativeInjectLocation(location); - } - - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { } - - @Override - public void onProviderEnabled(String provider) { } - - @Override - public void onProviderDisabled(String provider) { } - } - - private GeofenceHardwareImpl getGeofenceHardwareSink() { - if (mGeofenceHardwareSink == null) { - mGeofenceHardwareSink = GeofenceHardwareImpl.getInstance(mContext); - mGeofenceHardwareSink.setVersion(getVersion()); - } - - return mGeofenceHardwareSink; - } - - private static int translateToGeofenceHardwareStatus(int flpHalResult) { - switch(flpHalResult) { - case FLP_RESULT_SUCCESS: - return GeofenceHardware.GEOFENCE_SUCCESS; - case FLP_RESULT_ERROR: - return GeofenceHardware.GEOFENCE_FAILURE; - case FLP_RESULT_INSUFFICIENT_MEMORY: - return GeofenceHardware.GEOFENCE_ERROR_INSUFFICIENT_MEMORY; - case FLP_RESULT_TOO_MANY_GEOFENCES: - return GeofenceHardware.GEOFENCE_ERROR_TOO_MANY_GEOFENCES; - case FLP_RESULT_ID_EXISTS: - return GeofenceHardware.GEOFENCE_ERROR_ID_EXISTS; - case FLP_RESULT_ID_UNKNOWN: - return GeofenceHardware.GEOFENCE_ERROR_ID_UNKNOWN; - case FLP_RESULT_INVALID_GEOFENCE_TRANSITION: - return GeofenceHardware.GEOFENCE_ERROR_INVALID_TRANSITION; - default: - Log.e(TAG, String.format("Invalid FlpHal result code: %d", flpHalResult)); - return GeofenceHardware.GEOFENCE_FAILURE; - } - } - - private Location updateLocationInformation(Location location) { - location.setProvider(LocationManager.FUSED_PROVIDER); - // set the elapsed time-stamp just as GPS provider does - location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos()); - return location; + if (DEBUG) Log.d(TAG, "empty cleanup()"); } } diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index fb2b961fce77..eb8f8fc9af96 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -100,7 +100,7 @@ import java.util.HashMap; import libcore.io.IoUtils; /** - * A GPS implementation of LocationProvider used by LocationManager. + * A GNSS implementation of LocationProvider used by LocationManager. * * {@hide} */ @@ -115,23 +115,23 @@ public class GnssLocationProvider implements LocationProviderInterface { true, true, false, false, true, true, true, Criteria.POWER_HIGH, Criteria.ACCURACY_FINE); - // these need to match GpsPositionMode enum in gps.h + // these need to match GnssPositionMode enum in IGnss.hal private static final int GPS_POSITION_MODE_STANDALONE = 0; private static final int GPS_POSITION_MODE_MS_BASED = 1; private static final int GPS_POSITION_MODE_MS_ASSISTED = 2; - // these need to match GpsPositionRecurrence enum in gps.h + // these need to match GnssPositionRecurrence enum in IGnss.hal private static final int GPS_POSITION_RECURRENCE_PERIODIC = 0; private static final int GPS_POSITION_RECURRENCE_SINGLE = 1; - // these need to match GpsStatusValue defines in gps.h + // these need to match GnssStatusValue enum in IGnssCallback.hal private static final int GPS_STATUS_NONE = 0; private static final int GPS_STATUS_SESSION_BEGIN = 1; private static final int GPS_STATUS_SESSION_END = 2; private static final int GPS_STATUS_ENGINE_ON = 3; private static final int GPS_STATUS_ENGINE_OFF = 4; - // these need to match GpsApgsStatusValue defines in gps.h + // these need to match AGnssStatusValue enum in IAGnssCallback.hal /** AGPS status event values. */ private static final int GPS_REQUEST_AGPS_DATA_CONN = 1; private static final int GPS_RELEASE_AGPS_DATA_CONN = 2; @@ -139,15 +139,19 @@ public class GnssLocationProvider implements LocationProviderInterface { private static final int GPS_AGPS_DATA_CONN_DONE = 4; private static final int GPS_AGPS_DATA_CONN_FAILED = 5; - // these need to match GpsLocationFlags enum in gps.h + // these need to match GnssLocationFlags enum in types.hal private static final int LOCATION_INVALID = 0; private static final int LOCATION_HAS_LAT_LONG = 1; private static final int LOCATION_HAS_ALTITUDE = 2; private static final int LOCATION_HAS_SPEED = 4; private static final int LOCATION_HAS_BEARING = 8; - private static final int LOCATION_HAS_ACCURACY = 16; + private static final int LOCATION_HAS_HORIZONTAL_ACCURACY = 16; + private static final int LOCATION_HAS_VERTICAL_ACCURACY = 32; + private static final int LOCATION_HAS_SPEED_ACCURACY = 64; + private static final int LOCATION_HAS_BEARING_ACCURACY = 128; - // IMPORTANT - the GPS_DELETE_* symbols here must match constants in gps.h + + // IMPORTANT - the GPS_DELETE_* symbols here must match GnssAidingData enum in IGnss.hal private static final int GPS_DELETE_EPHEMERIS = 0x0001; private static final int GPS_DELETE_ALMANAC = 0x0002; private static final int GPS_DELETE_POSITION = 0x0004; @@ -162,7 +166,7 @@ public class GnssLocationProvider implements LocationProviderInterface { private static final int GPS_DELETE_CELLDB_INFO = 0x8000; private static final int GPS_DELETE_ALL = 0xFFFF; - // The GPS_CAPABILITY_* flags must match the values in gps.h + // The GPS_CAPABILITY_* flags must match Capabilities enum in IGnssCallback.hal private static final int GPS_CAPABILITY_SCHEDULING = 0x0000001; private static final int GPS_CAPABILITY_MSB = 0x0000002; private static final int GPS_CAPABILITY_MSA = 0x0000004; @@ -176,11 +180,11 @@ public class GnssLocationProvider implements LocationProviderInterface { private static final int AGPS_SUPL_MODE_MSA = 0x02; private static final int AGPS_SUPL_MODE_MSB = 0x01; - // these need to match AGpsType enum in gps.h + // these need to match AGnssType enum in IAGnssCallback.hal private static final int AGPS_TYPE_SUPL = 1; private static final int AGPS_TYPE_C2K = 2; - // these must match the definitions in gps.h + // these must match the ApnIpType enum in IAGnss.hal private static final int APN_INVALID = 0; private static final int APN_IPV4 = 1; private static final int APN_IPV6 = 2; @@ -227,7 +231,7 @@ public class GnssLocationProvider implements LocationProviderInterface { private static final int GPS_GEOFENCE_UNAVAILABLE = 1<<0L; private static final int GPS_GEOFENCE_AVAILABLE = 1<<1L; - // GPS Geofence errors. Should match gps.h constants. + // GPS Geofence errors. Should match GeofenceStatus enum in IGnssGeofenceCallback.hal. private static final int GPS_GEOFENCE_OPERATION_SUCCESS = 0; private static final int GPS_GEOFENCE_ERROR_TOO_MANY_GEOFENCES = 100; private static final int GPS_GEOFENCE_ERROR_ID_EXISTS = -101; @@ -1479,7 +1483,9 @@ public class GnssLocationProvider implements LocationProviderInterface { * called from native code to update our position. */ private void reportLocation(int flags, double latitude, double longitude, double altitude, - float speedMetersPerSecond, float bearing, float accuracy, long timestamp) { + float speedMetersPerSecond, float bearing, float horizontalAccuracyMeters, + float verticalAccuracyMeters, float speedAccuracyMetersPerSeconds, + float bearingAccuracyDegrees, long timestamp) { if ((flags & LOCATION_HAS_SPEED) == LOCATION_HAS_SPEED) { mItarSpeedLimitExceeded = speedMetersPerSecond > ITAR_SPEED_LIMIT_METERS_PER_SECOND; } @@ -1491,7 +1497,7 @@ public class GnssLocationProvider implements LocationProviderInterface { } if (VERBOSE) Log.v(TAG, "reportLocation lat: " + latitude + " long: " + longitude + - " timestamp: " + timestamp); + " timestamp: " + timestamp + " flags: " + flags); synchronized (mLocation) { mLocationFlags = flags; @@ -1518,11 +1524,26 @@ public class GnssLocationProvider implements LocationProviderInterface { } else { mLocation.removeBearing(); } - if ((flags & LOCATION_HAS_ACCURACY) == LOCATION_HAS_ACCURACY) { - mLocation.setAccuracy(accuracy); + if ((flags & LOCATION_HAS_HORIZONTAL_ACCURACY) == LOCATION_HAS_HORIZONTAL_ACCURACY) { + mLocation.setAccuracy(horizontalAccuracyMeters); } else { mLocation.removeAccuracy(); } + if ((flags & LOCATION_HAS_VERTICAL_ACCURACY) == LOCATION_HAS_VERTICAL_ACCURACY) { + mLocation.setVerticalAccuracyMeters(verticalAccuracyMeters); + } else { + mLocation.removeVerticalAccuracy(); + } + if((flags & LOCATION_HAS_SPEED_ACCURACY) == LOCATION_HAS_SPEED_ACCURACY) { + mLocation.setSpeedAccuracyMetersPerSecond(speedAccuracyMetersPerSeconds); + } else { + mLocation.removeSpeedAccuracy(); + } + if((flags & LOCATION_HAS_BEARING_ACCURACY) == LOCATION_HAS_BEARING_ACCURACY) { + mLocation.setBearingAccuracyDegrees(bearingAccuracyDegrees); + } else { + mLocation.removeBearingAccuracy(); + } mLocation.setExtras(mLocationExtras); try { @@ -1605,13 +1626,18 @@ public class GnssLocationProvider implements LocationProviderInterface { * called from native code to update SV info */ private void reportSvStatus() { - int svCount = native_read_sv_status(mSvidWithFlags, mCn0s, mSvElevations, mSvAzimuths); + int svCount = native_read_sv_status(mSvidWithFlags, + mCn0s, + mSvElevations, + mSvAzimuths, + mSvCarrierFreqs); mListenerHelper.onSvStatusChanged( svCount, mSvidWithFlags, mCn0s, mSvElevations, - mSvAzimuths); + mSvAzimuths, + mSvCarrierFreqs); if (VERBOSE) { Log.v(TAG, "SV count: " + svCount); @@ -1627,12 +1653,15 @@ public class GnssLocationProvider implements LocationProviderInterface { " cn0: " + mCn0s[i]/10 + " elev: " + mSvElevations[i] + " azimuth: " + mSvAzimuths[i] + + " carrier frequency: " + mSvCarrierFreqs[i] + ((mSvidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_HAS_EPHEMERIS_DATA) == 0 ? " " : " E") + ((mSvidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_HAS_ALMANAC_DATA) == 0 ? " " : " A") + ((mSvidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_USED_IN_FIX) == 0 - ? "" : "U")); + ? "" : "U") + + ((mSvidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_HAS_CARRIER_FREQUENCY) == 0 + ? "" : "F")); } } // return number of sets used in fix instead of total @@ -1780,7 +1809,10 @@ public class GnssLocationProvider implements LocationProviderInterface { double altitude, float speed, float bearing, - float accuracy, + float horizontalAccuracy, + float verticalAccuracy, + float speedAccuracy, + float bearingAccuracy, long timestamp) { Location location = new Location(LocationManager.GPS_PROVIDER); if((flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) { @@ -1798,8 +1830,17 @@ public class GnssLocationProvider implements LocationProviderInterface { if((flags & LOCATION_HAS_BEARING) == LOCATION_HAS_BEARING) { location.setBearing(bearing); } - if((flags & LOCATION_HAS_ACCURACY) == LOCATION_HAS_ACCURACY) { - location.setAccuracy(accuracy); + if((flags & LOCATION_HAS_HORIZONTAL_ACCURACY) == LOCATION_HAS_HORIZONTAL_ACCURACY) { + location.setAccuracy(horizontalAccuracy); + } + if((flags & LOCATION_HAS_VERTICAL_ACCURACY) == LOCATION_HAS_VERTICAL_ACCURACY) { + location.setVerticalAccuracyMeters(verticalAccuracy); + } + if((flags & LOCATION_HAS_SPEED_ACCURACY) == LOCATION_HAS_SPEED_ACCURACY) { + location.setSpeedAccuracyMetersPerSecond(speedAccuracy); + } + if((flags & LOCATION_HAS_BEARING_ACCURACY) == LOCATION_HAS_BEARING_ACCURACY) { + location.setBearingAccuracyDegrees(bearingAccuracy); } return location; } @@ -1831,8 +1872,9 @@ public class GnssLocationProvider implements LocationProviderInterface { * All geofence callbacks are called on the same thread */ private void reportGeofenceTransition(int geofenceId, int flags, double latitude, - double longitude, double altitude, float speed, float bearing, float accuracy, - long timestamp, int transition, long transitionTimestamp) { + double longitude, double altitude, float speed, float bearing, float horizontalAccuracy, + float verticalAccuracy, float speedAccuracy, float bearingAccuracy, long timestamp, + int transition, long transitionTimestamp) { if (mGeofenceHardwareImpl == null) { mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext); } @@ -1843,7 +1885,10 @@ public class GnssLocationProvider implements LocationProviderInterface { altitude, speed, bearing, - accuracy, + horizontalAccuracy, + verticalAccuracy, + speedAccuracy, + bearingAccuracy, timestamp); mGeofenceHardwareImpl.reportGeofenceTransition( geofenceId, @@ -1858,8 +1903,8 @@ public class GnssLocationProvider implements LocationProviderInterface { * called from native code to report GPS status change. */ private void reportGeofenceStatus(int status, int flags, double latitude, - double longitude, double altitude, float speed, float bearing, float accuracy, - long timestamp) { + double longitude, double altitude, float speed, float bearing, float horizontalAccuracy, + float verticalAccuracy, float speedAccuracy, float bearingAccuracy, long timestamp) { if (mGeofenceHardwareImpl == null) { mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext); } @@ -1870,7 +1915,10 @@ public class GnssLocationProvider implements LocationProviderInterface { altitude, speed, bearing, - accuracy, + horizontalAccuracy, + verticalAccuracy, + speedAccuracy, + bearingAccuracy, timestamp); int monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_UNAVAILABLE; if(status == GPS_GEOFENCE_AVAILABLE) { @@ -2435,6 +2483,7 @@ public class GnssLocationProvider implements LocationProviderInterface { private float mCn0s[] = new float[MAX_SVS]; private float mSvElevations[] = new float[MAX_SVS]; private float mSvAzimuths[] = new float[MAX_SVS]; + private float mSvCarrierFreqs[] = new float[MAX_SVS]; private int mSvCount; // preallocated to avoid memory allocation in reportNmea() private byte[] mNmeaBuffer = new byte[120]; @@ -2455,7 +2504,7 @@ public class GnssLocationProvider implements LocationProviderInterface { // returns number of SVs // mask[0] is ephemeris mask and mask[1] is almanac mask private native int native_read_sv_status(int[] prnWithFlags, float[] cn0s, float[] elevations, - float[] azimuths); + float[] azimuths, float[] carrierFrequencies); private native int native_read_nmea(byte[] buffer, int bufferSize); private native void native_inject_location(double latitude, double longitude, float accuracy); diff --git a/services/core/java/com/android/server/location/GnssStatusListenerHelper.java b/services/core/java/com/android/server/location/GnssStatusListenerHelper.java index d471e45eacdc..fe2bb38d7c98 100644 --- a/services/core/java/com/android/server/location/GnssStatusListenerHelper.java +++ b/services/core/java/com/android/server/location/GnssStatusListenerHelper.java @@ -77,7 +77,8 @@ abstract class GnssStatusListenerHelper extends RemoteListenerHelper<IGnssStatus final int[] prnWithFlags, final float[] cn0s, final float[] elevations, - final float[] azimuths) { + final float[] azimuths, + final float[] carrierFreqs) { Operation operation = new Operation() { @Override public void execute(IGnssStatusListener listener) throws RemoteException { @@ -86,7 +87,8 @@ abstract class GnssStatusListenerHelper extends RemoteListenerHelper<IGnssStatus prnWithFlags, cn0s, elevations, - azimuths); + azimuths, + carrierFreqs); } }; foreach(operation); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index b29bf7b90ed7..c9c855b30a29 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -576,7 +576,7 @@ public class PackageManagerService extends IPackageManager.Stub { /** * Whether the package parser cache is enabled. */ - private static final boolean DEFAULT_PACKAGE_PARSER_CACHE_ENABLED = false; + private static final boolean DEFAULT_PACKAGE_PARSER_CACHE_ENABLED = true; final ServiceThread mHandlerThread; @@ -2842,7 +2842,12 @@ public class PackageManagerService extends IPackageManager.Stub { return null; } - if (SystemProperties.getBoolean("ro.boot.disable_package_cache", false)) { + // Disable package parsing on eng builds to allow for faster incremental development. + if ("eng".equals(Build.TYPE)) { + return null; + } + + if (SystemProperties.getBoolean("pm.boot.disable_package_cache", false)) { Slog.i(TAG, "Disabling package parser cache due to system property."); return null; } @@ -2861,9 +2866,33 @@ public class PackageManagerService extends IPackageManager.Stub { FileUtils.deleteContents(cacheBaseDir); } + // Return the versioned package cache directory. This is something like // "/data/system/package_cache/1" - return FileUtils.createDir(cacheBaseDir, PACKAGE_PARSER_CACHE_VERSION); + File cacheDir = FileUtils.createDir(cacheBaseDir, PACKAGE_PARSER_CACHE_VERSION); + + // The following is a workaround to aid development on non-numbered userdebug + // builds or cases where "adb sync" is used on userdebug builds. If we detect that + // the system partition is newer. + // + // NOTE: When no BUILD_NUMBER is set by the build system, it defaults to a build + // that starts with "eng." to signify that this is an engineering build and not + // destined for release. + if ("userdebug".equals(Build.TYPE) && Build.VERSION.INCREMENTAL.startsWith("eng.")) { + Slog.w(TAG, "Wiping cache directory because the system partition changed."); + + // Heuristic: If the /system directory has been modified recently due to an "adb sync" + // or a regular make, then blow away the cache. Note that mtimes are *NOT* reliable + // in general and should not be used for production changes. In this specific case, + // we know that they will work. + File frameworkDir = new File(Environment.getRootDirectory(), "framework"); + if (cacheDir.lastModified() < frameworkDir.lastModified()) { + FileUtils.deleteContents(cacheBaseDir); + cacheDir = FileUtils.createDir(cacheBaseDir, PACKAGE_PARSER_CACHE_VERSION); + } + } + + return cacheDir; } @Override @@ -12149,7 +12178,7 @@ public class PackageManagerService extends IPackageManager.Stub { final InstallParams params = new InstallParams(origin, null /*moveInfo*/, observer, installFlags, installerPackageName, null /*volumeUuid*/, verificationInfo, user, null /*packageAbiOverride*/, null /*grantedPermissions*/, - null /*certificates*/); + null /*certificates*/, PackageManager.INSTALL_REASON_UNKNOWN); params.setTraceMethod("installAsUser").setTraceCookie(System.identityHashCode(params)); msg.obj = params; @@ -12185,7 +12214,7 @@ public class PackageManagerService extends IPackageManager.Stub { final InstallParams params = new InstallParams(origin, null, observer, sessionParams.installFlags, installerPackageName, sessionParams.volumeUuid, verificationInfo, user, sessionParams.abiOverride, - sessionParams.grantedRuntimePermissions, certificates); + sessionParams.grantedRuntimePermissions, certificates, sessionParams.installReason); params.setTraceMethod("installStage").setTraceCookie(System.identityHashCode(params)); msg.obj = params; @@ -12369,7 +12398,7 @@ public class PackageManagerService extends IPackageManager.Stub { * @hide */ @Override - public int installExistingPackageAsUser(String packageName, int userId) { + public int installExistingPackageAsUser(String packageName, int userId, int installReason) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null); PackageSetting pkgSetting; @@ -12394,6 +12423,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (!pkgSetting.getInstalled(userId)) { pkgSetting.setInstalled(true, userId); pkgSetting.setHidden(false, userId); + pkgSetting.setInstallReason(installReason, userId); mSettings.writePackageRestrictionsLPr(userId); installed = true; } @@ -13408,11 +13438,12 @@ public class PackageManagerService extends IPackageManager.Stub { final String[] grantedRuntimePermissions; final VerificationInfo verificationInfo; final Certificate[][] certificates; + final int installReason; InstallParams(OriginInfo origin, MoveInfo move, IPackageInstallObserver2 observer, int installFlags, String installerPackageName, String volumeUuid, VerificationInfo verificationInfo, UserHandle user, String packageAbiOverride, - String[] grantedPermissions, Certificate[][] certificates) { + String[] grantedPermissions, Certificate[][] certificates, int installReason) { super(user); this.origin = origin; this.move = move; @@ -13424,6 +13455,7 @@ public class PackageManagerService extends IPackageManager.Stub { this.packageAbiOverride = packageAbiOverride; this.grantedRuntimePermissions = grantedPermissions; this.certificates = certificates; + this.installReason = installReason; } @Override @@ -13893,6 +13925,7 @@ public class PackageManagerService extends IPackageManager.Stub { final String traceMethod; final int traceCookie; final Certificate[][] certificates; + final int installReason; // The list of instruction sets supported by this app. This is currently // only used during the rmdex() phase to clean up resources. We can get rid of this @@ -13903,7 +13936,8 @@ public class PackageManagerService extends IPackageManager.Stub { int installFlags, String installerPackageName, String volumeUuid, UserHandle user, String[] instructionSets, String abiOverride, String[] installGrantPermissions, - String traceMethod, int traceCookie, Certificate[][] certificates) { + String traceMethod, int traceCookie, Certificate[][] certificates, + int installReason) { this.origin = origin; this.move = move; this.installFlags = installFlags; @@ -13917,6 +13951,7 @@ public class PackageManagerService extends IPackageManager.Stub { this.traceMethod = traceMethod; this.traceCookie = traceCookie; this.certificates = certificates; + this.installReason = installReason; } abstract int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException; @@ -14011,7 +14046,8 @@ public class PackageManagerService extends IPackageManager.Stub { params.installerPackageName, params.volumeUuid, params.getUser(), null /*instructionSets*/, params.packageAbiOverride, params.grantedRuntimePermissions, - params.traceMethod, params.traceCookie, params.certificates); + params.traceMethod, params.traceCookie, params.certificates, + params.installReason); if (isFwdLocked()) { throw new IllegalArgumentException("Forward locking only supported in ASEC"); } @@ -14020,7 +14056,8 @@ public class PackageManagerService extends IPackageManager.Stub { /** Existing install */ FileInstallArgs(String codePath, String resourcePath, String[] instructionSets) { super(OriginInfo.fromNothing(), null, null, 0, null, null, null, instructionSets, - null, null, null, 0, null /*certificates*/); + null, null, null, 0, null /*certificates*/, + PackageManager.INSTALL_REASON_UNKNOWN); this.codeFile = (codePath != null) ? new File(codePath) : null; this.resourceFile = (resourcePath != null) ? new File(resourcePath) : null; } @@ -14245,15 +14282,17 @@ public class PackageManagerService extends IPackageManager.Stub { params.installerPackageName, params.volumeUuid, params.getUser(), null /* instruction sets */, params.packageAbiOverride, params.grantedRuntimePermissions, - params.traceMethod, params.traceCookie, params.certificates); + params.traceMethod, params.traceCookie, params.certificates, + params.installReason); } /** Existing install */ AsecInstallArgs(String fullCodePath, String[] instructionSets, boolean isExternal, boolean isForwardLocked) { super(OriginInfo.fromNothing(), null, null, (isExternal ? INSTALL_EXTERNAL : 0) - | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null, - instructionSets, null, null, null, 0, null /*certificates*/); + | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null, + instructionSets, null, null, null, 0, null /*certificates*/, + PackageManager.INSTALL_REASON_UNKNOWN); // Hackily pretend we're still looking at a full code path if (!fullCodePath.endsWith(RES_FILE_NAME)) { fullCodePath = new File(fullCodePath, RES_FILE_NAME).getAbsolutePath(); @@ -14269,8 +14308,9 @@ public class PackageManagerService extends IPackageManager.Stub { AsecInstallArgs(String cid, String[] instructionSets, boolean isForwardLocked) { super(OriginInfo.fromNothing(), null, null, (isAsecExternal(cid) ? INSTALL_EXTERNAL : 0) - | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null, - instructionSets, null, null, null, 0, null /*certificates*/); + | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null, + instructionSets, null, null, null, 0, null /*certificates*/, + PackageManager.INSTALL_REASON_UNKNOWN); this.cid = cid; setMountPath(PackageHelper.getSdDir(cid)); } @@ -14539,7 +14579,8 @@ public class PackageManagerService extends IPackageManager.Stub { params.installerPackageName, params.volumeUuid, params.getUser(), null /* instruction sets */, params.packageAbiOverride, params.grantedRuntimePermissions, - params.traceMethod, params.traceCookie, params.certificates); + params.traceMethod, params.traceCookie, params.certificates, + params.installReason); } int copyApk(IMediaContainerService imcs, boolean temp) { @@ -14770,7 +14811,7 @@ public class PackageManagerService extends IPackageManager.Stub { */ private void installNewPackageLIF(PackageParser.Package pkg, final int policyFlags, int scanFlags, UserHandle user, String installerPackageName, String volumeUuid, - PackageInstalledInfo res) { + PackageInstalledInfo res, int installReason) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installNewPackage"); // Remember this for later, in case we need to rollback this install @@ -14802,7 +14843,7 @@ public class PackageManagerService extends IPackageManager.Stub { PackageParser.Package newPackage = scanPackageTracedLI(pkg, policyFlags, scanFlags, System.currentTimeMillis(), user); - updateSettingsLI(newPackage, installerPackageName, null, res, user); + updateSettingsLI(newPackage, installerPackageName, null, res, user, installReason); if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) { prepareAppDataAfterInstallLIF(newPackage); @@ -14864,7 +14905,8 @@ public class PackageManagerService extends IPackageManager.Stub { } private void replacePackageLIF(PackageParser.Package pkg, final int policyFlags, int scanFlags, - UserHandle user, String installerPackageName, PackageInstalledInfo res) { + UserHandle user, String installerPackageName, PackageInstalledInfo res, + int installReason) { final boolean isEphemeral = (policyFlags & PackageParser.PARSE_IS_EPHEMERAL) != 0; final PackageParser.Package oldPackage; @@ -14964,17 +15006,26 @@ public class PackageManagerService extends IPackageManager.Stub { res.removedInfo.removedPackage = oldPackage.packageName; res.removedInfo.isUpdate = true; res.removedInfo.origUsers = installedUsers; + final PackageSetting ps = mSettings.getPackageLPr(pkgName); + res.removedInfo.installReasons = new SparseArray<>(installedUsers.length); + for (int i = 0; i < installedUsers.length; i++) { + final int userId = installedUsers[i]; + res.removedInfo.installReasons.put(userId, ps.getInstallReason(userId)); + } + final int childCount = (oldPackage.childPackages != null) ? oldPackage.childPackages.size() : 0; for (int i = 0; i < childCount; i++) { boolean childPackageUpdated = false; PackageParser.Package childPkg = oldPackage.childPackages.get(i); + final PackageSetting childPs = mSettings.getPackageLPr(childPkg.packageName); if (res.addedChildPackages != null) { PackageInstalledInfo childRes = res.addedChildPackages.get(childPkg.packageName); if (childRes != null) { childRes.removedInfo.uid = childPkg.applicationInfo.uid; childRes.removedInfo.removedPackage = childPkg.packageName; childRes.removedInfo.isUpdate = true; + childRes.removedInfo.installReasons = res.removedInfo.installReasons; childPackageUpdated = true; } } @@ -14984,7 +15035,6 @@ public class PackageManagerService extends IPackageManager.Stub { childRemovedRes.isUpdate = false; childRemovedRes.dataRemoved = true; synchronized (mPackages) { - PackageSetting childPs = mSettings.getPackageLPr(childPkg.packageName); if (childPs != null) { childRemovedRes.origUsers = childPs.queryInstalledUsers(allUsers, true); } @@ -15007,10 +15057,10 @@ public class PackageManagerService extends IPackageManager.Stub { | (privileged ? PackageParser.PARSE_IS_PRIVILEGED : 0); replaceSystemPackageLIF(oldPackage, pkg, systemPolicyFlags, scanFlags, - user, allUsers, installerPackageName, res); + user, allUsers, installerPackageName, res, installReason); } else { replaceNonSystemPackageLIF(oldPackage, pkg, policyFlags, scanFlags, - user, allUsers, installerPackageName, res); + user, allUsers, installerPackageName, res, installReason); } } @@ -15025,7 +15075,8 @@ public class PackageManagerService extends IPackageManager.Stub { private void replaceNonSystemPackageLIF(PackageParser.Package deletedPackage, PackageParser.Package pkg, final int policyFlags, int scanFlags, UserHandle user, - int[] allUsers, String installerPackageName, PackageInstalledInfo res) { + int[] allUsers, String installerPackageName, PackageInstalledInfo res, + int installReason) { if (DEBUG_INSTALL) Slog.d(TAG, "replaceNonSystemPackageLI: new=" + pkg + ", old=" + deletedPackage); @@ -15068,7 +15119,8 @@ public class PackageManagerService extends IPackageManager.Stub { try { final PackageParser.Package newPackage = scanPackageTracedLI(pkg, policyFlags, scanFlags | SCAN_UPDATE_TIME, System.currentTimeMillis(), user); - updateSettingsLI(newPackage, installerPackageName, allUsers, res, user); + updateSettingsLI(newPackage, installerPackageName, allUsers, res, user, + installReason); // Update the in-memory copy of the previous code paths. PackageSetting ps = mSettings.mPackages.get(pkgName); @@ -15164,7 +15216,8 @@ public class PackageManagerService extends IPackageManager.Stub { private void replaceSystemPackageLIF(PackageParser.Package deletedPackage, PackageParser.Package pkg, final int policyFlags, int scanFlags, UserHandle user, - int[] allUsers, String installerPackageName, PackageInstalledInfo res) { + int[] allUsers, String installerPackageName, PackageInstalledInfo res, + int installReason) { if (DEBUG_INSTALL) Slog.d(TAG, "replaceSystemPackageLI: new=" + pkg + ", old=" + deletedPackage); @@ -15237,7 +15290,8 @@ public class PackageManagerService extends IPackageManager.Stub { } } - updateSettingsLI(newPackage, installerPackageName, allUsers, res, user); + updateSettingsLI(newPackage, installerPackageName, allUsers, res, user, + installReason); prepareAppDataAfterInstallLIF(newPackage); } } catch (PackageManagerException e) { @@ -15425,10 +15479,10 @@ public class PackageManagerService extends IPackageManager.Stub { } private void updateSettingsLI(PackageParser.Package newPackage, String installerPackageName, - int[] allUsers, PackageInstalledInfo res, UserHandle user) { + int[] allUsers, PackageInstalledInfo res, UserHandle user, int installReason) { // Update the parent package setting updateSettingsInternalLI(newPackage, installerPackageName, allUsers, res.origUsers, - res, user); + res, user, installReason); // Update the child packages setting final int childCount = (newPackage.childPackages != null) ? newPackage.childPackages.size() : 0; @@ -15436,13 +15490,13 @@ public class PackageManagerService extends IPackageManager.Stub { PackageParser.Package childPackage = newPackage.childPackages.get(i); PackageInstalledInfo childRes = res.addedChildPackages.get(childPackage.packageName); updateSettingsInternalLI(childPackage, installerPackageName, allUsers, - childRes.origUsers, childRes, user); + childRes.origUsers, childRes, user, installReason); } } private void updateSettingsInternalLI(PackageParser.Package newPackage, String installerPackageName, int[] allUsers, int[] installedForUsers, - PackageInstalledInfo res, UserHandle user) { + PackageInstalledInfo res, UserHandle user, int installReason) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings"); String pkgName = newPackage.packageName; @@ -15500,6 +15554,30 @@ public class PackageManagerService extends IPackageManager.Stub { ps.setInstalled(true, userId); ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName); } + + // When replacing an existing package, preserve the original install reason for all + // users that had the package installed before. + final Set<Integer> previousUserIds = new ArraySet<>(); + if (res.removedInfo != null && res.removedInfo.installReasons != null) { + final int installReasonCount = res.removedInfo.installReasons.size(); + for (int i = 0; i < installReasonCount; i++) { + final int previousUserId = res.removedInfo.installReasons.keyAt(i); + final int previousInstallReason = res.removedInfo.installReasons.valueAt(i); + ps.setInstallReason(previousInstallReason, previousUserId); + previousUserIds.add(previousUserId); + } + } + + // Set install reason for users that are having the package newly installed. + if (userId == UserHandle.USER_ALL) { + for (int currentUserId : sUserManager.getUserIds()) { + if (!previousUserIds.contains(currentUserId)) { + ps.setInstallReason(installReason, currentUserId); + } + } + } else if (!previousUserIds.contains(userId)) { + ps.setInstallReason(installReason, userId); + } } res.name = pkgName; res.uid = newPackage.applicationInfo.uid; @@ -15877,10 +15955,10 @@ public class PackageManagerService extends IPackageManager.Stub { "installPackageLI")) { if (replace) { replacePackageLIF(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user, - installerPackageName, res); + installerPackageName, res, args.installReason); } else { installNewPackageLIF(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES, - args.user, installerPackageName, volumeUuid, res); + args.user, installerPackageName, volumeUuid, res, args.installReason); } } synchronized (mPackages) { @@ -16388,6 +16466,7 @@ public class PackageManagerService extends IPackageManager.Stub { int removedAppId = -1; int[] origUsers; int[] removedUsers = null; + SparseArray<Integer> installReasons; boolean isRemovedPackageSystemUpdate = false; boolean isUpdate; boolean dataRemoved; @@ -17026,7 +17105,8 @@ public class PackageManagerService extends IPackageManager.Stub { false /*installed*/, true /*stopped*/, true /*notLaunched*/, false /*hidden*/, false /*suspended*/, null, null, null, false /*blockUninstall*/, - ps.readUserState(nextUserId).domainVerificationStatus, 0); + ps.readUserState(nextUserId).domainVerificationStatus, 0, + PackageManager.INSTALL_REASON_UNKNOWN); } } @@ -21045,7 +21125,8 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); final OriginInfo origin = OriginInfo.fromExistingFile(codeFile); final InstallParams params = new InstallParams(origin, move, installObserver, installFlags, installerPackageName, volumeUuid, null /*verificationInfo*/, user, - packageAbiOverride, null /*grantedPermissions*/, null /*certificates*/); + packageAbiOverride, null /*grantedPermissions*/, null /*certificates*/, + PackageManager.INSTALL_REASON_UNKNOWN); params.setTraceMethod("movePackage").setTraceCookie(System.identityHashCode(params)); msg.obj = params; @@ -21829,4 +21910,18 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); public void deleteCompilerPackageStats(String pkgName) { mCompilerStats.deletePackageStats(pkgName); } + + @Override + public int getInstallReason(String packageName, int userId) { + enforceCrossUserPermission(Binder.getCallingUid(), userId, + true /* requireFullPermission */, false /* checkShell */, + "get install reason"); + synchronized (mPackages) { + final PackageSetting ps = mSettings.mPackages.get(packageName); + if (ps != null) { + return ps.getInstallReason(userId); + } + } + return PackageManager.INSTALL_REASON_UNKNOWN; + } } diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index 9456a5c4b6a2..b332fa5c6f9b 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -292,6 +292,14 @@ abstract class PackageSettingBase extends SettingBase { return readUserState(userId).installed; } + int getInstallReason(int userId) { + return readUserState(userId).installReason; + } + + void setInstallReason(int installReason, int userId) { + modifyUserState(userId).installReason = installReason; + } + /** Only use for testing. Do NOT use in production code. */ @VisibleForTesting SparseArray<PackageUserState> getUserState() { @@ -377,7 +385,7 @@ abstract class PackageSettingBase extends SettingBase { boolean notLaunched, boolean hidden, boolean suspended, String lastDisableAppCaller, ArraySet<String> enabledComponents, ArraySet<String> disabledComponents, boolean blockUninstall, int domainVerifState, - int linkGeneration) { + int linkGeneration, int installReason) { PackageUserState state = modifyUserState(userId); state.ceDataInode = ceDataInode; state.enabled = enabled; @@ -392,6 +400,7 @@ abstract class PackageSettingBase extends SettingBase { state.blockUninstall = blockUninstall; state.domainVerificationStatus = domainVerifState; state.appLinkGeneration = linkGeneration; + state.installReason = installReason; } ArraySet<String> getEnabledComponents(int userId) { diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 395108bfab94..8761a6dc60c1 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -216,6 +216,7 @@ final class Settings { private static final String ATTR_ENABLED_CALLER = "enabledCaller"; private static final String ATTR_DOMAIN_VERIFICATON_STATE = "domainVerificationStatus"; private static final String ATTR_APP_LINK_GENERATION = "app-link-generation"; + private static final String ATTR_INSTALL_REASON = "install-reason"; private static final String ATTR_PACKAGE_NAME = "packageName"; private static final String ATTR_FINGERPRINT = "fingerprint"; @@ -736,7 +737,8 @@ final class Settings { false, // suspended null, null, null, false, // blockUninstall - INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED, 0); + INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED, 0, + PackageManager.INSTALL_REASON_UNKNOWN); } } } @@ -1615,7 +1617,8 @@ final class Settings { false, // suspended null, null, null, false, // blockUninstall - INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED, 0); + INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED, 0, + PackageManager.INSTALL_REASON_UNKNOWN); } return; } @@ -1695,6 +1698,8 @@ final class Settings { if (linkGeneration > maxAppLinkGeneration) { maxAppLinkGeneration = linkGeneration; } + final int installReason = XmlUtils.readIntAttribute(parser, + ATTR_INSTALL_REASON, PackageManager.INSTALL_REASON_UNKNOWN); ArraySet<String> enabledComponents = null; ArraySet<String> disabledComponents = null; @@ -1717,7 +1722,7 @@ final class Settings { ps.setUserState(userId, ceDataInode, enabled, installed, stopped, notLaunched, hidden, suspended, enabledCaller, enabledComponents, disabledComponents, - blockUninstall, verifState, linkGeneration); + blockUninstall, verifState, linkGeneration, installReason); } else if (tagName.equals("preferred-activities")) { readPreferredActivitiesLPw(parser, userId); } else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) { @@ -2004,6 +2009,10 @@ final class Settings { XmlUtils.writeIntAttribute(serializer, ATTR_APP_LINK_GENERATION, ustate.appLinkGeneration); } + if (ustate.installReason != PackageManager.INSTALL_REASON_UNKNOWN) { + serializer.attribute(null, ATTR_INSTALL_REASON, + Integer.toString(ustate.installReason)); + } if (!ArrayUtils.isEmpty(ustate.enabledComponents)) { serializer.startTag(null, TAG_ENABLED_COMPONENTS); for (final String name : ustate.enabledComponents) { diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 862c1455d3a1..915951381ac7 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -57,6 +57,7 @@ import android.os.Debug; import android.os.IBinder; import android.os.IRemoteCallback; import android.os.RemoteException; +import android.os.SystemProperties; import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; @@ -250,6 +251,8 @@ public class AppTransition implements Dump { private boolean mLastHadClipReveal; private boolean mProlongedAnimationsEnded; + private final boolean mGridLayoutRecentsEnabled; + AppTransition(Context context, WindowManagerService service) { mContext = context; mService = service; @@ -288,6 +291,7 @@ public class AppTransition implements Dump { }; mClipRevealTranslationY = (int) (CLIP_REVEAL_TRANSLATION_Y_DP * mContext.getResources().getDisplayMetrics().density); + mGridLayoutRecentsEnabled = SystemProperties.getBoolean("ro.recents.grid", false); } boolean isTransitionSet() { @@ -952,7 +956,7 @@ public class AppTransition implements Dump { final float toY; final float pivotX; final float pivotY; - if (isTvUiMode(uiMode) || orientation == Configuration.ORIENTATION_PORTRAIT) { + if (shouldScaleDownThumbnailTransition(uiMode, orientation)) { fromX = mTmpRect.left; fromY = mTmpRect.top; @@ -1123,7 +1127,7 @@ public class AppTransition implements Dump { mTmpFromClipRect.inset(contentInsets); mNextAppTransitionInsets.set(contentInsets); - if (isTvUiMode(uiMode) || orientation == Configuration.ORIENTATION_PORTRAIT) { + if (shouldScaleDownThumbnailTransition(uiMode, orientation)) { // We scale the width and clip to the top/left square float scale = thumbWidth / (appWidth - contentInsets.left - contentInsets.right); @@ -2037,6 +2041,15 @@ public class AppTransition implements Dump { } /** + * @return whether the transition should show the thumbnail being scaled down. + */ + private boolean shouldScaleDownThumbnailTransition(int uiMode, int orientation) { + return isTvUiMode(uiMode) + || mGridLayoutRecentsEnabled + || orientation == Configuration.ORIENTATION_PORTRAIT; + } + + /** * @return whether the specified {@param uiMode} is the TV mode. */ private boolean isTvUiMode(int uiMode) { diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java index 9b2d3c6b090f..a9007029d929 100644 --- a/services/core/java/com/android/server/wm/AppWindowContainerController.java +++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java @@ -617,7 +617,7 @@ public class AppWindowContainerController } return dc.screenshotApplications(mToken.asBinder(), width, height, false /* includeFullDisplay */, frameScale, Bitmap.Config.RGB_565, - false /* wallpaperOnly */, false /* includeDecor */, true /* toAshmem */); + false /* wallpaperOnly */, false /* includeDecor */); } finally { Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 66267bdb018c..73bf3dc3e230 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -94,6 +94,7 @@ import android.annotation.NonNull; import android.app.ActivityManager.StackId; import android.content.res.Configuration; import android.graphics.Bitmap; +import android.graphics.GraphicBuffer; import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; @@ -2105,12 +2106,55 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * @param wallpaperOnly true if only the wallpaper layer should be included in the screenshot * @param includeDecor whether to include window decors, like the status or navigation bar * background of the window - * @param toAshmem whether to convert the resulting bitmap to ashmem; this should be set to - * true if the Bitmap is sent over binder, and false otherwise */ Bitmap screenshotApplications(IBinder appToken, int width, int height, boolean includeFullDisplay, float frameScale, Bitmap.Config config, - boolean wallpaperOnly, boolean includeDecor, boolean toAshmem) { + boolean wallpaperOnly, boolean includeDecor) { + Bitmap bitmap = screenshotApplications(appToken, width, height, includeFullDisplay, + frameScale, wallpaperOnly, includeDecor, SurfaceControl::screenshot); + + if (DEBUG_SCREENSHOT) { + // TEST IF IT's ALL BLACK + int[] buffer = new int[bitmap.getWidth() * bitmap.getHeight()]; + bitmap.getPixels(buffer, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), + bitmap.getHeight()); + boolean allBlack = true; + final int firstColor = buffer[0]; + for (int i = 0; i < buffer.length; i++) { + if (buffer[i] != firstColor) { + allBlack = false; + break; + } + } + if (allBlack) { + final WindowState appWin = mScreenshotApplicationState.appWin; + final int maxLayer = mScreenshotApplicationState.maxLayer; + final int minLayer = mScreenshotApplicationState.minLayer; + Slog.i(TAG_WM, "Screenshot " + appWin + " was monochrome(" + + Integer.toHexString(firstColor) + ")! mSurfaceLayer=" + + (appWin != null ? + appWin.mWinAnimator.mSurfaceController.getLayer() : "null") + + " minLayer=" + minLayer + " maxLayer=" + maxLayer); + } + } + + // Create a copy of the screenshot that is immutable and backed in ashmem. + // This greatly reduces the overhead of passing the bitmap between processes. + Bitmap ret = bitmap.createAshmemBitmap(config); + bitmap.recycle(); + return ret; + } + + GraphicBuffer screenshotApplicationsToBuffer(IBinder appToken, int width, int height, + boolean includeFullDisplay, float frameScale, boolean wallpaperOnly, + boolean includeDecor) { + return screenshotApplications(appToken, width, height, includeFullDisplay, frameScale, + wallpaperOnly, includeDecor, SurfaceControl::screenshotToBuffer); + } + + private <E> E screenshotApplications(IBinder appToken, int width, int height, + boolean includeFullDisplay, float frameScale, boolean wallpaperOnly, + boolean includeDecor, Screenshoter<E> screenshoter) { int dw = mDisplayInfo.logicalWidth; int dh = mDisplayInfo.logicalHeight; if (dw == 0 || dh == 0) { @@ -2119,7 +2163,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return null; } - Bitmap bm = null; + E bitmap; mScreenshotApplicationState.reset(appToken == null && !wallpaperOnly); final Rect frame = new Rect(); @@ -2327,48 +2371,15 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo SurfaceControl.openTransaction(); SurfaceControl.closeTransactionSync(); - bm = SurfaceControl.screenshot(crop, width, height, minLayer, maxLayer, + bitmap = screenshoter.screenshot(crop, width, height, minLayer, maxLayer, inRotation, rot); - if (bm == null) { + if (bitmap == null) { Slog.w(TAG_WM, "Screenshot failure taking screenshot for (" + dw + "x" + dh + ") to layer " + maxLayer); return null; } } - - if (DEBUG_SCREENSHOT) { - // TEST IF IT's ALL BLACK - int[] buffer = new int[bm.getWidth() * bm.getHeight()]; - bm.getPixels(buffer, 0, bm.getWidth(), 0, 0, bm.getWidth(), bm.getHeight()); - boolean allBlack = true; - final int firstColor = buffer[0]; - for (int i = 0; i < buffer.length; i++) { - if (buffer[i] != firstColor) { - allBlack = false; - break; - } - } - if (allBlack) { - final WindowState appWin = mScreenshotApplicationState.appWin; - final int maxLayer = mScreenshotApplicationState.maxLayer; - final int minLayer = mScreenshotApplicationState.minLayer; - Slog.i(TAG_WM, "Screenshot " + appWin + " was monochrome(" + - Integer.toHexString(firstColor) + ")! mSurfaceLayer=" + - (appWin != null ? - appWin.mWinAnimator.mSurfaceController.getLayer() : "null") + - " minLayer=" + minLayer + " maxLayer=" + maxLayer); - } - } - - // Create a copy of the screenshot that is immutable and backed in ashmem. - // This greatly reduces the overhead of passing the bitmap between processes. - if (toAshmem) { - Bitmap ret = bm.createAshmemBitmap(config); - bm.recycle(); - return ret; - } else { - return bm; - } + return bitmap; } // TODO: Can this use createRotationMatrix()? @@ -2721,4 +2732,13 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mName; } } + + /** + * Interface to screenshot into various types, i.e. {@link Bitmap} and {@link GraphicBuffer}. + */ + @FunctionalInterface + private interface Screenshoter<E> { + E screenshot(Rect sourceCrop, int width, int height, int minLayer, int maxLayer, + boolean useIdentityTransform, int rotation); + } } diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index 4421d618737a..68aceae8a6aa 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -96,17 +96,11 @@ class TaskSnapshotController { if (top == null) { return null; } - final Bitmap bmp = top.mDisplayContent.screenshotApplications(top.token, -1, -1, false, - 1.0f, ARGB_8888, false, true, false); - if (bmp == null) { + final GraphicBuffer buffer = top.mDisplayContent.screenshotApplicationsToBuffer(top.token, + -1, -1, false, 1.0f, false, true); + if (buffer == null) { return null; } - // TODO: Already use a GraphicBuffer when snapshotting the content. - final GraphicBuffer buffer = GraphicBuffer.create(bmp.getWidth(), bmp.getHeight(), - RGBA_8888, USAGE_HW_TEXTURE | USAGE_SW_WRITE_NEVER | USAGE_SW_READ_NEVER); - final Canvas c = buffer.lockCanvas(); - c.drawBitmap(bmp, 0, 0, null); - buffer.unlockCanvasAndPost(c); return new TaskSnapshot(buffer, top.getConfiguration().orientation, top.findMainWindow().mStableInsets); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 195d4c31f566..7cc77de8f9e5 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -3862,8 +3862,7 @@ public class WindowManagerService extends IWindowManager.Stub Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenshotWallpaper"); return screenshotApplications(null /* appToken */, DEFAULT_DISPLAY, -1 /* width */, -1 /* height */, true /* includeFullDisplay */, 1f /* frameScale */, - Bitmap.Config.ARGB_8888, true /* wallpaperOnly */, false /* includeDecor */, - true /* toAshmem */); + Bitmap.Config.ARGB_8888, true /* wallpaperOnly */, false /* includeDecor */); } finally { Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } @@ -3885,7 +3884,7 @@ public class WindowManagerService extends IWindowManager.Stub Bitmap bm = screenshotApplications(null /* appToken */, DEFAULT_DISPLAY, -1 /* width */, -1 /* height */, true /* includeFullDisplay */, 1f /* frameScale */, Bitmap.Config.ARGB_8888, false /* wallpaperOnly */, - false /* includeDecor */, true /* toAshmem */); + false /* includeDecor */); try { receiver.send(bm); } catch (RemoteException e) { @@ -3908,12 +3907,10 @@ public class WindowManagerService extends IWindowManager.Stub * @param wallpaperOnly true if only the wallpaper layer should be included in the screenshot * @param includeDecor whether to include window decors, like the status or navigation bar * background of the window - * @param toAshmem whether to convert the resulting bitmap to ashmem; this should be set to - * true if the Bitmap is sent over binder, and false otherwise */ private Bitmap screenshotApplications(IBinder appToken, int displayId, int width, int height, boolean includeFullDisplay, float frameScale, Bitmap.Config config, - boolean wallpaperOnly, boolean includeDecor, boolean toAshmem) { + boolean wallpaperOnly, boolean includeDecor) { final DisplayContent displayContent; synchronized(mWindowMap) { displayContent = mRoot.getDisplayContentOrCreate(displayId); @@ -3924,7 +3921,7 @@ public class WindowManagerService extends IWindowManager.Stub } } return displayContent.screenshotApplications(appToken, width, height, - includeFullDisplay, frameScale, config, wallpaperOnly, includeDecor, toAshmem); + includeFullDisplay, frameScale, config, wallpaperOnly, includeDecor); } /** diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk index d238215f7e10..eab5d8a48ba3 100644 --- a/services/core/jni/Android.mk +++ b/services/core/jni/Android.mk @@ -17,7 +17,6 @@ LOCAL_SRC_FILES += \ $(LOCAL_REL_DIR)/com_android_server_lights_LightsService.cpp \ $(LOCAL_REL_DIR)/com_android_server_location_ContextHubService.cpp \ $(LOCAL_REL_DIR)/com_android_server_location_GnssLocationProvider.cpp \ - $(LOCAL_REL_DIR)/com_android_server_location_FlpHardwareProvider.cpp \ $(LOCAL_REL_DIR)/com_android_server_power_PowerManagerService.cpp \ $(LOCAL_REL_DIR)/com_android_server_SerialService.cpp \ $(LOCAL_REL_DIR)/com_android_server_storage_AppFuseBridge.cpp \ diff --git a/services/core/jni/com_android_server_location_FlpHardwareProvider.cpp b/services/core/jni/com_android_server_location_FlpHardwareProvider.cpp deleted file mode 100644 index 06d2031e4a4a..000000000000 --- a/services/core/jni/com_android_server_location_FlpHardwareProvider.cpp +++ /dev/null @@ -1,1101 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the license at - * - * http://www.apache.org/license/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the license. - */ - -#define LOG_TAG "FlpHardwareProvider" -#define LOG_NDEBUG 0 - -#define WAKE_LOCK_NAME "FLP" -#define LOCATION_CLASS_NAME "android/location/Location" - -#include "jni.h" -#include "JNIHelp.h" -#include "android_runtime/AndroidRuntime.h" -#include "android_runtime/Log.h" -#include "hardware/fused_location.h" -#include "hardware_legacy/power.h" - -static jobject sCallbacksObj = NULL; -static JNIEnv *sCallbackEnv = NULL; -static hw_device_t* sHardwareDevice = NULL; - -static jmethodID sSetVersion = NULL; -static jmethodID sOnLocationReport = NULL; -static jmethodID sOnDataReport = NULL; -static jmethodID sOnBatchingCapabilities = NULL; -static jmethodID sOnBatchingStatus = NULL; -static jmethodID sOnGeofenceTransition = NULL; -static jmethodID sOnGeofenceMonitorStatus = NULL; -static jmethodID sOnGeofenceAdd = NULL; -static jmethodID sOnGeofenceRemove = NULL; -static jmethodID sOnGeofencePause = NULL; -static jmethodID sOnGeofenceResume = NULL; -static jmethodID sOnGeofencingCapabilities = NULL; - -static const FlpLocationInterface* sFlpInterface = NULL; -static const FlpDiagnosticInterface* sFlpDiagnosticInterface = NULL; -static const FlpGeofencingInterface* sFlpGeofencingInterface = NULL; -static const FlpDeviceContextInterface* sFlpDeviceContextInterface = NULL; - -namespace android { - -static inline void CheckExceptions(JNIEnv* env, const char* methodName) { - if(!env->ExceptionCheck()) { - return; - } - - ALOGE("An exception was thrown by '%s'.", methodName); - LOGE_EX(env); - env->ExceptionClear(); -} - -static inline void ThrowOnError( - JNIEnv* env, - int resultCode, - const char* methodName) { - if(resultCode == FLP_RESULT_SUCCESS) { - return; - } - - ALOGE("Error %d in '%s'", resultCode, methodName); - // TODO: this layer needs to be refactored to return error codes to Java - // raising a FatalError is harsh, and because FLP Hardware Provider is loaded inside the system - // service, it can cause the device to reboot, or remain in a reboot loop - // a simple exception is still dumped to logcat, but it is handled more gracefully - jclass exceptionClass = env->FindClass("java/lang/RuntimeException"); - env->ThrowNew(exceptionClass, methodName); -} - -static bool IsValidCallbackThreadEnvOnly() { - JNIEnv* env = AndroidRuntime::getJNIEnv(); - - if(sCallbackEnv == NULL || sCallbackEnv != env) { - ALOGE("CallbackThread check fail: env=%p, expected=%p", env, sCallbackEnv); - return false; - } - - return true; -} - -static bool IsValidCallbackThread() { - // sCallbacksObject is created when FlpHardwareProvider on Java side is - // initialized. Sometimes the hardware may call a function before the Java - // side is ready. In order to prevent a system crash, check whether - // sCallbacksObj has been created. If not, simply ignore this event from - // hardware. - if (sCallbacksObj == NULL) { - ALOGE("Attempt to use FlpHardwareProvider blocked, because it hasn't been initialized."); - return false; - } - - return IsValidCallbackThreadEnvOnly(); -} - -static void BatchingCapabilitiesCallback(int32_t capabilities) { - if(!IsValidCallbackThread()) { - return; - } - - sCallbackEnv->CallVoidMethod( - sCallbacksObj, - sOnBatchingCapabilities, - capabilities - ); - CheckExceptions(sCallbackEnv, __FUNCTION__); -} - -static void BatchingStatusCallback(int32_t status) { - if(!IsValidCallbackThread()) { - return; - } - - sCallbackEnv->CallVoidMethod( - sCallbacksObj, - sOnBatchingStatus, - status - ); - CheckExceptions(sCallbackEnv, __FUNCTION__); -} - -static int SetThreadEvent(ThreadEvent event) { - JavaVM* javaVm = AndroidRuntime::getJavaVM(); - - switch(event) { - case ASSOCIATE_JVM: - { - if(sCallbackEnv != NULL) { - ALOGE( - "Attempted to associate callback in '%s'. Callback already associated.", - __FUNCTION__ - ); - return FLP_RESULT_ERROR; - } - - JavaVMAttachArgs args = { - JNI_VERSION_1_6, - "FLP Service Callback Thread", - /* group */ NULL - }; - - jint attachResult = javaVm->AttachCurrentThread(&sCallbackEnv, &args); - if (attachResult != 0) { - ALOGE("Callback thread attachment error: %d", attachResult); - return FLP_RESULT_ERROR; - } - - ALOGV("Callback thread attached: %p", sCallbackEnv); - - // Send the version to the upper layer. - sCallbackEnv->CallVoidMethod( - sCallbacksObj, - sSetVersion, - sFlpInterface->size == sizeof(FlpLocationInterface) ? 2 : 1 - ); - CheckExceptions(sCallbackEnv, __FUNCTION__); - break; - } - case DISASSOCIATE_JVM: - { - if (!IsValidCallbackThreadEnvOnly()) { - ALOGE( - "Attempted to dissasociate an unnownk callback thread : '%s'.", - __FUNCTION__ - ); - return FLP_RESULT_ERROR; - } - - if (javaVm->DetachCurrentThread() != 0) { - return FLP_RESULT_ERROR; - } - - sCallbackEnv = NULL; - break; - } - default: - ALOGE("Invalid ThreadEvent request %d", event); - return FLP_RESULT_ERROR; - } - - return FLP_RESULT_SUCCESS; -} - -/* - * Initializes the FlpHardwareProvider class from the native side by opening - * the HW module and obtaining the proper interfaces. - */ -static void ClassInit(JNIEnv* env, jclass clazz) { - sFlpInterface = NULL; - - // get references to the Java provider methods - sSetVersion = env->GetMethodID( - clazz, - "setVersion", - "(I)V"); - sOnLocationReport = env->GetMethodID( - clazz, - "onLocationReport", - "([Landroid/location/Location;)V"); - sOnDataReport = env->GetMethodID( - clazz, - "onDataReport", - "(Ljava/lang/String;)V" - ); - sOnBatchingCapabilities = env->GetMethodID( - clazz, - "onBatchingCapabilities", - "(I)V"); - sOnBatchingStatus = env->GetMethodID( - clazz, - "onBatchingStatus", - "(I)V"); - sOnGeofencingCapabilities = env->GetMethodID( - clazz, - "onGeofencingCapabilities", - "(I)V"); - sOnGeofenceTransition = env->GetMethodID( - clazz, - "onGeofenceTransition", - "(ILandroid/location/Location;IJI)V" - ); - sOnGeofenceMonitorStatus = env->GetMethodID( - clazz, - "onGeofenceMonitorStatus", - "(IILandroid/location/Location;)V" - ); - sOnGeofenceAdd = env->GetMethodID(clazz, "onGeofenceAdd", "(II)V"); - sOnGeofenceRemove = env->GetMethodID(clazz, "onGeofenceRemove", "(II)V"); - sOnGeofencePause = env->GetMethodID(clazz, "onGeofencePause", "(II)V"); - sOnGeofenceResume = env->GetMethodID(clazz, "onGeofenceResume", "(II)V"); - - // open the hardware module - const hw_module_t* module = NULL; - int err = hw_get_module(FUSED_LOCATION_HARDWARE_MODULE_ID, &module); - if (err != 0) { - ALOGE("Error hw_get_module '%s': %d", FUSED_LOCATION_HARDWARE_MODULE_ID, err); - return; - } - - err = module->methods->open( - module, - FUSED_LOCATION_HARDWARE_MODULE_ID, - &sHardwareDevice); - if (err != 0) { - ALOGE("Error opening device '%s': %d", FUSED_LOCATION_HARDWARE_MODULE_ID, err); - return; - } - - // acquire the interfaces pointers - flp_device_t* flp_device = reinterpret_cast<flp_device_t*>(sHardwareDevice); - sFlpInterface = flp_device->get_flp_interface(flp_device); - - if (sFlpInterface != NULL) { - sFlpDiagnosticInterface = reinterpret_cast<const FlpDiagnosticInterface*>( - sFlpInterface->get_extension(FLP_DIAGNOSTIC_INTERFACE)); - - sFlpGeofencingInterface = reinterpret_cast<const FlpGeofencingInterface*>( - sFlpInterface->get_extension(FLP_GEOFENCING_INTERFACE)); - - sFlpDeviceContextInterface = reinterpret_cast<const FlpDeviceContextInterface*>( - sFlpInterface->get_extension(FLP_DEVICE_CONTEXT_INTERFACE)); - } -} - -/* - * Helper function to unwrap a java object back into a FlpLocation structure. - */ -static void TranslateFromObject( - JNIEnv* env, - jobject locationObject, - FlpLocation& location) { - location.size = sizeof(FlpLocation); - location.flags = 0; - - jclass locationClass = env->GetObjectClass(locationObject); - - jmethodID getLatitude = env->GetMethodID(locationClass, "getLatitude", "()D"); - location.latitude = env->CallDoubleMethod(locationObject, getLatitude); - jmethodID getLongitude = env->GetMethodID(locationClass, "getLongitude", "()D"); - location.longitude = env->CallDoubleMethod(locationObject, getLongitude); - jmethodID getTime = env->GetMethodID(locationClass, "getTime", "()J"); - location.timestamp = env->CallLongMethod(locationObject, getTime); - location.flags |= FLP_LOCATION_HAS_LAT_LONG; - - jmethodID hasAltitude = env->GetMethodID(locationClass, "hasAltitude", "()Z"); - if (env->CallBooleanMethod(locationObject, hasAltitude)) { - jmethodID getAltitude = env->GetMethodID(locationClass, "getAltitude", "()D"); - location.altitude = env->CallDoubleMethod(locationObject, getAltitude); - location.flags |= FLP_LOCATION_HAS_ALTITUDE; - } - - jmethodID hasSpeed = env->GetMethodID(locationClass, "hasSpeed", "()Z"); - if (env->CallBooleanMethod(locationObject, hasSpeed)) { - jmethodID getSpeed = env->GetMethodID(locationClass, "getSpeed", "()F"); - location.speed = env->CallFloatMethod(locationObject, getSpeed); - location.flags |= FLP_LOCATION_HAS_SPEED; - } - - jmethodID hasBearing = env->GetMethodID(locationClass, "hasBearing", "()Z"); - if (env->CallBooleanMethod(locationObject, hasBearing)) { - jmethodID getBearing = env->GetMethodID(locationClass, "getBearing", "()F"); - location.bearing = env->CallFloatMethod(locationObject, getBearing); - location.flags |= FLP_LOCATION_HAS_BEARING; - } - - jmethodID hasAccuracy = env->GetMethodID(locationClass, "hasAccuracy", "()Z"); - if (env->CallBooleanMethod(locationObject, hasAccuracy)) { - jmethodID getAccuracy = env->GetMethodID( - locationClass, - "getAccuracy", - "()F" - ); - location.accuracy = env->CallFloatMethod(locationObject, getAccuracy); - location.flags |= FLP_LOCATION_HAS_ACCURACY; - } - - // TODO: wire sources_used if Location class exposes them - - env->DeleteLocalRef(locationClass); -} - -/* - * Helper function to unwrap FlpBatchOptions from the Java Runtime calls. - */ -static void TranslateFromObject( - JNIEnv* env, - jobject batchOptionsObject, - FlpBatchOptions& batchOptions) { - jclass batchOptionsClass = env->GetObjectClass(batchOptionsObject); - - jmethodID getMaxPower = env->GetMethodID( - batchOptionsClass, - "getMaxPowerAllocationInMW", - "()D" - ); - batchOptions.max_power_allocation_mW = env->CallDoubleMethod( - batchOptionsObject, - getMaxPower - ); - - jmethodID getPeriod = env->GetMethodID( - batchOptionsClass, - "getPeriodInNS", - "()J" - ); - batchOptions.period_ns = env->CallLongMethod(batchOptionsObject, getPeriod); - - jmethodID getSourcesToUse = env->GetMethodID( - batchOptionsClass, - "getSourcesToUse", - "()I" - ); - batchOptions.sources_to_use = env->CallIntMethod( - batchOptionsObject, - getSourcesToUse - ); - - jmethodID getSmallestDisplacementMeters = env->GetMethodID( - batchOptionsClass, - "getSmallestDisplacementMeters", - "()F" - ); - batchOptions.smallest_displacement_meters - = env->CallFloatMethod(batchOptionsObject, getSmallestDisplacementMeters); - - jmethodID getFlags = env->GetMethodID(batchOptionsClass, "getFlags", "()I"); - batchOptions.flags = env->CallIntMethod(batchOptionsObject, getFlags); - - env->DeleteLocalRef(batchOptionsClass); -} - -/* - * Helper function to unwrap Geofence structures from the Java Runtime calls. - */ -static void TranslateGeofenceFromGeofenceHardwareRequestParcelable( - JNIEnv* env, - jobject geofenceRequestObject, - Geofence& geofence) { - jclass geofenceRequestClass = env->GetObjectClass(geofenceRequestObject); - - jmethodID getId = env->GetMethodID(geofenceRequestClass, "getId", "()I"); - geofence.geofence_id = env->CallIntMethod(geofenceRequestObject, getId); - - jmethodID getType = env->GetMethodID(geofenceRequestClass, "getType", "()I"); - // this works because GeofenceHardwareRequest.java and fused_location.h have - // the same notion of geofence types - GeofenceType type = (GeofenceType)env->CallIntMethod(geofenceRequestObject, getType); - if(type != TYPE_CIRCLE) { - ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); - } - geofence.data->type = type; - GeofenceCircle& circle = geofence.data->geofence.circle; - - jmethodID getLatitude = env->GetMethodID( - geofenceRequestClass, - "getLatitude", - "()D"); - circle.latitude = env->CallDoubleMethod(geofenceRequestObject, getLatitude); - - jmethodID getLongitude = env->GetMethodID( - geofenceRequestClass, - "getLongitude", - "()D"); - circle.longitude = env->CallDoubleMethod(geofenceRequestObject, getLongitude); - - jmethodID getRadius = env->GetMethodID(geofenceRequestClass, "getRadius", "()D"); - circle.radius_m = env->CallDoubleMethod(geofenceRequestObject, getRadius); - - GeofenceOptions* options = geofence.options; - jmethodID getMonitorTransitions = env->GetMethodID( - geofenceRequestClass, - "getMonitorTransitions", - "()I"); - options->monitor_transitions = env->CallIntMethod( - geofenceRequestObject, - getMonitorTransitions); - - jmethodID getUnknownTimer = env->GetMethodID( - geofenceRequestClass, - "getUnknownTimer", - "()I"); - options->unknown_timer_ms = env->CallIntMethod(geofenceRequestObject, getUnknownTimer); - - jmethodID getNotificationResponsiveness = env->GetMethodID( - geofenceRequestClass, - "getNotificationResponsiveness", - "()I"); - options->notification_responsivenes_ms = env->CallIntMethod( - geofenceRequestObject, - getNotificationResponsiveness); - - jmethodID getLastTransition = env->GetMethodID( - geofenceRequestClass, - "getLastTransition", - "()I"); - options->last_transition = env->CallIntMethod(geofenceRequestObject, getLastTransition); - - jmethodID getSourceTechnologies = - env->GetMethodID(geofenceRequestClass, "getSourceTechnologies", "()I"); - options->sources_to_use = env->CallIntMethod(geofenceRequestObject, getSourceTechnologies); - - env->DeleteLocalRef(geofenceRequestClass); -} - -/* - * Helper function to transform FlpLocation into a java object. - */ -static void TranslateToObject(const FlpLocation* location, jobject& locationObject) { - jclass locationClass = sCallbackEnv->FindClass(LOCATION_CLASS_NAME); - jmethodID locationCtor = sCallbackEnv->GetMethodID( - locationClass, - "<init>", - "(Ljava/lang/String;)V" - ); - - // the provider is set in the upper JVM layer - locationObject = sCallbackEnv->NewObject(locationClass, locationCtor, NULL); - jint flags = location->flags; - - // set the valid information in the object - if (flags & FLP_LOCATION_HAS_LAT_LONG) { - jmethodID setLatitude = sCallbackEnv->GetMethodID( - locationClass, - "setLatitude", - "(D)V" - ); - sCallbackEnv->CallVoidMethod(locationObject, setLatitude, location->latitude); - - jmethodID setLongitude = sCallbackEnv->GetMethodID( - locationClass, - "setLongitude", - "(D)V" - ); - sCallbackEnv->CallVoidMethod( - locationObject, - setLongitude, - location->longitude - ); - - jmethodID setTime = sCallbackEnv->GetMethodID( - locationClass, - "setTime", - "(J)V" - ); - sCallbackEnv->CallVoidMethod(locationObject, setTime, location->timestamp); - } - - if (flags & FLP_LOCATION_HAS_ALTITUDE) { - jmethodID setAltitude = sCallbackEnv->GetMethodID( - locationClass, - "setAltitude", - "(D)V" - ); - sCallbackEnv->CallVoidMethod(locationObject, setAltitude, location->altitude); - } - - if (flags & FLP_LOCATION_HAS_SPEED) { - jmethodID setSpeed = sCallbackEnv->GetMethodID( - locationClass, - "setSpeed", - "(F)V" - ); - sCallbackEnv->CallVoidMethod(locationObject, setSpeed, location->speed); - } - - if (flags & FLP_LOCATION_HAS_BEARING) { - jmethodID setBearing = sCallbackEnv->GetMethodID( - locationClass, - "setBearing", - "(F)V" - ); - sCallbackEnv->CallVoidMethod(locationObject, setBearing, location->bearing); - } - - if (flags & FLP_LOCATION_HAS_ACCURACY) { - jmethodID setAccuracy = sCallbackEnv->GetMethodID( - locationClass, - "setAccuracy", - "(F)V" - ); - sCallbackEnv->CallVoidMethod(locationObject, setAccuracy, location->accuracy); - } - - // TODO: wire FlpLocation::sources_used when needed - - sCallbackEnv->DeleteLocalRef(locationClass); -} - -/* - * Helper function to serialize FlpLocation structures. - */ -static void TranslateToObjectArray( - int32_t locationsCount, - FlpLocation** locations, - jobjectArray& locationsArray) { - jclass locationClass = sCallbackEnv->FindClass(LOCATION_CLASS_NAME); - locationsArray = sCallbackEnv->NewObjectArray( - locationsCount, - locationClass, - /* initialElement */ NULL - ); - - for (int i = 0; i < locationsCount; ++i) { - jobject locationObject = NULL; - TranslateToObject(locations[i], locationObject); - sCallbackEnv->SetObjectArrayElement(locationsArray, i, locationObject); - sCallbackEnv->DeleteLocalRef(locationObject); - } - - sCallbackEnv->DeleteLocalRef(locationClass); -} - -static void LocationCallback(int32_t locationsCount, FlpLocation** locations) { - if(!IsValidCallbackThread()) { - return; - } - - if(locationsCount == 0 || locations == NULL) { - ALOGE( - "Invalid LocationCallback. Count: %d, Locations: %p", - locationsCount, - locations - ); - return; - } - - jobjectArray locationsArray = NULL; - TranslateToObjectArray(locationsCount, locations, locationsArray); - - sCallbackEnv->CallVoidMethod( - sCallbacksObj, - sOnLocationReport, - locationsArray - ); - CheckExceptions(sCallbackEnv, __FUNCTION__); - - if(locationsArray != NULL) { - sCallbackEnv->DeleteLocalRef(locationsArray); - } -} - -static void AcquireWakelock() { - acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_NAME); -} - -static void ReleaseWakelock() { - release_wake_lock(WAKE_LOCK_NAME); -} - -FlpCallbacks sFlpCallbacks = { - sizeof(FlpCallbacks), - LocationCallback, - AcquireWakelock, - ReleaseWakelock, - SetThreadEvent, - BatchingCapabilitiesCallback, - BatchingStatusCallback -}; - -static void ReportData(char* data, int length) { - jstring stringData = NULL; - - if(length != 0 && data != NULL) { - stringData = sCallbackEnv->NewString(reinterpret_cast<jchar*>(data), length); - } else { - ALOGE("Invalid ReportData callback. Length: %d, Data: %p", length, data); - return; - } - - sCallbackEnv->CallVoidMethod(sCallbacksObj, sOnDataReport, stringData); - CheckExceptions(sCallbackEnv, __FUNCTION__); -} - -FlpDiagnosticCallbacks sFlpDiagnosticCallbacks = { - sizeof(FlpDiagnosticCallbacks), - SetThreadEvent, - ReportData -}; - -static void GeofenceTransitionCallback( - int32_t geofenceId, - FlpLocation* location, - int32_t transition, - FlpUtcTime timestamp, - uint32_t sourcesUsed - ) { - if(!IsValidCallbackThread()) { - return; - } - - if(location == NULL) { - ALOGE("GeofenceTransition received with invalid location: %p", location); - return; - } - - jobject locationObject = NULL; - TranslateToObject(location, locationObject); - - sCallbackEnv->CallVoidMethod( - sCallbacksObj, - sOnGeofenceTransition, - geofenceId, - locationObject, - transition, - timestamp, - sourcesUsed - ); - CheckExceptions(sCallbackEnv, __FUNCTION__); - - if(locationObject != NULL) { - sCallbackEnv->DeleteLocalRef(locationObject); - } -} - -static void GeofenceMonitorStatusCallback( - int32_t status, - uint32_t source, - FlpLocation* lastLocation) { - if(!IsValidCallbackThread()) { - return; - } - - jobject locationObject = NULL; - if(lastLocation != NULL) { - TranslateToObject(lastLocation, locationObject); - } - - sCallbackEnv->CallVoidMethod( - sCallbacksObj, - sOnGeofenceMonitorStatus, - status, - source, - locationObject - ); - CheckExceptions(sCallbackEnv, __FUNCTION__); - - if(locationObject != NULL) { - sCallbackEnv->DeleteLocalRef(locationObject); - } -} - -static void GeofenceAddCallback(int32_t geofenceId, int32_t result) { - if(!IsValidCallbackThread()) { - return; - } - - sCallbackEnv->CallVoidMethod(sCallbacksObj, sOnGeofenceAdd, geofenceId, result); - CheckExceptions(sCallbackEnv, __FUNCTION__); -} - -static void GeofenceRemoveCallback(int32_t geofenceId, int32_t result) { - if(!IsValidCallbackThread()) { - return; - } - - sCallbackEnv->CallVoidMethod( - sCallbacksObj, - sOnGeofenceRemove, - geofenceId, - result - ); - CheckExceptions(sCallbackEnv, __FUNCTION__); -} - -static void GeofencePauseCallback(int32_t geofenceId, int32_t result) { - if(!IsValidCallbackThread()) { - return; - } - - sCallbackEnv->CallVoidMethod( - sCallbacksObj, - sOnGeofencePause, - geofenceId, - result - ); - CheckExceptions(sCallbackEnv, __FUNCTION__); -} - -static void GeofenceResumeCallback(int32_t geofenceId, int32_t result) { - if(!IsValidCallbackThread()) { - return; - } - - sCallbackEnv->CallVoidMethod( - sCallbacksObj, - sOnGeofenceResume, - geofenceId, - result - ); - CheckExceptions(sCallbackEnv, __FUNCTION__); -} - -static void GeofencingCapabilitiesCallback(int32_t capabilities) { - if(!IsValidCallbackThread()) { - return; - } - - sCallbackEnv->CallVoidMethod( - sCallbacksObj, - sOnGeofencingCapabilities, - capabilities - ); - CheckExceptions(sCallbackEnv, __FUNCTION__); -} - -FlpGeofenceCallbacks sFlpGeofenceCallbacks = { - sizeof(FlpGeofenceCallbacks), - GeofenceTransitionCallback, - GeofenceMonitorStatusCallback, - GeofenceAddCallback, - GeofenceRemoveCallback, - GeofencePauseCallback, - GeofenceResumeCallback, - SetThreadEvent, - GeofencingCapabilitiesCallback -}; - -/* - * Initializes the Fused Location Provider in the native side. It ensures that - * the Flp interfaces are initialized properly. - */ -static void Init(JNIEnv* env, jobject obj) { - if(sCallbacksObj == NULL) { - sCallbacksObj = env->NewGlobalRef(obj); - } - - // initialize the Flp interfaces - if(sFlpInterface == NULL || sFlpInterface->init(&sFlpCallbacks) != 0) { - ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); - } - - if(sFlpDiagnosticInterface != NULL) { - sFlpDiagnosticInterface->init(&sFlpDiagnosticCallbacks); - } - - if(sFlpGeofencingInterface != NULL) { - sFlpGeofencingInterface->init(&sFlpGeofenceCallbacks); - } - - // TODO: inject any device context if when needed -} - -static jboolean IsSupported(JNIEnv* /* env */, jclass /* clazz */) { - if (sFlpInterface == NULL) { - return JNI_FALSE; - } - return JNI_TRUE; -} - -static jint GetBatchSize(JNIEnv* env, jobject /* object */) { - if(sFlpInterface == NULL) { - ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); - } - - return sFlpInterface->get_batch_size(); -} - -static void StartBatching( - JNIEnv* env, - jobject /* object */, - jint id, - jobject optionsObject) { - if(sFlpInterface == NULL || optionsObject == NULL) { - ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); - } - - FlpBatchOptions options; - TranslateFromObject(env, optionsObject, options); - int result = sFlpInterface->start_batching(id, &options); - ThrowOnError(env, result, __FUNCTION__); -} - -static void UpdateBatchingOptions( - JNIEnv* env, - jobject /* object */, - jint id, - jobject optionsObject) { - if(sFlpInterface == NULL || optionsObject == NULL) { - ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); - } - - FlpBatchOptions options; - TranslateFromObject(env, optionsObject, options); - int result = sFlpInterface->update_batching_options(id, &options); - ThrowOnError(env, result, __FUNCTION__); -} - -static void StopBatching(JNIEnv* env, jobject /* object */, jint id) { - if(sFlpInterface == NULL) { - ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); - } - - sFlpInterface->stop_batching(id); -} - -static void Cleanup(JNIEnv* env, jobject /* object */) { - if(sFlpInterface == NULL) { - ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); - } - - sFlpInterface->cleanup(); - - if(sCallbacksObj != NULL) { - env->DeleteGlobalRef(sCallbacksObj); - sCallbacksObj = NULL; - } -} - -static void GetBatchedLocation(JNIEnv* env, jobject /* object */, jint lastNLocations) { - if(sFlpInterface == NULL) { - ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); - } - - sFlpInterface->get_batched_location(lastNLocations); -} - -static void FlushBatchedLocations(JNIEnv* env, jobject /* object */) { - if(sFlpInterface == NULL) { - ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); - } - - sFlpInterface->flush_batched_locations(); -} - -static void InjectLocation(JNIEnv* env, jobject /* object */, jobject locationObject) { - if(locationObject == NULL) { - ALOGE("Invalid location for injection: %p", locationObject); - ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); - } - - if(sFlpInterface == NULL) { - // there is no listener, bail - return; - } - - FlpLocation location; - TranslateFromObject(env, locationObject, location); - int result = sFlpInterface->inject_location(&location); - if (result != FLP_RESULT_SUCCESS) { - // do not throw but log, this operation should be fire and forget - ALOGE("Error %d in '%s'", result, __FUNCTION__); - } -} - -static jboolean IsDiagnosticSupported() { - return sFlpDiagnosticInterface != NULL; -} - -static void InjectDiagnosticData(JNIEnv* env, jobject /* object */, jstring stringData) { - if(stringData == NULL) { - ALOGE("Invalid diagnostic data for injection: %p", stringData); - ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); - } - - if(sFlpDiagnosticInterface == NULL) { - ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); - } - - int length = env->GetStringLength(stringData); - const jchar* data = env->GetStringChars(stringData, /* isCopy */ NULL); - if(data == NULL) { - ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); - } - - int result = sFlpDiagnosticInterface->inject_data((char*) data, length); - ThrowOnError(env, result, __FUNCTION__); -} - -static jboolean IsDeviceContextSupported() { - return sFlpDeviceContextInterface != NULL; -} - -static void InjectDeviceContext(JNIEnv* env, jobject /* object */, jint enabledMask) { - if(sFlpDeviceContextInterface == NULL) { - ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); - } - - int result = sFlpDeviceContextInterface->inject_device_context(enabledMask); - ThrowOnError(env, result, __FUNCTION__); -} - -static jboolean IsGeofencingSupported() { - return sFlpGeofencingInterface != NULL; -} - -static void AddGeofences( - JNIEnv* env, - jobject /* object */, - jobjectArray geofenceRequestsArray) { - if(geofenceRequestsArray == NULL) { - ALOGE("Invalid Geofences to add: %p", geofenceRequestsArray); - ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); - } - - if (sFlpGeofencingInterface == NULL) { - ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); - } - - jint geofenceRequestsCount = env->GetArrayLength(geofenceRequestsArray); - if(geofenceRequestsCount == 0) { - return; - } - - Geofence* geofences = new Geofence[geofenceRequestsCount]; - if (geofences == NULL) { - ThrowOnError(env, FLP_RESULT_INSUFFICIENT_MEMORY, __FUNCTION__); - } - - for (int i = 0; i < geofenceRequestsCount; ++i) { - geofences[i].data = new GeofenceData(); - geofences[i].options = new GeofenceOptions(); - jobject geofenceObject = env->GetObjectArrayElement(geofenceRequestsArray, i); - - TranslateGeofenceFromGeofenceHardwareRequestParcelable(env, geofenceObject, geofences[i]); - env->DeleteLocalRef(geofenceObject); - } - - sFlpGeofencingInterface->add_geofences(geofenceRequestsCount, &geofences); - if (geofences != NULL) { - for(int i = 0; i < geofenceRequestsCount; ++i) { - delete geofences[i].data; - delete geofences[i].options; - } - delete[] geofences; - } -} - -static void PauseGeofence(JNIEnv* env, jobject /* object */, jint geofenceId) { - if(sFlpGeofencingInterface == NULL) { - ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); - } - - sFlpGeofencingInterface->pause_geofence(geofenceId); -} - -static void ResumeGeofence( - JNIEnv* env, - jobject /* object */, - jint geofenceId, - jint monitorTransitions) { - if(sFlpGeofencingInterface == NULL) { - ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); - } - - sFlpGeofencingInterface->resume_geofence(geofenceId, monitorTransitions); -} - -static void ModifyGeofenceOption( - JNIEnv* env, - jobject /* object */, - jint geofenceId, - jint lastTransition, - jint monitorTransitions, - jint notificationResponsiveness, - jint unknownTimer, - jint sourcesToUse) { - if(sFlpGeofencingInterface == NULL) { - ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); - } - - GeofenceOptions options = { - lastTransition, - monitorTransitions, - notificationResponsiveness, - unknownTimer, - (uint32_t)sourcesToUse - }; - - sFlpGeofencingInterface->modify_geofence_option(geofenceId, &options); -} - -static void RemoveGeofences( - JNIEnv* env, - jobject /* object */, - jintArray geofenceIdsArray) { - if(sFlpGeofencingInterface == NULL) { - ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); - } - - jsize geofenceIdsCount = env->GetArrayLength(geofenceIdsArray); - jint* geofenceIds = env->GetIntArrayElements(geofenceIdsArray, /* isCopy */ NULL); - if(geofenceIds == NULL) { - ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); - } - - sFlpGeofencingInterface->remove_geofences(geofenceIdsCount, geofenceIds); - env->ReleaseIntArrayElements(geofenceIdsArray, geofenceIds, 0 /*mode*/); -} - -static const JNINativeMethod sMethods[] = { - //{"name", "signature", functionPointer } - {"nativeClassInit", "()V", reinterpret_cast<void*>(ClassInit)}, - {"nativeInit", "()V", reinterpret_cast<void*>(Init)}, - {"nativeCleanup", "()V", reinterpret_cast<void*>(Cleanup)}, - {"nativeIsSupported", "()Z", reinterpret_cast<void*>(IsSupported)}, - {"nativeGetBatchSize", "()I", reinterpret_cast<void*>(GetBatchSize)}, - {"nativeStartBatching", - "(ILandroid/location/FusedBatchOptions;)V", - reinterpret_cast<void*>(StartBatching)}, - {"nativeUpdateBatchingOptions", - "(ILandroid/location/FusedBatchOptions;)V", - reinterpret_cast<void*>(UpdateBatchingOptions)}, - {"nativeStopBatching", "(I)V", reinterpret_cast<void*>(StopBatching)}, - {"nativeRequestBatchedLocation", - "(I)V", - reinterpret_cast<void*>(GetBatchedLocation)}, - {"nativeFlushBatchedLocations", - "()V", - reinterpret_cast<void*>(FlushBatchedLocations)}, - {"nativeInjectLocation", - "(Landroid/location/Location;)V", - reinterpret_cast<void*>(InjectLocation)}, - {"nativeIsDiagnosticSupported", - "()Z", - reinterpret_cast<void*>(IsDiagnosticSupported)}, - {"nativeInjectDiagnosticData", - "(Ljava/lang/String;)V", - reinterpret_cast<void*>(InjectDiagnosticData)}, - {"nativeIsDeviceContextSupported", - "()Z", - reinterpret_cast<void*>(IsDeviceContextSupported)}, - {"nativeInjectDeviceContext", - "(I)V", - reinterpret_cast<void*>(InjectDeviceContext)}, - {"nativeIsGeofencingSupported", - "()Z", - reinterpret_cast<void*>(IsGeofencingSupported)}, - {"nativeAddGeofences", - "([Landroid/hardware/location/GeofenceHardwareRequestParcelable;)V", - reinterpret_cast<void*>(AddGeofences)}, - {"nativePauseGeofence", "(I)V", reinterpret_cast<void*>(PauseGeofence)}, - {"nativeResumeGeofence", "(II)V", reinterpret_cast<void*>(ResumeGeofence)}, - {"nativeModifyGeofenceOption", - "(IIIIII)V", - reinterpret_cast<void*>(ModifyGeofenceOption)}, - {"nativeRemoveGeofences", "([I)V", reinterpret_cast<void*>(RemoveGeofences)} -}; - -/* - * Registration method invoked on JNI Load. - */ -int register_android_server_location_FlpHardwareProvider(JNIEnv* env) { - return jniRegisterNativeMethods( - env, - "com/android/server/location/FlpHardwareProvider", - sMethods, - NELEM(sMethods) - ); -} - -} /* name-space Android */ diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index fa6405a1dd87..5a20bed04d6e 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -318,7 +318,10 @@ Return<void> GnssCallback::gnssLocationCb( static_cast<jdouble>(location.altitudeMeters), static_cast<jfloat>(location.speedMetersPerSec), static_cast<jfloat>(location.bearingDegrees), - static_cast<jfloat>(location.accuracyMeters), + static_cast<jfloat>(location.horizontalAccuracyMeters), + static_cast<jfloat>(location.verticalAccuracyMeters), + static_cast<jfloat>(location.speedAccuracyMetersPerSecond), + static_cast<jfloat>(location.bearingAccuracyDegrees), static_cast<jlong>(location.timestamp)); checkAndClearExceptionFromCallback(env, __FUNCTION__); return Void(); @@ -458,7 +461,10 @@ Return<void> GnssGeofenceCallback::gnssGeofenceTransitionCb( static_cast<jdouble>(location.altitudeMeters), static_cast<jfloat>(location.speedMetersPerSec), static_cast<jfloat>(location.bearingDegrees), - static_cast<jfloat>(location.accuracyMeters), + static_cast<jfloat>(location.horizontalAccuracyMeters), + static_cast<jfloat>(location.verticalAccuracyMeters), + static_cast<jfloat>(location.speedAccuracyMetersPerSecond), + static_cast<jfloat>(location.bearingAccuracyDegrees), static_cast<jlong>(location.timestamp), transition, timestamp); @@ -480,7 +486,10 @@ Return<void> GnssGeofenceCallback::gnssGeofenceStatusCb( static_cast<jdouble>(location.altitudeMeters), static_cast<jfloat>(location.speedMetersPerSec), static_cast<jfloat>(location.bearingDegrees), - static_cast<jfloat>(location.accuracyMeters), + static_cast<jfloat>(location.horizontalAccuracyMeters), + static_cast<jfloat>(location.verticalAccuracyMeters), + static_cast<jfloat>(location.speedAccuracyMetersPerSecond), + static_cast<jfloat>(location.bearingAccuracyDegrees), static_cast<jlong>(location.timestamp)); checkAndClearExceptionFromCallback(env, __FUNCTION__); return Void(); @@ -663,6 +672,10 @@ jobject GnssMeasurementCallback::translateGnssMeasurement( SET(SnrInDb, measurement->snrDb); } + if (flags & static_cast<uint32_t>(GnssMeasurementFlags::HAS_AUTOMATIC_GAIN_CONTROL)) { + SET(AgcLevelDb, measurement->agcLevelDb); + } + return object.get(); } @@ -913,7 +926,7 @@ Return<void> AGnssRilCallback::requestRefLocCb() { } static void android_location_GnssLocationProvider_class_init_native(JNIEnv* env, jclass clazz) { - method_reportLocation = env->GetMethodID(clazz, "reportLocation", "(IDDDFFFJ)V"); + method_reportLocation = env->GetMethodID(clazz, "reportLocation", "(IDDDFFFFFFJ)V"); method_reportStatus = env->GetMethodID(clazz, "reportStatus", "(I)V"); method_reportSvStatus = env->GetMethodID(clazz, "reportSvStatus", "()V"); method_reportAGpsStatus = env->GetMethodID(clazz, "reportAGpsStatus", "(II[B)V"); @@ -927,9 +940,9 @@ static void android_location_GnssLocationProvider_class_init_native(JNIEnv* env, method_requestSetID = env->GetMethodID(clazz, "requestSetID", "(I)V"); method_requestUtcTime = env->GetMethodID(clazz, "requestUtcTime", "()V"); method_reportGeofenceTransition = env->GetMethodID(clazz, "reportGeofenceTransition", - "(IIDDDFFFJIJ)V"); + "(IIDDDFFFFFFJIJ)V"); method_reportGeofenceStatus = env->GetMethodID(clazz, "reportGeofenceStatus", - "(IIDDDFFFJ)V"); + "(IIDDDFFFFFFJ)V"); method_reportGeofenceAddStatus = env->GetMethodID(clazz, "reportGeofenceAddStatus", "(II)V"); method_reportGeofenceRemoveStatus = env->GetMethodID(clazz, "reportGeofenceRemoveStatus", @@ -1170,7 +1183,7 @@ enum ShiftWidth: uint8_t { static jint android_location_GnssLocationProvider_read_sv_status(JNIEnv* env, jobject /* obj */, jintArray svidWithFlagArray, jfloatArray cn0Array, jfloatArray elevArray, - jfloatArray azumArray) { + jfloatArray azumArray, jfloatArray carrierFreqArray) { /* * This method should only be called from within a call to reportSvStatus. */ @@ -1178,6 +1191,7 @@ static jint android_location_GnssLocationProvider_read_sv_status(JNIEnv* env, jo jfloat* cn0s = env->GetFloatArrayElements(cn0Array, 0); jfloat* elev = env->GetFloatArrayElements(elevArray, 0); jfloat* azim = env->GetFloatArrayElements(azumArray, 0); + jfloat* carrierFreq = env->GetFloatArrayElements(carrierFreqArray, 0); /* * Read GNSS SV info. @@ -1190,12 +1204,14 @@ static jint android_location_GnssLocationProvider_read_sv_status(JNIEnv* env, jo cn0s[i] = info.cN0Dbhz; elev[i] = info.elevationDegrees; azim[i] = info.azimuthDegrees; + carrierFreq[i] = info.carrierFrequencyHz; } env->ReleaseIntArrayElements(svidWithFlagArray, svidWithFlags, 0); env->ReleaseFloatArrayElements(cn0Array, cn0s, 0); env->ReleaseFloatArrayElements(elevArray, elev, 0); env->ReleaseFloatArrayElements(azumArray, azim, 0); + env->ReleaseFloatArrayElements(carrierFreqArray, carrierFreq, 0); return static_cast<jint>(GnssCallback::sGnssSvListSize); } @@ -1378,7 +1394,12 @@ static jstring android_location_GnssLocationProvider_get_internal_state(JNIEnv* internalState << "Gnss Location Data:: LatitudeDegrees: " << data.position.latitudeDegrees << ", LongitudeDegrees: " << data.position.longitudeDegrees << ", altitudeMeters: " << data.position.altitudeMeters - << ", accuracyMeters: " << data.position.accuracyMeters + << ", speedMetersPerSecond: " << data.position.speedMetersPerSec + << ", bearingDegrees: " << data.position.bearingDegrees + << ", horizontalAccuracyMeters: " << data.position.horizontalAccuracyMeters + << ", verticalAccuracyMeters: " << data.position.verticalAccuracyMeters + << ", speedAccuracyMetersPerSecond: " << data.position.speedAccuracyMetersPerSecond + << ", bearingAccuracyDegrees: " << data.position.bearingAccuracyDegrees << ", ageSeconds: " << data.position.ageSeconds << std::endl; } @@ -1706,7 +1727,7 @@ static const JNINativeMethod sMethods[] = { "(I)V", reinterpret_cast<void*>(android_location_GnssLocationProvider_delete_aiding_data)}, {"native_read_sv_status", - "([I[F[F[F)I", + "([I[F[F[F[F)I", reinterpret_cast<void *>(android_location_GnssLocationProvider_read_sv_status)}, {"native_read_nmea", "([BI)I", reinterpret_cast<void *>( android_location_GnssLocationProvider_read_nmea)}, diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 975867852f5c..6f505d51b15c 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -38,7 +38,6 @@ int register_android_server_vr_VrManagerService(JNIEnv* env); int register_android_server_VibratorService(JNIEnv* env); int register_android_server_location_ContextHubService(JNIEnv* env); int register_android_server_location_GnssLocationProvider(JNIEnv* env); -int register_android_server_location_FlpHardwareProvider(JNIEnv* env); int register_android_server_connectivity_Vpn(JNIEnv* env); int register_android_server_hdmi_HdmiCecController(JNIEnv* env); int register_android_server_tv_TvUinputBridge(JNIEnv* env); @@ -76,7 +75,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_SystemServer(env); register_android_server_location_ContextHubService(env); register_android_server_location_GnssLocationProvider(env); - register_android_server_location_FlpHardwareProvider(env); register_android_server_connectivity_Vpn(env); register_android_server_ConsumerIrService(env); register_android_server_BatteryStatsService(env); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index f1a28e2b8a18..040188dded2c 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -17,7 +17,6 @@ package com.android.server.devicepolicy; import static android.Manifest.permission.MANAGE_CA_CERTIFICATES; -import static android.app.admin.DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG; import static android.app.admin.DevicePolicyManager.CODE_ACCOUNTS_NOT_EMPTY; import static android.app.admin.DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED; import static android.app.admin.DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE; @@ -49,7 +48,6 @@ import android.Manifest.permission; import android.accessibilityservice.AccessibilityServiceInfo; import android.accounts.Account; import android.accounts.AccountManager; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -180,8 +178,6 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.nio.charset.StandardCharsets; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; @@ -337,7 +333,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final long MINIMUM_STRONG_AUTH_TIMEOUT_MS = 1 * 60 * 60 * 1000; // 1h /** - * Strings logged with {@link #PROVISIONING_ENTRY_POINT_ADB}. + * Strings logged with {@link + * com.android.internal.logging.nano.MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB}. */ private static final String LOG_TAG_PROFILE_OWNER = "profile-owner"; private static final String LOG_TAG_DEVICE_OWNER = "device-owner"; @@ -552,11 +549,25 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } if (Intent.ACTION_USER_ADDED.equals(action)) { sendUserAddedOrRemovedCommand(DeviceAdminReceiver.ACTION_USER_ADDED, userHandle); - disableDeviceOwnerManagedSingleUserFeaturesIfNeeded(); + synchronized (DevicePolicyManagerService.this) { + // It might take a while for the user to become affiliated. Make security + // and network logging unavailable in the meantime. + maybePauseDeviceWideLoggingLocked(); + } } else if (Intent.ACTION_USER_REMOVED.equals(action)) { sendUserAddedOrRemovedCommand(DeviceAdminReceiver.ACTION_USER_REMOVED, userHandle); - disableDeviceOwnerManagedSingleUserFeaturesIfNeeded(); - removeUserData(userHandle); + synchronized (DevicePolicyManagerService.this) { + // Check whether the user is affiliated, *before* removing its data. + boolean isRemovedUserAffiliated = isUserAffiliatedWithDeviceLocked(userHandle); + removeUserData(userHandle); + if (!isRemovedUserAffiliated) { + // We discard the logs when unaffiliated users are deleted (so that the + // device owner cannot retrieve data about that user after it's gone). + discardDeviceWideLogsLocked(); + // Resume logging if all remaining users are affiliated. + maybeResumeDeviceWideLoggingLocked(); + } + } } else if (Intent.ACTION_USER_STARTED.equals(action)) { synchronized (DevicePolicyManagerService.this) { // Reset the policy data @@ -1858,9 +1869,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (mOwners.hasDeviceOwner()) { mInjector.systemPropertiesSet(PROPERTY_DEVICE_OWNER_PRESENT, "true"); Slog.i(LOG_TAG, "Set ro.device_owner property to true"); - disableDeviceOwnerManagedSingleUserFeaturesIfNeeded(); + if (mInjector.securityLogGetLoggingEnabledProperty()) { mSecurityLogMonitor.start(); + maybePauseDeviceWideLoggingLocked(); } } else { mInjector.systemPropertiesSet(PROPERTY_DEVICE_OWNER_PRESENT, "false"); @@ -2218,15 +2230,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { /** * Send an update to all admins of a user that enforce a specified policy. */ - void sendAdminCommandLocked(String action, int reqPolicy, int userHandle) { + void sendAdminCommandLocked(String action, int reqPolicy, int userHandle, Bundle adminExtras) { final DevicePolicyData policy = getUserData(userHandle); final int count = policy.mAdminList.size(); - if (count > 0) { - for (int i = 0; i < count; i++) { - final ActiveAdmin admin = policy.mAdminList.get(i); - if (admin.info.usesPolicy(reqPolicy)) { - sendAdminCommandLocked(admin, action); - } + for (int i = 0; i < count; i++) { + final ActiveAdmin admin = policy.mAdminList.get(i); + if (admin.info.usesPolicy(reqPolicy)) { + sendAdminCommandLocked(admin, action, adminExtras, null); } } } @@ -2236,10 +2246,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * enforce a specified policy. */ private void sendAdminCommandToSelfAndProfilesLocked(String action, int reqPolicy, - int userHandle) { + int userHandle, Bundle adminExtras) { int[] profileIds = mUserManager.getProfileIdsWithDisabled(userHandle); for (int profileId : profileIds) { - sendAdminCommandLocked(action, reqPolicy, profileId); + sendAdminCommandLocked(action, reqPolicy, profileId, adminExtras); } } @@ -2248,10 +2258,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { */ private void sendAdminCommandForLockscreenPoliciesLocked( String action, int reqPolicy, int userHandle) { + final Bundle extras = new Bundle(); + extras.putParcelable(Intent.EXTRA_USER, UserHandle.of(userHandle)); if (isSeparateProfileChallengeEnabled(userHandle)) { - sendAdminCommandLocked(action, reqPolicy, userHandle); + sendAdminCommandLocked(action, reqPolicy, userHandle, extras); } else { - sendAdminCommandToSelfAndProfilesLocked(action, reqPolicy, userHandle); + sendAdminCommandToSelfAndProfilesLocked(action, reqPolicy, userHandle, extras); } } @@ -2829,6 +2841,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private void handlePasswordExpirationNotification(int userHandle) { + final Bundle adminExtras = new Bundle(); + adminExtras.putParcelable(Intent.EXTRA_USER, UserHandle.of(userHandle)); + synchronized (this) { final long now = System.currentTimeMillis(); @@ -2842,7 +2857,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { && now >= admin.passwordExpirationDate - EXPIRATION_GRACE_PERIOD_MS && admin.passwordExpirationDate > 0L) { sendAdminCommandLocked(admin, - DeviceAdminReceiver.ACTION_PASSWORD_EXPIRING); + DeviceAdminReceiver.ACTION_PASSWORD_EXPIRING, adminExtras, null); } } setExpirationAlarmCheckLocked(mContext, userHandle, /* parent */ false); @@ -3152,7 +3167,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } // It's temporary solution to clear DISALLOW_ADD_USER after CTS - // TODO: b/31952368 when the restriction is moved from system to the device owner, + // STOPSHIP(b/31952368) when the restriction is moved from system to the device owner, // it can be removed. private void clearDeviceOwnerUserRestrictionLocked(UserHandle userHandle) { if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, userHandle)) { @@ -5647,34 +5662,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - private boolean isDeviceOwnerManagedSingleUserDevice() { - synchronized (this) { - if (!mOwners.hasDeviceOwner()) { - return false; - } - } - final long callingIdentity = mInjector.binderClearCallingIdentity(); - try { - if (mInjector.userManagerIsSplitSystemUser()) { - // In split system user mode, only allow the case where the device owner is managing - // the only non-system user of the device - return (mUserManager.getUserCount() == 2 - && mOwners.getDeviceOwnerUserId() != UserHandle.USER_SYSTEM); - } else { - return mUserManager.getUserCount() == 1; - } - } finally { - mInjector.binderRestoreCallingIdentity(callingIdentity); - } - } - - private void ensureDeviceOwnerManagingSingleUser(ComponentName who) throws SecurityException { + private void ensureDeviceOwnerAndAllUsersAffiliated(ComponentName who) throws SecurityException { synchronized (this) { getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); - } - if (!isDeviceOwnerManagedSingleUserDevice()) { - throw new SecurityException( - "There should only be one user, managed by Device Owner"); + if (!areAllUsersAffiliatedWithDeviceLocked()) { + throw new SecurityException("Not all users are affiliated."); + } } } @@ -5684,7 +5677,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } Preconditions.checkNotNull(who, "ComponentName is null"); - ensureDeviceOwnerManagingSingleUser(who); + + // TODO: If an unaffiliated user is removed, the admin will be able to request a bugreport + // which could still contain data related to that user. Should we disallow that, e.g. until + // next boot? Might not be needed given that this still requires user consent. + ensureDeviceOwnerAndAllUsersAffiliated(who); if (mRemoteBugreportServiceIsActive.get() || (getDeviceOwnerRemoteBugreportUri() != null)) { @@ -5709,7 +5706,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mRemoteBugreportServiceIsActive.set(true); mRemoteBugreportSharingAccepted.set(false); registerRemoteBugreportReceivers(); - mInjector.getNotificationManager().notifyAsUser(LOG_TAG, RemoteBugreportUtils.NOTIFICATION_ID, + mInjector.getNotificationManager().notifyAsUser(LOG_TAG, + RemoteBugreportUtils.NOTIFICATION_ID, RemoteBugreportUtils.buildNotification(mContext, DevicePolicyManager.NOTIFICATION_BUGREPORT_STARTED), UserHandle.ALL); mHandler.postDelayed(mRemoteBugreportTimeoutRunnable, @@ -6256,6 +6254,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { admin.userRestrictions = null; admin.defaultEnabledRestrictionsAlreadySet.clear(); admin.forceEphemeralUsers = false; + admin.isNetworkLoggingEnabled = false; mUserManagerInternal.setForceEphemeralUsers(admin.forceEphemeralUsers); final DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM); policyData.mLastSecurityLogRetrievalTime = -1; @@ -6268,7 +6267,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mOwners.clearDeviceOwner(); mOwners.writeDeviceOwner(); updateDeviceOwnerLocked(); - disableDeviceOwnerManagedSingleUserFeaturesIfNeeded(); + + mInjector.securityLogSetLoggingEnabledProperty(false); + mSecurityLogMonitor.stop(); + setNetworkLoggingActiveInternal(false); + try { if (mInjector.getIBackupManager() != null) { // Reactivate backup service. @@ -7604,7 +7607,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { try { // Install the profile owner if not present. if (!mIPackageManager.isPackageAvailable(adminPkg, userHandle)) { - mIPackageManager.installExistingPackageAsUser(adminPkg, userHandle); + mIPackageManager.installExistingPackageAsUser(adminPkg, userHandle, + PackageManager.INSTALL_REASON_POLICY); } } catch (RemoteException e) { Slog.e(LOG_TAG, "Failed to make remote calls for createAndManageUser, " @@ -7903,7 +7907,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } // Install the app. - mIPackageManager.installExistingPackageAsUser(packageName, userId); + mIPackageManager.installExistingPackageAsUser(packageName, userId, + PackageManager.INSTALL_REASON_POLICY); } catch (RemoteException re) { // shouldn't happen @@ -7945,7 +7950,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { String packageName = info.activityInfo.packageName; if (isSystemApp(mIPackageManager, packageName, parentUserId)) { numberOfAppsInstalled++; - mIPackageManager.installExistingPackageAsUser(packageName, userId); + mIPackageManager.installExistingPackageAsUser(packageName, userId, + PackageManager.INSTALL_REASON_POLICY); } else { Slog.d(LOG_TAG, "Not enabling " + packageName + " since is not a" + " system app"); @@ -8255,7 +8261,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (this) { getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); final int userHandle = mInjector.userHandleGetCallingUserId(); - if (isUserAffiliatedWithDevice(userHandle)) { + if (isUserAffiliatedWithDeviceLocked(userHandle)) { setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages))); } else { throw new SecurityException("Admin " + who + @@ -9436,6 +9442,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { getUserData(UserHandle.USER_SYSTEM).mAffiliationIds = affiliationIds; saveSettingsLocked(UserHandle.USER_SYSTEM); } + + // Affiliation status for any user, not just the calling user, might have changed. + // The device owner user will still be affiliated after changing its affiliation ids, + // but as a result of that other users might become affiliated or un-affiliated. + maybePauseDeviceWideLoggingLocked(); + maybeResumeDeviceWideLoggingLocked(); } } @@ -9455,84 +9467,78 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean isAffiliatedUser() { - return isUserAffiliatedWithDevice(mInjector.userHandleGetCallingUserId()); - } + if (!mHasFeature) { + return false; + } - private boolean isUserAffiliatedWithDevice(int userId) { synchronized (this) { - if (!mOwners.hasDeviceOwner()) { - return false; - } - if (userId == mOwners.getDeviceOwnerUserId()) { - // The user that the DO is installed on is always affiliated with the device. - return true; - } - if (userId == UserHandle.USER_SYSTEM) { - // The system user is always affiliated in a DO device, even if the DO is set on a - // different user. This could be the case if the DO is set in the primary user - // of a split user device. - return true; - } - final ComponentName profileOwner = getProfileOwner(userId); - if (profileOwner == null) { - return false; - } - final Set<String> userAffiliationIds = getUserData(userId).mAffiliationIds; - final Set<String> deviceAffiliationIds = - getUserData(UserHandle.USER_SYSTEM).mAffiliationIds; - for (String id : userAffiliationIds) { - if (deviceAffiliationIds.contains(id)) { - return true; - } - } + return isUserAffiliatedWithDeviceLocked(mInjector.userHandleGetCallingUserId()); } - return false; } - private synchronized void disableDeviceOwnerManagedSingleUserFeaturesIfNeeded() { - final boolean isSingleUserManagedDevice = isDeviceOwnerManagedSingleUserDevice(); - - // disable security logging if needed - if (!isSingleUserManagedDevice) { - mInjector.securityLogSetLoggingEnabledProperty(false); - Slog.w(LOG_TAG, "Security logging turned off as it's no longer a single user managed" - + " device."); + private boolean isUserAffiliatedWithDeviceLocked(int userId) { + if (!mOwners.hasDeviceOwner()) { + return false; } - - // disable backup service if needed - // note: when clearing DO, the backup service shouldn't be disabled if it was enabled by - // the device owner - if (mOwners.hasDeviceOwner() && !isSingleUserManagedDevice) { - setBackupServiceEnabledInternal(false); - Slog.w(LOG_TAG, "Backup is off as it's a managed device that has more that one user."); + if (userId == mOwners.getDeviceOwnerUserId()) { + // The user that the DO is installed on is always affiliated with the device. + return true; + } + if (userId == UserHandle.USER_SYSTEM) { + // The system user is always affiliated in a DO device, even if the DO is set on a + // different user. This could be the case if the DO is set in the primary user + // of a split user device. + return true; + } + final ComponentName profileOwner = getProfileOwner(userId); + if (profileOwner == null) { + return false; + } + final Set<String> userAffiliationIds = getUserData(userId).mAffiliationIds; + final Set<String> deviceAffiliationIds = + getUserData(UserHandle.USER_SYSTEM).mAffiliationIds; + for (String id : userAffiliationIds) { + if (deviceAffiliationIds.contains(id)) { + return true; + } } + return false; + } - // disable network logging if needed - if (!isSingleUserManagedDevice) { - setNetworkLoggingActiveInternal(false); - Slog.w(LOG_TAG, "Network logging turned off as it's no longer a single user managed" - + " device."); - // if there still is a device owner, disable logging policy, otherwise the admin - // has been nuked - if (mOwners.hasDeviceOwner()) { - getDeviceOwnerAdminLocked().isNetworkLoggingEnabled = false; - saveSettingsLocked(mOwners.getDeviceOwnerUserId()); + private boolean areAllUsersAffiliatedWithDeviceLocked() { + final long ident = mInjector.binderClearCallingIdentity(); + try { + final List<UserInfo> userInfos = mUserManager.getUsers(); + for (int i = 0; i < userInfos.size(); i++) { + int userId = userInfos.get(i).id; + if (!isUserAffiliatedWithDeviceLocked(userId)) { + Slog.d(LOG_TAG, "User id " + userId + " not affiliated."); + return false; + } } + } finally { + mInjector.binderRestoreCallingIdentity(ident); } + + return true; } @Override public void setSecurityLoggingEnabled(ComponentName admin, boolean enabled) { + if (!mHasFeature) { + return; + } Preconditions.checkNotNull(admin); - ensureDeviceOwnerManagingSingleUser(admin); synchronized (this) { + getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); if (enabled == mInjector.securityLogGetLoggingEnabledProperty()) { return; } mInjector.securityLogSetLoggingEnabledProperty(enabled); if (enabled) { mSecurityLogMonitor.start(); + maybePauseDeviceWideLoggingLocked(); } else { mSecurityLogMonitor.stop(); } @@ -9541,6 +9547,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean isSecurityLoggingEnabled(ComponentName admin) { + if (!mHasFeature) { + return false; + } + Preconditions.checkNotNull(admin); synchronized (this) { getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); @@ -9559,10 +9569,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public ParceledListSlice<SecurityEvent> retrievePreRebootSecurityLogs(ComponentName admin) { + if (!mHasFeature) { + return null; + } + Preconditions.checkNotNull(admin); - ensureDeviceOwnerManagingSingleUser(admin); + ensureDeviceOwnerAndAllUsersAffiliated(admin); - if (!mContext.getResources().getBoolean(R.bool.config_supportPreRebootSecurityLogs)) { + if (!mContext.getResources().getBoolean(R.bool.config_supportPreRebootSecurityLogs) + || !mInjector.securityLogGetLoggingEnabledProperty()) { return null; } @@ -9580,8 +9595,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public ParceledListSlice<SecurityEvent> retrieveSecurityLogs(ComponentName admin) { + if (!mHasFeature) { + return null; + } + Preconditions.checkNotNull(admin); - ensureDeviceOwnerManagingSingleUser(admin); + ensureDeviceOwnerAndAllUsersAffiliated(admin); + + if (!mInjector.securityLogGetLoggingEnabledProperty()) { + return null; + } recordSecurityLogRetrievalTime(); @@ -9788,18 +9811,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + // TODO(b/22388012): When backup is available for secondary users and profiles, consider + // whether there are any privacy/security implications of enabling the backup service here + // if there are other users or profiles unmanaged or managed by a different entity (i.e. not + // affiliated). @Override public void setBackupServiceEnabled(ComponentName admin, boolean enabled) { - Preconditions.checkNotNull(admin); if (!mHasFeature) { return; } - ensureDeviceOwnerManagingSingleUser(admin); - setBackupServiceEnabledInternal(enabled); - } + Preconditions.checkNotNull(admin); + synchronized (this) { + getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + } - private synchronized void setBackupServiceEnabledInternal(boolean enabled) { - long ident = mInjector.binderClearCallingIdentity(); + final long ident = mInjector.binderClearCallingIdentity(); try { IBackupManager ibm = mInjector.getIBackupManager(); if (ibm != null) { @@ -9900,7 +9926,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final boolean isCallerDeviceOwner = isDeviceOwner(callingOwner); final boolean isCallerManagedProfile = isManagedProfile(callingUserId); if ((!isCallerDeviceOwner && !isCallerManagedProfile) - || !isUserAffiliatedWithDevice(callingUserId)) { + || !isUserAffiliatedWithDeviceLocked(callingUserId)) { return targetUsers; } @@ -9920,7 +9946,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Both must be the same package and be affiliated in order to bind. if (callingOwnerPackage.equals(targetOwnerPackage) - && isUserAffiliatedWithDevice(userId)) { + && isUserAffiliatedWithDeviceLocked(userId)) { targetUsers.add(UserHandle.of(userId)); } } @@ -10018,7 +10044,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return; } Preconditions.checkNotNull(admin); - ensureDeviceOwnerManagingSingleUser(admin); + getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); if (enabled == isNetworkLoggingEnabledInternalLocked()) { // already in the requested state @@ -10045,10 +10071,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Slog.wtf(LOG_TAG, "Network logging could not be started due to the logging" + " service not being available yet."); } + maybePauseDeviceWideLoggingLocked(); sendNetworkLoggingNotificationLocked(); } else { if (mNetworkLogger != null && !mNetworkLogger.stopNetworkLogging()) { - mNetworkLogger = null; Slog.wtf(LOG_TAG, "Network logging could not be stopped due to the logging" + " service not being available yet."); } @@ -10060,6 +10086,44 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + /** Pauses security and network logging if there are unaffiliated users on the device */ + private void maybePauseDeviceWideLoggingLocked() { + if (!areAllUsersAffiliatedWithDeviceLocked()) { + Slog.i(LOG_TAG, "There are unaffiliated users, security and network logging will be " + + "paused if enabled."); + mSecurityLogMonitor.pause(); + if (mNetworkLogger != null) { + mNetworkLogger.pause(); + } + } + } + + /** Resumes security and network logging (if they are enabled) if all users are affiliated */ + private void maybeResumeDeviceWideLoggingLocked() { + if (areAllUsersAffiliatedWithDeviceLocked()) { + final long ident = mInjector.binderClearCallingIdentity(); + try { + mSecurityLogMonitor.resume(); + if (mNetworkLogger != null) { + mNetworkLogger.resume(); + } + } finally { + mInjector.binderRestoreCallingIdentity(ident); + } + } + } + + /** Deletes any security and network logs that might have been collected so far */ + private void discardDeviceWideLogsLocked() { + mSecurityLogMonitor.discardLogs(); + if (mNetworkLogger != null) { + mNetworkLogger.discardLogs(); + } + // TODO: We should discard pre-boot security logs here too, as otherwise those + // logs (which might contain data from the user just removed) will be + // available after next boot. + } + @Override public boolean isNetworkLoggingEnabled(ComponentName admin) { if (!mHasFeature) { @@ -10084,32 +10148,27 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * @see NetworkLoggingHandler#MAX_EVENTS_PER_BATCH */ @Override - public synchronized List<NetworkEvent> retrieveNetworkLogs(ComponentName admin, - long batchToken) { + public List<NetworkEvent> retrieveNetworkLogs(ComponentName admin, long batchToken) { if (!mHasFeature) { return null; } Preconditions.checkNotNull(admin); - ensureDeviceOwnerManagingSingleUser(admin); - - if (mNetworkLogger == null) { - return null; - } + ensureDeviceOwnerAndAllUsersAffiliated(admin); - if (!isNetworkLoggingEnabledInternalLocked()) { - return null; - } - - final long currentTime = System.currentTimeMillis(); synchronized (this) { + if (mNetworkLogger == null + || !isNetworkLoggingEnabledInternalLocked()) { + return null; + } + + final long currentTime = System.currentTimeMillis(); DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM); if (currentTime > policyData.mLastNetworkLogsRetrievalTime) { policyData.mLastNetworkLogsRetrievalTime = currentTime; saveSettingsLocked(UserHandle.USER_SYSTEM); } + return mNetworkLogger.retrieveLogs(batchToken); } - - return mNetworkLogger.retrieveLogs(batchToken); } private void sendNetworkLoggingNotificationLocked() { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java index b82cb3cfbeaf..00859311af84 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java @@ -31,7 +31,6 @@ import android.util.Slog; import com.android.server.ServiceThread; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -130,6 +129,8 @@ final class NetworkLogger { Log.d(TAG, "Stopping network logging"); // stop the logging regardless of whether we fail to unregister listener mIsLoggingEnabled.set(false); + discardLogs(); + try { if (!checkIpConnectivityMetricsService()) { // the IIpConnectivityMetrics service should have been present at this point @@ -140,9 +141,43 @@ final class NetworkLogger { return mIpConnectivityMetrics.unregisterNetdEventCallback(); } catch (RemoteException re) { Slog.wtf(TAG, "Failed to make remote calls to unregister the callback", re); - } finally { - mHandlerThread.quitSafely(); return true; + } finally { + if (mHandlerThread != null) { + mHandlerThread.quitSafely(); + } + } + } + + /** + * If logs are being collected, keep collecting them but stop notifying the device owner that + * new logs are available (since they cannot be retrieved) + */ + void pause() { + if (mNetworkLoggingHandler != null) { + mNetworkLoggingHandler.pause(); + } + } + + /** + * If logs are being collected, start notifying the device owner when logs are ready to be + * collected again (if it was paused). + * <p>If logging is enabled and there are logs ready to be retrieved, this method will attempt + * to notify the device owner. Therefore calling identity should be cleared before calling it + * (in case the method is called from a user other than the DO's user). + */ + void resume() { + if (mNetworkLoggingHandler != null) { + mNetworkLoggingHandler.resume(); + } + } + + /** + * Discard all collected logs. + */ + void discardLogs() { + if (mNetworkLoggingHandler != null) { + mNetworkLoggingHandler.discardLogs(); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java index baa4c13f5190..7d6841248dcd 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java @@ -55,10 +55,16 @@ final class NetworkLoggingHandler extends Handler { @GuardedBy("this") private ArrayList<NetworkEvent> mFullBatch; - // each full batch is represented by its token, which the DPC has to provide back to revieve it + @GuardedBy("this") + private boolean mPaused = false; + + // each full batch is represented by its token, which the DPC has to provide back to retrieve it @GuardedBy("this") private long mCurrentFullBatchToken; + @GuardedBy("this") + private long mLastRetrievedFullBatchToken; + NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm) { super(looper); mDpm = dpm; @@ -70,15 +76,19 @@ final class NetworkLoggingHandler extends Handler { case LOG_NETWORK_EVENT_MSG: { NetworkEvent networkEvent = msg.getData().getParcelable(NETWORK_EVENT_KEY); if (networkEvent != null) { - mNetworkEvents.add(networkEvent); - if (mNetworkEvents.size() >= MAX_EVENTS_PER_BATCH) { - finalizeBatchAndNotifyDeviceOwnerIfNotEmpty(); + synchronized (NetworkLoggingHandler.this) { + mNetworkEvents.add(networkEvent); + if (mNetworkEvents.size() >= MAX_EVENTS_PER_BATCH) { + finalizeBatchAndNotifyDeviceOwnerLocked(); + } } } break; } case FINALIZE_BATCH_MSG: { - finalizeBatchAndNotifyDeviceOwnerIfNotEmpty(); + synchronized (NetworkLoggingHandler.this) { + finalizeBatchAndNotifyDeviceOwnerLocked(); + } break; } } @@ -91,22 +101,49 @@ final class NetworkLoggingHandler extends Handler { + "ms from now."); } - private synchronized void finalizeBatchAndNotifyDeviceOwnerIfNotEmpty() { + synchronized void pause() { + Log.d(TAG, "Paused network logging"); + mPaused = true; + } + + synchronized void resume() { + if (!mPaused) { + Log.d(TAG, "Attempted to resume network logging, but logging is not paused."); + return; + } + + Log.d(TAG, "Resumed network logging. Current batch=" + + mCurrentFullBatchToken + ", LastRetrievedBatch=" + mLastRetrievedFullBatchToken); + mPaused = false; + + // If there is a full batch ready that the device owner hasn't been notified about, do it + // now. + if (mFullBatch != null && mFullBatch.size() > 0 + && mLastRetrievedFullBatchToken != mCurrentFullBatchToken) { + scheduleBatchFinalization(); + notifyDeviceOwnerLocked(); + } + } + + synchronized void discardLogs() { + mFullBatch = null; + mNetworkEvents = new ArrayList<NetworkEvent>(); + Log.d(TAG, "Discarded all network logs"); + } + + @GuardedBy("this") + private void finalizeBatchAndNotifyDeviceOwnerLocked() { if (mNetworkEvents.size() > 0) { // finalize the batch and start a new one from scratch mFullBatch = mNetworkEvents; mCurrentFullBatchToken++; mNetworkEvents = new ArrayList<NetworkEvent>(); - // notify DO that there's a new non-empty batch waiting - Bundle extras = new Bundle(); - extras.putLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, mCurrentFullBatchToken); - extras.putInt(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_COUNT, mFullBatch.size()); - Log.d(TAG, "Sending network logging batch broadcast to device owner, batchToken: " - + mCurrentFullBatchToken); - mDpm.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE, extras); + if (!mPaused) { + notifyDeviceOwnerLocked(); + } } else { // don't notify the DO, since there are no events; DPC can still retrieve - // the last full batch + // the last full batch if not paused. Log.d(TAG, "Was about to finalize the batch, but there were no events to send to" + " the DPC, the batchToken of last available batch: " + mCurrentFullBatchToken); @@ -115,10 +152,21 @@ final class NetworkLoggingHandler extends Handler { scheduleBatchFinalization(); } + @GuardedBy("this") + private void notifyDeviceOwnerLocked() { + Bundle extras = new Bundle(); + extras.putLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, mCurrentFullBatchToken); + extras.putInt(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_COUNT, mFullBatch.size()); + Log.d(TAG, "Sending network logging batch broadcast to device owner, batchToken: " + + mCurrentFullBatchToken); + mDpm.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE, extras); + } + synchronized List<NetworkEvent> retrieveFullLogBatch(long batchToken) { if (batchToken != mCurrentFullBatchToken) { return null; } + mLastRetrievedFullBatchToken = mCurrentFullBatchToken; return mFullBatch; } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java index 79702a8de505..18f06be063cd 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java @@ -19,6 +19,7 @@ package com.android.server.devicepolicy; import android.app.admin.DeviceAdminReceiver; import android.app.admin.SecurityLog; import android.app.admin.SecurityLog.SecurityEvent; +import android.os.SystemClock; import android.util.Log; import android.util.Slog; @@ -50,7 +51,7 @@ class SecurityLogMonitor implements Runnable { mService = service; } - private static final boolean DEBUG = false; + private static final boolean DEBUG = false; // STOPSHIP if true. private static final String TAG = "SecurityLogMonitor"; /** * Each log entry can hold up to 4K bytes (but as of {@link android.os.Build.VERSION_CODES#N} @@ -78,17 +79,25 @@ class SecurityLogMonitor implements Runnable { private ArrayList<SecurityEvent> mPendingLogs = new ArrayList<SecurityEvent>(); @GuardedBy("mLock") private boolean mAllowedToRetrieve = false; - // When DO will be allowed to retrieves the log, in milliseconds. + + /** + * When DO will be allowed to retrieve the log, in milliseconds since boot (as per + * {@link SystemClock#elapsedRealtime()}) + */ + @GuardedBy("mLock") + private long mNextAllowedRetrievalTimeMillis = -1; @GuardedBy("mLock") - private long mNextAllowedRetrivalTimeMillis = -1; + private boolean mPaused = false; void start() { + Slog.i(TAG, "Starting security logging."); mLock.lock(); try { if (mMonitorThread == null) { mPendingLogs = new ArrayList<SecurityEvent>(); mAllowedToRetrieve = false; - mNextAllowedRetrivalTimeMillis = -1; + mNextAllowedRetrievalTimeMillis = -1; + mPaused = false; mMonitorThread = new Thread(this); mMonitorThread.start(); @@ -99,6 +108,7 @@ class SecurityLogMonitor implements Runnable { } void stop() { + Slog.i(TAG, "Stopping security logging."); mLock.lock(); try { if (mMonitorThread != null) { @@ -111,7 +121,8 @@ class SecurityLogMonitor implements Runnable { // Reset state and clear buffer mPendingLogs = new ArrayList<SecurityEvent>(); mAllowedToRetrieve = false; - mNextAllowedRetrivalTimeMillis = -1; + mNextAllowedRetrievalTimeMillis = -1; + mPaused = false; mMonitorThread = null; } } finally { @@ -120,6 +131,58 @@ class SecurityLogMonitor implements Runnable { } /** + * If logs are being collected, keep collecting them but stop notifying the device owner that + * new logs are available (since they cannot be retrieved). + */ + void pause() { + Slog.i(TAG, "Paused."); + + mLock.lock(); + mPaused = true; + mAllowedToRetrieve = false; + mLock.unlock(); + } + + /** + * If logs are being collected, start notifying the device owner when logs are ready to be + * retrieved again (if it was paused). + * <p>If logging is enabled and there are logs ready to be retrieved, this method will attempt + * to notify the device owner. Therefore calling identity should be cleared before calling it + * (in case the method is called from a user other than the DO's user). + */ + void resume() { + mLock.lock(); + try { + if (!mPaused) { + Log.d(TAG, "Attempted to resume, but logging is not paused."); + return; + } + mPaused = false; + mAllowedToRetrieve = false; + } finally { + mLock.unlock(); + } + + Slog.i(TAG, "Resumed."); + try { + notifyDeviceOwnerIfNeeded(); + } catch (InterruptedException e) { + Log.w(TAG, "Thread interrupted.", e); + } + } + + /** + * Discard all collected logs. + */ + void discardLogs() { + mLock.lock(); + mAllowedToRetrieve = false; + mPendingLogs = new ArrayList<SecurityEvent>(); + mLock.unlock(); + Slog.i(TAG, "Discarded all logs."); + } + + /** * Returns the new batch of logs since the last call to this method. Returns null if * rate limit is exceeded. */ @@ -128,7 +191,7 @@ class SecurityLogMonitor implements Runnable { try { if (mAllowedToRetrieve) { mAllowedToRetrieve = false; - mNextAllowedRetrivalTimeMillis = System.currentTimeMillis() + mNextAllowedRetrievalTimeMillis = SystemClock.elapsedRealtime() + RATE_LIMIT_INTERVAL_MILLISECONDS; List<SecurityEvent> result = mPendingLogs; mPendingLogs = new ArrayList<SecurityEvent>(); @@ -163,7 +226,7 @@ class SecurityLogMonitor implements Runnable { SecurityLog.readEventsSince(lastLogTimestampNanos + 1, logs); } if (!logs.isEmpty()) { - if (DEBUG) Slog.d(TAG, "processing new logs"); + if (DEBUG) Slog.d(TAG, "processing new logs. Events: " + logs.size()); mLock.lockInterruptibly(); try { mPendingLogs.addAll(logs); @@ -172,6 +235,7 @@ class SecurityLogMonitor implements Runnable { mPendingLogs = new ArrayList<SecurityEvent>(mPendingLogs.subList( mPendingLogs.size() - (BUFFER_ENTRIES_MAXIMUM_LEVEL / 2), mPendingLogs.size())); + Slog.i(TAG, "Pending logs buffer full. Discarding old logs."); } } finally { mLock.unlock(); @@ -188,7 +252,7 @@ class SecurityLogMonitor implements Runnable { break; } } - if (DEBUG) Slog.d(TAG, "MonitorThread exit."); + Slog.i(TAG, "MonitorThread exit."); } private void notifyDeviceOwnerIfNeeded() throws InterruptedException { @@ -196,15 +260,24 @@ class SecurityLogMonitor implements Runnable { boolean allowToRetrieveNow = false; mLock.lockInterruptibly(); try { + if (mPaused) { + return; + } + + // STOPSHIP(b/34186771): If the previous notification didn't reach the DO and logs were + // not retrieved (e.g. the broadcast was sent before the user was unlocked), no more + // subsequent callbacks will be sent. We should make sure that the DO gets notified + // before logs are lost. int logSize = mPendingLogs.size(); if (logSize >= BUFFER_ENTRIES_NOTIFICATION_LEVEL) { // Allow DO to retrieve logs if too many pending logs allowToRetrieveNow = true; + if (DEBUG) Slog.d(TAG, "Number of log entries over threshold: " + logSize); } else if (logSize > 0) { - if (mNextAllowedRetrivalTimeMillis == -1 || - System.currentTimeMillis() >= mNextAllowedRetrivalTimeMillis) { + if (SystemClock.elapsedRealtime() >= mNextAllowedRetrievalTimeMillis) { // Rate limit reset allowToRetrieveNow = true; + if (DEBUG) Slog.d(TAG, "Timeout reached"); } } shouldNotifyDO = (!mAllowedToRetrieve) && allowToRetrieveNow; @@ -213,7 +286,7 @@ class SecurityLogMonitor implements Runnable { mLock.unlock(); } if (shouldNotifyDO) { - if (DEBUG) Slog.d(TAG, "notify DO"); + Slog.i(TAG, "notify DO"); mService.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_SECURITY_LOGS_AVAILABLE, null); } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 68cb0c5e3eaa..08fb5911374f 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -175,6 +175,8 @@ public final class SystemServer { "com.google.android.clockwork.bluetooth.WearBluetoothService"; private static final String WEAR_WIFI_MEDIATOR_SERVICE_CLASS = "com.google.android.clockwork.wifi.WearWifiMediatorService"; + private static final String WEAR_CELLULAR_MEDIATOR_SERVICE_CLASS = + "com.google.android.clockwork.cellular.WearCellularMediatorService"; private static final String WEAR_TIME_SERVICE_CLASS = "com.google.android.clockwork.time.WearTimeService"; private static final String ACCOUNT_SERVICE_CLASS = @@ -1381,6 +1383,13 @@ public final class SystemServer { traceBeginAndSlog("StartWearWifiMediator"); mSystemServiceManager.startService(WEAR_WIFI_MEDIATOR_SERVICE_CLASS); traceEnd(); + + if (SystemProperties.getBoolean("config.enable_cellmediator", false)) { + traceBeginAndSlog("StartWearCellularMediator"); + mSystemServiceManager.startService(WEAR_CELLULAR_MEDIATOR_SERVICE_CLASS); + traceEnd(); + } + if (!disableNonCoreServices) { traceBeginAndSlog("StartWearTimeService"); mSystemServiceManager.startService(WEAR_TIME_SERVICE_CLASS); diff --git a/services/retaildemo/java/com/android/server/retaildemo/PreloadAppsInstaller.java b/services/retaildemo/java/com/android/server/retaildemo/PreloadAppsInstaller.java index 2038c9e35f0c..3fa72dc92a88 100644 --- a/services/retaildemo/java/com/android/server/retaildemo/PreloadAppsInstaller.java +++ b/services/retaildemo/java/com/android/server/retaildemo/PreloadAppsInstaller.java @@ -103,7 +103,8 @@ class PreloadAppsInstaller { Log.d(TAG, "installExistingPackage " + packageName + " u" + userId); } try { - mPackageManager.installExistingPackageAsUser(packageName, userId); + mPackageManager.installExistingPackageAsUser(packageName, userId, + PackageManager.INSTALL_REASON_UNKNOWN); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } finally { diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 182f0457de3d..60f436019fa8 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -71,6 +71,7 @@ import static org.mockito.Matchers.isNull; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -544,6 +545,83 @@ public class DevicePolicyManagerTest extends DpmTestBase { } /** + * Test for: @{link DevicePolicyManager#setActivePasswordState} + * + * Validates that when the password for a user changes, the notification broadcast intent + * {@link DeviceAdminReceiver#ACTION_PASSWORD_CHANGED} is sent to managed profile owners, in + * addition to ones in the original user. + */ + public void testSetActivePasswordState_sendToProfiles() throws Exception { + mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN); + + final int MANAGED_PROFILE_USER_ID = 78; + final int MANAGED_PROFILE_ADMIN_UID = + UserHandle.getUid(MANAGED_PROFILE_USER_ID, DpmMockContext.SYSTEM_UID); + + // Setup device owner. + mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; + mContext.packageName = admin1.getPackageName(); + setupDeviceOwner(); + + // Add a managed profile belonging to the system user. + addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1); + + // Change the parent user's password. + dpm.reportPasswordChanged(UserHandle.USER_SYSTEM); + + // Both the device owner and the managed profile owner should receive this broadcast. + final Intent intent = new Intent(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED); + intent.setComponent(admin1); + intent.putExtra(Intent.EXTRA_USER, UserHandle.of(UserHandle.USER_SYSTEM)); + + verify(mContext.spiedContext, times(1)).sendBroadcastAsUser( + MockUtils.checkIntent(intent), + MockUtils.checkUserHandle(UserHandle.USER_SYSTEM)); + verify(mContext.spiedContext, times(1)).sendBroadcastAsUser( + MockUtils.checkIntent(intent), + MockUtils.checkUserHandle(MANAGED_PROFILE_USER_ID)); + } + + /** + * Test for: @{link DevicePolicyManager#setActivePasswordState} + * + * Validates that when the password for a managed profile changes, the notification broadcast + * intent {@link DeviceAdminReceiver#ACTION_PASSWORD_CHANGED} is only sent to the profile, not + * its parent. + */ + public void testSetActivePasswordState_notSentToParent() throws Exception { + mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN); + + final int MANAGED_PROFILE_USER_ID = 78; + final int MANAGED_PROFILE_ADMIN_UID = + UserHandle.getUid(MANAGED_PROFILE_USER_ID, DpmMockContext.SYSTEM_UID); + + // Setup device owner. + mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; + mContext.packageName = admin1.getPackageName(); + doReturn(true).when(mContext.lockPatternUtils) + .isSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID); + setupDeviceOwner(); + + // Add a managed profile belonging to the system user. + addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1); + + // Change the profile's password. + dpm.reportPasswordChanged(MANAGED_PROFILE_USER_ID); + + // Both the device owner and the managed profile owner should receive this broadcast. + final Intent intent = new Intent(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED); + intent.setComponent(admin1); + intent.putExtra(Intent.EXTRA_USER, UserHandle.of(MANAGED_PROFILE_USER_ID)); + + verify(mContext.spiedContext, never()).sendBroadcastAsUser( + MockUtils.checkIntent(intent), + MockUtils.checkUserHandle(UserHandle.USER_SYSTEM)); + verify(mContext.spiedContext, times(1)).sendBroadcastAsUser( + MockUtils.checkIntent(intent), + MockUtils.checkUserHandle(MANAGED_PROFILE_USER_ID)); + } + /** * Test for: {@link DevicePolicyManager#setDeviceOwner} DO on system user installs successfully. */ public void testSetDeviceOwner() throws Exception { @@ -2767,7 +2845,10 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testGetLastSecurityLogRetrievalTime() throws Exception { mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; setupDeviceOwner(); - when(mContext.userManager.getUserCount()).thenReturn(1); + + // setUp() adds a secondary user for CALLER_USER_HANDLE. Remove it as otherwise the + // feature is disabled because there are non-affiliated secondary users. + mContext.removeUser(DpmMockContext.CALLER_USER_HANDLE); when(mContext.resources.getBoolean(R.bool.config_supportPreRebootSecurityLogs)) .thenReturn(true); @@ -2776,6 +2857,10 @@ public class DevicePolicyManagerTest extends DpmTestBase { // Enabling logging should not change the timestamp. dpm.setSecurityLoggingEnabled(admin1, true); + verify(mContext.settings) + .securityLogSetLoggingEnabledProperty(true); + when(mContext.settings.securityLogGetLoggingEnabledProperty()) + .thenReturn(true); assertEquals(-1, dpm.getLastSecurityLogRetrievalTime()); // Retrieving the logs should update the timestamp. @@ -2828,7 +2913,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testGetLastBugReportRequestTime() throws Exception { mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; setupDeviceOwner(); - when(mContext.userManager.getUserCount()).thenReturn(1); + mContext.packageName = admin1.getPackageName(); mContext.applicationInfo = new ApplicationInfo(); when(mContext.resources.getColor(eq(R.color.notification_action_list), anyObject())) @@ -2836,6 +2921,10 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(mContext.resources.getColor(eq(R.color.notification_material_background_color), anyObject())).thenReturn(Color.WHITE); + // setUp() adds a secondary user for CALLER_USER_HANDLE. Remove it as otherwise the + // feature is disabled because there are non-affiliated secondary users. + mContext.removeUser(DpmMockContext.CALLER_USER_HANDLE); + // No bug reports were requested so far. assertEquals(-1, dpm.getLastBugReportRequestTime()); @@ -2873,7 +2962,16 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testGetLastNetworkLogRetrievalTime() throws Exception { mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; setupDeviceOwner(); - when(mContext.userManager.getUserCount()).thenReturn(1); + mContext.packageName = admin1.getPackageName(); + mContext.applicationInfo = new ApplicationInfo(); + when(mContext.resources.getColor(eq(R.color.notification_action_list), anyObject())) + .thenReturn(Color.WHITE); + when(mContext.resources.getColor(eq(R.color.notification_material_background_color), + anyObject())).thenReturn(Color.WHITE); + + // setUp() adds a secondary user for CALLER_USER_HANDLE. Remove it as otherwise the + // feature is disabled because there are non-affiliated secondary users. + mContext.removeUser(DpmMockContext.CALLER_USER_HANDLE); when(mContext.iipConnectivityMetrics.registerNetdEventCallback(anyObject())) .thenReturn(true); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java index 65255d9ef7ad..44bf547460dd 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java @@ -18,6 +18,7 @@ package com.android.server.devicepolicy; import android.accounts.Account; import android.accounts.AccountManager; +import android.app.AlarmManager; import android.app.IActivityManager; import android.app.NotificationManager; import android.app.backup.IBackupManager; @@ -276,6 +277,7 @@ public class DpmMockContext extends MockContext { public final MockContentResolver contentResolver; public final TelephonyManager telephonyManager; public final AccountManager accountManager; + public final AlarmManager alarmManager; /** Note this is a partial mock, not a real mock. */ public final PackageManager packageManager; @@ -319,6 +321,7 @@ public class DpmMockContext extends MockContext { settings = mock(SettingsForMock.class); telephonyManager = mock(TelephonyManager.class); accountManager = mock(AccountManager.class); + alarmManager = mock(AlarmManager.class); // Package manager is huge, so we use a partial mock instead. packageManager = spy(context.getPackageManager()); @@ -327,7 +330,8 @@ public class DpmMockContext extends MockContext { contentResolver = new MockContentResolver(); - // Add the system user + // Add the system user with a fake profile group already set up (this can happen in the real + // world if a managed profile is added and then removed). systemUserDataDir = addUser(UserHandle.USER_SYSTEM, UserInfo.FLAG_PRIMARY, UserHandle.USER_SYSTEM); @@ -389,6 +393,18 @@ public class DpmMockContext extends MockContext { return dir; } + public void removeUser(int userId) { + for (int i = 0; i < mUserInfos.size(); i++) { + if (mUserInfos.get(i).id == userId) { + mUserInfos.remove(i); + break; + } + } + when(userManager.getUserInfo(eq(userId))).thenReturn(null); + + when(userManager.isUserRunning(eq(new UserHandle(userId)))).thenReturn(false); + } + private UserInfo getUserInfo(int userId) { for (UserInfo ui : mUserInfos) { if (ui.id == userId) { @@ -410,9 +426,9 @@ public class DpmMockContext extends MockContext { if (parent == null) { return ret; } - ret.add(parent); for (UserInfo ui : mUserInfos) { - if (ui.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID + if (ui == parent + || ui.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID && ui.profileGroupId == parent.profileGroupId) { ret.add(ui); } @@ -463,6 +479,8 @@ public class DpmMockContext extends MockContext { @Override public Object getSystemService(String name) { switch (name) { + case Context.ALARM_SERVICE: + return alarmManager; case Context.USER_SERVICE: return userManager; case Context.POWER_SERVICE: diff --git a/services/tests/servicestests/src/com/android/server/retaildemo/PreloadAppsInstallerTest.java b/services/tests/servicestests/src/com/android/server/retaildemo/PreloadAppsInstallerTest.java index 3eeeaf679891..346dc4235e6c 100644 --- a/services/tests/servicestests/src/com/android/server/retaildemo/PreloadAppsInstallerTest.java +++ b/services/tests/servicestests/src/com/android/server/retaildemo/PreloadAppsInstallerTest.java @@ -103,7 +103,8 @@ public class PreloadAppsInstallerTest { observer.getValue().onPackageInstalled(path, PackageManager.INSTALL_SUCCEEDED, null, null); // Verify that we try to install the package in system user. - verify(mIpm).installExistingPackageAsUser(path, UserHandle.USER_SYSTEM); + verify(mIpm).installExistingPackageAsUser(path, UserHandle.USER_SYSTEM, + PackageManager.INSTALL_REASON_UNKNOWN); } assertEquals("DEMO_USER_SETUP should be set to 1 after preloaded apps are installed", "1", @@ -139,7 +140,8 @@ public class PreloadAppsInstallerTest { observer.getValue().onPackageInstalled(path, PackageManager.INSTALL_SUCCEEDED, null, null); // Verify that we try to install the package in system user. - verify(mIpm).installExistingPackageAsUser(path, UserHandle.USER_SYSTEM); + verify(mIpm).installExistingPackageAsUser(path, UserHandle.USER_SYSTEM, + PackageManager.INSTALL_REASON_UNKNOWN); } assertEquals("DEMO_USER_SETUP should be set to 1 after preloaded apps are installed", "1", diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index ba770ef5053c..d9f352cbbf7b 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -188,7 +188,6 @@ class UserUsageStatsService { String[] annotations = event.mContentAnnotations; if (annotations != null) { for (String annotation : annotations) { - // TODO(kanlig): update with confidences of annotations. stats.updateChooserCounts(event.mPackage, annotation, event.mAction); } } diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java index 715896971137..c46d4a5aff99 100644 --- a/test-runner/src/android/test/mock/MockPackageManager.java +++ b/test-runner/src/android/test/mock/MockPackageManager.java @@ -1064,4 +1064,11 @@ public class MockPackageManager extends PackageManager { public Drawable loadUnbadgedItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo) { throw new UnsupportedOperationException(); } + + /** + * @hide + */ + public int getInstallReason(String packageName, UserHandle user) { + throw new UnsupportedOperationException(); + } } diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java index f9cac43fff55..415911ed4ce6 100644 --- a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java +++ b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java @@ -58,8 +58,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " link_layer: 0", " time_ms: 1", - " transport: 0", " default_network_event <", " network_id <", " network_id: 102", @@ -89,8 +89,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " link_layer: 0", " time_ms: 1", - " transport: 0", " dhcp_event <", " duration_ms: 192", " if_name: \"wlan0\"", @@ -112,8 +112,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " link_layer: 0", " time_ms: 1", - " transport: 0", " dhcp_event <", " duration_ms: 0", " if_name: \"wlan0\"", @@ -137,8 +137,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " link_layer: 0", " time_ms: 1", - " transport: 0", " dns_lookup_batch <", " event_types: 1", " event_types: 1", @@ -185,8 +185,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " link_layer: 0", " time_ms: 1", - " transport: 0", " ip_provisioning_event <", " event_type: 1", " if_name: \"wlan0\"", @@ -208,8 +208,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " link_layer: 0", " time_ms: 1", - " transport: 0", " ip_reachability_event <", " event_type: 512", " if_name: \"wlan0\"", @@ -231,8 +231,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " link_layer: 0", " time_ms: 1", - " transport: 0", " network_event <", " event_type: 5", " latency_ms: 20410", @@ -258,8 +258,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " link_layer: 0", " time_ms: 1", - " transport: 0", " validation_probe_event <", " latency_ms: 40730", " network_id <", @@ -287,8 +287,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " link_layer: 0", " time_ms: 1", - " transport: 0", " apf_program_event <", " current_ras: 9", " drop_multicast: true", @@ -319,8 +319,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " link_layer: 0", " time_ms: 1", - " transport: 0", " apf_statistics <", " dropped_ras: 2", " duration_ms: 45000", @@ -351,8 +351,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " link_layer: 0", " time_ms: 1", - " transport: 0", " ra_event <", " dnssl_lifetime: -1", " prefix_preferred_lifetime: 300", diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java index e9257fa619ba..f56f3f80ef69 100644 --- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java +++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java @@ -158,16 +158,16 @@ public class IpConnectivityMetricsTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " link_layer: 0", " time_ms: 100", - " transport: 0", " ip_reachability_event <", " event_type: 512", " if_name: \"wlan0\"", " >", ">", "events <", + " link_layer: 0", " time_ms: 200", - " transport: 0", " dhcp_event <", " duration_ms: 192", " if_name: \"wlan0\"", @@ -175,8 +175,8 @@ public class IpConnectivityMetricsTest extends TestCase { " >", ">", "events <", + " link_layer: 0", " time_ms: 300", - " transport: 0", " default_network_event <", " network_id <", " network_id: 102", @@ -191,8 +191,8 @@ public class IpConnectivityMetricsTest extends TestCase { " >", ">", "events <", + " link_layer: 0", " time_ms: 400", - " transport: 0", " ip_provisioning_event <", " event_type: 1", " if_name: \"wlan0\"", @@ -200,8 +200,8 @@ public class IpConnectivityMetricsTest extends TestCase { " >", ">", "events <", + " link_layer: 0", " time_ms: 500", - " transport: 0", " validation_probe_event <", " latency_ms: 40730", " network_id <", @@ -212,8 +212,8 @@ public class IpConnectivityMetricsTest extends TestCase { " >", ">", "events <", + " link_layer: 0", " time_ms: 600", - " transport: 0", " apf_statistics <", " dropped_ras: 2", " duration_ms: 45000", @@ -226,8 +226,8 @@ public class IpConnectivityMetricsTest extends TestCase { " >", ">", "events <", + " link_layer: 0", " time_ms: 700", - " transport: 0", " ra_event <", " dnssl_lifetime: -1", " prefix_preferred_lifetime: 300", diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java index 75d2f1a9edfd..cfd559802e3e 100644 --- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java +++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java @@ -213,8 +213,8 @@ public class NetdEventListenerServiceTest extends TestCase { IpConnectivityEvent got = events.get(0); String want = String.join("\n", + "link_layer: 0", "time_ms: 0", - "transport: 0", "connect_statistics <", " connect_count: 12", " errnos_counters <", diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java index 98073ce1e59b..21c2de79d68b 100644 --- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java +++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java @@ -80,6 +80,7 @@ public class NetworkNotificationManagerTest extends TestCase { when(mCtx.getResources()).thenReturn(mResources); when(mCtx.getPackageManager()).thenReturn(mPm); when(mCtx.getApplicationInfo()).thenReturn(new ApplicationInfo()); + when(mNetworkInfo.getExtraInfo()).thenReturn("extra"); when(mResources.getColor(anyInt(), any())).thenReturn(0xFF607D8B); mManager = new NetworkNotificationManager(mCtx, mTelephonyManager, mNotificationManager); diff --git a/tools/layoutlib/bridge/src/android/animation/AnimationThread.java b/tools/layoutlib/bridge/src/android/animation/AnimationThread.java index b10ec9fec2ad..ce2aec790119 100644 --- a/tools/layoutlib/bridge/src/android/animation/AnimationThread.java +++ b/tools/layoutlib/bridge/src/android/animation/AnimationThread.java @@ -25,7 +25,6 @@ import com.android.layoutlib.bridge.impl.RenderSessionImpl; import android.os.Handler; import android.os.Handler_Delegate; -import android.os.Handler_Delegate.IHandlerCallback; import android.os.Message; import java.util.PriorityQueue; diff --git a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java index c3d4cef61b35..e0f8e1c33bc6 100644 --- a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java +++ b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java @@ -21,6 +21,7 @@ import com.android.ide.common.rendering.api.ArrayResourceValue; import com.android.ide.common.rendering.api.DensityBasedResourceValue; import com.android.ide.common.rendering.api.LayoutLog; import com.android.ide.common.rendering.api.LayoutlibCallback; +import com.android.ide.common.rendering.api.PluralsResourceValue; import com.android.ide.common.rendering.api.RenderResources; import com.android.ide.common.rendering.api.ResourceValue; import com.android.layoutlib.bridge.Bridge; @@ -43,6 +44,7 @@ import android.annotation.Nullable; import android.content.res.Resources.NotFoundException; import android.content.res.Resources.Theme; import android.graphics.drawable.Drawable; +import android.icu.text.PluralRules; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.LruCache; @@ -405,9 +407,6 @@ public class Resources_Delegate { rv = resources.mContext.getRenderResources().resolveResValue(rv); if (rv != null) { return rv.getValue(); - } else { - Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE, - "Unable to resolve resource " + ref, null); } } // Not a reference. @@ -738,6 +737,48 @@ public class Resources_Delegate { } @LayoutlibDelegate + static String getQuantityString(Resources resources, int id, int quantity) throws + NotFoundException { + Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); + + if (value != null) { + if (value.getSecond() instanceof PluralsResourceValue) { + PluralsResourceValue pluralsResourceValue = (PluralsResourceValue) value.getSecond(); + PluralRules pluralRules = PluralRules.forLocale(resources.getConfiguration().getLocales() + .get(0)); + String strValue = pluralsResourceValue.getValue(pluralRules.select(quantity)); + if (strValue == null) { + strValue = pluralsResourceValue.getValue(PluralRules.KEYWORD_OTHER); + } + + return strValue; + } + else { + return value.getSecond().getValue(); + } + } + + // id was not found or not resolved. Throw a NotFoundException. + throwException(resources, id); + + // this is not used since the method above always throws + return null; + } + + @LayoutlibDelegate + static String getQuantityString(Resources resources, int id, int quantity, Object... formatArgs) + throws NotFoundException { + String raw = getQuantityString(resources, id, quantity); + return String.format(resources.getConfiguration().getLocales().get(0), raw, formatArgs); + } + + @LayoutlibDelegate + static CharSequence getQuantityText(Resources resources, int id, int quantity) throws + NotFoundException { + return getQuantityString(resources, id, quantity); + } + + @LayoutlibDelegate static void getValue(Resources resources, int id, TypedValue outValue, boolean resolveRefs) throws NotFoundException { Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); diff --git a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java index 21f36ceb0c1b..c6827a3c9c5f 100644 --- a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java +++ b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java @@ -30,6 +30,7 @@ import java.awt.Graphics2D; import java.awt.Toolkit; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; +import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.LinkedList; @@ -41,6 +42,7 @@ import java.util.List; */ @SuppressWarnings("deprecation") public class BidiRenderer { + private static String JAVA_VENDOR = System.getProperty("java.vendor"); private static class ScriptRun { int start; @@ -221,9 +223,16 @@ public class BidiRenderer { frc = mGraphics.getFontRenderContext(); } else { frc = Toolkit.getDefaultToolkit().getFontMetrics(font).getFontRenderContext(); + // Metrics obtained this way don't have anti-aliasing set. So, // we create a new FontRenderContext with anti-aliasing set. - frc = new FontRenderContext(font.getTransform(), mPaint.isAntiAliased(), frc.usesFractionalMetrics()); + AffineTransform transform = font.getTransform(); + if (mPaint.isAntiAliased() && + // Workaround for http://b.android.com/211659 + (transform.getScaleX() <= 9.9 || + !"JetBrains s.r.o".equals(JAVA_VENDOR))) { + frc = new FontRenderContext(transform, true, frc.usesFractionalMetrics()); + } } GlyphVector gv = font.layoutGlyphVector(frc, mText, start, limit, flag); int ng = gv.getNumGlyphs(); diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java index 43a0ff5a23dc..c599e9d6d6bc 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java @@ -20,24 +20,14 @@ import com.android.ide.common.rendering.api.LayoutLog; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.layoutlib.bridge.impl.GcSnapshot; -import com.android.layoutlib.bridge.impl.PorterDuffUtility; -import com.android.ninepatch.NinePatchChunk; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import android.annotation.Nullable; import android.graphics.Bitmap.Config; -import android.text.TextUtils; -import java.awt.Color; -import java.awt.Composite; import java.awt.Graphics2D; import java.awt.Rectangle; -import java.awt.RenderingHints; -import java.awt.Shape; import java.awt.geom.AffineTransform; -import java.awt.geom.Arc2D; -import java.awt.geom.Rectangle2D; -import java.awt.image.BufferedImage; import libcore.util.NativeAllocationRegistry_Delegate; @@ -401,23 +391,6 @@ public final class Canvas_Delegate extends BaseCanvas_Delegate { } @LayoutlibDelegate - public static boolean nClipRegion(long nativeCanvas, - long nativeRegion, - int regionOp) { - Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); - if (canvasDelegate == null) { - return true; - } - - Region_Delegate region = Region_Delegate.getDelegate(nativeRegion); - if (region == null) { - return true; - } - - return canvasDelegate.mSnapshot.clip(region.getJavaArea(), regionOp); - } - - @LayoutlibDelegate public static void nSetDrawFilter(long nativeCanvas, long nativeFilter) { Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas); if (canvasDelegate == null) { diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java index 50efc7f7db86..d32693575ef0 100644 --- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java @@ -248,14 +248,17 @@ public class FontFamily_Delegate { // ---- delegate methods ---- @LayoutlibDelegate /*package*/ static boolean addFont(FontFamily thisFontFamily, String path, int ttcIndex) { - final FontFamily_Delegate delegate = getDelegate(thisFontFamily.mNativePtr); + if (thisFontFamily.mBuilderPtr == 0) { + throw new IllegalStateException("Unable to call addFont after freezing."); + } + final FontFamily_Delegate delegate = getDelegate(thisFontFamily.mBuilderPtr); return delegate != null && delegate.addFont(path, ttcIndex); } // ---- native methods ---- @LayoutlibDelegate - /*package*/ static long nCreateFamily(String lang, int variant) { + /*package*/ static long nInitBuilder(String lang, int variant) { // TODO: support lang. This is required for japanese locale. FontFamily_Delegate delegate = new FontFamily_Delegate(); // variant can be 0, 1 or 2. @@ -270,6 +273,11 @@ public class FontFamily_Delegate { } @LayoutlibDelegate + /*package*/ static long nCreateFamily(long builderPtr) { + return builderPtr; + } + + @LayoutlibDelegate /*package*/ static void nUnrefFamily(long nativePtr) { // Removing the java reference for the object doesn't mean that it's freed for garbage // collection. Typeface_Delegate may still hold a reference for it. @@ -277,22 +285,22 @@ public class FontFamily_Delegate { } @LayoutlibDelegate - /*package*/ static boolean nAddFont(long nativeFamily, ByteBuffer font, int ttcIndex) { + /*package*/ static boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex) { assert false : "The only client of this method has been overriden."; return false; } @LayoutlibDelegate - /*package*/ static boolean nAddFontWeightStyle(long nativeFamily, ByteBuffer font, + /*package*/ static boolean nAddFontWeightStyle(long builderPtr, ByteBuffer font, int ttcIndex, List<FontListParser.Axis> listOfAxis, int weight, boolean isItalic) { assert false : "The only client of this method has been overriden."; return false; } - static boolean addFont(long nativeFamily, final String path, final int weight, + static boolean addFont(long builderPtr, final String path, final int weight, final boolean isItalic) { - final FontFamily_Delegate delegate = getDelegate(nativeFamily); + final FontFamily_Delegate delegate = getDelegate(builderPtr); if (delegate != null) { if (sFontLocation == null) { delegate.mPostInitRunnables.add(() -> delegate.addFont(path, weight, isItalic)); @@ -304,8 +312,8 @@ public class FontFamily_Delegate { } @LayoutlibDelegate - /*package*/ static boolean nAddFontFromAsset(long nativeFamily, AssetManager mgr, String path) { - FontFamily_Delegate ffd = sManager.getDelegate(nativeFamily); + /*package*/ static boolean nAddFontFromAsset(long builderPtr, AssetManager mgr, String path) { + FontFamily_Delegate ffd = sManager.getDelegate(builderPtr); if (ffd == null) { return false; } diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java index 5cd34f6e000f..a554b6da31eb 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java @@ -212,9 +212,10 @@ public final class Typeface_Delegate { Map<String, ByteBuffer> bufferForPath) { FontFamily fontFamily = new FontFamily(family.lang, family.variant); for (FontListParser.Font font : family.fonts) { - FontFamily_Delegate.addFont(fontFamily.mNativePtr, font.fontName, font.weight, + FontFamily_Delegate.addFont(fontFamily.mBuilderPtr, font.fontName, font.weight, font.isItalic); } + fontFamily.freeze(); return fontFamily; } diff --git a/tools/layoutlib/bridge/src/android/view/PointerIcon_Delegate.java b/tools/layoutlib/bridge/src/android/view/PointerIcon_Delegate.java new file mode 100644 index 000000000000..4a5ea9b5bb65 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/view/PointerIcon_Delegate.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.content.Context; +import android.content.res.Resources; + +public class PointerIcon_Delegate { + + @LayoutlibDelegate + /*package*/ static void loadResource(PointerIcon icon, Context context, Resources resources, + int resourceId) { + // HACK: This bypasses the problem of having an enum resolved as a resourceId. + // PointerIcon would not be displayed by layoutlib anyway, so we always return the null + // icon. + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java index 3ac1889dbe72..77b131fa9d86 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java @@ -76,6 +76,23 @@ public class BridgeRenderSession extends RenderSession { } @Override + public Result measure(long timeout) { + try { + Bridge.prepareThread(); + mLastResult = mSession.acquire(timeout); + if (mLastResult.isSuccess()) { + mSession.invalidateRenderingSize(); + mLastResult = mSession.measure(); + } + } finally { + mSession.release(); + Bridge.cleanupThread(); + } + + return mLastResult; + } + + @Override public Result render(long timeout, boolean forceMeasure) { try { Bridge.prepareThread(); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index 1b3b563fb51e..663e56d2ccdf 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -66,7 +66,6 @@ import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManager; import android.net.Uri; -import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -104,6 +103,7 @@ import java.util.IdentityHashMap; import java.util.List; import java.util.Map; +import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static com.android.layoutlib.bridge.android.RenderParamsFlags.FLAG_KEY_APPLICATION_PACKAGE; /** @@ -113,6 +113,28 @@ import static com.android.layoutlib.bridge.android.RenderParamsFlags.FLAG_KEY_AP public final class BridgeContext extends Context { private static final String PREFIX_THEME_APPCOMPAT = "Theme.AppCompat"; + private static final Map<String, ResourceValue> FRAMEWORK_PATCHED_VALUES = new HashMap<>(2); + private static final Map<String, ResourceValue> FRAMEWORK_REPLACE_VALUES = new HashMap<>(3); + + static { + FRAMEWORK_PATCHED_VALUES.put("animateFirstView", new ResourceValue( + ResourceType.BOOL, "animateFirstView", "false", false)); + FRAMEWORK_PATCHED_VALUES.put("animateLayoutChanges", + new ResourceValue(ResourceType.BOOL, "animateLayoutChanges", "false", false)); + + + FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionItemLayout", + new ResourceValue(ResourceType.LAYOUT, "textEditSuggestionItemLayout", + "text_edit_suggestion_item", true)); + FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionContainerLayout", + new ResourceValue(ResourceType.LAYOUT, "textEditSuggestionContainerLayout", + "text_edit_suggestion_container", true)); + FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionHighlightStyle", + new ResourceValue(ResourceType.STYLE, "textEditSuggestionHighlightStyle", + "TextAppearance.Holo.SuggestionHighlight", true)); + + } + /** The map adds cookies to each view so that IDE can link xml tags to views. */ private final HashMap<View, Object> mViewKeyMap = new HashMap<>(); /** @@ -312,7 +334,7 @@ public final class BridgeContext extends Context { * Returns the current parser at the top the of the stack. * @return a parser or null. */ - public BridgeXmlBlockParser getCurrentParser() { + private BridgeXmlBlockParser getCurrentParser() { return mParserStack.peek(); } @@ -406,7 +428,8 @@ public final class BridgeContext extends Context { } public Pair<View, Boolean> inflateView(ResourceReference resource, ViewGroup parent, - boolean attachToRoot, boolean skipCallbackParser) { + @SuppressWarnings("SameParameterValue") boolean attachToRoot, + boolean skipCallbackParser) { boolean isPlatformLayout = resource.isFramework(); if (!isPlatformLayout && !skipCallbackParser) { @@ -711,11 +734,7 @@ public final class BridgeContext extends Context { Object key = parser.getViewCookie(); if (key != null) { - defaultPropMap = mDefaultPropMaps.get(key); - if (defaultPropMap == null) { - defaultPropMap = new PropertiesMap(); - mDefaultPropMaps.put(key, defaultPropMap); - } + defaultPropMap = mDefaultPropMaps.computeIfAbsent(key, k -> new PropertiesMap()); } } else if (set instanceof BridgeLayoutParamsMapAttributes) { @@ -909,6 +928,16 @@ public final class BridgeContext extends Context { // if there's no direct value for this attribute in the XML, we look for default // values in the widget defStyle, and then in the theme. if (value == null) { + if (frameworkAttr) { + // For some framework values, layoutlib patches the actual value in the + // theme when it helps to improve the final preview. In most cases + // we just disable animations. + ResourceValue patchedValue = FRAMEWORK_PATCHED_VALUES.get(attrName); + if (patchedValue != null) { + defaultValue = patchedValue; + } + } + // if we found a value, we make sure this doesn't reference another value. // So we resolve it. if (defaultValue != null) { @@ -916,16 +945,21 @@ public final class BridgeContext extends Context { // exist, we should log a warning and omit it. String val = defaultValue.getValue(); if (val != null && val.startsWith(SdkConstants.PREFIX_THEME_REF)) { - if (!attrName.equals(RTL_ATTRS.get(val)) || - getApplicationInfo().targetSdkVersion < - VERSION_CODES.JELLY_BEAN_MR1) { + // Because we always use the latest framework code, some resources might + // fail to resolve when using old themes (they haven't been backported). + // Since this is an artifact caused by us using always the latest + // code, we check for some of those values and replace them here. + defaultValue = FRAMEWORK_REPLACE_VALUES.get(attrName); + + if (defaultValue == null && + (getApplicationInfo().targetSdkVersion < JELLY_BEAN_MR1 || + !attrName.equals(RTL_ATTRS.get(val)))) { // Only log a warning if the referenced value isn't one of the RTL // attributes, or the app targets old API. Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR, String.format("Failed to find '%s' in current theme.", val), val); } - defaultValue = null; } } @@ -1944,7 +1978,7 @@ public final class BridgeContext extends Context { Map<List<StyleResourceValue>, Map<Integer, Pair<BridgeTypedArray, PropertiesMap>>>> mCache; - public TypedArrayCache() { + private TypedArrayCache() { mCache = new IdentityHashMap<>(); } @@ -1965,17 +1999,9 @@ public final class BridgeContext extends Context { public void put(int[] attrs, List<StyleResourceValue> themes, int resId, Pair<BridgeTypedArray, PropertiesMap> value) { Map<List<StyleResourceValue>, Map<Integer, Pair<BridgeTypedArray, PropertiesMap>>> - cacheFromThemes = mCache.get(attrs); - if (cacheFromThemes == null) { - cacheFromThemes = new HashMap<>(); - mCache.put(attrs, cacheFromThemes); - } + cacheFromThemes = mCache.computeIfAbsent(attrs, k -> new HashMap<>()); Map<Integer, Pair<BridgeTypedArray, PropertiesMap>> cacheFromResId = - cacheFromThemes.get(themes); - if (cacheFromResId == null) { - cacheFromResId = new HashMap<>(); - cacheFromThemes.put(themes, cacheFromResId); - } + cacheFromThemes.computeIfAbsent(themes, k -> new HashMap<>()); cacheFromResId.put(resId, value); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java index 8835b5845175..bc7bc74ae2c8 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java @@ -868,4 +868,9 @@ public class BridgePackageManager extends PackageManager { public boolean isPackageAvailable(String packageName) { return false; } + + @Override + public int getInstallReason(String packageName, UserHandle user) { + return INSTALL_REASON_UNKNOWN; + } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java index 30317012fb52..d7d16cf6d764 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java @@ -17,6 +17,7 @@ package com.android.layoutlib.bridge.android.view; import android.util.DisplayMetrics; import android.view.Display; +import android.view.Display.Mode; import android.view.DisplayAdjustments; import android.view.DisplayInfo; import android.view.View; @@ -33,6 +34,9 @@ public class WindowManagerImpl implements WindowManager { DisplayInfo info = new DisplayInfo(); info.logicalHeight = mMetrics.heightPixels; info.logicalWidth = mMetrics.widthPixels; + info.supportedModes = new Mode[] { + new Mode(0, mMetrics.widthPixels, mMetrics.heightPixels, 60f) + }; mDisplay = new Display(null, Display.DEFAULT_DISPLAY, info, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java index 91a783ae7efc..85fe2a450e4f 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java @@ -208,7 +208,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { /** * Measures the the current layout if needed (see {@link #invalidateRenderingSize}). */ - private void measure(@NonNull SessionParams params) { + private void measureLayout(@NonNull SessionParams params) { // only do the screen measure when needed. if (mMeasuredScreenWidth != -1) { return; @@ -353,7 +353,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { setActiveToolbar(view, context, params); - measure(params); + measureLayout(params); measureView(mViewRoot, null /*measuredView*/, mMeasuredScreenWidth, MeasureSpec.EXACTLY, mMeasuredScreenHeight, MeasureSpec.EXACTLY); @@ -385,13 +385,10 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } /** - * Renders the given view hierarchy to the passed canvas and returns the result of the render - * operation. - * @param canvas an optional canvas to render the views to. If null, only the measure and - * layout steps will be executed. + * Runs a layout pass for the given view root */ - private static Result render(@NonNull BridgeContext context, @NonNull ViewGroup viewRoot, - @Nullable Canvas canvas, int width, int height) { + private static void doLayout(@NonNull BridgeContext context, @NonNull ViewGroup viewRoot, + int width, int height) { // measure again with the size we need // This must always be done before the call to layout measureView(viewRoot, null /*measuredView*/, @@ -401,7 +398,16 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // now do the layout. viewRoot.layout(0, 0, width, height); handleScrolling(context, viewRoot); + } + /** + * Renders the given view hierarchy to the passed canvas and returns the result of the render + * operation. + * @param canvas an optional canvas to render the views to. If null, only the measure and + * layout steps will be executed. + */ + private static Result renderAndBuildResult(@NonNull BridgeContext context, @NonNull ViewGroup viewRoot, + @Nullable Canvas canvas, int width, int height) { if (canvas == null) { return SUCCESS.createResult(); } @@ -428,6 +434,40 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { * @see RenderSession#render(long) */ public Result render(boolean freshRender) { + return renderAndBuildResult(freshRender, false); + } + + /** + * Measures the layout + * <p> + * {@link #acquire(long)} must have been called before this. + * + * @throws IllegalStateException if the current context is different than the one owned by + * the scene, or if {@link #acquire(long)} was not called. + * + * @see SessionParams#getRenderingMode() + * @see RenderSession#render(long) + */ + public Result measure() { + return renderAndBuildResult(false, true); + } + + /** + * Renders the scene. + * <p> + * {@link #acquire(long)} must have been called before this. + * + * @param freshRender whether the render is a new one and should erase the existing bitmap (in + * the case where bitmaps are reused). This is typically needed when not playing + * animations.) + * + * @throws IllegalStateException if the current context is different than the one owned by + * the scene, or if {@link #acquire(long)} was not called. + * + * @see SessionParams#getRenderingMode() + * @see RenderSession#render(long) + */ + private Result renderAndBuildResult(boolean freshRender, boolean onlyMeasure) { checkLock(); SessionParams params = getParams(); @@ -437,14 +477,15 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { return ERROR_NOT_INFLATED.createResult(); } - measure(params); + measureLayout(params); HardwareConfig hardwareConfig = params.getHardwareConfig(); Result renderResult = SUCCESS.createResult(); - if (params.isLayoutOnly()) { + if (onlyMeasure) { // delete the canvas and image to reset them on the next full rendering mImage = null; mCanvas = null; + doLayout(getContext(), mViewRoot, mMeasuredScreenWidth, mMeasuredScreenHeight); } else { // draw the views // create the BufferedImage into which the layout will be rendered. @@ -505,11 +546,12 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { gc.dispose(); } + doLayout(getContext(), mViewRoot, mMeasuredScreenWidth, mMeasuredScreenHeight); if (mElapsedFrameTimeNanos >= 0) { long initialTime = System_Delegate.nanoTime(); if (!mFirstFrameExecuted) { // We need to run an initial draw call to initialize the animations - render(getContext(), mViewRoot, NOP_CANVAS, mMeasuredScreenWidth, mMeasuredScreenHeight); + renderAndBuildResult(getContext(), mViewRoot, NOP_CANVAS, mMeasuredScreenWidth, mMeasuredScreenHeight); // The first frame will initialize the animations Choreographer_Delegate.doFrame(initialTime); @@ -518,7 +560,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // Second frame will move the animations Choreographer_Delegate.doFrame(initialTime + mElapsedFrameTimeNanos); } - renderResult = render(getContext(), mViewRoot, mCanvas, mMeasuredScreenWidth, + renderResult = renderAndBuildResult(getContext(), mViewRoot, mCanvas, mMeasuredScreenWidth, mMeasuredScreenHeight); } diff --git a/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java b/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java index 6246ec1b8661..04fabc242454 100644 --- a/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java +++ b/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java @@ -52,9 +52,15 @@ public class NativeAllocationRegistry_Delegate { @LayoutlibDelegate /*package*/ static void applyFreeFunction(long freeFunction, long nativePtr) { - NativeAllocationRegistry_Delegate delegate = sManager.getDelegate(freeFunction); - if (delegate != null) { - delegate.mFinalizer.free(nativePtr); + // This method MIGHT run in the context of the finalizer thread. If the delegate method + // crashes, it could bring down the VM. That's why we catch all the exceptions and ignore + // them. + try { + NativeAllocationRegistry_Delegate delegate = sManager.getDelegate(freeFunction); + if (delegate != null) { + delegate.mFinalizer.free(nativePtr); + } + } catch (Throwable ignore) { } } diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity-old-theme.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity-old-theme.png Binary files differnew file mode 100644 index 000000000000..eb431b0280da --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity-old-theme.png diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml index adb58a322abb..05a3665deab1 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml @@ -28,7 +28,8 @@ android:layout_alignParentStart="true" android:layout_below="@id/frameLayout" android:text="Large Text" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceLarge" + android:pointerIcon="hand" /> <TextView android:id="@id/textView3" diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml index f6e14d2b83b9..5f58d390bf32 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml @@ -17,6 +17,7 @@ <!-- theme ref in android NS. value = @string/candidates_style = <u>candidates</u> --> <item>?android:attr/candidatesTextStyleSpans</item> <item>@android:string/unknownName</item> <!-- value = Unknown --> + <item>?EC</item> </string-array> <!-- resources that the above array can refer to --> diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml index c8a5fec71f09..debe33bea72e 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml @@ -3,7 +3,6 @@ <!-- Base application theme. --> <style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar"> <item name="myattr">@integer/ten</item> - <item name="android:animateFirstView">false</item> </style> </resources> diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java index cdcae89097b5..d0c04d7e3de4 100644 --- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java @@ -69,6 +69,7 @@ import java.util.concurrent.TimeUnit; import com.google.android.collect.Lists; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -313,6 +314,12 @@ public class Main { sBridge = new Bridge(); sBridge.init(ConfigGenerator.loadProperties(buildProp), fontLocation, ConfigGenerator.getEnumMap(attrs), getLayoutLog()); + Bridge.getLock().lock(); + try { + Bridge.setLog(getLayoutLog()); + } finally { + Bridge.getLock().unlock(); + } } @Before @@ -327,6 +334,20 @@ public class Main { } @Test + public void testActivityOnOldTheme() throws ClassNotFoundException { + LayoutLibTestCallback layoutLibCallback = + new LayoutLibTestCallback(getLogger(), mDefaultClassLoader); + layoutLibCallback.initResources(); + + LayoutPullParser parser = createLayoutPullParser("simple_activity.xml"); + SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5, + layoutLibCallback, "Theme.NoTitleBar", false, + RenderingMode.NORMAL, 22); + + renderAndVerify(params, "simple_activity-old-theme.png"); + } + + @Test public void testTranslucentBars() throws ClassNotFoundException { LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger(), mDefaultClassLoader); @@ -555,7 +576,7 @@ public class Main { /** Test activity.xml */ @Test - public void testScrolling() throws ClassNotFoundException { + public void testScrollingAndMeasure() throws ClassNotFoundException { // Create the layout pull parser. LayoutPullParser parser = createLayoutPullParser("scrolled.xml"); // Create LayoutLibCallback. @@ -569,7 +590,10 @@ public class Main { params.setForceNoDecor(); params.setExtendedViewInfoMode(true); - RenderResult result = renderAndVerify(params, "scrolled.png"); + // Do an only-measure pass + RenderSession session = sBridge.createSession(params); + session.measure(); + RenderResult result = RenderResult.getFromSession(session); assertNotNull(result); assertNotNull(result.getResult()); assertTrue(result.getResult().isSuccess()); @@ -586,6 +610,20 @@ public class Main { assertEquals(90, rootLayout.getChildren().get(5).getChildren().get(0).getLeft()); assertEquals(-270, rootLayout.getChildren().get(5).getChildren().get(0).getBottom()); assertEquals(690, rootLayout.getChildren().get(5).getChildren().get(0).getRight()); + + // Do a full render pass + parser = createLayoutPullParser("scrolled.xml"); + + params = getSessionParams(parser, ConfigGenerator.NEXUS_5, + layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false, + RenderingMode.V_SCROLL, 22); + params.setForceNoDecor(); + params.setExtendedViewInfoMode(true); + + result = renderAndVerify(params, "scrolled.png"); + assertNotNull(result); + assertNotNull(result.getResult()); + assertTrue(result.getResult().isSuccess()); } @Test @@ -624,6 +662,36 @@ public class Main { assertEquals("app_name", resources.getResourceEntryName(id)); } + @Test + public void testStringEscaping() throws Exception { + // Setup + // Create the layout pull parser for our resources (empty.xml can not be part of the test + // app as it won't compile). + LayoutPullParser parser = new LayoutPullParser("/empty.xml"); + // Create LayoutLibCallback. + LayoutLibTestCallback layoutLibCallback = + new LayoutLibTestCallback(getLogger(), mDefaultClassLoader); + layoutLibCallback.initResources(); + SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4, + layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22); + AssetManager assetManager = AssetManager.getSystem(); + DisplayMetrics metrics = new DisplayMetrics(); + Configuration configuration = RenderAction.getConfiguration(params); + Resources resources = new Resources(assetManager, metrics, configuration); + resources.mLayoutlibCallback = params.getLayoutlibCallback(); + resources.mContext = + new BridgeContext(params.getProjectKey(), metrics, params.getResources(), + params.getAssets(), params.getLayoutlibCallback(), configuration, + params.getTargetSdkVersion(), params.isRtlSupported()); + + int id = resources.mLayoutlibCallback.getResourceId(ResourceType.ARRAY, "string_array"); + String[] strings = resources.getStringArray(id); + assertArrayEquals( + new String[]{"mystring", "Hello world!", "candidates", "Unknown", "?EC"}, + strings); + assertTrue(sRenderMessages.isEmpty()); + } + @NonNull private LayoutPullParser createLayoutPullParser(String layoutPath) { return new LayoutPullParser(APP_TEST_RES + "/layout/" + layoutPath); diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java index 92fd75cc5a36..741eb27558ed 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -154,6 +154,8 @@ public final class CreateInfo implements ICreateInfo { "android.content.res.Resources#getIntArray", "android.content.res.Resources#getInteger", "android.content.res.Resources#getLayout", + "android.content.res.Resources#getQuantityString", + "android.content.res.Resources#getQuantityText", "android.content.res.Resources#getResourceEntryName", "android.content.res.Resources#getResourceName", "android.content.res.Resources#getResourcePackageName", @@ -232,6 +234,7 @@ public final class CreateInfo implements ICreateInfo { "android.view.RenderNode#nSetScaleY", "android.view.RenderNode#nGetScaleY", "android.view.RenderNode#nIsPivotExplicitlySet", + "android.view.PointerIcon#loadResource", "android.view.ViewGroup#drawChild", "com.android.internal.view.menu.MenuBuilder#createNewMenuItem", "com.android.internal.util.XmlUtils#convertValueToInt", @@ -336,7 +339,8 @@ public final class CreateInfo implements ICreateInfo { */ private final static String[] PROMOTED_FIELDS = new String[] { "android.graphics.drawable.VectorDrawable#mVectorState", - "android.view.Choreographer#mLastFrameTimeNanos" + "android.view.Choreographer#mLastFrameTimeNanos", + "android.graphics.FontFamily#mBuilderPtr" }; /** |