diff options
40 files changed, 1391 insertions, 547 deletions
diff --git a/api/current.txt b/api/current.txt index e4615e99f9de..e743881e63aa 100644 --- a/api/current.txt +++ b/api/current.txt @@ -28707,6 +28707,7 @@ package android.os { field public static final int TEMPERATURE_CURRENT = 0; // 0x0 field public static final int TEMPERATURE_SHUTDOWN = 2; // 0x2 field public static final int TEMPERATURE_THROTTLING = 1; // 0x1 + field public static final int TEMPERATURE_THROTTLING_BELOW_VR_MIN = 3; // 0x3 field public static final float UNDEFINED_TEMPERATURE = -3.4028235E38f; } @@ -49260,9 +49261,7 @@ package java.io { public final class FilePermission extends java.security.Permission implements java.io.Serializable { ctor public FilePermission(java.lang.String, java.lang.String); - method public boolean equals(java.lang.Object); method public java.lang.String getActions(); - method public int hashCode(); method public boolean implies(java.security.Permission); } @@ -52776,9 +52775,7 @@ package java.net { public final class SocketPermission extends java.security.Permission implements java.io.Serializable { ctor public SocketPermission(java.lang.String, java.lang.String); - method public boolean equals(java.lang.Object); method public java.lang.String getActions(); - method public int hashCode(); method public boolean implies(java.security.Permission); } @@ -53860,9 +53857,7 @@ package java.security { public final class AllPermission extends java.security.Permission { ctor public AllPermission(); ctor public AllPermission(java.lang.String, java.lang.String); - method public boolean equals(java.lang.Object); method public java.lang.String getActions(); - method public int hashCode(); method public boolean implies(java.security.Permission); } @@ -53876,9 +53871,7 @@ package java.security { public abstract class BasicPermission extends java.security.Permission implements java.io.Serializable { ctor public BasicPermission(java.lang.String); ctor public BasicPermission(java.lang.String, java.lang.String); - method public boolean equals(java.lang.Object); method public java.lang.String getActions(); - method public int hashCode(); method public boolean implies(java.security.Permission); } @@ -54256,10 +54249,8 @@ package java.security { public abstract class Permission implements java.security.Guard java.io.Serializable { ctor public Permission(java.lang.String); method public void checkGuard(java.lang.Object) throws java.lang.SecurityException; - method public abstract boolean equals(java.lang.Object); method public abstract java.lang.String getActions(); method public final java.lang.String getName(); - method public abstract int hashCode(); method public abstract boolean implies(java.security.Permission); method public java.security.PermissionCollection newPermissionCollection(); } @@ -54517,13 +54508,11 @@ package java.security { public final class UnresolvedPermission extends java.security.Permission implements java.io.Serializable { ctor public UnresolvedPermission(java.lang.String, java.lang.String, java.lang.String, java.security.cert.Certificate[]); - method public boolean equals(java.lang.Object); method public java.lang.String getActions(); method public java.lang.String getUnresolvedActions(); method public java.security.cert.Certificate[] getUnresolvedCerts(); method public java.lang.String getUnresolvedName(); method public java.lang.String getUnresolvedType(); - method public int hashCode(); method public boolean implies(java.security.Permission); } @@ -54581,8 +54570,6 @@ package java.security.acl { } public abstract interface Permission { - method public abstract boolean equals(java.lang.Object); - method public abstract java.lang.String toString(); } } @@ -63891,11 +63878,9 @@ package javax.security.auth { public final class PrivateCredentialPermission extends java.security.Permission { ctor public PrivateCredentialPermission(java.lang.String, java.lang.String); - method public boolean equals(java.lang.Object); method public java.lang.String getActions(); method public java.lang.String getCredentialClass(); method public java.lang.String[][] getPrincipals(); - method public int hashCode(); method public boolean implies(java.security.Permission); } diff --git a/api/removed.txt b/api/removed.txt index 86085c8ded67..3f16bca3eff1 100644 --- a/api/removed.txt +++ b/api/removed.txt @@ -94,6 +94,10 @@ package android.media { package android.media.tv { + public final class TvInputManager { + method public android.media.tv.TvInputManager.Hardware acquireTvInputHardware(int, android.media.tv.TvInputManager.HardwareCallback, android.media.tv.TvInputInfo); + } + public class TvView extends android.view.ViewGroup { method public void requestUnblockContent(android.media.tv.TvContentRating); } diff --git a/api/system-current.txt b/api/system-current.txt index 087b25065f2f..008b8603afca 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -24638,7 +24638,7 @@ package android.media.tv { } public final class TvInputManager { - method public android.media.tv.TvInputManager.Hardware acquireTvInputHardware(int, android.media.tv.TvInputManager.HardwareCallback, android.media.tv.TvInputInfo); + method public android.media.tv.TvInputManager.Hardware acquireTvInputHardware(int, android.media.tv.TvInputInfo, android.media.tv.TvInputManager.HardwareCallback); method public void addBlockedRating(android.media.tv.TvContentRating); method public boolean captureFrame(java.lang.String, android.view.Surface, android.media.tv.TvStreamConfig); method public java.util.List<android.media.tv.TvStreamConfig> getAvailableTvStreamConfigList(java.lang.String); @@ -30951,6 +30951,7 @@ package android.os { field public static final int TEMPERATURE_CURRENT = 0; // 0x0 field public static final int TEMPERATURE_SHUTDOWN = 2; // 0x2 field public static final int TEMPERATURE_THROTTLING = 1; // 0x1 + field public static final int TEMPERATURE_THROTTLING_BELOW_VR_MIN = 3; // 0x3 field public static final float UNDEFINED_TEMPERATURE = -3.4028235E38f; } @@ -52351,9 +52352,7 @@ package java.io { public final class FilePermission extends java.security.Permission implements java.io.Serializable { ctor public FilePermission(java.lang.String, java.lang.String); - method public boolean equals(java.lang.Object); method public java.lang.String getActions(); - method public int hashCode(); method public boolean implies(java.security.Permission); } @@ -55867,9 +55866,7 @@ package java.net { public final class SocketPermission extends java.security.Permission implements java.io.Serializable { ctor public SocketPermission(java.lang.String, java.lang.String); - method public boolean equals(java.lang.Object); method public java.lang.String getActions(); - method public int hashCode(); method public boolean implies(java.security.Permission); } @@ -56951,9 +56948,7 @@ package java.security { public final class AllPermission extends java.security.Permission { ctor public AllPermission(); ctor public AllPermission(java.lang.String, java.lang.String); - method public boolean equals(java.lang.Object); method public java.lang.String getActions(); - method public int hashCode(); method public boolean implies(java.security.Permission); } @@ -56967,9 +56962,7 @@ package java.security { public abstract class BasicPermission extends java.security.Permission implements java.io.Serializable { ctor public BasicPermission(java.lang.String); ctor public BasicPermission(java.lang.String, java.lang.String); - method public boolean equals(java.lang.Object); method public java.lang.String getActions(); - method public int hashCode(); method public boolean implies(java.security.Permission); } @@ -57347,10 +57340,8 @@ package java.security { public abstract class Permission implements java.security.Guard java.io.Serializable { ctor public Permission(java.lang.String); method public void checkGuard(java.lang.Object) throws java.lang.SecurityException; - method public abstract boolean equals(java.lang.Object); method public abstract java.lang.String getActions(); method public final java.lang.String getName(); - method public abstract int hashCode(); method public abstract boolean implies(java.security.Permission); method public java.security.PermissionCollection newPermissionCollection(); } @@ -57608,13 +57599,11 @@ package java.security { public final class UnresolvedPermission extends java.security.Permission implements java.io.Serializable { ctor public UnresolvedPermission(java.lang.String, java.lang.String, java.lang.String, java.security.cert.Certificate[]); - method public boolean equals(java.lang.Object); method public java.lang.String getActions(); method public java.lang.String getUnresolvedActions(); method public java.security.cert.Certificate[] getUnresolvedCerts(); method public java.lang.String getUnresolvedName(); method public java.lang.String getUnresolvedType(); - method public int hashCode(); method public boolean implies(java.security.Permission); } @@ -57672,8 +57661,6 @@ package java.security.acl { } public abstract interface Permission { - method public abstract boolean equals(java.lang.Object); - method public abstract java.lang.String toString(); } } @@ -66982,11 +66969,9 @@ package javax.security.auth { public final class PrivateCredentialPermission extends java.security.Permission { ctor public PrivateCredentialPermission(java.lang.String, java.lang.String); - method public boolean equals(java.lang.Object); method public java.lang.String getActions(); method public java.lang.String getCredentialClass(); method public java.lang.String[][] getPrincipals(); - method public int hashCode(); method public boolean implies(java.security.Permission); } diff --git a/api/system-removed.txt b/api/system-removed.txt index bc1762722c18..03cf8b0902a4 100644 --- a/api/system-removed.txt +++ b/api/system-removed.txt @@ -92,6 +92,10 @@ package android.media { package android.media.tv { + public final class TvInputManager { + method public android.media.tv.TvInputManager.Hardware acquireTvInputHardware(int, android.media.tv.TvInputManager.HardwareCallback, android.media.tv.TvInputInfo); + } + public class TvView extends android.view.ViewGroup { method public void requestUnblockContent(android.media.tv.TvContentRating); } diff --git a/api/test-current.txt b/api/test-current.txt index fac2219f7196..78a1d3af422a 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -28772,6 +28772,7 @@ package android.os { field public static final int TEMPERATURE_CURRENT = 0; // 0x0 field public static final int TEMPERATURE_SHUTDOWN = 2; // 0x2 field public static final int TEMPERATURE_THROTTLING = 1; // 0x1 + field public static final int TEMPERATURE_THROTTLING_BELOW_VR_MIN = 3; // 0x3 field public static final float UNDEFINED_TEMPERATURE = -3.4028235E38f; } @@ -49334,9 +49335,7 @@ package java.io { public final class FilePermission extends java.security.Permission implements java.io.Serializable { ctor public FilePermission(java.lang.String, java.lang.String); - method public boolean equals(java.lang.Object); method public java.lang.String getActions(); - method public int hashCode(); method public boolean implies(java.security.Permission); } @@ -52850,9 +52849,7 @@ package java.net { public final class SocketPermission extends java.security.Permission implements java.io.Serializable { ctor public SocketPermission(java.lang.String, java.lang.String); - method public boolean equals(java.lang.Object); method public java.lang.String getActions(); - method public int hashCode(); method public boolean implies(java.security.Permission); } @@ -53934,9 +53931,7 @@ package java.security { public final class AllPermission extends java.security.Permission { ctor public AllPermission(); ctor public AllPermission(java.lang.String, java.lang.String); - method public boolean equals(java.lang.Object); method public java.lang.String getActions(); - method public int hashCode(); method public boolean implies(java.security.Permission); } @@ -53950,9 +53945,7 @@ package java.security { public abstract class BasicPermission extends java.security.Permission implements java.io.Serializable { ctor public BasicPermission(java.lang.String); ctor public BasicPermission(java.lang.String, java.lang.String); - method public boolean equals(java.lang.Object); method public java.lang.String getActions(); - method public int hashCode(); method public boolean implies(java.security.Permission); } @@ -54330,10 +54323,8 @@ package java.security { public abstract class Permission implements java.security.Guard java.io.Serializable { ctor public Permission(java.lang.String); method public void checkGuard(java.lang.Object) throws java.lang.SecurityException; - method public abstract boolean equals(java.lang.Object); method public abstract java.lang.String getActions(); method public final java.lang.String getName(); - method public abstract int hashCode(); method public abstract boolean implies(java.security.Permission); method public java.security.PermissionCollection newPermissionCollection(); } @@ -54591,13 +54582,11 @@ package java.security { public final class UnresolvedPermission extends java.security.Permission implements java.io.Serializable { ctor public UnresolvedPermission(java.lang.String, java.lang.String, java.lang.String, java.security.cert.Certificate[]); - method public boolean equals(java.lang.Object); method public java.lang.String getActions(); method public java.lang.String getUnresolvedActions(); method public java.security.cert.Certificate[] getUnresolvedCerts(); method public java.lang.String getUnresolvedName(); method public java.lang.String getUnresolvedType(); - method public int hashCode(); method public boolean implies(java.security.Permission); } @@ -54655,8 +54644,6 @@ package java.security.acl { } public abstract interface Permission { - method public abstract boolean equals(java.lang.Object); - method public abstract java.lang.String toString(); } } @@ -63965,11 +63952,9 @@ package javax.security.auth { public final class PrivateCredentialPermission extends java.security.Permission { ctor public PrivateCredentialPermission(java.lang.String, java.lang.String); - method public boolean equals(java.lang.Object); method public java.lang.String getActions(); method public java.lang.String getCredentialClass(); method public java.lang.String[][] getPrincipals(); - method public int hashCode(); method public boolean implies(java.security.Permission); } diff --git a/api/test-removed.txt b/api/test-removed.txt index 86085c8ded67..3f16bca3eff1 100644 --- a/api/test-removed.txt +++ b/api/test-removed.txt @@ -94,6 +94,10 @@ package android.media { package android.media.tv { + public final class TvInputManager { + method public android.media.tv.TvInputManager.Hardware acquireTvInputHardware(int, android.media.tv.TvInputManager.HardwareCallback, android.media.tv.TvInputInfo); + } + public class TvView extends android.view.ViewGroup { method public void requestUnblockContent(android.media.tv.TvContentRating); } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index baaa55d34841..d1f514327731 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -377,6 +377,12 @@ public class ActivityManager { /** @hide Process is being cached for later use and is empty. */ public static final int PROCESS_STATE_CACHED_EMPTY = 16; + /** @hide The lowest process state number */ + public static final int MIN_PROCESS_STATE = PROCESS_STATE_NONEXISTENT; + + /** @hide The highest process state number */ + public static final int MAX_PROCESS_STATE = PROCESS_STATE_CACHED_EMPTY; + /** @hide Should this process state be considered a background state? */ public static final boolean isProcStateBackground(int procState) { return procState >= PROCESS_STATE_BACKUP; diff --git a/core/java/android/content/pm/LauncherActivityInfo.java b/core/java/android/content/pm/LauncherActivityInfo.java index a5617b46b403..40e1a9f8ba79 100644 --- a/core/java/android/content/pm/LauncherActivityInfo.java +++ b/core/java/android/content/pm/LauncherActivityInfo.java @@ -103,53 +103,22 @@ public class LauncherActivityInfo { * @return The drawable associated with the activity. */ public Drawable getIcon(int density) { - final int iconRes = mResolveInfo.getIconResource(); - Drawable icon = getDrawableForDensity(iconRes, density); - // Get the default density icon - if (icon == null) { - icon = mResolveInfo.loadIcon(mPm); - } - return icon; - } - - /** - * Returns the icon for this activity, without any badging for the profile. - * This function can get the icon no matter the icon needs to be badged or not. - * @param density The preferred density of the icon, zero for default density. Use - * density DPI values from {@link DisplayMetrics}. - * @see #getBadgedIcon(int) - * @see DisplayMetrics - * @return The drawable associated with the activity. - */ - private Drawable getOriginalIcon(int density) { final int iconRes = mResolveInfo.getIconResourceInternal(); - Drawable icon = getDrawableForDensity(iconRes, density); - // Get the default density icon - if (icon == null) { - icon = mResolveInfo.loadIcon(mPm); - } - return icon; - } - - /** - * Returns the drawable for this activity, without any badging for the profile. - * @param iconRes id of the drawable. - * @param density The preferred density of the icon, zero for default density. Use - * density DPI values from {@link DisplayMetrics}. - * @see DisplayMetrics - * @return The drawable associated with the resource id. - */ - private Drawable getDrawableForDensity(int iconRes, int density) { + Drawable icon = null; // Get the preferred density icon from the app's resources if (density != 0 && iconRes != 0) { try { final Resources resources = mPm.getResourcesForApplication(mActivityInfo.applicationInfo); - return resources.getDrawableForDensity(iconRes, density); + icon = resources.getDrawableForDensity(iconRes, density); } catch (NameNotFoundException | Resources.NotFoundException exc) { } } - return null; + // Get the default density icon + if (icon == null) { + icon = mResolveInfo.loadIcon(mPm); + } + return icon; } /** @@ -201,7 +170,7 @@ public class LauncherActivityInfo { * @return A badged icon for the activity. */ public Drawable getBadgedIcon(int density) { - Drawable originalIcon = getOriginalIcon(density); + Drawable originalIcon = getIcon(density); if (originalIcon instanceof BitmapDrawable) { return mPm.getUserBadgedIcon(originalIcon, mUser); diff --git a/core/java/android/os/HardwarePropertiesManager.java b/core/java/android/os/HardwarePropertiesManager.java index f13e5b57cbd0..9d362d61d9d4 100644 --- a/core/java/android/os/HardwarePropertiesManager.java +++ b/core/java/android/os/HardwarePropertiesManager.java @@ -48,7 +48,8 @@ public class HardwarePropertiesManager { */ @Retention(RetentionPolicy.SOURCE) @IntDef({ - TEMPERATURE_CURRENT, TEMPERATURE_THROTTLING, TEMPERATURE_SHUTDOWN + TEMPERATURE_CURRENT, TEMPERATURE_THROTTLING, TEMPERATURE_SHUTDOWN, + TEMPERATURE_THROTTLING_BELOW_VR_MIN }) public @interface TemperatureSource {} @@ -77,6 +78,12 @@ public class HardwarePropertiesManager { /** Get shutdown temperature threshold. */ public static final int TEMPERATURE_SHUTDOWN = 2; + /** + * Get throttling temperature threshold above which minimum clockrates for VR mode will not be + * met. + */ + public static final int TEMPERATURE_THROTTLING_BELOW_VR_MIN = 3; + /** Undefined temperature constant. */ public static final float UNDEFINED_TEMPERATURE = -Float.MAX_VALUE; @@ -96,7 +103,8 @@ public class HardwarePropertiesManager { * {@link #DEVICE_TEMPERATURE_GPU}, {@link #DEVICE_TEMPERATURE_BATTERY} or {@link * #DEVICE_TEMPERATURE_SKIN}. * @param source source of requested device temperature, one of {@link #TEMPERATURE_CURRENT}, - * {@link #TEMPERATURE_THROTTLING} or {@link #TEMPERATURE_SHUTDOWN}. + * {@link #TEMPERATURE_THROTTLING}, {@link #TEMPERATURE_THROTTLING_BELOW_VR_MIN} or + * {@link #TEMPERATURE_SHUTDOWN}. * @return an array of requested float device temperatures. Temperature equals to * {@link #UNDEFINED_TEMPERATURE} if undefined. * Empty if platform doesn't provide the queried temperature. @@ -115,6 +123,7 @@ public class HardwarePropertiesManager { case TEMPERATURE_CURRENT: case TEMPERATURE_THROTTLING: case TEMPERATURE_SHUTDOWN: + case TEMPERATURE_THROTTLING_BELOW_VR_MIN: try { return mService.getDeviceTemperatures(mContext.getOpPackageName(), type, source); diff --git a/core/java/android/webkit/WebViewProviderInfo.java b/core/java/android/webkit/WebViewProviderInfo.java index 75ccf355ecd7..d106dba4dd3a 100644 --- a/core/java/android/webkit/WebViewProviderInfo.java +++ b/core/java/android/webkit/WebViewProviderInfo.java @@ -16,32 +16,16 @@ package android.webkit; -import android.app.AppGlobals; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.Signature; -import android.os.Build; import android.os.Parcel; import android.os.Parcelable; -import android.util.AndroidRuntimeException; -import android.util.Base64; import java.util.Arrays; /** @hide */ -public class WebViewProviderInfo implements Parcelable { +public final class WebViewProviderInfo implements Parcelable { - /** - * @hide - */ - public static class WebViewPackageNotFoundException extends AndroidRuntimeException { - public WebViewPackageNotFoundException(String message) { super(message); } - public WebViewPackageNotFoundException(Exception e) { super(e); } - } - - public WebViewProviderInfo(String packageName, String description, boolean availableByDefault, - boolean isFallback, String[] signatures) { + public WebViewProviderInfo(String packageName, String description, + boolean availableByDefault, boolean isFallback, String[] signatures) { this.packageName = packageName; this.description = description; this.availableByDefault = availableByDefault; @@ -49,92 +33,6 @@ public class WebViewProviderInfo implements Parcelable { this.signatures = signatures; } - private boolean hasValidSignature() { - if (Build.IS_DEBUGGABLE) - return true; - Signature[] packageSignatures; - try { - // If no signature is declared, instead check whether the package is included in the - // system. - if (signatures == null || signatures.length == 0) - return getPackageInfo().applicationInfo.isSystemApp(); - - packageSignatures = getPackageInfo().signatures; - } catch (WebViewPackageNotFoundException e) { - return false; - } - if (packageSignatures.length != 1) - return false; - - final byte[] packageSignature = packageSignatures[0].toByteArray(); - // Return whether the package signature matches any of the valid signatures - for (String signature : signatures) { - final byte[] validSignature = Base64.decode(signature, Base64.DEFAULT); - if (Arrays.equals(packageSignature, validSignature)) - return true; - } - return false; - } - - /** - * Returns whether this provider is valid for use as a WebView provider. - */ - public boolean isValidProvider() { - ApplicationInfo applicationInfo; - try { - applicationInfo = getPackageInfo().applicationInfo; - } catch (WebViewPackageNotFoundException e) { - return false; - } - if (hasValidSignature() && WebViewFactory.getWebViewLibrary(applicationInfo) != null) { - return true; - } - return false; - } - - /** - * Returns whether this package is enabled. - * This state can be changed by the user from Settings->Apps - */ - public boolean isEnabled() { - try { - // Explicitly fetch up-to-date package info here since the enabled-state of the package - // might have changed since we last fetched its package info. - updatePackageInfo(); - return getPackageInfo().applicationInfo.enabled; - } catch (WebViewPackageNotFoundException e) { - return false; - } - } - - /** - * Returns whether the provider is always available as long as it is valid. - * If this returns false, the provider will only be used if the user chose this provider. - */ - public boolean isAvailableByDefault() { - return availableByDefault; - } - - public boolean isFallbackPackage() { - return isFallback; - } - - private void updatePackageInfo() { - try { - PackageManager pm = AppGlobals.getInitialApplication().getPackageManager(); - packageInfo = pm.getPackageInfo(packageName, PACKAGE_FLAGS); - } catch (PackageManager.NameNotFoundException e) { - throw new WebViewPackageNotFoundException(e); - } - } - - public PackageInfo getPackageInfo() { - if (packageInfo == null) { - updatePackageInfo(); - } - return packageInfo; - } - // aidl stuff public static final Parcelable.Creator<WebViewProviderInfo> CREATOR = new Parcelable.Creator<WebViewProviderInfo>() { @@ -153,7 +51,6 @@ public class WebViewProviderInfo implements Parcelable { availableByDefault = (in.readInt() > 0); isFallback = (in.readInt() > 0); signatures = in.createStringArray(); - packageInfo = null; } @Override @@ -171,16 +68,9 @@ public class WebViewProviderInfo implements Parcelable { } // fields read from framework resource - public String packageName; - public String description; - private boolean availableByDefault; - private boolean isFallback; - - private String[] signatures; - - private PackageInfo packageInfo; - - // flags declaring we want extra info from the package manager - private final static int PACKAGE_FLAGS = PackageManager.GET_META_DATA - | PackageManager.GET_SIGNATURES | PackageManager.MATCH_DEBUG_TRIAGED_MISSING; + public final String packageName; + public final String description; + public final boolean availableByDefault; + public final boolean isFallback; + public final String[] signatures; } diff --git a/core/tests/benchmarks/src/android/text/util/LinkifyBenchmark.java b/core/tests/benchmarks/src/android/text/util/LinkifyBenchmark.java new file mode 100644 index 000000000000..a6e433fcb8d8 --- /dev/null +++ b/core/tests/benchmarks/src/android/text/util/LinkifyBenchmark.java @@ -0,0 +1,89 @@ +/* + * 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.text.util; + +import com.google.caliper.AfterExperiment; +import com.google.caliper.BeforeExperiment; +import com.google.caliper.Benchmark; +import com.google.caliper.Param; + +import android.text.Spannable; +import android.text.SpannableString; +import android.util.Patterns; + +import java.util.regex.Pattern; + +public class LinkifyBenchmark { + private static final String MATCHING_STR = " http://user:pass@host.com:5432/path?k=v#f " + + "host.com:5432/path?k=v#f "; + + private static final String NONMATCHING_STR = " Neque porro quisquam est qui dolorem ipsum " + + "quia dolor sit amet, consectetur, adipisci velit "; + + // this pattern does not recognize strings without http scheme therefore is expected to be + // faster in MATCHING_STR case. + private static final Pattern BASIC_PATTERN = Pattern.compile( + "(?:\\b|$|^)http://[a-zA-Z0-9:\\.@\\?=#/]+(?:\\b|$|^)"); + + @Param({"1", "4", "16", "64", "256"}) + private String mParamCopyAmount; + + @Param({MATCHING_STR, NONMATCHING_STR}) + private String mParamBasicText; + + private Spannable mTestSpannable; + + @BeforeExperiment + protected void setUp() throws Exception { + int copyAmount = Integer.valueOf(mParamCopyAmount); + StringBuilder strBuilder = new StringBuilder(); + for (int i = 0; i < copyAmount; i++) { + strBuilder.append(mParamBasicText); + } + mTestSpannable = new SpannableString(strBuilder.toString()); + } + + @AfterExperiment + protected void tearDown() { + mTestSpannable = null; + } + + @Benchmark + public void timeNewRegEx(int reps) throws Exception { + for (int i = 0; i < reps; i++) { + Linkify.addLinks(mTestSpannable, Patterns.AUTOLINK_WEB_URL, "http://", + new String[]{"http://", "https://", "rtsp://"}, null, null); + } + } + + @Benchmark + public void timeOldRegEx(int reps) throws Exception { + for (int i = 0; i < reps; i++) { + Linkify.addLinks(mTestSpannable, Patterns.WEB_URL, "http://", + new String[]{"http://", "https://", "rtsp://"}, null, null); + } + } + + @Benchmark + public void timeBasicRegEx(int reps) throws Exception { + for (int i = 0; i < reps; i++) { + Linkify.addLinks(mTestSpannable, BASIC_PATTERN, "http://", + new String[]{"http://", "https://", "rtsp://"}, null, null); + } + } + +} diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp index ea4f4eb98f36..f43bf860946b 100644 --- a/libs/hwui/BakedOpDispatcher.cpp +++ b/libs/hwui/BakedOpDispatcher.cpp @@ -195,7 +195,7 @@ void BakedOpDispatcher::onMergedPatchOps(BakedOpRenderer& renderer, } static void renderTextShadow(BakedOpRenderer& renderer, FontRenderer& fontRenderer, - const TextOp& op, const BakedOpState& state) { + const TextOp& op, const BakedOpState& textOpState) { renderer.caches().textureState().activateTexture(0); PaintUtils::TextShadow textShadow; @@ -216,13 +216,41 @@ static void renderTextShadow(BakedOpRenderer& renderer, FontRenderer& fontRender Glop glop; GlopBuilder(renderer.renderState(), renderer.caches(), &glop) - .setRoundRectClipState(state.roundRectClipState) + .setRoundRectClipState(textOpState.roundRectClipState) .setMeshTexturedUnitQuad(nullptr) - .setFillShadowTexturePaint(*texture, textShadow.color, *op.paint, state.alpha) - .setTransform(state.computedState.transform, TransformFlags::None) + .setFillShadowTexturePaint(*texture, textShadow.color, *op.paint, textOpState.alpha) + .setTransform(textOpState.computedState.transform, TransformFlags::None) .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width(), sy + texture->height())) .build(); - renderer.renderGlop(state, glop); + + // Compute damage bounds and clip (since may differ from those in textOpState). + // Bounds should be same as text op, but with dx/dy offset and radius outset + // applied in local space. + auto& transform = textOpState.computedState.transform; + Rect shadowBounds = op.unmappedBounds; // STROKE + const bool expandForStroke = op.paint->getStyle() != SkPaint::kFill_Style; + if (expandForStroke) { + shadowBounds.outset(op.paint->getStrokeWidth() * 0.5f); + } + shadowBounds.translate(textShadow.dx, textShadow.dy); + shadowBounds.outset(textShadow.radius, textShadow.radius); + transform.mapRect(shadowBounds); + if (CC_UNLIKELY(expandForStroke && + (!transform.isPureTranslate() || op.paint->getStrokeWidth() < 1.0f))) { + shadowBounds.outset(0.5f); + } + + auto clipState = textOpState.computedState.clipState; + if (clipState->mode != ClipMode::Rectangle + || !clipState->rect.contains(shadowBounds)) { + // need clip, so pass it and clip bounds + shadowBounds.doIntersect(clipState->rect); + } else { + // don't need clip, ignore + clipState = nullptr; + } + + renderer.renderGlop(&shadowBounds, clipState, glop); } enum class TextRenderType { diff --git a/libs/hwui/Glop.h b/libs/hwui/Glop.h index 704bd69056fe..6a9663412a00 100644 --- a/libs/hwui/Glop.h +++ b/libs/hwui/Glop.h @@ -163,11 +163,13 @@ public: GLenum dst; } blend; +#if !HWUI_NEW_OPS /** * Bounds of the drawing command in layer space. Only mapped into layer * space once GlopBuilder::build() is called. */ Rect bounds; // TODO: remove for HWUI_NEW_OPS +#endif /** * Additional render state to enumerate: diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp index 2799def16b98..7d4f4100313f 100644 --- a/libs/hwui/GlopBuilder.cpp +++ b/libs/hwui/GlopBuilder.cpp @@ -492,7 +492,9 @@ GlopBuilder& GlopBuilder::setModelViewMapUnitToRect(const Rect destination) { mOutGlop->transform.modelView.loadTranslate(destination.left, destination.top, 0.0f); mOutGlop->transform.modelView.scale(destination.getWidth(), destination.getHeight(), 1.0f); +#if !HWUI_NEW_OPS mOutGlop->bounds = destination; +#endif return *this; } @@ -516,7 +518,9 @@ GlopBuilder& GlopBuilder::setModelViewMapUnitToRectSnap(const Rect destination) mOutGlop->transform.modelView.loadTranslate(left, top, 0.0f); mOutGlop->transform.modelView.scale(destination.getWidth(), destination.getHeight(), 1.0f); +#if !HWUI_NEW_OPS mOutGlop->bounds = destination; +#endif return *this; } @@ -524,8 +528,10 @@ GlopBuilder& GlopBuilder::setModelViewOffsetRect(float offsetX, float offsetY, c TRIGGER_STAGE(kModelViewStage); mOutGlop->transform.modelView.loadTranslate(offsetX, offsetY, 0.0f); +#if !HWUI_NEW_OPS mOutGlop->bounds = source; mOutGlop->bounds.translate(offsetX, offsetY); +#endif return *this; } @@ -545,8 +551,10 @@ GlopBuilder& GlopBuilder::setModelViewOffsetRectSnap(float offsetX, float offset } mOutGlop->transform.modelView.loadTranslate(offsetX, offsetY, 0.0f); +#if !HWUI_NEW_OPS mOutGlop->bounds = source; mOutGlop->bounds.translate(offsetX, offsetY); +#endif return *this; } @@ -643,7 +651,9 @@ void GlopBuilder::build() { // Final step: populate program and map bounds into render target space mOutGlop->fill.program = mCaches.programCache.get(mDescription); +#if !HWUI_NEW_OPS mOutGlop->transform.meshTransform().mapRect(mOutGlop->bounds); +#endif } void GlopBuilder::dump(const Glop& glop) { @@ -683,7 +693,9 @@ void GlopBuilder::dump(const Glop& glop) { ALOGD_IF(glop.roundRectClipState, "Glop RRCS %p", glop.roundRectClipState); ALOGD("Glop blend %d %d", glop.blend.src, glop.blend.dst); +#if !HWUI_NEW_OPS ALOGD("Glop bounds " RECT_STRING, RECT_ARGS(glop.bounds)); +#endif } } /* namespace uirenderer */ diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp index 47d01088f6f7..d1ff67027d07 100644 --- a/libs/hwui/JankTracker.cpp +++ b/libs/hwui/JankTracker.cpp @@ -56,24 +56,17 @@ static const Comparison COMPARISONS[] = { static const int64_t IGNORE_EXCEEDING = seconds_to_nanoseconds(10); /* - * Frames that are exempt from jank metrics. - * First-draw frames, for example, are expected to - * be slow, this is hidden from the user with window animations and - * other tricks - * - * Similarly, we don't track direct-drawing via Surface:lockHardwareCanvas() + * We don't track direct-drawing via Surface:lockHardwareCanvas() * for now * * TODO: kSurfaceCanvas can negatively impact other drawing by using up * time on the RenderThread, figure out how to attribute that as a jank-causer */ -static const int64_t EXEMPT_FRAMES_FLAGS - = FrameInfoFlags::WindowLayoutChanged - | FrameInfoFlags::SurfaceCanvas; +static const int64_t EXEMPT_FRAMES_FLAGS = FrameInfoFlags::SurfaceCanvas; // The bucketing algorithm controls so to speak // If a frame is <= to this it goes in bucket 0 -static const uint32_t kBucketMinThreshold = 7; +static const uint32_t kBucketMinThreshold = 5; // If a frame is > this, start counting in increments of 2ms static const uint32_t kBucket2msIntervals = 32; // If a frame is > this, start counting in increments of 4ms @@ -84,9 +77,14 @@ static const uint32_t kBucket4msIntervals = 48; // and filter it out of the frame profile data static FrameInfoIndex sFrameStart = FrameInfoIndex::IntendedVsync; +// The interval of the slow frame histogram +static const uint32_t kSlowFrameBucketIntervalMs = 50; +// The start point of the slow frame bucket in ms +static const uint32_t kSlowFrameBucketStartMs = 150; + // This will be called every frame, performance sensitive // Uses bit twiddling to avoid branching while achieving the packing desired -static uint32_t frameCountIndexForFrameTime(nsecs_t frameTime, uint32_t max) { +static uint32_t frameCountIndexForFrameTime(nsecs_t frameTime) { uint32_t index = static_cast<uint32_t>(ns2ms(frameTime)); // If index > kBucketMinThreshold mask will be 0xFFFFFFFF as a result // of negating 1 (twos compliment, yaay) else mask will be 0 @@ -104,7 +102,7 @@ static uint32_t frameCountIndexForFrameTime(nsecs_t frameTime, uint32_t max) { // be a pretty garbage value right now. However, mask is 0 so we'll end // up with the desired result of 0. index = (index - kBucketMinThreshold) & mask; - return index < max ? index : max; + return index; } // Only called when dumping stats, less performance sensitive @@ -211,63 +209,34 @@ void JankTracker::setFrameInterval(nsecs_t frameInterval) { } -static bool shouldReplace(SlowFrame& existing, SlowFrame& candidate) { - if (candidate.whenHours - existing.whenHours >= 24) { - // If the old slowframe is over 24 hours older than the candidate, - // replace it. It's too stale - return true; - } - if (candidate.frametimeMs > existing.frametimeMs) { - return true; - } - return false; -} - -void JankTracker::updateSlowest(const FrameInfo& frame) { - uint16_t durationMs = static_cast<uint16_t>(std::min( - ns2ms(frame[FrameInfoIndex::FrameCompleted] - frame[FrameInfoIndex::IntendedVsync]), - static_cast<nsecs_t>(std::numeric_limits<uint16_t>::max()))); - uint16_t startHours = static_cast<uint16_t>(std::lround( - ns2s(frame[FrameInfoIndex::IntendedVsync]) / 3600.0f)); - SlowFrame* toReplace = nullptr; - SlowFrame thisFrame{startHours, durationMs}; - // First find the best candidate for replacement - for (SlowFrame& existing : mData->slowestFrames) { - // If we should replace the current data with the replacement candidate, - // it means the current data is worse than the replacement candidate - if (!toReplace || shouldReplace(existing, *toReplace)) { - toReplace = &existing; - } - } - // Now see if we should replace it - if (shouldReplace(*toReplace, thisFrame)) { - *toReplace = thisFrame; - } -} - void JankTracker::addFrame(const FrameInfo& frame) { mData->totalFrameCount++; // Fast-path for jank-free frames int64_t totalDuration = frame[FrameInfoIndex::FrameCompleted] - frame[sFrameStart]; - uint32_t framebucket = frameCountIndexForFrameTime( - totalDuration, mData->frameCounts.size() - 1); + uint32_t framebucket = frameCountIndexForFrameTime(totalDuration); // Keep the fast path as fast as possible. if (CC_LIKELY(totalDuration < mFrameInterval)) { mData->frameCounts[framebucket]++; return; } - // For slowest frames we are still interested in frames that are otherwise - // exempt (such as first-draw). Although those frames don't directly impact - // smoothness, they do impact responsiveness. - updateSlowest(frame); - + // Only things like Surface.lockHardwareCanvas() are exempt from tracking if (frame[FrameInfoIndex::Flags] & EXEMPT_FRAMES_FLAGS) { return; } - mData->frameCounts[framebucket]++; + if (framebucket <= mData->frameCounts.size()) { + mData->frameCounts[framebucket]++; + } else { + framebucket = (ns2ms(totalDuration) - kSlowFrameBucketStartMs) + / kSlowFrameBucketIntervalMs; + framebucket = std::min(framebucket, + static_cast<uint32_t>(mData->slowFrameCounts.size() - 1)); + framebucket = std::max(framebucket, 0u); + mData->slowFrameCounts[framebucket]++; + } + mData->jankFrameCount++; for (int i = 0; i < NUM_BUCKETS; i++) { @@ -298,14 +267,18 @@ void JankTracker::dumpData(const ProfileData* data, int fd) { dprintf(fd, "\n90th percentile: %ums", findPercentile(data, 90)); dprintf(fd, "\n95th percentile: %ums", findPercentile(data, 95)); dprintf(fd, "\n99th percentile: %ums", findPercentile(data, 99)); - dprintf(fd, "\nSlowest frames over last 24h: "); - for (auto& slowFrame : data->slowestFrames) { - if (!slowFrame.frametimeMs) continue; - dprintf(fd, "%ums ", slowFrame.frametimeMs); - } for (int i = 0; i < NUM_BUCKETS; i++) { dprintf(fd, "\nNumber %s: %u", JANK_TYPE_NAMES[i], data->jankTypeCounts[i]); } + dprintf(fd, "\nHISTOGRAM:"); + for (size_t i = 0; i < data->frameCounts.size(); i++) { + dprintf(fd, " %ums=%u", frameTimeForFrameCountIndex(i), + data->frameCounts[i]); + } + for (size_t i = 0; i < data->slowFrameCounts.size(); i++) { + dprintf(fd, " %zums=%u", (i * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs, + data->slowFrameCounts[i]); + } dprintf(fd, "\n"); } @@ -323,6 +296,12 @@ void JankTracker::reset() { uint32_t JankTracker::findPercentile(const ProfileData* data, int percentile) { int pos = percentile * data->totalFrameCount / 100; int remaining = data->totalFrameCount - pos; + for (int i = data->slowFrameCounts.size() - 1; i >= 0; i--) { + remaining -= data->slowFrameCounts[i]; + if (remaining <= 0) { + return (i * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs; + } + } for (int i = data->frameCounts.size() - 1; i >= 0; i--) { remaining -= data->frameCounts[i]; if (remaining <= 0) { diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h index 1a4a4897f496..84b8c3f3f155 100644 --- a/libs/hwui/JankTracker.h +++ b/libs/hwui/JankTracker.h @@ -39,23 +39,18 @@ enum JankType { NUM_BUCKETS, }; -struct SlowFrame { - uint16_t whenHours; // When this occurred in CLOCK_MONOTONIC in hours - uint16_t frametimeMs; // How long the frame took in ms -}; - // Try to keep as small as possible, should match ASHMEM_SIZE in // GraphicsStatsService.java struct ProfileData { std::array<uint32_t, NUM_BUCKETS> jankTypeCounts; // See comments on kBucket* constants for what this holds - std::array<uint32_t, 55> frameCounts; + std::array<uint32_t, 57> frameCounts; + // Holds a histogram of frame times in 50ms increments from 150ms to 5s + std::array<uint16_t, 97> slowFrameCounts; uint32_t totalFrameCount; uint32_t jankFrameCount; nsecs_t statStartTime; - - std::array<SlowFrame, 10> slowestFrames; }; // TODO: Replace DrawProfiler with this @@ -78,7 +73,6 @@ public: private: void freeData(); void setFrameInterval(nsecs_t frameIntervalNanos); - void updateSlowest(const FrameInfo& frame); static uint32_t findPercentile(const ProfileData* data, int p); static void dumpData(const ProfileData* data, int fd); diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index c0994272c964..53ea7fa6f77d 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -1413,7 +1413,9 @@ void OpenGLRenderer::renderGlop(const Glop& glop, GlopRenderType type) { if (type == GlopRenderType::Standard && !mRenderState.stencil().isWriteEnabled()) { // TODO: specify more clearly when a draw should dirty the layer. // is writing to the stencil the only time we should ignore this? +#if !HWUI_NEW_OPS dirtyLayer(glop.bounds.left, glop.bounds.top, glop.bounds.right, glop.bounds.bottom); +#endif mDirty = true; } } diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 04223a7d5188..16dd108488b2 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -56,6 +56,7 @@ namespace DumpFlags { enum { FrameStats = 1 << 0, Reset = 1 << 1, + JankStats = 1 << 2, }; }; @@ -415,7 +416,6 @@ void RenderProxy::notifyFramePending() { CREATE_BRIDGE4(dumpProfileInfo, CanvasContext* context, RenderThread* thread, int fd, int dumpFlags) { args->context->profiler().dumpData(args->fd); - args->thread->jankTracker().dump(args->fd); if (args->dumpFlags & DumpFlags::FrameStats) { args->context->dumpFrames(args->fd); } diff --git a/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp b/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp index 654ddc6a28e5..54714860c19d 100644 --- a/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp +++ b/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp @@ -85,7 +85,9 @@ RENDERTHREAD_TEST(BakedOpDispatcher, onArc_position) { << "Should see conservative offset from PathCache::computeBounds"; Rect expectedBounds(10, 15, 20, 25); expectedBounds.outset(expectedOffset); +#if !HWUI_NEW_OPS EXPECT_EQ(expectedBounds, glop.bounds) << "bounds outset by stroke 'offset'"; +#endif Matrix4 expectedModelView; expectedModelView.loadTranslate(10 - expectedOffset, 15 - expectedOffset, 0); expectedModelView.scale(10 + 2 * expectedOffset, 10 + 2 * expectedOffset, 1); diff --git a/libs/hwui/tests/unit/GlopBuilderTests.cpp b/libs/hwui/tests/unit/GlopBuilderTests.cpp index 454011fa47f0..95543d33b1ef 100644 --- a/libs/hwui/tests/unit/GlopBuilderTests.cpp +++ b/libs/hwui/tests/unit/GlopBuilderTests.cpp @@ -85,7 +85,9 @@ static void expectTransformEq(Glop::Transform& expectedTransform, Glop::Transfor } static void expectGlopEq(Glop& expectedGlop, Glop& builtGlop) { +#if !HWUI_NEW_OPS EXPECT_EQ(expectedGlop.bounds, builtGlop.bounds); +#endif expectBlendEq(expectedGlop.blend, builtGlop.blend); expectFillEq(expectedGlop.fill, builtGlop.fill); expectMeshEq(expectedGlop.mesh, builtGlop.mesh); @@ -136,7 +138,9 @@ RENDERTHREAD_TEST(GlopBuilder, rectSnapTest) { // unit quad also should be translate by additional (0.3, 0.3) to snap to exact pixels. goldenGlop->transform.modelView.loadTranslate(1.3, 1.3, 0); goldenGlop->transform.modelView.scale(99, 99, 1); +#if !HWUI_NEW_OPS goldenGlop->bounds = android::uirenderer::Rect(1.70, 1.70, 100.70, 100.70); +#endif goldenGlop->transform.canvas = simpleTranslate; goldenGlop->fill.texture.filter = GL_NEAREST; expectGlopEq(*goldenGlop, glop); diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index c72a7a027e90..04ea683b505f 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -56,7 +56,26 @@ import java.util.Map; /** * Central system API to the overall TV input framework (TIF) architecture, which arbitrates - * interaction between applications and the selected TV inputs. + * interaction between applications and the selected TV inputs. You can retrieve an instance of + * this interface with {@link android.content.Context#getSystemService + * Context.getSystemService(Context.TV_INPUT_SERVICE)}. + * + * <p>There are three primary parties involved in the TV input framework (TIF) architecture: + * + * <ul> + * <li>The <strong>TV input manager</strong> as expressed by this class is the central point of the + * system that manages interaction between all other parts. It is expressed as the client-side API + * here which exists in each application context and communicates with a global system service that + * manages the interaction across all processes. + * <li>A <strong>TV input</strong> implemented by {@link TvInputService} represents an input source + * of TV, which can be a pass-through input such as HDMI, or a tuner input which provides broadcast + * TV programs. The system binds to the TV input per application’s request. + * on implementing TV inputs. + * <li><strong>Applications</strong> talk to the TV input manager to list TV inputs and check their + * status. Once an application find the input to use, it uses {@link TvView} or + * {@link TvRecordingClient} for further interaction such as watching and recording broadcast TV + * programs. + * </ul> */ public final class TvInputManager { private static final String TAG = "TvInputManager"; @@ -824,11 +843,21 @@ public final class TvInputManager { /** * Interface used to receive events from Hardware objects. + * * @hide */ @SystemApi public abstract static class HardwareCallback { + /** + * This is called when {@link Hardware} is no longer available for the client. + */ public abstract void onReleased(); + + /** + * This is called when the underlying {@link TvStreamConfig} has been changed. + * + * @param configs A list of new {@link TvStreamConfig}s. + */ public abstract void onStreamConfigChanged(TvStreamConfig[] configs); } @@ -1470,18 +1499,41 @@ public final class TvInputManager { } /** - * Returns acquired TvInputManager.Hardware object for given deviceId. + * Acquires {@link Hardware} object for the given device ID. * - * If there are other Hardware object acquired for the same deviceId, calling this method will - * preempt the previously acquired object and report {@link HardwareCallback#onReleased} to the - * old object. + * <p>A subsequent call to this method on the same {@code deviceId} will release the currently + * acquired Hardware. * - * @hide + * @param deviceId The device ID to acquire Hardware for. + * @param callback A callback to receive updates on Hardware. + * @param info The TV input which will use the acquired Hardware. + * @return Hardware on success, {@code null} otherwise. + * + * @removed */ - @SystemApi @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE) public Hardware acquireTvInputHardware(int deviceId, final HardwareCallback callback, TvInputInfo info) { + return acquireTvInputHardware(deviceId, info, callback); + } + + /** + * Acquires {@link Hardware} object for the given device ID. + * + * <p>A subsequent call to this method on the same {@code deviceId} will release the currently + * acquired Hardware. + * + * @param deviceId The device ID to acquire Hardware for. + * @param callback A callback to receive updates on Hardware. + * @param info The TV input which will use the acquired Hardware. + * @return Hardware on success, {@code null} otherwise. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE) + public Hardware acquireTvInputHardware(int deviceId, TvInputInfo info, + final HardwareCallback callback) { try { return new Hardware( mService.acquireTvInputHardware(deviceId, new ITvInputHardwareCallback.Stub() { @@ -1503,6 +1555,9 @@ public final class TvInputManager { /** * Releases previously acquired hardware object. * + * @param deviceId The device ID this Hardware was acquired for + * @param hardware Hardware to release. + * * @hide */ @SystemApi diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_disconnected.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_disconnected.xml new file mode 100644 index 000000000000..4d2a35e508a9 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_qs_wifi_disconnected.xml @@ -0,0 +1,45 @@ +<!-- + Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="26.0dp" + android:height="24.0dp" + android:viewportWidth="26.0" + android:viewportHeight="24.0"> + <path + android:pathData="M0 0h26v24H0z" + android:fillColor="#00000000"/> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M21.0,8.5 + c0.85,0.0 1.6,0.23 2.3,0.62l2.24,-2.79 + C25.1,5.96 20.26,2.0 13.0,2.0 + S0.9,5.9 0.42,6.32 + l12.57,15.6 4.21,-5.17 + c-0.76,-0.87 -1.22,-2.0 -1.22,-3.25 + c0.0,-2.76 2.24,-5.0 5.0,-5.0z" + android:fillAlpha=".3"/> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M21.0,10.0 + c-1.93,0.0 -3.5,1.57 -3.5,3.5l1.75,0.0 + c0.0,-0.9 0.78,-1.75 1.75,-1.75s1.7,0.78 1.75,1.75 + c0.0,0.48 -0.2,0.92 -0.51,1.24l-1.09,1.1 + c-0.6,0.63 -1.02,1.51 -1.02,2.47l0.0,0.44l1.75,0.0 + c0.0,-1.3 0.39,-1.84 1.03,-2.47l0.78,-0.8 + c0.5,-0.5 0.82,-1.2 0.82,-1.97 + C24.5,11.57 22.93,10.0 21.0,10.0z + m-0.95,11.95l1.9,0.0l0.0,-1.9l-1.9,0.0l0.0,1.9z"/> +</vector> diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java index 83ac45c5c8c6..716185f343b5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java @@ -125,6 +125,7 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene mClipper.animateCircularClip(x, y, true, mExpandAnimationListener); new TileQueryHelper(mContext, mHost).setListener(mTileAdapter); mNotifQsContainer.setCustomizerAnimating(true); + mNotifQsContainer.setCustomizerShowing(true); } } @@ -137,6 +138,7 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene save(); mClipper.animateCircularClip(x, y, false, mCollapseAnimationListener); mNotifQsContainer.setCustomizerAnimating(true); + mNotifQsContainer.setCustomizerShowing(false); } } @@ -175,6 +177,7 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene specs.add(tile.getTileSpec()); } mTileAdapter.setTileSpecs(specs); + mRecyclerView.setAdapter(mTileAdapter); } private void save() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java index 6ddd46ec134e..203278300914 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java @@ -21,6 +21,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.graphics.drawable.Drawable; import android.os.Handler; +import android.os.Looper; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.RelativeSizeSpan; @@ -44,7 +45,6 @@ import java.text.NumberFormat; public class BatteryTile extends QSTile<QSTile.State> implements BatteryController.BatteryStateChangeCallback { - private final BatteryMeterDrawable mDrawable; private final BatteryController mBatteryController; private final BatteryDetail mBatteryDetail = new BatteryDetail(); @@ -52,13 +52,11 @@ public class BatteryTile extends QSTile<QSTile.State> implements BatteryControll private boolean mPowerSave; private boolean mCharging; private boolean mDetailShown; + private boolean mPluggedIn; public BatteryTile(Host host) { super(host); mBatteryController = host.getBatteryController(); - mDrawable = new BatteryMeterDrawable(host.getContext(), new Handler(), - host.getContext().getColor(R.color.batterymeter_frame_color)); - mDrawable.setBatteryController(mBatteryController); } @Override @@ -79,10 +77,8 @@ public class BatteryTile extends QSTile<QSTile.State> implements BatteryControll @Override public void setListening(boolean listening) { if (listening) { - mDrawable.startListening(); mBatteryController.addStateChangedCallback(this); } else { - mDrawable.stopListening(); mBatteryController.removeStateChangedCallback(this); } } @@ -118,7 +114,12 @@ public class BatteryTile extends QSTile<QSTile.State> implements BatteryControll state.icon = new Icon() { @Override public Drawable getDrawable(Context context) { - return mDrawable; + BatteryMeterDrawable drawable = + new BatteryMeterDrawable(context, new Handler(Looper.getMainLooper()), + context.getColor(R.color.batterymeter_frame_color)); + drawable.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging); + drawable.onPowerSaveChanged(mPowerSave); + return drawable; } @Override @@ -133,6 +134,7 @@ public class BatteryTile extends QSTile<QSTile.State> implements BatteryControll @Override public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { mLevel = level; + mPluggedIn = pluggedIn; mCharging = charging; refreshState((Integer) level); if (mDetailShown) { @@ -143,6 +145,7 @@ public class BatteryTile extends QSTile<QSTile.State> implements BatteryControll @Override public void onPowerSaveChanged(boolean isPowerSave) { mPowerSave = isPowerSave; + refreshState(null); if (mDetailShown) { mBatteryDetail.postBindView(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java index 65154f2bad34..c72bbf394beb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java @@ -154,7 +154,7 @@ public class WifiTile extends QSTile<QSTile.SignalState> { state.label = removeDoubleQuotes(cb.enabledDesc); signalContentDescription = cb.wifiSignalContentDescription; } else if (wifiNotConnected) { - state.icon = ResourceIcon.get(R.drawable.ic_qs_wifi_full_0); + state.icon = ResourceIcon.get(R.drawable.ic_qs_wifi_disconnected); state.label = r.getString(R.string.quick_settings_wifi_label); signalContentDescription = r.getString(R.string.accessibility_no_wifi); } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java index 960515b9137a..35fb2a50306e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java @@ -35,6 +35,7 @@ import com.android.systemui.qs.customize.QSCustomizer; public class NotificationsQuickSettingsContainer extends FrameLayout implements ViewStub.OnInflateListener, DensityContainer.InflateListener { + private DensityContainer mQsContainer; private View mUserSwitcher; private View mStackScroller; @@ -43,6 +44,9 @@ public class NotificationsQuickSettingsContainer extends FrameLayout private boolean mQsExpanded; private boolean mCustomizerAnimating; + private int mBottomPadding; + private int mStackScrollerMargin; + public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) { super(context, attrs); } @@ -53,6 +57,7 @@ public class NotificationsQuickSettingsContainer extends FrameLayout mQsContainer = (DensityContainer) findViewById(R.id.qs_density_container); mQsContainer.addInflateListener(this); mStackScroller = findViewById(R.id.notification_stack_scroller); + mStackScrollerMargin = ((LayoutParams) mStackScroller.getLayoutParams()).bottomMargin; mKeyguardStatusBar = findViewById(R.id.keyguard_header); ViewStub userSwitcher = (ViewStub) findViewById(R.id.keyguard_user_switcher); userSwitcher.setOnInflateListener(this); @@ -75,7 +80,8 @@ public class NotificationsQuickSettingsContainer extends FrameLayout @Override public WindowInsets onApplyWindowInsets(WindowInsets insets) { - setPadding(0, 0, 0, insets.getStableInsetBottom()); + mBottomPadding = insets.getStableInsetBottom(); + setPadding(0, 0, 0, mBottomPadding); return insets; } @@ -141,4 +147,22 @@ public class NotificationsQuickSettingsContainer extends FrameLayout invalidate(); } } + + public void setCustomizerShowing(boolean isShowing) { + if (isShowing) { + // Clear out bottom paddings/margins so the qs customization can be full height. + setPadding(0, 0, 0, 0); + setBottomMargin(mStackScroller, 0); + } else { + setPadding(0, 0, 0, mBottomPadding); + setBottomMargin(mStackScroller, mStackScrollerMargin); + } + + } + + private void setBottomMargin(View v, int bottomMargin) { + LayoutParams params = (LayoutParams) v.getLayoutParams(); + params.bottomMargin = bottomMargin; + v.setLayoutParams(params); + } } diff --git a/services/core/java/com/android/server/GraphicsStatsService.java b/services/core/java/com/android/server/GraphicsStatsService.java index e29515fc36ae..ecbe1ca98911 100644 --- a/services/core/java/com/android/server/GraphicsStatsService.java +++ b/services/core/java/com/android/server/GraphicsStatsService.java @@ -51,7 +51,7 @@ import java.util.ArrayList; * 2) ASHMEM_SIZE (for scratch space used during dumping) * 3) ASHMEM_SIZE * HISTORY_SIZE * - * This is currently under 16KiB total memory in the worst case of + * This is currently under 20KiB total memory in the worst case of * 20 processes in history + 10 unique active processes. * * @hide */ @@ -59,7 +59,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { public static final String GRAPHICS_STATS_SERVICE = "graphicsstats"; private static final String TAG = "GraphicsStatsService"; - private static final int ASHMEM_SIZE = 296; + private static final int ASHMEM_SIZE = 464; private static final int HISTORY_SIZE = 20; private final Context mContext; diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 1632f92dd40c..bb323031c011 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -65,6 +65,7 @@ import android.database.sqlite.SQLiteStatement; import android.os.Binder; import android.os.Bundle; import android.os.Environment; +import android.os.FileUtils; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -75,11 +76,13 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; +import android.os.storage.StorageManager; import android.text.TextUtils; import android.util.Log; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import com.android.internal.R; import com.android.internal.util.ArrayUtils; @@ -91,6 +94,7 @@ import com.google.android.collect.Sets; import java.io.File; import java.io.FileDescriptor; +import java.io.IOException; import java.io.PrintWriter; import java.security.GeneralSecurityException; import java.security.MessageDigest; @@ -107,7 +111,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; @@ -126,7 +129,9 @@ public class AccountManagerService private static final String TAG = "AccountManagerService"; private static final String DATABASE_NAME = "accounts.db"; - private static final int DATABASE_VERSION = 9; + private static final int PRE_N_DATABASE_VERSION = 9; + private static final int CE_DATABASE_VERSION = 10; + private static final int DE_DATABASE_VERSION = 1; private static final int MAX_DEBUG_DB_SIZE = 64; @@ -176,6 +181,15 @@ public class AccountManagerService private static final String META_VALUE = "value"; private static final String TABLE_SHARED_ACCOUNTS = "shared_accounts"; + private static final String SHARED_ACCOUNTS_ID = "_id"; + + private static final String PRE_N_DATABASE_NAME = "accounts.db"; + private static final String CE_DATABASE_NAME = "accounts_ce.db"; + private static final String DE_DATABASE_NAME = "accounts_de.db"; + private static final String CE_DB_PREFIX = "ceDb."; + private static final String CE_TABLE_ACCOUNTS = CE_DB_PREFIX + TABLE_ACCOUNTS; + private static final String CE_TABLE_AUTHTOKENS = CE_DB_PREFIX + TABLE_AUTHTOKENS; + private static final String CE_TABLE_EXTRAS = CE_DB_PREFIX + TABLE_EXTRAS; private static final String[] ACCOUNT_TYPE_COUNT_PROJECTION = new String[] { ACCOUNTS_TYPE, ACCOUNTS_TYPE_COUNT}; @@ -214,7 +228,7 @@ public class AccountManagerService static class UserAccounts { private final int userId; - private final DatabaseHelper openHelper; + private final DeDatabaseHelper openHelper; private final HashMap<Pair<Pair<Account, String>, Integer>, Integer> credentialsPermissionNotificationIds = new HashMap<Pair<Pair<Account, String>, Integer>, Integer>(); @@ -255,15 +269,15 @@ public class AccountManagerService UserAccounts(Context context, int userId) { this.userId = userId; synchronized (cacheLock) { - openHelper = new DatabaseHelper(context, userId); + openHelper = DeDatabaseHelper.create(context, userId); } } } - private final SparseArray<UserAccounts> mUsers = new SparseArray<UserAccounts>(); + private final SparseArray<UserAccounts> mUsers = new SparseArray<>(); + private final SparseBooleanArray mUnlockedUsers = new SparseBooleanArray(); - private static AtomicReference<AccountManagerService> sThis = - new AtomicReference<AccountManagerService>(); + private static AtomicReference<AccountManagerService> sThis = new AtomicReference<>(); private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[]{}; /** @@ -381,6 +395,11 @@ public class AccountManagerService */ private void validateAccountsInternal( UserAccounts accounts, boolean invalidateAuthenticatorCache) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "validateAccountsInternal " + accounts.userId + + " isCeDatabaseAttached=" + accounts.openHelper.isCeDatabaseAttached() + + " userLocked=" + mUnlockedUsers.get(accounts.userId)); + } if (invalidateAuthenticatorCache) { mAuthenticatorCache.invalidateCache(accounts.userId); } @@ -453,8 +472,7 @@ public class AccountManagerService null, null, null, null, ACCOUNTS_ID); try { accounts.accountCache.clear(); - final HashMap<String, ArrayList<String>> accountNamesByType = - new LinkedHashMap<String, ArrayList<String>>(); + final HashMap<String, ArrayList<String>> accountNamesByType = new LinkedHashMap<>(); while (cursor.moveToNext()) { final long accountId = cursor.getLong(0); final String accountType = cursor.getString(1); @@ -482,15 +500,12 @@ public class AccountManagerService accountNames.add(accountName); } } - for (Map.Entry<String, ArrayList<String>> cur - : accountNamesByType.entrySet()) { + for (Map.Entry<String, ArrayList<String>> cur : accountNamesByType.entrySet()) { final String accountType = cur.getKey(); final ArrayList<String> accountNames = cur.getValue(); final Account[] accountsForType = new Account[accountNames.size()]; - int i = 0; - for (String accountName : accountNames) { - accountsForType[i] = new Account(accountName, accountType); - ++i; + for (int i = 0; i < accountsForType.length; i++) { + accountsForType[i] = new Account(accountNames.get(i), accountType); } accounts.accountCache.put(accountType, accountsForType); } @@ -522,12 +537,25 @@ public class AccountManagerService protected UserAccounts getUserAccounts(int userId) { synchronized (mUsers) { UserAccounts accounts = mUsers.get(userId); + boolean validateAccounts = false; if (accounts == null) { accounts = new UserAccounts(mContext, userId); initializeDebugDbSizeAndCompileSqlStatementForLogging( accounts.openHelper.getWritableDatabase(), accounts); mUsers.append(userId, accounts); purgeOldGrants(accounts); + validateAccounts = true; + } + // open CE database if necessary + if (!accounts.openHelper.isCeDatabaseAttached() && mUnlockedUsers.get(userId)) { + Log.i(TAG, "User " + userId + " is unlocked - opening CE database"); + synchronized (accounts.cacheLock) { + CeDatabaseHelper.create(mContext, userId); + accounts.openHelper.attachCeDatabase(); + } + // TODO Synchronize accounts by removing CE account not available in DE + } + if (validateAccounts) { validateAccountsInternal(accounts, true /* invalidateAuthenticatorCache */); } return accounts; @@ -571,27 +599,51 @@ public class AccountManagerService if (userId < 1) return; UserAccounts accounts; + boolean userUnlocked; synchronized (mUsers) { accounts = mUsers.get(userId); mUsers.remove(userId); + userUnlocked = mUnlockedUsers.get(userId); + mUnlockedUsers.delete(userId); } - if (accounts == null) { - File dbFile = new File(getDatabaseName(userId)); - dbFile.delete(); - return; + if (accounts != null) { + synchronized (accounts.cacheLock) { + accounts.openHelper.close(); + } } + Log.i(TAG, "Removing database files for user " + userId); + File dbFile = new File(getDeDatabaseName(userId)); - synchronized (accounts.cacheLock) { - accounts.openHelper.close(); - File dbFile = new File(getDatabaseName(userId)); - dbFile.delete(); + deleteDbFileWarnIfFailed(dbFile); + // Remove CE file if user is unlocked, or FBE is not enabled + boolean fbeEnabled = StorageManager.isFileEncryptedNativeOrEmulated(); + if (!fbeEnabled || userUnlocked) { + File ceDb = new File(getCeDatabaseName(userId)); + if (ceDb.exists()) { + deleteDbFileWarnIfFailed(ceDb); + } + } + } + + private static void deleteDbFileWarnIfFailed(File dbFile) { + if (!SQLiteDatabase.deleteDatabase(dbFile)) { + Log.w(TAG, "Database at " + dbFile + " was not deleted successfully"); } } private void onUserUnlocked(Intent intent) { int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "onUserUnlocked " + userId); + } + synchronized (mUsers) { + mUnlockedUsers.put(userId, true); + } if (userId < 1) return; + syncSharedAccounts(userId); + } + private void syncSharedAccounts(int userId) { // Check if there's a shared account that needs to be created as an account Account[] sharedAccounts = getSharedAccountsAsUser(userId); if (sharedAccounts == null || sharedAccounts.length == 0) return; @@ -645,20 +697,15 @@ public class AccountManagerService if (account == null) { return null; } + if (!isUserUnlocked(accounts.userId)) { + Log.w(TAG, "Password is not available - user " + accounts.userId + " data is locked"); + return null; + } synchronized (accounts.cacheLock) { - final SQLiteDatabase db = accounts.openHelper.getReadableDatabase(); - Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_PASSWORD}, - ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?", - new String[]{account.name, account.type}, null, null, null); - try { - if (cursor.moveToNext()) { - return cursor.getString(0); - } - return null; - } finally { - cursor.close(); - } + final SQLiteDatabase db = accounts.openHelper.getReadableDatabaseUserIsUnlocked(); + return CeDatabaseHelper.findAccountPasswordByNameAndType(db, account.name, + account.type); } } @@ -699,7 +746,7 @@ public class AccountManagerService try { if (cursor.moveToNext()) { String previousName = cursor.getString(0); - previousNameRef = new AtomicReference<String>(previousName); + previousNameRef = new AtomicReference<>(previousName); accounts.previousNameCache.put(account, previousNameRef); return previousName; } else { @@ -825,7 +872,7 @@ public class AccountManagerService long identityToken = clearCallingIdentity(); try { UserAccounts accounts = getUserAccounts(userId); - return addAccountInternal(accounts, account, password, extras, false, callingUid); + return addAccountInternal(accounts, account, password, extras, callingUid); } finally { restoreCallingIdentity(identityToken); } @@ -999,17 +1046,22 @@ public class AccountManagerService } private boolean addAccountInternal(UserAccounts accounts, Account account, String password, - Bundle extras, boolean restricted, int callingUid) { + Bundle extras, int callingUid) { Bundle.setDefusable(extras, true); if (account == null) { return false; } + if (!isUserUnlocked(accounts.userId)) { + Log.w(TAG, "Account " + account + " cannot be added - user " + accounts.userId + + " is locked. callingUid=" + callingUid); + return false; + } synchronized (accounts.cacheLock) { - final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + final SQLiteDatabase db = accounts.openHelper.getWritableDatabaseUserIsUnlocked(); db.beginTransaction(); try { long numMatches = DatabaseUtils.longForQuery(db, - "select count(*) from " + TABLE_ACCOUNTS + "select count(*) from " + CE_TABLE_ACCOUNTS + " WHERE " + ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?", new String[]{account.name, account.type}); if (numMatches > 0) { @@ -1021,13 +1073,24 @@ public class AccountManagerService values.put(ACCOUNTS_NAME, account.name); values.put(ACCOUNTS_TYPE, account.type); values.put(ACCOUNTS_PASSWORD, password); - values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, System.currentTimeMillis()); - long accountId = db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values); + long accountId = db.insert(CE_TABLE_ACCOUNTS, ACCOUNTS_NAME, values); if (accountId < 0) { Log.w(TAG, "insertAccountIntoDatabase: " + account + ", skipping the DB insert failed"); return false; } + // Insert into DE table + values = new ContentValues(); + values.put(ACCOUNTS_ID, accountId); + values.put(ACCOUNTS_NAME, account.name); + values.put(ACCOUNTS_TYPE, account.type); + values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, + System.currentTimeMillis()); + if (db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values) < 0) { + Log.w(TAG, "insertAccountIntoDatabase: " + account + + ", skipping the DB insert failed"); + return false; + } if (extras != null) { for (String key : extras.keySet()) { final String value = extras.getString(key); @@ -1055,6 +1118,12 @@ public class AccountManagerService return true; } + private boolean isUserUnlocked(int userId) { + synchronized (mUsers) { + return mUnlockedUsers.get(userId); + } + } + /** * Adds the account to all linked restricted users as shared accounts. If the user is currently * running, then clone the account too. @@ -1079,7 +1148,7 @@ public class AccountManagerService values.put(EXTRAS_KEY, key); values.put(EXTRAS_ACCOUNTS_ID, accountId); values.put(EXTRAS_VALUE, value); - return db.insert(TABLE_EXTRAS, EXTRAS_KEY, values); + return db.insert(CE_TABLE_EXTRAS, EXTRAS_KEY, values); } @Override @@ -1226,17 +1295,19 @@ public class AccountManagerService } } synchronized (accounts.cacheLock) { - final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + final SQLiteDatabase db = accounts.openHelper.getWritableDatabaseUserIsUnlocked(); db.beginTransaction(); boolean isSuccessful = false; Account renamedAccount = new Account(newName, accountToRename.type); try { - final ContentValues values = new ContentValues(); - values.put(ACCOUNTS_NAME, newName); - values.put(ACCOUNTS_PREVIOUS_NAME, accountToRename.name); final long accountId = getAccountIdLocked(db, accountToRename); if (accountId >= 0) { + final ContentValues values = new ContentValues(); + values.put(ACCOUNTS_NAME, newName); final String[] argsAccountId = { String.valueOf(accountId) }; + db.update(CE_TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId); + // Update NAME/PREVIOUS_NAME in DE accounts table + values.put(ACCOUNTS_PREVIOUS_NAME, accountToRename.name); db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId); db.setTransactionSuccessful(); isSuccessful = true; @@ -1332,7 +1403,7 @@ public class AccountManagerService * authenticator. This will let users remove accounts (via Settings in the system) but not * arbitrary applications (like competing authenticators). */ - UserHandle user = new UserHandle(userId); + UserHandle user = UserHandle.of(userId); if (!isAccountManagedByCaller(account.type, callingUid, user.getIdentifier()) && !isSystemUid(callingUid)) { String msg = String.format( @@ -1467,13 +1538,17 @@ public class AccountManagerService } private boolean removeAccountInternal(UserAccounts accounts, Account account, int callingUid) { + // For now user is required to be unlocked. TODO: Handle both cases in the future int deleted; synchronized (accounts.cacheLock) { - final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + final SQLiteDatabase db = accounts.openHelper.getWritableDatabaseUserIsUnlocked(); final long accountId = getAccountIdLocked(db, account); deleted = db.delete(TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?", new String[]{account.name, account.type}); + // Delete from CE table + db.delete(CE_TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?", + new String[]{account.name, account.type}); removeAccountFromCacheLocked(accounts, account); sendAccountsChangedBroadcast(accounts.userId); @@ -1512,7 +1587,7 @@ public class AccountManagerService try { UserAccounts accounts = getUserAccounts(userId); synchronized (accounts.cacheLock) { - final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + final SQLiteDatabase db = accounts.openHelper.getWritableDatabaseUserIsUnlocked(); db.beginTransaction(); try { invalidateAuthTokenLocked(accounts, db, accountType, authToken); @@ -1544,22 +1619,22 @@ public class AccountManagerService return; } Cursor cursor = db.rawQuery( - "SELECT " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_ID - + ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_NAME - + ", " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_TYPE - + " FROM " + TABLE_ACCOUNTS - + " JOIN " + TABLE_AUTHTOKENS - + " ON " + TABLE_ACCOUNTS + "." + ACCOUNTS_ID - + " = " + AUTHTOKENS_ACCOUNTS_ID - + " WHERE " + AUTHTOKENS_AUTHTOKEN + " = ? AND " - + TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE + " = ?", + "SELECT " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_ID + + ", " + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_NAME + + ", " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_TYPE + + " FROM " + CE_TABLE_ACCOUNTS + + " JOIN " + CE_TABLE_AUTHTOKENS + + " ON " + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_ID + + " = " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_ACCOUNTS_ID + + " WHERE " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_AUTHTOKEN + + " = ? AND " + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE + " = ?", new String[]{authToken, accountType}); try { while (cursor.moveToNext()) { long authTokenId = cursor.getLong(0); String accountName = cursor.getString(1); String authTokenType = cursor.getString(2); - db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ID + "=" + authTokenId, null); + db.delete(CE_TABLE_AUTHTOKENS, AUTHTOKENS_ID + "=" + authTokenId, null); writeAuthTokenIntoCacheLocked( accounts, db, @@ -1585,7 +1660,7 @@ public class AccountManagerService return; } cancelNotification(getSigninRequiredNotificationId(accounts, account), - new UserHandle(accounts.userId)); + UserHandle.of(accounts.userId)); synchronized (accounts.cacheLock) { accounts.accountTokenCaches.put( account, token, tokenType, callerPkg, callerSigDigest, expiryMillis); @@ -1598,23 +1673,23 @@ public class AccountManagerService return false; } cancelNotification(getSigninRequiredNotificationId(accounts, account), - new UserHandle(accounts.userId)); + UserHandle.of(accounts.userId)); synchronized (accounts.cacheLock) { - final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + final SQLiteDatabase db = accounts.openHelper.getWritableDatabaseUserIsUnlocked(); db.beginTransaction(); try { long accountId = getAccountIdLocked(db, account); if (accountId < 0) { return false; } - db.delete(TABLE_AUTHTOKENS, + db.delete(CE_TABLE_AUTHTOKENS, AUTHTOKENS_ACCOUNTS_ID + "=" + accountId + " AND " + AUTHTOKENS_TYPE + "=?", new String[]{type}); ContentValues values = new ContentValues(); values.put(AUTHTOKENS_ACCOUNTS_ID, accountId); values.put(AUTHTOKENS_TYPE, type); values.put(AUTHTOKENS_AUTHTOKEN, authToken); - if (db.insert(TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values) >= 0) { + if (db.insert(CE_TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values) >= 0) { db.setTransactionSuccessful(); writeAuthTokenIntoCacheLocked(accounts, db, account, type, authToken); return true; @@ -1714,7 +1789,7 @@ public class AccountManagerService return; } synchronized (accounts.cacheLock) { - final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + final SQLiteDatabase db = accounts.openHelper.getWritableDatabaseUserIsUnlocked(); db.beginTransaction(); try { final ContentValues values = new ContentValues(); @@ -1722,8 +1797,8 @@ public class AccountManagerService final long accountId = getAccountIdLocked(db, account); if (accountId >= 0) { final String[] argsAccountId = {String.valueOf(accountId)}; - db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId); - db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ACCOUNTS_ID + "=?", argsAccountId); + db.update(CE_TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId); + db.delete(CE_TABLE_AUTHTOKENS, AUTHTOKENS_ACCOUNTS_ID + "=?", argsAccountId); accounts.authTokenCache.remove(account); accounts.accountTokenCaches.remove(account); db.setTransactionSuccessful(); @@ -1806,7 +1881,7 @@ public class AccountManagerService return; } synchronized (accounts.cacheLock) { - final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + final SQLiteDatabase db = accounts.openHelper.getWritableDatabaseUserIsUnlocked(); db.beginTransaction(); try { long accountId = getAccountIdLocked(db, account); @@ -1822,7 +1897,7 @@ public class AccountManagerService } else { ContentValues values = new ContentValues(); values.put(EXTRAS_VALUE, value); - if (1 != db.update(TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) { + if (1 != db.update(CE_TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) { return; } @@ -3307,7 +3382,6 @@ public class AccountManagerService long sharedTableAccountId = getAccountIdFromSharedTable(db, account); final ContentValues values = new ContentValues(); values.put(ACCOUNTS_NAME, newName); - values.put(ACCOUNTS_PREVIOUS_NAME, account.name); int r = db.update( TABLE_SHARED_ACCOUNTS, values, @@ -3347,7 +3421,7 @@ public class AccountManagerService public Account[] getSharedAccountsAsUser(int userId) { userId = handleIncomingUser(userId); UserAccounts accounts = getUserAccounts(userId); - ArrayList<Account> accountList = new ArrayList<Account>(); + ArrayList<Account> accountList = new ArrayList<>(); Cursor cursor = null; try { cursor = accounts.openHelper.getReadableDatabase() @@ -3488,7 +3562,7 @@ public class AccountManagerService } private long getExtrasIdLocked(SQLiteDatabase db, long accountId, String key) { - Cursor cursor = db.query(TABLE_EXTRAS, new String[]{EXTRAS_ID}, + Cursor cursor = db.query(CE_TABLE_EXTRAS, new String[]{EXTRAS_ID}, EXTRAS_ACCOUNTS_ID + "=" + accountId + " AND " + EXTRAS_KEY + "=?", new String[]{key}, null, null, null); try { @@ -3729,8 +3803,8 @@ public class AccountManagerService if (accountPresent) { lastAuthenticatedTime = DatabaseUtils.longForQuery( mAccounts.openHelper.getReadableDatabase(), - "select " + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS - + " from " + + "SELECT " + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + + " FROM " + TABLE_ACCOUNTS + " WHERE " + ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?", new String[] { @@ -3859,7 +3933,7 @@ public class AccountManagerService Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName); } if (!mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE, - new UserHandle(mAccounts.userId))) { + UserHandle.of(mAccounts.userId))) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "bindService to " + authenticatorInfo.componentName + " failed"); } @@ -3893,15 +3967,16 @@ public class AccountManagerService } } - private static String getDatabaseName(int userId) { + static String getPreNDatabaseName(int userId) { File systemDir = Environment.getDataSystemDirectory(); - File databaseFile = new File(Environment.getUserSystemDirectory(userId), DATABASE_NAME); + File databaseFile = new File(Environment.getUserSystemDirectory(userId), + PRE_N_DATABASE_NAME); if (userId == 0) { // Migrate old file, if it exists, to the new location. // Make sure the new file doesn't already exist. A dummy file could have been // accidentally created in the old location, causing the new one to become corrupted // as well. - File oldFile = new File(systemDir, DATABASE_NAME); + File oldFile = new File(systemDir, PRE_N_DATABASE_NAME); if (oldFile.exists() && !databaseFile.exists()) { // Check for use directory; create if it doesn't exist, else renameTo will fail File userDir = Environment.getUserSystemDirectory(userId); @@ -3918,6 +3993,18 @@ public class AccountManagerService return databaseFile.getPath(); } + static String getDeDatabaseName(int userId) { + File databaseFile = new File(Environment.getDataSystemDeDirectory(userId), + DE_DATABASE_NAME); + return databaseFile.getPath(); + } + + static String getCeDatabaseName(int userId) { + File databaseFile = new File(Environment.getDataSystemCeDirectory(userId), + CE_DATABASE_NAME); + return databaseFile.getPath(); + } + private static class DebugDbHelper{ private DebugDbHelper() { } @@ -4050,58 +4137,22 @@ public class AccountManagerService return DatabaseUtils.longForQuery(db, queryCountDebugDbRows, null); } - static class DatabaseHelper extends SQLiteOpenHelper { + static class PreNDatabaseHelper extends SQLiteOpenHelper { private final Context mContext; private final int mUserId; - public DatabaseHelper(Context context, int userId) { - super(context, AccountManagerService.getDatabaseName(userId), null, DATABASE_VERSION); + public PreNDatabaseHelper(Context context, int userId) { + super(context, AccountManagerService.getPreNDatabaseName(userId), null, + PRE_N_DATABASE_VERSION); mContext = context; mUserId = userId; } - /** - * This call needs to be made while the mCacheLock is held. The way to - * ensure this is to get the lock any time a method is called ont the DatabaseHelper - * @param db The database. - */ @Override public void onCreate(SQLiteDatabase db) { - db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( " - + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " - + ACCOUNTS_NAME + " TEXT NOT NULL, " - + ACCOUNTS_TYPE + " TEXT NOT NULL, " - + ACCOUNTS_PASSWORD + " TEXT, " - + ACCOUNTS_PREVIOUS_NAME + " TEXT, " - + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " INTEGER DEFAULT 0, " - + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))"); - - db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " ( " - + AUTHTOKENS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " - + AUTHTOKENS_ACCOUNTS_ID + " INTEGER NOT NULL, " - + AUTHTOKENS_TYPE + " TEXT NOT NULL, " - + AUTHTOKENS_AUTHTOKEN + " TEXT, " - + "UNIQUE (" + AUTHTOKENS_ACCOUNTS_ID + "," + AUTHTOKENS_TYPE + "))"); - - createGrantsTable(db); - - db.execSQL("CREATE TABLE " + TABLE_EXTRAS + " ( " - + EXTRAS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " - + EXTRAS_ACCOUNTS_ID + " INTEGER, " - + EXTRAS_KEY + " TEXT NOT NULL, " - + EXTRAS_VALUE + " TEXT, " - + "UNIQUE(" + EXTRAS_ACCOUNTS_ID + "," + EXTRAS_KEY + "))"); - - db.execSQL("CREATE TABLE " + TABLE_META + " ( " - + META_KEY + " TEXT PRIMARY KEY NOT NULL, " - + META_VALUE + " TEXT)"); - - createSharedAccountsTable(db); - - createAccountsDeletionTrigger(db); - - DebugDbHelper.createDebugTable(db); + // We use PreNDatabaseHelper only if pre-N db exists + throw new IllegalStateException("Legacy database cannot be created - only upgraded!"); } private void createSharedAccountsTable(SQLiteDatabase db) { @@ -4161,6 +4212,9 @@ public class AccountManagerService } } + /** + * Pre-N database may need an upgrade before splitting + */ @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion); @@ -4222,6 +4276,315 @@ public class AccountManagerService } } + static class DeDatabaseHelper extends SQLiteOpenHelper { + + private final int mUserId; + private volatile boolean mCeAttached; + + private DeDatabaseHelper(Context context, int userId) { + super(context, getDeDatabaseName(userId), null, DE_DATABASE_VERSION); + mUserId = userId; + } + + /** + * This call needs to be made while the mCacheLock is held. The way to + * ensure this is to get the lock any time a method is called ont the DatabaseHelper + * @param db The database. + */ + @Override + public void onCreate(SQLiteDatabase db) { + Log.i(TAG, "Creating DE database for user " + mUserId); + db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( " + + ACCOUNTS_ID + " INTEGER PRIMARY KEY, " + + ACCOUNTS_NAME + " TEXT NOT NULL, " + + ACCOUNTS_TYPE + " TEXT NOT NULL, " + + ACCOUNTS_PREVIOUS_NAME + " TEXT, " + + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " INTEGER DEFAULT 0, " + + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))"); + + db.execSQL("CREATE TABLE " + TABLE_META + " ( " + + META_KEY + " TEXT PRIMARY KEY NOT NULL, " + + META_VALUE + " TEXT)"); + + createGrantsTable(db); + createSharedAccountsTable(db); + createAccountsDeletionTrigger(db); + DebugDbHelper.createDebugTable(db); + } + + private void createSharedAccountsTable(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + TABLE_SHARED_ACCOUNTS + " ( " + + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + ACCOUNTS_NAME + " TEXT NOT NULL, " + + ACCOUNTS_TYPE + " TEXT NOT NULL, " + + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))"); + } + + private void createAccountsDeletionTrigger(SQLiteDatabase db) { + db.execSQL("" + + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS + + " BEGIN" + + " DELETE FROM " + TABLE_GRANTS + + " WHERE " + GRANTS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;" + + " END"); + } + + private void createGrantsTable(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + TABLE_GRANTS + " ( " + + GRANTS_ACCOUNTS_ID + " INTEGER NOT NULL, " + + GRANTS_AUTH_TOKEN_TYPE + " STRING NOT NULL, " + + GRANTS_GRANTEE_UID + " INTEGER NOT NULL, " + + "UNIQUE (" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE + + "," + GRANTS_GRANTEE_UID + "))"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + Log.i(TAG, "upgrade from version " + oldVersion + " to version " + newVersion); + + if (oldVersion != newVersion) { + Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion); + } + } + + public void attachCeDatabase() { + File ceDbFile = new File(getCeDatabaseName(mUserId)); + SQLiteDatabase db = getWritableDatabase(); + db.execSQL("ATTACH DATABASE '" + ceDbFile.getPath()+ "' AS ceDb"); + mCeAttached = true; + } + + public boolean isCeDatabaseAttached() { + return mCeAttached; + } + + + public SQLiteDatabase getReadableDatabaseUserIsUnlocked() { + if(!mCeAttached) { + Log.wtf(TAG, "getReadableDatabaseUserIsUnlocked called while user " + + mUserId + " is still locked ", new Throwable()); + } + return super.getReadableDatabase(); + } + + public SQLiteDatabase getWritableDatabaseUserIsUnlocked() { + if(!mCeAttached) { + Log.wtf(TAG, "getWritableDatabaseUserIsUnlocked called while user " + mUserId + + " is still locked ", new Throwable()); + } + return super.getWritableDatabase(); + } + + @Override + public void onOpen(SQLiteDatabase db) { + if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DE_DATABASE_NAME); + } + + private void migratePreNDbToDe(File preNDbFile) { + Log.i(TAG, "Migrate pre-N database to DE preNDbFile=" + preNDbFile); + SQLiteDatabase db = getWritableDatabase(); + db.execSQL("ATTACH DATABASE '" + preNDbFile.getPath() + "' AS preNDb"); + db.beginTransaction(); + // Copy accounts fields + db.execSQL("INSERT INTO " + TABLE_ACCOUNTS + + "(" + ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + ", " + + ACCOUNTS_PREVIOUS_NAME + ", " + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + + ") " + + "SELECT " + ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + ", " + + ACCOUNTS_PREVIOUS_NAME + ", " + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + + " FROM preNDb." + TABLE_ACCOUNTS); + // Copy SHARED_ACCOUNTS + db.execSQL("INSERT INTO " + TABLE_SHARED_ACCOUNTS + + "(" + SHARED_ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + ") " + + "SELECT " + SHARED_ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + + " FROM preNDb." + TABLE_SHARED_ACCOUNTS); + // Copy DEBUG_TABLE + db.execSQL("INSERT INTO " + DebugDbHelper.TABLE_DEBUG + + "(" + ACCOUNTS_ID + "," + DebugDbHelper.ACTION_TYPE + "," + + DebugDbHelper.TIMESTAMP + "," + DebugDbHelper.CALLER_UID + "," + + DebugDbHelper.TABLE_NAME + "," + DebugDbHelper.KEY + ") " + + "SELECT " + ACCOUNTS_ID + "," + DebugDbHelper.ACTION_TYPE + "," + + DebugDbHelper.TIMESTAMP + "," + DebugDbHelper.CALLER_UID + "," + + DebugDbHelper.TABLE_NAME + "," + DebugDbHelper.KEY + + " FROM preNDb." + DebugDbHelper.TABLE_DEBUG); + // Copy GRANTS + db.execSQL("INSERT INTO " + TABLE_GRANTS + + "(" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE + "," + + GRANTS_GRANTEE_UID + ") " + + "SELECT " + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE + "," + + GRANTS_GRANTEE_UID + " FROM preNDb." + TABLE_GRANTS); + // Copy META + db.execSQL("INSERT INTO " + TABLE_META + + "(" + META_KEY + "," + META_VALUE + ") " + + "SELECT " + META_KEY + "," + META_VALUE + " FROM preNDb." + TABLE_META); + db.setTransactionSuccessful(); + db.endTransaction(); + + db.execSQL("DETACH DATABASE preNDb"); + } + + static DeDatabaseHelper create(Context context, int userId) { + File oldDb = new File(getPreNDatabaseName(userId)); + File newDb = new File(getDeDatabaseName(userId)); + boolean newDbExists = newDb.exists(); + DeDatabaseHelper deDatabaseHelper = new DeDatabaseHelper(context, userId); + // If the db just created, and there is a legacy db, migrate it + if (!newDbExists && oldDb.exists()) { + // Migrate legacy db to the latest version - PRE_N_DATABASE_VERSION + PreNDatabaseHelper preNDatabaseHelper = new PreNDatabaseHelper(context, userId); + // Open the database to force upgrade if required + preNDatabaseHelper.getWritableDatabase(); + preNDatabaseHelper.close(); + // Move data without SPII to DE + deDatabaseHelper.migratePreNDbToDe(oldDb); + } + return deDatabaseHelper; + } + } + + static class CeDatabaseHelper extends SQLiteOpenHelper { + + public CeDatabaseHelper(Context context, int userId) { + super(context, getCeDatabaseName(userId), null, CE_DATABASE_VERSION); + } + + /** + * This call needs to be made while the mCacheLock is held. + * @param db The database. + */ + @Override + public void onCreate(SQLiteDatabase db) { + Log.i(TAG, "Creating CE database " + getDatabaseName()); + db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( " + + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + ACCOUNTS_NAME + " TEXT NOT NULL, " + + ACCOUNTS_TYPE + " TEXT NOT NULL, " + + ACCOUNTS_PASSWORD + " TEXT, " + + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))"); + + db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " ( " + + AUTHTOKENS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + AUTHTOKENS_ACCOUNTS_ID + " INTEGER NOT NULL, " + + AUTHTOKENS_TYPE + " TEXT NOT NULL, " + + AUTHTOKENS_AUTHTOKEN + " TEXT, " + + "UNIQUE (" + AUTHTOKENS_ACCOUNTS_ID + "," + AUTHTOKENS_TYPE + "))"); + + db.execSQL("CREATE TABLE " + TABLE_EXTRAS + " ( " + + EXTRAS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + EXTRAS_ACCOUNTS_ID + " INTEGER, " + + EXTRAS_KEY + " TEXT NOT NULL, " + + EXTRAS_VALUE + " TEXT, " + + "UNIQUE(" + EXTRAS_ACCOUNTS_ID + "," + EXTRAS_KEY + "))"); + + createAccountsDeletionTrigger(db); + } + + private void createAccountsDeletionTrigger(SQLiteDatabase db) { + db.execSQL("" + + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS + + " BEGIN" + + " DELETE FROM " + TABLE_AUTHTOKENS + + " WHERE " + AUTHTOKENS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;" + + " DELETE FROM " + TABLE_EXTRAS + + " WHERE " + EXTRAS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;" + + " END"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + Log.i(TAG, "Upgrade CE from version " + oldVersion + " to version " + newVersion); + + if (oldVersion == 9) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "onUpgrade upgrading to v10"); + } + db.execSQL("DROP TABLE IF EXISTS " + TABLE_META); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_SHARED_ACCOUNTS); + // Recreate the trigger, since the old one references the table to be removed + db.execSQL("DROP TRIGGER IF EXISTS " + TABLE_ACCOUNTS + "Delete"); + createAccountsDeletionTrigger(db); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_GRANTS); + db.execSQL("DROP TABLE IF EXISTS " + DebugDbHelper.TABLE_DEBUG); + oldVersion ++; + } + + if (oldVersion != newVersion) { + Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion); + } + } + + @Override + public void onOpen(SQLiteDatabase db) { + if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + CE_DATABASE_NAME); + } + + static String findAccountPasswordByNameAndType(SQLiteDatabase db, String name, + String type) { + Cursor cursor = db.query(CE_TABLE_ACCOUNTS, new String[]{ACCOUNTS_PASSWORD}, + ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?", + new String[]{name, type}, null, null, null); + try { + if (cursor.moveToNext()) { + return cursor.getString(0); + } + return null; + } finally { + cursor.close(); + } + } + + /** + * Creates a new {@code CeDatabaseHelper}. If pre-N db file is present at the old location, + * it also performs migration to the new CE database. + * @param context + * @param userId id of the user where the database is located + */ + static CeDatabaseHelper create(Context context, int userId) { + + File oldDatabaseFile = new File(getPreNDatabaseName(userId)); + File ceDatabaseFile = new File(getCeDatabaseName(userId)); + boolean newDbExists = ceDatabaseFile.exists(); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "CeDatabaseHelper.create userId=" + userId + " oldDbExists=" + + oldDatabaseFile.exists() + " newDbExists=" + newDbExists); + } + boolean removeOldDb = false; + if (!newDbExists && oldDatabaseFile.exists()) { + removeOldDb = migratePreNDbToCe(oldDatabaseFile, ceDatabaseFile); + } + // Try to open and upgrade if necessary + CeDatabaseHelper ceHelper = new CeDatabaseHelper(context, userId); + ceHelper.getWritableDatabase(); + ceHelper.close(); + if (removeOldDb) { + // TODO STOPSHIP - backup file during testing. Remove file before the release + Log.i(TAG, "Migration complete - creating backup of old db " + oldDatabaseFile); + renameToBakFile(oldDatabaseFile); + } + return ceHelper; + } + + private static void renameToBakFile(File file) { + File bakFile = new File(file.getPath() + ".bak"); + if (!file.renameTo(bakFile)) { + Log.e(TAG, "Cannot move file to " + bakFile); + } + } + + private static boolean migratePreNDbToCe(File oldDbFile, File ceDbFile) { + Log.i(TAG, "Moving pre-N DB " + oldDbFile + " to CE " + ceDbFile); + try { + FileUtils.copyFileOrThrow(oldDbFile, ceDbFile); + } catch (IOException e) { + Log.e(TAG, "Cannot copy file to " + ceDbFile + " from " + oldDbFile, e); + // Try to remove potentially damaged file if I/O error occurred + deleteDbFileWarnIfFailed(ceDbFile); + return false; + } + return true; + } + } + public IBinder onBind(@SuppressWarnings("unused") Intent intent) { return asBinder(); } @@ -4666,7 +5029,7 @@ public class AccountManagerService db.endTransaction(); } cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid), - new UserHandle(accounts.userId)); + UserHandle.of(accounts.userId)); } } @@ -4891,7 +5254,7 @@ public class AccountManagerService HashMap<String, String> authTokensForAccount = accounts.authTokenCache.get(account); if (authTokensForAccount == null) { // need to populate the cache for this account - final SQLiteDatabase db = accounts.openHelper.getReadableDatabase(); + final SQLiteDatabase db = accounts.openHelper.getReadableDatabaseUserIsUnlocked(); authTokensForAccount = readAuthTokensForAccountFromDatabaseLocked(db, account); accounts.authTokenCache.put(account, authTokensForAccount); } @@ -4904,7 +5267,7 @@ public class AccountManagerService HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account); if (userDataForAccount == null) { // need to populate the cache for this account - final SQLiteDatabase db = accounts.openHelper.getReadableDatabase(); + final SQLiteDatabase db = accounts.openHelper.getReadableDatabaseUserIsUnlocked(); userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account); accounts.userDataCache.put(account, userDataForAccount); } @@ -4914,8 +5277,8 @@ public class AccountManagerService protected HashMap<String, String> readUserDataForAccountFromDatabaseLocked( final SQLiteDatabase db, Account account) { - HashMap<String, String> userDataForAccount = new HashMap<String, String>(); - Cursor cursor = db.query(TABLE_EXTRAS, + HashMap<String, String> userDataForAccount = new HashMap<>(); + Cursor cursor = db.query(CE_TABLE_EXTRAS, COLUMNS_EXTRAS_KEY_AND_VALUE, SELECTION_USERDATA_BY_ACCOUNT, new String[]{account.name, account.type}, @@ -4934,8 +5297,8 @@ public class AccountManagerService protected HashMap<String, String> readAuthTokensForAccountFromDatabaseLocked( final SQLiteDatabase db, Account account) { - HashMap<String, String> authTokensForAccount = new HashMap<String, String>(); - Cursor cursor = db.query(TABLE_AUTHTOKENS, + HashMap<String, String> authTokensForAccount = new HashMap<>(); + Cursor cursor = db.query(CE_TABLE_AUTHTOKENS, COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN, SELECTION_AUTHTOKENS_BY_ACCOUNT, new String[]{account.name, account.type}, diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index f99caba59b40..d741c4948280 100755 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -940,7 +940,7 @@ public final class ActiveServices { } } - mAm.startAssociationLocked(callerApp.uid, callerApp.processName, + mAm.startAssociationLocked(callerApp.uid, callerApp.processName, callerApp.curProcState, s.appInfo.uid, s.name, s.processName); AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 42cf42fdd650..e82c6c7a22a4 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -891,6 +891,12 @@ public final class ActivityManagerService extends ActivityManagerNative int mNesting; long mStartTime; + // states of the source process when the bind occurred. + int mLastState = ActivityManager.MAX_PROCESS_STATE + 1; + long mLastStateUptime; + long[] mStateTimes = new long[ActivityManager.MAX_PROCESS_STATE + - ActivityManager.MIN_PROCESS_STATE+1]; + Association(int sourceUid, String sourceProcess, int targetUid, ComponentName targetComponent, String targetProcess) { mSourceUid = sourceUid; @@ -6056,8 +6062,7 @@ public final class ActivityManagerService extends ActivityManagerNative "No more processes in " + old.uidRecord); enqueueUidChangeLocked(old.uidRecord, -1, UidRecord.CHANGE_GONE); mActiveUids.remove(uid); - mBatteryStatsService.noteUidProcessState(uid, - ActivityManager.PROCESS_STATE_NONEXISTENT); + noteUidProcessState(uid, ActivityManager.PROCESS_STATE_NONEXISTENT); } old.uidRecord = null; } @@ -6082,7 +6087,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS, "Creating new process uid: " + uidRec); mActiveUids.put(proc.uid, uidRec); - mBatteryStatsService.noteUidProcessState(uidRec.uid, uidRec.curProcState); + noteUidProcessState(uidRec.uid, uidRec.curProcState); enqueueUidChangeLocked(uidRec, -1, UidRecord.CHANGE_ACTIVE); } proc.uidRecord = uidRec; @@ -10190,7 +10195,8 @@ public final class ActivityManagerService extends ActivityManagerNative } cpr.connections.add(conn); r.conProviders.add(conn); - startAssociationLocked(r.uid, r.processName, cpr.uid, cpr.name, cpr.info.processName); + startAssociationLocked(r.uid, r.processName, r.curProcState, + cpr.uid, cpr.name, cpr.info.processName); return conn; } cpr.addExternalProcessHandleLocked(externalProcessToken); @@ -13797,10 +13803,27 @@ public final class ActivityManagerService extends ActivityManagerNative TimeUtils.formatDuration(dur, pw); pw.print(" ("); pw.print(ass.mCount); - pw.println(" times)"); + pw.print(" times)"); + pw.print(" "); + for (int i=0; i<ass.mStateTimes.length; i++) { + long amt = ass.mStateTimes[i]; + if (ass.mLastState-ActivityManager.MIN_PROCESS_STATE == i) { + amt += now - ass.mLastStateUptime; + } + if (amt != 0) { + pw.print(" "); + pw.print(ProcessList.makeProcStateString( + i + ActivityManager.MIN_PROCESS_STATE)); + pw.print("="); + TimeUtils.formatDuration(amt, pw); + if (ass.mLastState-ActivityManager.MIN_PROCESS_STATE == i) { + pw.print("*"); + } + } + } + pw.println(); if (ass.mNesting > 0) { - pw.print(" "); - pw.print(" Currently active: "); + pw.print(" Currently active: "); TimeUtils.formatDuration(now - ass.mStartTime, pw); pw.println(); } @@ -18238,8 +18261,8 @@ public final class ActivityManagerService extends ActivityManagerNative return null; } - Association startAssociationLocked(int sourceUid, String sourceProcess, int targetUid, - ComponentName targetComponent, String targetProcess) { + Association startAssociationLocked(int sourceUid, String sourceProcess, int sourceState, + int targetUid, ComponentName targetComponent, String targetProcess) { if (!mTrackingAssociations) { return null; } @@ -18268,7 +18291,8 @@ public final class ActivityManagerService extends ActivityManagerNative ass.mCount++; ass.mNesting++; if (ass.mNesting == 1) { - ass.mStartTime = SystemClock.uptimeMillis(); + ass.mStartTime = ass.mLastStateUptime = SystemClock.uptimeMillis(); + ass.mLastState = sourceState; } return ass; } @@ -18297,7 +18321,39 @@ public final class ActivityManagerService extends ActivityManagerNative } ass.mNesting--; if (ass.mNesting == 0) { - ass.mTime += SystemClock.uptimeMillis() - ass.mStartTime; + long uptime = SystemClock.uptimeMillis(); + ass.mTime += uptime - ass.mStartTime; + ass.mStateTimes[ass.mLastState-ActivityManager.MIN_PROCESS_STATE] + += uptime - ass.mLastStateUptime; + ass.mLastState = ActivityManager.MAX_PROCESS_STATE + 2; + } + } + + private void noteUidProcessState(final int uid, final int state) { + mBatteryStatsService.noteUidProcessState(uid, state); + if (mTrackingAssociations) { + for (int i1=0, N1=mAssociations.size(); i1<N1; i1++) { + ArrayMap<ComponentName, SparseArray<ArrayMap<String, Association>>> targetComponents + = mAssociations.valueAt(i1); + for (int i2=0, N2=targetComponents.size(); i2<N2; i2++) { + SparseArray<ArrayMap<String, Association>> sourceUids + = targetComponents.valueAt(i2); + ArrayMap<String, Association> sourceProcesses = sourceUids.get(uid); + if (sourceProcesses != null) { + for (int i4=0, N4=sourceProcesses.size(); i4<N4; i4++) { + Association ass = sourceProcesses.valueAt(i4); + if (ass.mNesting >= 1) { + // currently associated + long uptime = SystemClock.uptimeMillis(); + ass.mStateTimes[ass.mLastState-ActivityManager.MIN_PROCESS_STATE] + += uptime - ass.mLastStateUptime; + ass.mLastState = state; + ass.mLastStateUptime = uptime; + } + } + } + } + } } } @@ -20176,7 +20232,7 @@ public final class ActivityManagerService extends ActivityManagerNative } uidRec.setProcState = uidRec.curProcState; enqueueUidChangeLocked(uidRec, -1, uidChange); - mBatteryStatsService.noteUidProcessState(uidRec.uid, uidRec.curProcState); + noteUidProcessState(uidRec.uid, uidRec.curProcState); } } diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 4ca615dd55c9..d13f47266f25 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -42,7 +42,7 @@ import static com.android.server.pm.Installer.DEXOPT_PUBLIC; import static com.android.server.pm.Installer.DEXOPT_SAFEMODE; import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets; -import static com.android.server.pm.PackageManagerServiceCompilerMapping.getFullCompilerFilter; +import static com.android.server.pm.PackageManagerServiceCompilerMapping.getNonProfileGuidedCompilerFilter; /** * Helper class for running dexopt command on packages. @@ -144,9 +144,10 @@ class PackageDexOptimizer { if (isUsedByOtherApps(path)) { checkProfiles = false; - // TODO: Should we only upgrade to the non-profile-guided version? That is, - // given verify-profile, should we move to interpret-only? - targetCompilerFilter = getFullCompilerFilter(); + targetCompilerFilter = getNonProfileGuidedCompilerFilter(targetCompilerFilter); + if (DexFile.isProfileGuidedCompilerFilter(targetCompilerFilter)) { + throw new IllegalStateException(targetCompilerFilter); + } isProfileGuidedFilter = false; break; @@ -181,6 +182,10 @@ class PackageDexOptimizer { return DEX_OPT_FAILED; } dexoptNeeded = adjustDexoptNeeded(dexoptNeeded); + if (PackageManagerService.DEBUG_DEXOPT) { + Log.i(TAG, "DexoptNeeded for " + path + "@" + targetCompilerFilter + " is " + + dexoptNeeded); + } final String dexoptType; String oatDir = null; diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java index f1b7991819b7..a7512db55615 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java @@ -126,4 +126,10 @@ class PackageManagerServiceCompilerMapping { return value; } + /** + * Return the non-profile-guided filter corresponding to the given filter. + */ + public static String getNonProfileGuidedCompilerFilter(String filter) { + return DexFile.getNonProfileGuidedCompilerFilter(filter); + } } diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java index 50699f8ccf4a..c4f9cc125411 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java @@ -26,9 +26,11 @@ import android.content.pm.ApplicationInfo; import android.content.pm.IPackageDeleteObserver; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.Signature; import android.content.pm.UserInfo; import android.os.Binder; +import android.os.Build; import android.os.PatternMatcher; import android.os.Process; import android.os.RemoteException; @@ -38,6 +40,7 @@ import android.os.UserManager; import android.provider.Settings.Global; import android.provider.Settings; import android.util.AndroidRuntimeException; +import android.util.Base64; import android.util.Slog; import android.webkit.IWebViewUpdateService; import android.webkit.WebViewFactory; @@ -72,8 +75,6 @@ public class WebViewUpdateService extends SystemService { // The WebView package currently in use (or the one we are preparing). private PackageInfo mCurrentWebViewPackage = null; - // The WebView providers that are currently available. - private WebViewProviderInfo[] mCurrentValidWebViewPackages = null; private BroadcastReceiver mWebViewUpdatedReceiver; private WebViewUtilityInterface mWebViewUtility; @@ -126,7 +127,6 @@ public class WebViewUpdateService extends SystemService { PackageInfo newPackage = null; synchronized(WebViewUpdateService.this) { try { - updateValidWebViewPackages(); newPackage = findPreferredWebViewPackage(); if (mCurrentWebViewPackage != null) oldProviderName = mCurrentWebViewPackage.packageName; @@ -180,11 +180,17 @@ public class WebViewUpdateService extends SystemService { publishBinderService("webviewupdate", new BinderService(), true /*allowIsolated*/); } - private static boolean existsValidNonFallbackProvider(WebViewProviderInfo[] providers) { + private boolean existsValidNonFallbackProvider(WebViewProviderInfo[] providers) { for (WebViewProviderInfo provider : providers) { - if (provider.isAvailableByDefault() && provider.isEnabled() - && provider.isValidProvider() && !provider.isFallbackPackage()) { - return true; + if (provider.availableByDefault && !provider.isFallback) { + try { + PackageInfo packageInfo = getPackageInfoForProvider(provider); + if (isEnabledPackage(packageInfo) && isValidProvider(provider, packageInfo)) { + return true; + } + } catch (NameNotFoundException e) { + // A non-existent provider is neither valid nor enabled + } } } return false; @@ -211,11 +217,9 @@ public class WebViewUpdateService extends SystemService { WebViewProviderInfo[] webviewProviders = mWebViewUtility.getWebViewPackages(); WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders); if (fallbackProvider == null) return; - boolean existsValidNonFallbackProvider = - existsValidNonFallbackProvider(webviewProviders); - enablePackageForUser(fallbackProvider.packageName, !existsValidNonFallbackProvider, - userId); + enablePackageForUser(fallbackProvider.packageName, + !existsValidNonFallbackProvider(webviewProviders), userId); } /** @@ -236,7 +240,7 @@ public class WebViewUpdateService extends SystemService { for (WebViewProviderInfo provider : webviewProviders) { String webviewPackage = "package:" + provider.packageName; if (webviewPackage.equals(intent.getDataString())) { - if (provider.isAvailableByDefault()) { + if (provider.availableByDefault) { changedPackage = provider.packageName; } break; @@ -251,10 +255,16 @@ public class WebViewUpdateService extends SystemService { if (fallbackProvider == null) return; boolean existsValidNonFallbackProvider = existsValidNonFallbackProvider(webviewProviders); + boolean isFallbackEnabled = false; + try { + isFallbackEnabled = isEnabledPackage(getPackageInfoForProvider(fallbackProvider)); + } catch (NameNotFoundException e) { + } + if (existsValidNonFallbackProvider // During an OTA the primary user's WebView state might differ from other users', so // ignore the state of that user during boot. - && (fallbackProvider.isEnabled() || intent == null)) { + && (isFallbackEnabled || intent == null)) { // Uninstall and disable fallback package for all users. context.getPackageManager().deletePackage(fallbackProvider.packageName, new IPackageDeleteObserver.Stub() { @@ -273,7 +283,7 @@ public class WebViewUpdateService extends SystemService { } else if (!existsValidNonFallbackProvider // During an OTA the primary user's WebView state might differ from other users', so // ignore the state of that user during boot. - && (!fallbackProvider.isEnabled() || intent==null)) { + && (!isFallbackEnabled || intent==null)) { // Enable the fallback package for all users. UserManager userManager = (UserManager)context.getSystemService(Context.USER_SERVICE); @@ -299,24 +309,13 @@ public class WebViewUpdateService extends SystemService { */ private static WebViewProviderInfo getFallbackProvider(WebViewProviderInfo[] webviewPackages) { for (WebViewProviderInfo provider : webviewPackages) { - if (provider.isFallbackPackage()) { + if (provider.isFallback) { return provider; } } return null; } - private static boolean containsAvailableNonFallbackProvider( - WebViewProviderInfo[] webviewPackages) { - for (WebViewProviderInfo provider : webviewPackages) { - if (provider.isAvailableByDefault() && provider.isEnabled() - && provider.isValidProvider() && !provider.isFallbackPackage()) { - return true; - } - } - return false; - } - private boolean isFallbackPackage(String packageName) { if (packageName == null || !isFallbackLogicEnabled()) return false; @@ -336,7 +335,6 @@ public class WebViewUpdateService extends SystemService { updateFallbackState(getContext(), null); try { synchronized(this) { - updateValidWebViewPackages(); mCurrentWebViewPackage = findPreferredWebViewPackage(); onWebViewProviderChanged(mCurrentWebViewPackage); } @@ -408,24 +406,41 @@ public class WebViewUpdateService extends SystemService { } } + private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos() { + WebViewProviderInfo[] allProviders = mWebViewUtility.getWebViewPackages(); + List<ProviderAndPackageInfo> providers = new ArrayList<>(); + for(int n = 0; n < allProviders.length; n++) { + try { + PackageInfo packageInfo = getPackageInfoForProvider(allProviders[n]); + if (isValidProvider(allProviders[n], packageInfo)) { + providers.add(new ProviderAndPackageInfo(allProviders[n], packageInfo)); + } + } catch (NameNotFoundException e) { + // Don't add non-existent packages + } + } + return providers.toArray(new ProviderAndPackageInfo[providers.size()]); + } + /** - * Updates the currently valid WebView provider packages. - * Should be used when a provider has been installed or removed. - * @hide - * */ - private void updateValidWebViewPackages() { - List<WebViewProviderInfo> webViewProviders = - new ArrayList<WebViewProviderInfo>(Arrays.asList(mWebViewUtility.getWebViewPackages())); - Iterator<WebViewProviderInfo> it = webViewProviders.iterator(); - // remove non-valid packages - while(it.hasNext()) { - WebViewProviderInfo current = it.next(); - if (!current.isValidProvider()) - it.remove(); + * Fetch only the currently valid WebView packages. + **/ + private WebViewProviderInfo[] getValidWebViewPackages() { + ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos(); + WebViewProviderInfo[] providers = new WebViewProviderInfo[providersAndPackageInfos.length]; + for(int n = 0; n < providersAndPackageInfos.length; n++) { + providers[n] = providersAndPackageInfos[n].provider; } - synchronized(this) { - mCurrentValidWebViewPackages = - webViewProviders.toArray(new WebViewProviderInfo[webViewProviders.size()]); + return providers; + } + + private class ProviderAndPackageInfo { + public final WebViewProviderInfo provider; + public final PackageInfo packageInfo; + + public ProviderAndPackageInfo(WebViewProviderInfo provider, PackageInfo packageInfo) { + this.provider = provider; + this.packageInfo = packageInfo; } } @@ -437,28 +452,30 @@ public class WebViewUpdateService extends SystemService { * @hide */ private PackageInfo findPreferredWebViewPackage() { - WebViewProviderInfo[] providers = mCurrentValidWebViewPackages; + ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos(); String userChosenProvider = mWebViewUtility.getUserChosenWebViewProvider(getContext()); // If the user has chosen provider, use that - for (WebViewProviderInfo provider : providers) { - if (provider.packageName.equals(userChosenProvider) && provider.isEnabled()) { - return provider.getPackageInfo(); + for (ProviderAndPackageInfo providerAndPackage : providers) { + if (providerAndPackage.provider.packageName.equals(userChosenProvider) + && isEnabledPackage(providerAndPackage.packageInfo)) { + return providerAndPackage.packageInfo; } } // User did not choose, or the choice failed; use the most stable provider that is // enabled and available by default (not through user choice). - for (WebViewProviderInfo provider : providers) { - if (provider.isAvailableByDefault() && provider.isEnabled()) { - return provider.getPackageInfo(); + for (ProviderAndPackageInfo providerAndPackage : providers) { + if (providerAndPackage.provider.availableByDefault + && isEnabledPackage(providerAndPackage.packageInfo)) { + return providerAndPackage.packageInfo; } } // Could not find any enabled package either, use the most stable provider. - for (WebViewProviderInfo provider : providers) { - return provider.getPackageInfo(); + for (ProviderAndPackageInfo providerAndPackage : providers) { + return providerAndPackage.packageInfo; } mAnyWebViewInstalled = false; @@ -467,6 +484,60 @@ public class WebViewUpdateService extends SystemService { } /** + * Returns whether this provider is valid for use as a WebView provider. + */ + private static boolean isValidProvider(WebViewProviderInfo configInfo, + PackageInfo packageInfo) { + if (providerHasValidSignature(configInfo, packageInfo) && + WebViewFactory.getWebViewLibrary(packageInfo.applicationInfo) != null) { + return true; + } + return false; + } + + private static boolean providerHasValidSignature(WebViewProviderInfo provider, + PackageInfo packageInfo) { + if (Build.IS_DEBUGGABLE) + return true; + Signature[] packageSignatures; + // If no signature is declared, instead check whether the package is included in the + // system. + if (provider.signatures == null || provider.signatures.length == 0) { + return packageInfo.applicationInfo.isSystemApp(); + } + packageSignatures = packageInfo.signatures; + if (packageSignatures.length != 1) + return false; + + final byte[] packageSignature = packageSignatures[0].toByteArray(); + // Return whether the package signature matches any of the valid signatures + for (String signature : provider.signatures) { + final byte[] validSignature = Base64.decode(signature, Base64.DEFAULT); + if (Arrays.equals(packageSignature, validSignature)) + return true; + } + return false; + } + + /** + * Returns whether the given package is enabled. + * This state can be changed by the user from Settings->Apps + */ + private static boolean isEnabledPackage(PackageInfo packageInfo) { + return packageInfo.applicationInfo.enabled; + } + + private static PackageInfo getPackageInfoForProvider(WebViewProviderInfo configInfo) + throws NameNotFoundException { + PackageManager pm = AppGlobals.getInitialApplication().getPackageManager(); + return pm.getPackageInfo(configInfo.packageName, PACKAGE_FLAGS); + } + + // flags declaring we want extra info from the package manager for webview providers + private final static int PACKAGE_FLAGS = PackageManager.GET_META_DATA + | PackageManager.GET_SIGNATURES | PackageManager.MATCH_DEBUG_TRIAGED_MISSING; + + /** * Returns whether WebView is ready and is not going to go through its preparation phase again * directly. */ @@ -593,9 +664,7 @@ public class WebViewUpdateService extends SystemService { @Override // Binder call public WebViewProviderInfo[] getValidWebViewPackages() { - synchronized(WebViewUpdateService.this) { - return mCurrentValidWebViewPackages; - } + return WebViewUpdateService.this.getValidWebViewPackages(); } @Override // Binder call diff --git a/services/core/java/com/android/server/webkit/WebViewUtilityImpl.java b/services/core/java/com/android/server/webkit/WebViewUtilityImpl.java index 4dbd02d1ede5..aaa7977208a1 100644 --- a/services/core/java/com/android/server/webkit/WebViewUtilityImpl.java +++ b/services/core/java/com/android/server/webkit/WebViewUtilityImpl.java @@ -87,10 +87,10 @@ public class WebViewUtilityImpl implements WebViewUtilityInterface { parser.getAttributeValue(null, TAG_AVAILABILITY)); boolean isFallback = "true".equals( parser.getAttributeValue(null, TAG_FALLBACK)); - WebViewProviderInfo currentProvider = - new WebViewProviderInfo(packageName, description, availableByDefault, - isFallback, readSignatures(parser)); - if (currentProvider.isFallbackPackage()) { + WebViewProviderInfo currentProvider = new WebViewProviderInfo( + packageName, description, availableByDefault, isFallback, + readSignatures(parser)); + if (currentProvider.isFallback) { numFallbackPackages++; if (numFallbackPackages > 1) { throw new AndroidRuntimeException( diff --git a/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp b/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp index ec5e8c921979..14d50ce957fe 100644 --- a/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp +++ b/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp @@ -36,7 +36,8 @@ namespace android { enum { TEMPERATURE_CURRENT = 0, TEMPERATURE_THROTTLING = 1, - TEMPERATURE_SHUTDOWN = 2 + TEMPERATURE_SHUTDOWN = 2, + TEMPERATURE_THROTTLING_BELOW_VR_MIN = 3 }; static struct { @@ -127,6 +128,13 @@ static jfloatArray nativeGetDeviceTemperatures(JNIEnv *env, jclass /* clazz */, values[length++] = list[i].shutdown_threshold; } break; + case TEMPERATURE_THROTTLING_BELOW_VR_MIN: + if (list[i].vr_throttling_threshold == UNKNOWN_TEMPERATURE) { + values[length++] = gUndefinedTemperature; + } else { + values[length++] = list[i].vr_throttling_threshold; + } + break; } } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 22480738c587..ac6fc5855dfb 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -1480,6 +1480,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return "/data/system/"; } + void registerContentObserver(Uri uri, boolean notifyForDescendents, + ContentObserver observer, int userHandle) { + mContext.getContentResolver().registerContentObserver(uri, notifyForDescendents, + observer, userHandle); + } + int settingsSecureGetIntForUser(String name, int def, int userHandle) { return Settings.Secure.getIntForUser(mContext.getContentResolver(), name, def, userHandle); @@ -2536,7 +2542,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { onStartUser(UserHandle.USER_SYSTEM); // Register an observer for watching for user setup complete. - new SetupContentObserver(mHandler).register(mContext.getContentResolver()); + new SetupContentObserver(mHandler).register(); // Initialize the user setup state, to handle the upgrade case. updateUserSetupComplete(); @@ -7847,9 +7853,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { super(handler); } - void register(ContentResolver resolver) { - resolver.registerContentObserver(mUserSetupComplete, false, this, UserHandle.USER_ALL); - resolver.registerContentObserver(mDeviceProvisioned, false, this, UserHandle.USER_ALL); + void register() { + mInjector.registerContentObserver(mUserSetupComplete, false, this, UserHandle.USER_ALL); + mInjector.registerContentObserver(mDeviceProvisioned, false, this, UserHandle.USER_ALL); } @Override diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index b026bc577e2d..0f5930244f6e 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -155,6 +155,8 @@ public final class SystemServer { "com.android.server.search.SearchManagerService$Lifecycle"; private static final String THERMAL_OBSERVER_CLASS = "com.google.android.clockwork.ThermalObserver"; + private static final String WEAR_BLUETOOTH_SERVICE_CLASS = + "com.google.android.clockwork.bluetooth.WearBluetoothService"; private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst"; @@ -959,8 +961,7 @@ public final class SystemServer { if (!disableNonCoreServices) { mSystemServiceManager.startService(DockObserver.class); - if (context.getPackageManager().hasSystemFeature - (PackageManager.FEATURE_WATCH)) { + if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) { mSystemServiceManager.startService(THERMAL_OBSERVER_CLASS); } } @@ -1172,6 +1173,10 @@ public final class SystemServer { mSystemServiceManager.startService(MediaProjectionManagerService.class); } + if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) { + mSystemServiceManager.startService(WEAR_BLUETOOTH_SERVICE_CLASS); + } + // Before things start rolling, be sure we have decided whether // we are in safe mode. final boolean safeMode = wm.detectSafeMode(); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java index 35777ce86c4a..744443f1d7e7 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java @@ -15,14 +15,14 @@ */ package com.android.server.devicepolicy; -import com.android.internal.widget.LockPatternUtils; - import android.app.IActivityManager; import android.app.NotificationManager; import android.app.backup.IBackupManager; import android.content.pm.IPackageManager; import android.content.pm.PackageManagerInternal; +import android.database.ContentObserver; import android.media.IAudioService; +import android.net.Uri; import android.os.Looper; import android.os.PowerManagerInternal; import android.os.UserHandle; @@ -30,12 +30,15 @@ import android.os.UserManager; import android.os.UserManagerInternal; import android.os.storage.StorageManager; import android.telephony.TelephonyManager; +import android.util.ArrayMap; +import android.util.Log; +import android.util.Pair; import android.view.IWindowManager; -import java.io.File; +import com.android.internal.widget.LockPatternUtils; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.when; +import java.io.File; +import java.util.Map; /** * Overrides {@link #DevicePolicyManagerService} for dependency injection. @@ -77,6 +80,7 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi } public final DpmMockContext context; + private final MockInjector mMockInjector; public DevicePolicyManagerServiceTestable(DpmMockContext context, File dataDir) { this(new MockInjector(context, dataDir)); @@ -84,15 +88,36 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi private DevicePolicyManagerServiceTestable(MockInjector injector) { super(injector); + mMockInjector = injector; this.context = injector.context; } + + public void notifyChangeToContentObserver(Uri uri, int userHandle) { + ContentObserver co = mMockInjector.mContentObservers + .get(new Pair<Uri, Integer>(uri, userHandle)); + if (co != null) { + co.onChange(false, uri, userHandle); // notify synchronously + } + + // Notify USER_ALL observer too. + co = mMockInjector.mContentObservers + .get(new Pair<Uri, Integer>(uri, UserHandle.USER_ALL)); + if (co != null) { + co.onChange(false, uri, userHandle); // notify synchronously + } + } + + private static class MockInjector extends Injector { public final DpmMockContext context; public final File dataDir; + // Key is a pair of uri and userId + private final Map<Pair<Uri, Integer>, ContentObserver> mContentObservers = new ArrayMap<>(); + private MockInjector(DpmMockContext context, File dataDir) { super(context); this.context = context; @@ -265,6 +290,12 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi } @Override + void registerContentObserver(Uri uri, boolean notifyForDescendents, + ContentObserver observer, int userHandle) { + mContentObservers.put(new Pair<Uri, Integer>(uri, userHandle), observer); + } + + @Override int settingsSecureGetIntForUser(String name, int def, int userHandle) { return context.settings.settingsSecureGetIntForUser(name, def, userHandle); } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index e6963d52e88a..3a2e946e617f 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -543,10 +543,31 @@ public class DevicePolicyManagerTest extends DpmTestBase { } /** - * Test for: {@link DevicePolicyManager#setDeviceOwner} DO on system user installs - * successfully. + * Test for: {@link DevicePolicyManager#setDeviceOwner} DO on system user installs successfully. */ public void testSetDeviceOwner() throws Exception { + setDeviceOwner(); + + // Try to set a profile owner on the same user, which should fail. + setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_SYSTEM_USER_UID); + dpm.setActiveAdmin(admin2, /* refreshing= */ true, UserHandle.USER_SYSTEM); + try { + dpm.setProfileOwner(admin2, "owner-name", UserHandle.USER_SYSTEM); + fail("IllegalStateException not thrown"); + } catch (IllegalStateException expected) { + assertTrue("Message was: " + expected.getMessage(), + expected.getMessage().contains("already has a device owner")); + } + + // DO admin can't be deactivated. + dpm.removeActiveAdmin(admin1); + assertTrue(dpm.isAdminActive(admin1)); + + // TODO Test getDeviceOwnerName() too. To do so, we need to change + // DPMS.getApplicationLabel() because Context.createPackageContextAsUser() is not mockable. + } + + private void setDeviceOwner() throws Exception { mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS); mContext.callerPermissions.add(permission.MANAGE_USERS); mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); @@ -594,24 +615,6 @@ public class DevicePolicyManagerTest extends DpmTestBase { MockUtils.checkUserHandle(UserHandle.USER_SYSTEM)); assertEquals(admin1, dpm.getDeviceOwnerComponentOnAnyUser()); - - // Try to set a profile owner on the same user, which should fail. - setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_SYSTEM_USER_UID); - dpm.setActiveAdmin(admin2, /* refreshing= */ true, UserHandle.USER_SYSTEM); - try { - dpm.setProfileOwner(admin2, "owner-name", UserHandle.USER_SYSTEM); - fail("IllegalStateException not thrown"); - } catch (IllegalStateException expected) { - assertTrue("Message was: " + expected.getMessage(), - expected.getMessage().contains("already has a device owner")); - } - - // DO admin can't be deactivated. - dpm.removeActiveAdmin(admin1); - assertTrue(dpm.isAdminActive(admin1)); - - // TODO Test getDeviceOwnerName() too. To do so, we need to change - // DPMS.getApplicationLabel() because Context.createPackageContextAsUser() is not mockable. } private void checkGetDeviceOwnerInfoApi(DevicePolicyManager dpm, boolean hasDeviceOwner) { @@ -1934,5 +1937,211 @@ public class DevicePolicyManagerTest extends DpmTestBase { // TODO Verify calls to settingsGlobalPutInt. Tried but somehow mockito threw // UnfinishedVerificationException. } -} + public void testIsProvisioningAllowed_DeviceAdminFeatureOff() throws Exception { + when(mContext.packageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)) + .thenReturn(false); + when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) + .thenReturn(false); + initializeDpms(); + when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(false); + when(mContext.userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true)) + .thenReturn(true); + setUserSetupCompleteForUser(false, UserHandle.USER_SYSTEM); + + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, + false); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_USER, false); + } + + public void testIsProvisioningAllowed_ManagedProfileFeatureOff() throws Exception { + when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) + .thenReturn(false); + initializeDpms(); + when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(false); + when(mContext.userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true)) + .thenReturn(true); + setUserSetupCompleteForUser(false, UserHandle.USER_SYSTEM); + + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, + false); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_USER, false); + + // Test again when split user is on + when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, + true); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_USER, false); + } + + public void testIsProvisioningAllowed_nonSplitUser_firstBoot_primaryUser() throws Exception { + when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) + .thenReturn(true); + when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(false); + when(mContext.userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true)) + .thenReturn(true); + setUserSetupCompleteForUser(false, UserHandle.USER_SYSTEM); + + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, + false /* because of non-split user */); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_USER, + false /* because of non-split user */); + } + + public void testIsProvisioningAllowed_nonSplitUser_afterDeviceSetup_primaryUser() + throws Exception { + when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) + .thenReturn(true); + when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(false); + when(mContext.userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true)) + .thenReturn(true); + setUserSetupCompleteForUser(true, UserHandle.USER_SYSTEM); + + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, + false/* because of completed device setup */); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, + false/* because of non-split user */); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_USER, + false/* because of non-split user */); + } + + public void testIsProvisioningAllowed_splitUser_firstBoot_systemUser() throws Exception { + when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) + .thenReturn(true); + when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true); + when(mContext.userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true)) + .thenReturn(false); + setUserSetupCompleteForUser(false, UserHandle.USER_SYSTEM); + + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, + false /* because canAddMoreManagedProfiles returns false */); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, + true); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_USER, + false/* because calling uid is system user */); + + } + + public void testIsProvisioningAllowed_splitUser_afterDeviceSetup_systemUser() throws Exception { + when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) + .thenReturn(true); + when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true); + when(mContext.userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true)) + .thenReturn(false); + setUserSetupCompleteForUser(true, UserHandle.USER_SYSTEM); + + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, + true/* it's undefined behavior. Can be changed into false in the future */); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, + false /* because canAddMoreManagedProfiles returns false */); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, + true/* it's undefined behavior. Can be changed into false in the future */); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_USER, + false/* because calling uid is system user */); + } + + public void testIsProvisioningAllowed_splitUser_firstBoot_primaryUser() throws Exception { + when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) + .thenReturn(true); + when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true); + when(mContext.userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE, + true)).thenReturn(true); + setUserSetupCompleteForUser(false, DpmMockContext.CALLER_USER_HANDLE); + + mContext.binder.callingUid = DpmMockContext.CALLER_UID; + + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, + true); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_USER, true); + + } + + public void testIsProvisioningAllowed_splitUser_afterDeviceSetup_primaryUser() + throws Exception { + when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) + .thenReturn(true); + when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true); + when(mContext.userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE, + true)).thenReturn(true); + setUserSetupCompleteForUser(true, DpmMockContext.CALLER_USER_HANDLE); + + mContext.binder.callingUid = DpmMockContext.CALLER_UID; + + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, + true/* it's undefined behavior. Can be changed into false in the future */); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, + true/* it's undefined behavior. Can be changed into false in the future */); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_USER, + false/* because user setup completed */); + } + + public void testIsProvisioningAllowed_provisionManagedProfileWithDeviceOwner_systemUser() + throws Exception { + setDeviceOwner(); + + when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) + .thenReturn(true); + when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true); + when(mContext.userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true)) + .thenReturn(false); + setUserSetupCompleteForUser(true, UserHandle.USER_SYSTEM); + + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, + false /* can't provision managed profile on system user */); + } + + public void testIsProvisioningAllowed_provisionManagedProfileWithDeviceOwner_primaryUser() + throws Exception { + setDeviceOwner(); + + when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) + .thenReturn(true); + when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true); + when(mContext.userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE, + true)).thenReturn(true); + setUserSetupCompleteForUser(false, DpmMockContext.CALLER_USER_HANDLE); + + mContext.binder.callingUid = DpmMockContext.CALLER_UID; + + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); + } + + private void setUserSetupCompleteForUser(boolean isUserSetupComplete, int userhandle) { + when(mContext.settings.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, + userhandle)).thenReturn(isUserSetupComplete ? 1 : 0); + dpms.notifyChangeToContentObserver( + Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), userhandle); + } + + private void assertProvisioningAllowed(String action, boolean expected) { + assertEquals("isProvisioningAllowed(" + action + ") returning unexpected result", expected, + dpm.isProvisioningAllowed(action)); + } +} 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 8e2ef703f3c6..60d7382a7233 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java @@ -53,6 +53,7 @@ import java.io.File; import java.util.ArrayList; import java.util.List; +import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; @@ -172,36 +173,36 @@ public class DpmMockContext extends MockContext { } public static class SettingsForMock { - int settingsSecureGetIntForUser(String name, int def, int userHandle) { + public int settingsSecureGetIntForUser(String name, int def, int userHandle) { return 0; } - void settingsSecurePutIntForUser(String name, int value, int userHandle) { + public void settingsSecurePutIntForUser(String name, int value, int userHandle) { } - void settingsSecurePutStringForUser(String name, String value, int userHandle) { + public void settingsSecurePutStringForUser(String name, String value, int userHandle) { } - void settingsGlobalPutStringForUser(String name, String value, int userHandle) { + public void settingsGlobalPutStringForUser(String name, String value, int userHandle) { } - void settingsSecurePutInt(String name, int value) { + public void settingsSecurePutInt(String name, int value) { } - void settingsGlobalPutInt(String name, int value) { + public void settingsGlobalPutInt(String name, int value) { } - void settingsSecurePutString(String name, String value) { + public void settingsSecurePutString(String name, String value) { } - void settingsGlobalPutString(String name, String value) { + public void settingsGlobalPutString(String name, String value) { } - int settingsGlobalGetInt(String name, int def) { + public int settingsGlobalGetInt(String name, int value) { return 0; } - void securityLogSetLoggingEnabledProperty(boolean enabled) { + public void securityLogSetLoggingEnabledProperty(boolean enabled) { } public boolean securityLogGetLoggingEnabledProperty() { @@ -321,6 +322,8 @@ public class DpmMockContext extends MockContext { mUserInfos.add(uh); when(userManager.getUsers()).thenReturn(mUserInfos); + when(userManager.getUsers(anyBoolean())).thenReturn(mUserInfos); + when(userManager.isUserRunning(eq(new UserHandle(userId)))).thenReturn(true); when(userManager.getUserInfo(anyInt())).thenAnswer( new Answer<UserInfo>() { @Override |